├── .vscode └── settings.json ├── 01-bookstore-cli-flag-json ├── README.md ├── books.go ├── books.json ├── go.mod └── main.go ├── 02-organize-folder ├── README.md ├── go.mod └── main.go ├── 03-web-monitor ├── README.md ├── go.mod └── main.go ├── 04-bookstore-api ├── Bookstore-REST-API.postman_collection.json ├── README.md ├── books.go ├── books.json ├── go.mod └── main.go ├── 05-random-password-flag ├── README.md ├── go.mod └── main.go ├── 05-random-password ├── README.md ├── go.mod └── main.go ├── 06-system-monitor ├── README.md ├── go.mod ├── go.sum ├── info.go └── main.go ├── 07-ssh-sftp-agent ├── README.md ├── go.mod ├── go.sum ├── main.go └── upload.txt ├── 08-file-folder-zip ├── .gitignore ├── README.md ├── go.mod ├── main-bk └── main.go ├── 09-pack-mod-demo ├── README.md ├── go.mod ├── go.sum └── main.go ├── 10-golang-ssh-concurrent-file-uploder ├── .gitignore ├── README.md ├── Vagrantfile ├── go.mod ├── go.sum ├── hello.txt ├── hi.txt ├── image-go └── main.go ├── 11-jwt-golang ├── Golang-JWT.postman_collection.json ├── README.md ├── go.mod ├── go.sum └── main.go ├── 12-fiber-book-rest ├── .env ├── .gitignore ├── README.md ├── Vagrantfile ├── controllers │ ├── booksController.go │ └── usersController.go ├── fiber-rest-book.postman_collection.json ├── go.mod ├── go.sum ├── helpers │ └── utils.go ├── initializers │ ├── db.go │ └── env.go ├── main.go ├── models │ ├── booksModel.go │ └── usersModel.go └── routes │ └── routes.go ├── 13-k8s-client-go ├── README.md ├── dep.yaml ├── go.mod ├── go.sum ├── k8s-api-ref.md └── main.go ├── 14_akit-ops ├── Dockerfile ├── README.md ├── akit-ops-ui-app │ ├── Dockerfile │ └── index.html ├── createUpdate.go ├── git.go ├── go.mod ├── go.sum ├── k8s-api-ref.md ├── main.go └── sa-role-rb-akit-ops.yaml ├── README.md ├── demos ├── akit-ops.gif ├── fiber-book-rest.gif ├── golang-bookstore-api.gif ├── golang-bookstore-cli.gif ├── golang-folder.gif ├── golang-random-password-flag.gif ├── golang-random-password.gif ├── golang-ssh-concurrent-file-uploder.gif ├── golang-ssh-sftp.gif ├── golang-system-metrics.gif ├── golang-web-monitor.gif ├── golang-zip.gif ├── jwt-with-golang.gif ├── k8s-client-go.gif └── rest-api-to-exec-shell.gif ├── images ├── akit-ops.png ├── golang-system-metrics.png ├── web-monitor.png └── web-monitor.xml ├── notes.md ├── playground ├── go.mod └── main.go ├── rest-api-to-exec-shell ├── README.md └── main.go └── syllabus.md /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /01-bookstore-cli-flag-json/README.md: -------------------------------------------------------------------------------- 1 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fakilans%2Fgolang-mini-projects%2Ftree%2Fmain%2F01-bookstore-cli-flag-json&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 2 | 3 | # Book Store Cli 4 | 5 | It is golang command line application to list, add, update and delete books using flag, json, ioutils package 6 | 7 | ## Demo 8 | 9 | ![Alt Organize Folder](https://raw.githubusercontent.com/akilans/golang-mini-projects/main/demos/golang-bookstore-cli.gif) 10 | 11 | ## Usage 12 | 13 | ```bash 14 | 15 | # clone a repo 16 | git clone https://github.com/akilans/golang-mini-projects.git 17 | 18 | # go to the 01-bookstore-cli-flag-json dir 19 | cd 01-bookstore-cli-flag-json 20 | 21 | # build 22 | go build 23 | 24 | # run 25 | 26 | # get books 27 | ./bookstore get --all 28 | ./bookstore get --id 5 29 | 30 | # add a book with id ,title, author, price, image_url 31 | ./bookstore add --id 6 --title test-book --author akilan --price 200 --image_url http://akilan.com/test.png 32 | 33 | # update a book with id ,title, author, price, image_url 34 | ./bookstore update --id 6 --title test-book-1 --author akilan1 --price 2001 --image_url http://akilan.com/test.png1 35 | 36 | # delete a book by --id 37 | ./bookstore delete --id 6 38 | 39 | ``` 40 | 41 | ## Credits and references 42 | 43 | 1. [That DevOps Guy](https://www.youtube.com/c/MarcelDempers) 44 | 2. [Donald Feury](https://www.youtube.com/c/DonaldFeury) 45 | 46 | ## Contributing 47 | 48 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 49 | -------------------------------------------------------------------------------- /01-bookstore-cli-flag-json/books.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | ) 10 | 11 | func checkError(err error) { 12 | if err != nil { 13 | fmt.Println("Error Happened ", err) 14 | os.Exit(1) 15 | } 16 | 17 | } 18 | 19 | // get all the books 20 | func getBooks() (books []Book) { 21 | booksBytes, err := ioutil.ReadFile("./books.json") 22 | checkError(err) 23 | 24 | err = json.Unmarshal(booksBytes, &books) 25 | checkError(err) 26 | 27 | return books 28 | 29 | } 30 | 31 | // save books to books.json file 32 | func saveBooks(books []Book) error { 33 | 34 | // converting into bytes for writing into a file 35 | booksBytes, err := json.Marshal(books) 36 | 37 | checkError(err) 38 | 39 | err = ioutil.WriteFile("./books.json", booksBytes, 0644) 40 | 41 | return err 42 | 43 | } 44 | 45 | // get all the books logic 46 | func handleGetBooks(getCmd *flag.FlagSet, all *bool, id *string) { 47 | 48 | getCmd.Parse(os.Args[2:]) 49 | 50 | // checking for all or id 51 | if !*all && *id == "" { 52 | fmt.Println("subcommand --all or --id needed") 53 | getCmd.PrintDefaults() 54 | os.Exit(1) 55 | } 56 | 57 | // if for all return all books 58 | if *all { 59 | books := getBooks() 60 | fmt.Printf("Id \t Title \t Author \t Price \t ImageURL \n") 61 | 62 | for _, book := range books { 63 | fmt.Printf("%v \t %v \t %v \t %v \t %v \n", book.Id, book.Title, book.Author, book.Price, book.Imageurl) 64 | } 65 | } 66 | 67 | // if id, return only that book 68 | // Throw error if book not found 69 | if *id != "" { 70 | books := getBooks() 71 | fmt.Printf("Id \t Title \t Author \t Price \t ImageURL \n") 72 | // to check a book exist or not 73 | var foundBook bool 74 | for _, book := range books { 75 | foundBook = true 76 | if *id == book.Id { 77 | fmt.Printf("%v \t %v \t %v \t %v \t %v \n", book.Id, book.Title, book.Author, book.Price, book.Imageurl) 78 | } 79 | 80 | } 81 | // if no book found with mentioned id throws an error 82 | if !foundBook { 83 | fmt.Println("Book not found") 84 | os.Exit(1) 85 | } 86 | } 87 | 88 | } 89 | 90 | // add or update a book logic 91 | func handleAddBook(addCmd *flag.FlagSet, id, title, author, price, image_url *string, addNewBook bool) { 92 | addCmd.Parse(os.Args[2:]) 93 | 94 | if *id == "" || *title == "" || *author == "" || *price == "" || *image_url == "" { 95 | fmt.Println("Please provide book id, title, author,price") 96 | addCmd.PrintDefaults() 97 | os.Exit(1) 98 | } 99 | 100 | books := getBooks() 101 | var newBook Book 102 | // to check a book exist or not 103 | var foundBook bool 104 | 105 | // checking for add or update 106 | if addNewBook { 107 | newBook = Book{*id, *title, *author, *price, *image_url} 108 | books = append(books, newBook) 109 | } else { 110 | for i, book := range books { 111 | if book.Id == *id { 112 | // replace old values with new ones 113 | books[i] = Book{*id, *title, *author, *price, *image_url} 114 | foundBook = true 115 | } 116 | } 117 | 118 | // if no book found with mentioned id throws an error 119 | if !foundBook { 120 | fmt.Println("Book not found") 121 | os.Exit(1) 122 | } 123 | } 124 | 125 | err := saveBooks(books) 126 | 127 | checkError(err) 128 | 129 | fmt.Println("Book added successfully") 130 | 131 | } 132 | 133 | func handleDeleteBook(deleteCmd *flag.FlagSet, id *string) { 134 | 135 | deleteCmd.Parse(os.Args[2:]) 136 | 137 | if *id == "" { 138 | fmt.Println("Please provide book --id") 139 | deleteCmd.PrintDefaults() 140 | os.Exit(1) 141 | } 142 | 143 | books := getBooks() 144 | var foundBook bool 145 | 146 | for i, book := range books { 147 | if book.Id == *id { 148 | // There is no direct delete 149 | // so creating 2 books structs without the targeted book and appending 150 | books = append(books[:i], books[i+1:]...) 151 | foundBook = true 152 | } 153 | } 154 | 155 | // if no book found with mentioned id throws an error 156 | if !foundBook { 157 | fmt.Println("Book not found") 158 | os.Exit(1) 159 | } 160 | 161 | err := saveBooks(books) 162 | 163 | checkError(err) 164 | 165 | fmt.Println("Book deleted successfully") 166 | 167 | } 168 | -------------------------------------------------------------------------------- /01-bookstore-cli-flag-json/books.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "1", 4 | "title": "How to Win Friends and Influence People", 5 | "author": "Dale Carnegie", 6 | "price": "600", 7 | "image_url": "https://images-na.ssl-images-amazon.com/images/I/51C4Tpxn4KL._SX316_BO1,204,203,200_.jpg" 8 | }, 9 | { 10 | "id": "2", 11 | "title": "Think and Grow Rich", 12 | "author": "Napoleon Hill", 13 | "price": "500", 14 | "image_url": "https://images-na.ssl-images-amazon.com/images/I/51Y8jwGiebL._SX328_BO1,204,203,200_.jpg" 15 | }, 16 | { 17 | "id": "3", 18 | "title": "The 7 Habits of Highly Effective People", 19 | "author": "Stephen R. Covey", 20 | "price": "700", 21 | "image_url": "https://images-na.ssl-images-amazon.com/images/I/51qy14G7knL._SX318_BO1,204,203,200_.jpg" 22 | }, 23 | { 24 | "id": "4", 25 | "title": "Atomic Habits", 26 | "author": "James Clear", 27 | "price": "300", 28 | "image_url": "https://prodimage.images-bn.com/pimages/9780735211292_p0_v5_s600x595.jpg" 29 | }, 30 | { 31 | "id": "5", 32 | "title": "The 4-hour work week", 33 | "author": "Tim Ferrisss", 34 | "price": "4000", 35 | "image_url": "https://images-eu.ssl-images-amazon.com/images/I/51iGkLC6jhL._SY264_BO1,204,203,200_QL40_FMwebp_.jpg" 36 | } 37 | ] -------------------------------------------------------------------------------- /01-bookstore-cli-flag-json/go.mod: -------------------------------------------------------------------------------- 1 | module bookstore 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /01-bookstore-cli-flag-json/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | // struct based on books.json file. Please refer 10 | type Book struct { 11 | Id string `json:"id"` 12 | Title string `json:"title"` 13 | Author string `json:"author"` 14 | Price string `json:"price"` 15 | Imageurl string `json:"image_url"` 16 | } 17 | 18 | func main() { 19 | /* 20 | get books --all or --id 21 | ./bookstore get --all 22 | ./bookstore get --id 5 23 | */ 24 | getCmd := flag.NewFlagSet("get", flag.ExitOnError) 25 | getAll := getCmd.Bool("all", false, "List all the books") 26 | getId := getCmd.String("id", "", "Get book by id") 27 | 28 | /* 29 | add a book with id ,title, author, price, image_url 30 | ./bookstore add --id 6 --title test-book --author akilan --price 200 --image_url http://akilan.com/test.png 31 | */ 32 | 33 | addCmd := flag.NewFlagSet("add", flag.ExitOnError) 34 | addId := addCmd.String("id", "", "Book id") 35 | addTitle := addCmd.String("title", "", "Book title") 36 | addAuthor := addCmd.String("author", "", "Book author") 37 | addPrice := addCmd.String("price", "", "Book price") 38 | addImageUrl := addCmd.String("image_url", "", "Book image URL") 39 | 40 | /* 41 | update a book with id ,title, author, price, image_url 42 | ./bookstore update --id 6 --title test-book-1 --author akilan1 --price 2001 --image_url http://akilan.com/test.png1 43 | */ 44 | 45 | updateCmd := flag.NewFlagSet("update", flag.ExitOnError) 46 | updateId := updateCmd.String("id", "", "Book id") 47 | updateTitle := updateCmd.String("title", "", "Book title") 48 | updateAuthor := updateCmd.String("author", "", "Book author") 49 | updatePrice := updateCmd.String("price", "", "Book price") 50 | updateImageUrl := updateCmd.String("image_url", "", "Book image URL") 51 | 52 | /* 53 | delete a book by --id 54 | ./bookstore delete --id 6 55 | */ 56 | deleteCmd := flag.NewFlagSet("delete", flag.ExitOnError) 57 | deleteId := deleteCmd.String("id", "", "Delete book by id") 58 | 59 | // validation 60 | if len(os.Args) < 2 { 61 | fmt.Println("Expected get, add, update, delete commands") 62 | os.Exit(1) 63 | } 64 | 65 | switch os.Args[1] { 66 | case "get": 67 | handleGetBooks(getCmd, getAll, getId) 68 | case "add": 69 | handleAddBook(addCmd, addId, addTitle, addAuthor, addPrice, addImageUrl, true) 70 | case "update": 71 | handleAddBook(updateCmd, updateId, updateTitle, updateAuthor, updatePrice, updateImageUrl, false) 72 | case "delete": 73 | handleDeleteBook(deleteCmd, deleteId) 74 | default: 75 | fmt.Println("Please provide get, update, update, delete commands") 76 | os.Exit(1) 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /02-organize-folder/README.md: -------------------------------------------------------------------------------- 1 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fakilans%2Fgolang-mini-projects%2Ftree%2Fmain%2F02-organize-folder&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 2 | 3 | # Organize folder based on file extensions 4 | 5 | It is golang command line application to Organize folder based on file extensions 6 | 7 | ## Demo 8 | 9 | ![Alt Organize Folder](https://raw.githubusercontent.com/akilans/golang-mini-projects/main/demos/golang-folder.gif) 10 | 11 | ## Usage 12 | 13 | ```bash 14 | 15 | # clone a repo 16 | git clone https://github.com/akilans/golang-mini-projects.git 17 | 18 | # go to the 01-bookstore-cli-flag-json dir 19 | cd 02-organize-folder 20 | 21 | # build 22 | go build 23 | 24 | # run 25 | 26 | ./organize-folder 27 | 28 | # Enter the folder name needs to organized 29 | # Which folder do you want to organize? - /home/akilan/Downloads 30 | ``` 31 | 32 | ## Contributing 33 | 34 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 35 | -------------------------------------------------------------------------------- /02-organize-folder/go.mod: -------------------------------------------------------------------------------- 1 | module organize-folder 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /02-organize-folder/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | // check for any error 11 | func check(err error) { 12 | if err != nil { 13 | fmt.Printf("Error Happened %s \n", err) 14 | os.Exit(1) 15 | } 16 | } 17 | 18 | // Main function 19 | func main() { 20 | 21 | // Get the user input - target folder needs to be organized 22 | scanner := bufio.NewScanner(os.Stdin) 23 | fmt.Printf("Which folder do you want to organize? - ") 24 | scanner.Scan() 25 | 26 | targetFolder := scanner.Text() 27 | 28 | // check the folder exists or not 29 | _, err := os.Stat(targetFolder) 30 | if os.IsNotExist(err) { 31 | fmt.Println("Folder does not exist.") 32 | os.Exit(1) 33 | } else { 34 | // create default folders such as Images, Music, Docs, Others, Videos 35 | createDefaultFolders(targetFolder) 36 | 37 | //Oraganize folders 38 | organizeFolder(targetFolder) 39 | } 40 | 41 | } 42 | 43 | // function to create default folders such as Images, Music, Docs, Others, Videos 44 | func createDefaultFolders(targetFolder string) { 45 | defaultFolders := []string{"Music", "Videos", "Docs", "Images", "Others"} 46 | 47 | for _, folder := range defaultFolders { 48 | _, err := os.Stat(folder) 49 | if os.IsNotExist(err) { 50 | os.Mkdir(filepath.Join(targetFolder, folder), 0755) 51 | } 52 | } 53 | } 54 | 55 | // function to Oraganize folders 56 | func organizeFolder(targetFolder string) { 57 | // read the dir 58 | filesAndFolders, err := os.ReadDir(targetFolder) 59 | check(err) 60 | 61 | // to track how many files moved 62 | noOfFiles := 0 63 | 64 | for _, filesAndFolder := range filesAndFolders { 65 | // check for files 66 | if !filesAndFolder.IsDir() { 67 | fileInfo, err := filesAndFolder.Info() 68 | check(err) 69 | 70 | //get the file full path 71 | oldPath := filepath.Join(targetFolder, fileInfo.Name()) 72 | fileExt := filepath.Ext(oldPath) 73 | 74 | // switch case to move files based on ext 75 | switch fileExt { 76 | case ".png", ".jpg", ".jpeg": 77 | newPath := filepath.Join(targetFolder, "Images", fileInfo.Name()) 78 | err = os.Rename(oldPath, newPath) 79 | check(err) 80 | noOfFiles++ 81 | case ".mp4", ".mov", ".avi", ".amv": 82 | newPath := filepath.Join(targetFolder, "Videos", fileInfo.Name()) 83 | err = os.Rename(oldPath, newPath) 84 | check(err) 85 | noOfFiles++ 86 | case ".pdf", ".docx", ".csv", ".xlsx": 87 | newPath := filepath.Join(targetFolder, "Docs", fileInfo.Name()) 88 | err = os.Rename(oldPath, newPath) 89 | check(err) 90 | noOfFiles++ 91 | case ".mp3", ".wav", ".aac": 92 | newPath := filepath.Join(targetFolder, "Music", fileInfo.Name()) 93 | err = os.Rename(oldPath, newPath) 94 | check(err) 95 | noOfFiles++ 96 | default: 97 | newPath := filepath.Join(targetFolder, "Others", fileInfo.Name()) 98 | err = os.Rename(oldPath, newPath) 99 | check(err) 100 | noOfFiles++ 101 | } 102 | } 103 | } 104 | 105 | // print how many files moved 106 | if noOfFiles > 0 { 107 | fmt.Printf("%v number of files moved\n", noOfFiles) 108 | } else { 109 | fmt.Printf("No files moved") 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /03-web-monitor/README.md: -------------------------------------------------------------------------------- 1 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fakilans%2Fgolang-mini-projects%2Ftree%2Fmain%2F03-web-monitor&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 2 | 3 | # Website Monitoring and Alerting using Golang 4 | 5 | This application monitor websites with expected status code. If the response status code does not match with expected status code, connection refused from server, then it triggers an email and alerts user 6 | 7 | ### Prerequisites 8 | 9 | - Go 10 | - Configure a SMTP server - [SMTP settings for Gmail](hhttps://myaccount.google.com/u/4/security) - Enable Less secure app access 11 | - set GMAIL_ID, GMAIL_PASSWORD as environment variables 12 | 13 | ### Golang packages 14 | 15 | - net/http 16 | - time 17 | - net/smtp 18 | - os 19 | 20 | ### Flow chart 21 | 22 | ![Web monitoring flow chart](https://github.com/akilans/golang-mini-projects/blob/main/images/web-monitor.png?raw=true) 23 | 24 | ### Demo 25 | 26 | ![Alt Web monitoring](https://raw.githubusercontent.com/akilans/golang-mini-projects/main/demos/golang-web-monitor.gif) 27 | -------------------------------------------------------------------------------- /03-web-monitor/go.mod: -------------------------------------------------------------------------------- 1 | module web-monitor 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /03-web-monitor/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/smtp" 7 | "os" 8 | "time" 9 | ) 10 | 11 | // websites needs to be monitored 12 | var websites = map[string]int{ 13 | "https://facebook.com": 200, 14 | "https://google.com": 200, 15 | "https://twitter.com": 200, 16 | "http://localhost": 200, 17 | } 18 | 19 | // 5 seconds interval 20 | const checkInterval int = 5 21 | 22 | // 5 mins interval for alerting user for same web down 23 | const reminderInterval int = 5 24 | 25 | // to track web status 26 | type webStatus struct { 27 | web string 28 | status string 29 | lastFailure time.Time 30 | } 31 | 32 | // email config 33 | 34 | // Sender data. 35 | var from string = os.Getenv("GMAIL_ID") 36 | var password string = os.Getenv("GMAIL_PASSWORD") 37 | 38 | // Receiver email address. 39 | var to = []string{ 40 | os.Getenv("GMAIL_ID"), 41 | } 42 | 43 | // smtp server configuration. 44 | var smtpHost string = "smtp.gmail.com" 45 | var smtpPort string = "587" 46 | 47 | func main() { 48 | 49 | // initial web status slice - empty. 50 | // It start stores web status info in case of down 51 | webStatusSlice := []webStatus{} 52 | 53 | // infinite loop to keep on looping 54 | for { 55 | 56 | if len(webStatusSlice) == 0 { 57 | fmt.Println("All websites are up!!!") 58 | } 59 | 60 | for web, expectedStatusCode := range websites { 61 | 62 | res, err := http.Get(web) 63 | 64 | if err != nil { 65 | // in case of website down/connection refused 66 | alertUser(web, err, &webStatusSlice) 67 | continue 68 | } else { 69 | // check the response code 70 | if res.StatusCode != expectedStatusCode { 71 | errMsg := fmt.Errorf("%v is down", web) 72 | alertUser(web, errMsg, &webStatusSlice) 73 | } 74 | } 75 | } 76 | // sleep for $checkInterval seconds 77 | fmt.Printf("Sleep for %v seconds \n", checkInterval) 78 | time.Sleep(time.Duration(checkInterval) * time.Second) 79 | } 80 | } 81 | 82 | func alertUser(web string, err error, webStatusSlice *[]webStatus) { 83 | 84 | // this info has to be appended in status slice 85 | downWebInfo := webStatus{web, "down", time.Now()} 86 | 87 | if len(*webStatusSlice) > 0 { 88 | // check for this web down is tracked in webstatus slice 89 | // if no, then it is down for first time then trigger email 90 | // if yes, then check for which time it triggered email 91 | // If it is above the reminderInterval then trigger email 92 | previousAlert := checkForPreviousAlert(webStatusSlice, web) 93 | 94 | if !previousAlert { 95 | fmt.Printf("%v added to alert list\n", web) 96 | *webStatusSlice = append(*webStatusSlice, downWebInfo) 97 | triggerEmail(web) 98 | } else { 99 | fmt.Printf("%v already in alert list\n", web) 100 | // check when it triggered email last time 101 | triggerAnother := checkForReminderInterval(webStatusSlice, web) 102 | 103 | if triggerAnother { 104 | triggerEmail(web) 105 | } 106 | } 107 | } else { 108 | fmt.Printf("%v added to alert list\n", web) 109 | *webStatusSlice = append(*webStatusSlice, downWebInfo) 110 | triggerEmail(web) 111 | } 112 | } 113 | 114 | // alert user by sending an email 115 | func triggerEmail(web string) { 116 | 117 | //message 118 | //message := []byte("This is a test email message.") 119 | message := []byte("Subject: Web Monitor Alert \r\n\r\n" + web + " - Website is down\r\n") 120 | // Authentication. 121 | auth := smtp.PlainAuth("", from, password, smtpHost) 122 | 123 | // Sending email. 124 | err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, to, message) 125 | if err != nil { 126 | fmt.Println(err) 127 | return 128 | } 129 | fmt.Println("Email Sent Successfully!") 130 | } 131 | 132 | // check for the web is in webstatus slice 133 | func checkForPreviousAlert(webStatusSlice *[]webStatus, web string) bool { 134 | 135 | alreadyDown := false 136 | 137 | for _, webStatusInfo := range *webStatusSlice { 138 | if webStatusInfo.web == web { 139 | alreadyDown = true 140 | } 141 | } 142 | 143 | if !alreadyDown { 144 | //fmt.Printf("%v not in the alert list\n", web) 145 | return false 146 | } else { 147 | //fmt.Printf("%v already in list\n", web) 148 | return true 149 | } 150 | 151 | } 152 | 153 | // to when it trigger email last 154 | // last time triggered email + reminder interval > current timem then return true 155 | // then update time 156 | func checkForReminderInterval(webStatusSlice *[]webStatus, web string) bool { 157 | triggerAnother := false 158 | 159 | for i, webStatusInfo := range *webStatusSlice { 160 | if webStatusInfo.web == web { 161 | lastFailurePlusReminderMins := webStatusInfo.lastFailure.Add(time.Duration(reminderInterval) * time.Minute) 162 | if lastFailurePlusReminderMins.Before(time.Now()) { 163 | triggerAnother = true 164 | // update with current time 165 | (*webStatusSlice)[i] = webStatus{web, "down", time.Now()} 166 | fmt.Printf("%v - Time for new alert\n", web) 167 | } else { 168 | fmt.Printf("%v - Next alert will be send after reminder interval!!!\n", web) 169 | } 170 | } 171 | } 172 | 173 | return triggerAnother 174 | } 175 | -------------------------------------------------------------------------------- /04-bookstore-api/Bookstore-REST-API.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "3dfee47e-a751-48a3-8e55-98481a600939", 4 | "name": "Bookstore-REST-API", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "GET BOOKS", 10 | "request": { 11 | "method": "GET", 12 | "header": [], 13 | "url": { 14 | "raw": "http://localhost:8080", 15 | "protocol": "http", 16 | "host": [ 17 | "localhost" 18 | ], 19 | "port": "8080" 20 | } 21 | }, 22 | "response": [] 23 | }, 24 | { 25 | "name": "DELETE BOOK BY ID Copy", 26 | "request": { 27 | "method": "GET", 28 | "header": [], 29 | "url": { 30 | "raw": "http://localhost:8080/delete?id=5", 31 | "protocol": "http", 32 | "host": [ 33 | "localhost" 34 | ], 35 | "port": "8080", 36 | "path": [ 37 | "delete" 38 | ], 39 | "query": [ 40 | { 41 | "key": "id", 42 | "value": "5" 43 | } 44 | ] 45 | } 46 | }, 47 | "response": [] 48 | }, 49 | { 50 | "name": "GET BOOK BY ID", 51 | "request": { 52 | "method": "GET", 53 | "header": [], 54 | "url": { 55 | "raw": "http://localhost:8080/book?id=1", 56 | "protocol": "http", 57 | "host": [ 58 | "localhost" 59 | ], 60 | "port": "8080", 61 | "path": [ 62 | "book" 63 | ], 64 | "query": [ 65 | { 66 | "key": "id", 67 | "value": "1" 68 | } 69 | ] 70 | } 71 | }, 72 | "response": [] 73 | }, 74 | { 75 | "name": "ADD BOOK", 76 | "request": { 77 | "method": "POST", 78 | "header": [], 79 | "body": { 80 | "mode": "raw", 81 | "raw": "[\n {\n \"id\": \"4\",\n \"title\": \"Atomic Habits\",\n \"author\": \"James Clear\",\n \"price\": \"300\",\n \"image_url\": \"https://prodimage.images-bn.com/pimages/9780735211292_p0_v5_s600x595.jpg\"\n },\n {\n \"id\": \"5\",\n \"title\": \"The 4-hour workweekk\",\n \"author\": \"Tim Ferrisss\",\n \"price\": \"4000\",\n \"image_url\": \"https://images-eu.ssl-images-amazon.com/images/I/51iGkLC6jhL._SY264_BO1,204,203,200_QL40_FMwebp_.jpg\"\n }\n]", 82 | "options": { 83 | "raw": { 84 | "language": "json" 85 | } 86 | } 87 | }, 88 | "url": { 89 | "raw": "http://localhost:8080/add", 90 | "protocol": "http", 91 | "host": [ 92 | "localhost" 93 | ], 94 | "port": "8080", 95 | "path": [ 96 | "add" 97 | ] 98 | } 99 | }, 100 | "response": [] 101 | }, 102 | { 103 | "name": "UPDATE BOOK", 104 | "request": { 105 | "method": "POST", 106 | "header": [], 107 | "body": { 108 | "mode": "raw", 109 | "raw": "{\n \"id\": \"5\",\n \"title\": \"The 4-hour work week\",\n \"author\": \"Tim Ferrisss\",\n \"price\": \"4000\",\n \"image_url\": \"https://images-eu.ssl-images-amazon.com/images/I/51iGkLC6jhL._SY264_BO1,204,203,200_QL40_FMwebp_.jpg\"\n}", 110 | "options": { 111 | "raw": { 112 | "language": "json" 113 | } 114 | } 115 | }, 116 | "url": { 117 | "raw": "http://localhost:8080/update", 118 | "protocol": "http", 119 | "host": [ 120 | "localhost" 121 | ], 122 | "port": "8080", 123 | "path": [ 124 | "update" 125 | ] 126 | } 127 | }, 128 | "response": [] 129 | } 130 | ] 131 | } -------------------------------------------------------------------------------- /04-bookstore-api/README.md: -------------------------------------------------------------------------------- 1 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fakilans%2Fgolang-mini-projects%2Ftree%2Fmain%2F04-bookstore-api&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 2 | 3 | # Bookstore REST API 4 | 5 | This is REST based API to list, add, update and delete books. 6 | As it for beginners there will be no 3rd party packages, authentication and DB 7 | 8 | ### Demo 9 | 10 | ![Alt Bookstore API](https://raw.githubusercontent.com/akilans/golang-mini-projects/main/demos/golang-bookstore-api.gif) 11 | 12 | ### Build and run 13 | 14 | ```bash 15 | go build 16 | ./bookstore 17 | # Access localhost:8080 18 | 19 | # Import Bookstore-REST-API.postman_collection.json if you are using postman 20 | ``` 21 | 22 | ### URL and sample request 23 | 24 | - Get all the books - http://localhost:8080 25 | - Get book by id - http://localhost:8080/book?id=1 26 | - Delete book by id - http://localhost:8080/delete?id=5 27 | - Add books - http://localhost:8080/add 28 | - Update book - http://localhost:8080/add 29 | 30 | ``` 31 | # There is a typo in book id 5. We will update in next step 32 | # Method POST 33 | [ 34 | { 35 | "id": "4", 36 | "title": "Atomic Habits", 37 | "author": "James Clear", 38 | "price": "300", 39 | "image_url": "https://prodimage.images-bn.com/pimages/9780735211292_p0_v5_s600x595.jpg" 40 | }, 41 | { 42 | "id": "5", 43 | "title": "The 4-hour workweekk", 44 | "author": "Tim Ferrisss", 45 | "price": "4000", 46 | "image_url": "https://images-eu.ssl-images-amazon.com/images/I/51iGkLC6jhL._SY264_BO1,204,203,200_QL40_FMwebp_.jpg" 47 | } 48 | ] 49 | 50 | ``` 51 | 52 | - Update book by id - http://localhost:8080/update 53 | 54 | ``` 55 | # Method POST 56 | { 57 | "id": "5", 58 | "title": "The 4-hour workweek", 59 | "author": "Tim Ferriss", 60 | "price": "400", 61 | "image_url": "https://images-eu.ssl-images-amazon.com/images/I/51iGkLC6jhL._SY264_BO1,204,203,200_QL40_FMwebp_.jpg" 62 | } 63 | 64 | ``` 65 | 66 | ## Credits and references 67 | 68 | 1. [That DevOps Guy](https://www.youtube.com/c/MarcelDempers) 69 | 70 | ## Contributing 71 | 72 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 73 | -------------------------------------------------------------------------------- /04-bookstore-api/books.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | ) 7 | 8 | // Get books - returns books and error 9 | func getBooks() ([]Book, error) { 10 | books := []Book{} 11 | booksByte, err := ioutil.ReadFile("./books.json") 12 | if err != nil { 13 | return nil, err 14 | } 15 | err = json.Unmarshal(booksByte, &books) 16 | if err != nil { 17 | return nil, err 18 | } 19 | return books, nil 20 | } 21 | 22 | // Get books - returns book, book index and error 23 | func getBookById(id string) (Book, int, error) { 24 | books, err := getBooks() 25 | var requestedBook Book 26 | var requestedBookIndex int 27 | 28 | if err != nil { 29 | return Book{}, 0, err 30 | } 31 | 32 | for i, book := range books { 33 | if book.Id == id { 34 | requestedBook = book 35 | requestedBookIndex = i 36 | } 37 | } 38 | 39 | return requestedBook, requestedBookIndex, nil 40 | } 41 | 42 | // save books to books.json file 43 | func saveBooks(books []Book) error { 44 | 45 | // converting into bytes for writing into a file 46 | booksBytes, err := json.Marshal(books) 47 | 48 | checkError(err) 49 | 50 | err = ioutil.WriteFile("./books.json", booksBytes, 0644) 51 | 52 | return err 53 | 54 | } 55 | -------------------------------------------------------------------------------- /04-bookstore-api/books.json: -------------------------------------------------------------------------------- 1 | [{"id":"1","title":"How to Win Friends and Influence People","author":"Dale Carnegie","price":"600","image_url":"https://images-na.ssl-images-amazon.com/images/I/51C4Tpxn4KL._SX316_BO1,204,203,200_.jpg"},{"id":"2","title":"Think and Grow Rich","author":"Napoleon Hill","price":"500","image_url":"https://images-na.ssl-images-amazon.com/images/I/51Y8jwGiebL._SX328_BO1,204,203,200_.jpg"},{"id":"3","title":"The 7 Habits of Highly Effective People","author":"Stephen R. Covey","price":"700","image_url":"https://images-na.ssl-images-amazon.com/images/I/51qy14G7knL._SX318_BO1,204,203,200_.jpg"}] -------------------------------------------------------------------------------- /04-bookstore-api/go.mod: -------------------------------------------------------------------------------- 1 | module bookstore 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /04-bookstore-api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | ) 10 | 11 | // struct based on books.json file. Please refer 12 | type Book struct { 13 | Id string `json:"id"` 14 | Title string `json:"title"` 15 | Author string `json:"author"` 16 | Price string `json:"price"` 17 | Imageurl string `json:"image_url"` 18 | } 19 | 20 | // define port 21 | const PORT string = ":8080" 22 | 23 | // message to send as json response 24 | type Message struct { 25 | Msg string 26 | } 27 | 28 | // response as json format 29 | func jsonMessageByte(msg string) []byte { 30 | errrMessage := Message{msg} 31 | byteContent, _ := json.Marshal(errrMessage) 32 | return byteContent 33 | } 34 | 35 | // print logs in console 36 | func checkError(err error) { 37 | if err != nil { 38 | log.Printf("Error - %v", err) 39 | } 40 | 41 | } 42 | 43 | // main function starts here 44 | func main() { 45 | 46 | // http://localhost:8080 47 | http.HandleFunc("/", handleGetBooks) 48 | 49 | // http://localhost:8080/book?id=1 50 | http.HandleFunc("/book", handleGetBookById) 51 | 52 | // http://localhost:8080/add 53 | http.HandleFunc("/add", handleAddBook) 54 | 55 | // http://localhost:8080/update 56 | http.HandleFunc("/update", handleUpdateBook) 57 | 58 | // http://localhost:8080/delete?id=1 59 | http.HandleFunc("/delete", handleDeleteBookById) 60 | 61 | fmt.Printf("App is listening on %v\n", PORT) 62 | err := http.ListenAndServe(PORT, nil) 63 | // stop the app is any error to start the server 64 | if err != nil { 65 | log.Fatal(err) 66 | } 67 | } 68 | 69 | // List all the books handler 70 | func handleGetBooks(w http.ResponseWriter, r *http.Request) { 71 | books, err := getBooks() 72 | 73 | // send server error as response 74 | if err != nil { 75 | log.Printf("Server Error %v\n", err) 76 | w.WriteHeader(500) 77 | w.Write(jsonMessageByte("Internal server error")) 78 | } else { 79 | booksByte, _ := json.Marshal(books) 80 | w.Write(booksByte) 81 | } 82 | 83 | } 84 | 85 | // get book by id handler 86 | func handleGetBookById(w http.ResponseWriter, r *http.Request) { 87 | 88 | query := r.URL.Query() 89 | // get book id from URL 90 | bookId := query.Get("id") 91 | book, _, err := getBookById(bookId) 92 | // send server error as response 93 | if err != nil { 94 | log.Printf("Server Error %v\n", err) 95 | w.WriteHeader(500) 96 | w.Write(jsonMessageByte("Internal server error")) 97 | } else { 98 | // check requested book exists or not 99 | if (Book{}) == book { 100 | w.Write(jsonMessageByte("Book Not found")) 101 | } else { 102 | bookByte, _ := json.Marshal(book) 103 | w.Write(bookByte) 104 | } 105 | } 106 | } 107 | 108 | // add book handler 109 | func handleAddBook(w http.ResponseWriter, r *http.Request) { 110 | // check for post method 111 | if r.Method != "POST" { 112 | w.WriteHeader(405) 113 | w.Write(jsonMessageByte(r.Method + " - Method not allowed")) 114 | } else { 115 | // read the body 116 | newBookByte, err := ioutil.ReadAll(r.Body) 117 | // check for valid data from client 118 | if err != nil { 119 | log.Printf("Client Error %v\n", err) 120 | w.WriteHeader(400) 121 | w.Write(jsonMessageByte("Bad Request")) 122 | } else { 123 | books, _ := getBooks() // get all books 124 | var newBooks []Book // to add new book 125 | 126 | json.Unmarshal(newBookByte, &newBooks) // new book added 127 | books = append(books, newBooks...) // add both 128 | // Write all the books in books.json file 129 | err = saveBooks(books) 130 | // send server error as response 131 | if err != nil { 132 | log.Printf("Server Error %v\n", err) 133 | w.WriteHeader(500) 134 | w.Write(jsonMessageByte("Internal server error")) 135 | } else { 136 | w.Write(jsonMessageByte("New book added successfully")) 137 | } 138 | 139 | } 140 | } 141 | } 142 | 143 | // update book handler 144 | func handleUpdateBook(w http.ResponseWriter, r *http.Request) { 145 | // check for post method 146 | if r.Method != "POST" { 147 | w.WriteHeader(405) 148 | w.Write(jsonMessageByte(r.Method + " - Method not allowed")) 149 | } else { 150 | // read the body 151 | updateBookByte, err := ioutil.ReadAll(r.Body) 152 | // check for valid data from client 153 | if err != nil { 154 | log.Printf("Client Error %v\n", err) 155 | w.WriteHeader(400) 156 | w.Write(jsonMessageByte("Bad Request")) 157 | } else { 158 | var updateBook Book // to update a book 159 | 160 | err = json.Unmarshal(updateBookByte, &updateBook) // new book added 161 | checkError(err) 162 | id := updateBook.Id 163 | 164 | book, _, _ := getBookById(id) 165 | // check requested book exists or not 166 | if (Book{}) == book { 167 | w.Write(jsonMessageByte("Book Not found")) 168 | } else { 169 | books, _ := getBooks() 170 | 171 | for i, book := range books { 172 | if book.Id == updateBook.Id { 173 | books[i] = updateBook 174 | } 175 | } 176 | // write books in books.json 177 | err = saveBooks(books) 178 | // send server error as response 179 | if err != nil { 180 | log.Printf("Server Error %v\n", err) 181 | w.WriteHeader(500) 182 | w.Write(jsonMessageByte("Internal server error")) 183 | } else { 184 | w.Write(jsonMessageByte("Book updated successfully")) 185 | } 186 | } 187 | } 188 | } 189 | } 190 | 191 | // delete book by id handler 192 | func handleDeleteBookById(w http.ResponseWriter, r *http.Request) { 193 | 194 | query := r.URL.Query() 195 | // get book id from URL 196 | bookId := query.Get("id") 197 | book, book_index, err := getBookById(bookId) 198 | // send server error as response 199 | if err != nil { 200 | log.Printf("Server Error %v\n", err) 201 | w.WriteHeader(500) 202 | w.Write(jsonMessageByte("Internal server error")) 203 | } else { 204 | // check requested book exists or not 205 | if (Book{}) == book { 206 | w.Write(jsonMessageByte("Book Not found")) 207 | } else { 208 | books, _ := getBooks() 209 | // remove books from slice 210 | books = append(books[:book_index], books[book_index+1:]...) 211 | saveBooks(books) 212 | w.Write(jsonMessageByte("Book deleted successfully")) 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /05-random-password-flag/README.md: -------------------------------------------------------------------------------- 1 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fakilans%2Fgolang-mini-projects%2Ftree%2Fmain%2F05-random-password-flag&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 2 | 3 | # Generate Random Passwords using Golang 4 | 5 | It is golang command line application to generate random passwords 6 | using "math/rand" and flag golang inbuilt package 7 | 8 | ## Demo 9 | 10 | ![Alt Generate random password](https://raw.githubusercontent.com/akilans/golang-mini-projects/main/demos/golang-random-password-flag.gif) 11 | 12 | ## Usage 13 | 14 | ```bash 15 | 16 | # clone a repo 17 | git clone https://github.com/akilans/golang-mini-projects.git 18 | 19 | # go to the 05-random-password dir 20 | cd 05-random-password-flag 21 | 22 | # build 23 | go build 24 | 25 | # run 26 | 27 | ./random-password 28 | 29 | # Enter the number of passwords you want to generate 30 | # sample oputput 31 | ./random-password --count=3 --length=10 --min-number=2 --min-special=2 --min-upper=2 32 | Password 1 is bC#09z3v55 33 | Password 2 is f2311wm5M- 34 | Password 3 is 9$q660h6hH 35 | 36 | ``` 37 | 38 | ## Reference 39 | 40 | [Golang By Example](https://golangbyexample.com/generate-random-password-golang/) 41 | 42 | ## Credits 43 | 44 | [Jackson Atkins](https://github.com/realugbun) 45 | 46 | ## Contributing 47 | 48 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 49 | -------------------------------------------------------------------------------- /05-random-password-flag/go.mod: -------------------------------------------------------------------------------- 1 | module random-password 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /05-random-password-flag/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "math/rand" 7 | "os" 8 | "time" 9 | ) 10 | 11 | // define all the global variables 12 | const ( 13 | lowerCharSet = "abcdefghijklmnopqrstuvwxyz" 14 | upperCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 15 | specialCharSet string = "!@#$%^&*()-" 16 | numberCharSet = "123567890" 17 | ) 18 | 19 | func main() { 20 | 21 | var ( 22 | passwordLength int 23 | numberOfPasswords int 24 | minSpecialChar int 25 | minUpperChar int 26 | minNumberChar int 27 | ) 28 | 29 | // Set the flags. The first value is the flag, the second is the default value, the third is the help message. Try go run main.go -h 30 | pwl := flag.Int("length", 10, "The length of the desired password") 31 | npw := flag.Int("count", 1, "The number of desired passwords") 32 | s := flag.Int("min-special", 2, "The minimum number of special characters") 33 | u := flag.Int("min-upper", 2, "The minimum number of uppercase characters") 34 | n := flag.Int("min-number", 2, "The minimum number of numbers") 35 | 36 | // Tell the application to read the values 37 | flag.Parse() 38 | 39 | // Set the variables to be equal to what was provided via flags. Note: flags are pointers 40 | passwordLength = *pwl 41 | numberOfPasswords = *npw 42 | minSpecialChar = *s 43 | minUpperChar = *u 44 | minNumberChar = *n 45 | 46 | //check if the password length matches the criteria 47 | 48 | totalCharLenWithoutLowerChar := minUpperChar + minSpecialChar + minNumberChar 49 | 50 | if totalCharLenWithoutLowerChar >= passwordLength { 51 | fmt.Println("Please provide valid password length") 52 | os.Exit(1) 53 | } 54 | 55 | // it generate random number e 56 | rand.Seed(time.Now().UnixNano()) 57 | 58 | for i := 0; i < numberOfPasswords; i++ { 59 | password := generatePassword(passwordLength, minSpecialChar, minUpperChar, minNumberChar) 60 | fmt.Printf("Password %v is %v \n", i+1, password) 61 | } 62 | 63 | } 64 | 65 | func generatePassword(passwordLength int, minSpecialChar int, minUpperChar int, minNumberChar int) string { 66 | 67 | // declare empty password variable 68 | password := "" 69 | 70 | // generate random special character based on minSpecialChar 71 | 72 | for i := 0; i < minSpecialChar; i++ { 73 | random := rand.Intn(len(specialCharSet)) 74 | //fmt.Println(specialCharSet[random]) 75 | //fmt.Printf("%v and %T \n", random, specialCharSet[random]) 76 | password = password + string(specialCharSet[random]) 77 | } 78 | 79 | // generate random upper character based on minUpperChar 80 | for i := 0; i < minUpperChar; i++ { 81 | random := rand.Intn(len(upperCharSet)) 82 | password = password + string(upperCharSet[random]) 83 | } 84 | 85 | // generate random upper character based on minNumberChar 86 | for i := 0; i < minNumberChar; i++ { 87 | random := rand.Intn(len(numberCharSet)) 88 | password = password + string(numberCharSet[random]) 89 | } 90 | 91 | // find remaining lowerChar 92 | totalCharLenWithoutLowerChar := minUpperChar + minSpecialChar + minNumberChar 93 | 94 | remainingCharLen := passwordLength - totalCharLenWithoutLowerChar 95 | 96 | // generate random lower character based on remainingCharLen 97 | for i := 0; i < remainingCharLen; i++ { 98 | random := rand.Intn(len(lowerCharSet)) 99 | password = password + string(lowerCharSet[random]) 100 | } 101 | 102 | // shuffle the password string 103 | 104 | passwordRune := []rune(password) 105 | rand.Shuffle(len(passwordRune), func(i, j int) { 106 | passwordRune[i], passwordRune[j] = passwordRune[j], passwordRune[i] 107 | }) 108 | 109 | password = string(passwordRune) 110 | return password 111 | } 112 | -------------------------------------------------------------------------------- /05-random-password/README.md: -------------------------------------------------------------------------------- 1 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fakilans%2Fgolang-mini-projects%2Ftree%2Fmain%2F05-random-password&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 2 | 3 | # Generate Random Passwords using Golang 4 | 5 | It is golang command line application to generate random passwords 6 | using "math/rand" golang inbuilt package 7 | 8 | ## Demo 9 | 10 | ![Alt Generate random password](https://raw.githubusercontent.com/akilans/golang-mini-projects/main/demos/golang-random-password.gif) 11 | 12 | ## Usage 13 | 14 | ```bash 15 | 16 | # clone a repo 17 | git clone https://github.com/akilans/golang-mini-projects.git 18 | 19 | # go to the 05-random-password dir 20 | cd 05-random-password 21 | 22 | # build 23 | go build 24 | 25 | # run 26 | 27 | ./random-password 28 | 29 | # Enter the number of passwords you want to generate 30 | # sample oputput 31 | How many passwords you want to generate? - 3 32 | Password 1 is 6l*@7CnzxC 33 | Password 2 is !k63orMwI) 34 | Password 3 is jS20!f&pNk 35 | 36 | ``` 37 | 38 | ## Reference 39 | 40 | [Golang By Example](https://golangbyexample.com/generate-random-password-golang/) 41 | 42 | ## Contributing 43 | 44 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 45 | -------------------------------------------------------------------------------- /05-random-password/go.mod: -------------------------------------------------------------------------------- 1 | module random-password 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /05-random-password/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "math/rand" 7 | "os" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | // define all the global variables 13 | var ( 14 | lowerCharSet = "abcdefghijklmnopqrstuvwxyz" 15 | upperCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 16 | specialCharSet string = "!@#$%^&*()-" 17 | numberCharSet = "123567890" 18 | minSpecialChar = 2 19 | minUpperChar = 2 20 | minNumberChar = 2 21 | passwordLength = 10 22 | ) 23 | 24 | func main() { 25 | 26 | //check if the password length matches the criteria 27 | 28 | totalCharLenWithoutLowerChar := minUpperChar + minSpecialChar + minNumberChar 29 | 30 | if totalCharLenWithoutLowerChar >= passwordLength { 31 | fmt.Println("Please provide valid password length") 32 | os.Exit(1) 33 | } 34 | 35 | // Get the user input - target folder needs to be organized 36 | scanner := bufio.NewScanner(os.Stdin) 37 | fmt.Printf("How many passwords you want to generate? - ") 38 | scanner.Scan() 39 | 40 | numberOfPasswords, err := strconv.Atoi(scanner.Text()) 41 | 42 | if err != nil { 43 | fmt.Println("Please provide correct value for number of passwords") 44 | os.Exit(1) 45 | } 46 | 47 | // it generate random number e 48 | rand.Seed(time.Now().Unix()) 49 | 50 | for i := 0; i < numberOfPasswords; i++ { 51 | password := generatePassword() 52 | fmt.Printf("Password %v is %v \n", i+1, password) 53 | } 54 | 55 | } 56 | 57 | func generatePassword() string { 58 | 59 | // declare empty password variable 60 | password := "" 61 | 62 | // generate random special character based on minSpecialChar 63 | 64 | for i := 0; i < minSpecialChar; i++ { 65 | random := rand.Intn(len(specialCharSet)) 66 | //fmt.Println(specialCharSet[random]) 67 | //fmt.Printf("%v and %T \n", random, specialCharSet[random]) 68 | password = password + string(specialCharSet[random]) 69 | } 70 | 71 | // generate random upper character based on minUpperChar 72 | for i := 0; i < minUpperChar; i++ { 73 | random := rand.Intn(len(upperCharSet)) 74 | password = password + string(upperCharSet[random]) 75 | } 76 | 77 | // generate random upper character based on minNumberChar 78 | for i := 0; i < minNumberChar; i++ { 79 | random := rand.Intn(len(numberCharSet)) 80 | password = password + string(numberCharSet[random]) 81 | } 82 | 83 | // find remaining lowerChar 84 | totalCharLenWithoutLowerChar := minUpperChar + minSpecialChar + minNumberChar 85 | 86 | remainingCharLen := passwordLength - totalCharLenWithoutLowerChar 87 | 88 | // generate random lower character based on remainingCharLen 89 | for i := 0; i < remainingCharLen; i++ { 90 | random := rand.Intn(len(lowerCharSet)) 91 | password = password + string(lowerCharSet[random]) 92 | } 93 | 94 | // shuffle the password string 95 | 96 | passwordRune := []rune(password) 97 | rand.Shuffle(len(passwordRune), func(i, j int) { 98 | passwordRune[i], passwordRune[j] = passwordRune[j], passwordRune[i] 99 | }) 100 | 101 | password = string(passwordRune) 102 | return password 103 | } 104 | -------------------------------------------------------------------------------- /06-system-monitor/README.md: -------------------------------------------------------------------------------- 1 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fakilans%2Fgolang-mini-projects%2Ftree%2Fmain%2F06-system-monitor&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 2 | 3 | # Get system metrics and Expose as REST API 4 | 5 | It is golang REST based API to get the below system metrics using [gopsutil](https://github.com/shirou/gopsutil) package 6 | 7 | - Hostname 8 | - Total memory 9 | - Free memory 10 | - Memory usage in percentage 11 | - System architecture 12 | - Operating system 13 | - Number of CPU cores 14 | - Cpu usage in percentage 15 | 16 | ## Demo 17 | 18 | ![Alt System Metrics](https://raw.githubusercontent.com/akilans/golang-mini-projects/main/demos/golang-system-metrics.gif) 19 | 20 | ## Usage 21 | 22 | ```bash 23 | 24 | # clone a repo 25 | git clone https://github.com/akilans/golang-mini-projects.git 26 | 27 | # go to the 06-system-monitor dir 28 | cd 06-system-monitor 29 | 30 | # build 31 | go build 32 | 33 | # run 34 | 35 | ./monitor-agent 36 | 37 | # Access localhost:8080 in browser 38 | 39 | 40 | ``` 41 | 42 | ![Alt System Metrics](https://raw.githubusercontent.com/akilans/golang-mini-projects/main/images/golang-system-metrics.png?raw=true) 43 | 44 | ## Contributing 45 | 46 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 47 | -------------------------------------------------------------------------------- /06-system-monitor/go.mod: -------------------------------------------------------------------------------- 1 | module monitor-agent 2 | 3 | go 1.17 4 | 5 | require github.com/shirou/gopsutil v3.21.10+incompatible 6 | 7 | require ( 8 | github.com/StackExchange/wmi v1.2.1 // indirect 9 | github.com/go-ole/go-ole v1.2.5 // indirect 10 | github.com/stretchr/testify v1.7.0 // indirect 11 | github.com/tklauser/go-sysconf v0.3.9 // indirect 12 | github.com/tklauser/numcpus v0.3.0 // indirect 13 | golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /06-system-monitor/go.sum: -------------------------------------------------------------------------------- 1 | github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= 2 | github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= 6 | github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/shirou/gopsutil v3.21.10+incompatible h1:AL2kpVykjkqeN+MFe1WcwSBVUjGjvdU8/ubvCuXAjrU= 10 | github.com/shirou/gopsutil v3.21.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 11 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 12 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 13 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 14 | github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= 15 | github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= 16 | github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= 17 | github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= 18 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 19 | golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 20 | golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 h1:TyHqChC80pFkXWraUUf6RuB5IqFdQieMLwwCJokV2pc= 21 | golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 22 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 23 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 24 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 25 | -------------------------------------------------------------------------------- /06-system-monitor/info.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/shirou/gopsutil/cpu" 8 | "github.com/shirou/gopsutil/host" 9 | "github.com/shirou/gopsutil/mem" 10 | ) 11 | 12 | // print logs in console 13 | func checkError(err error) { 14 | if err != nil { 15 | log.Printf("Error - %v", err) 16 | } 17 | 18 | } 19 | 20 | // Get memory info 21 | // It returns total, free memory in MB and used memory percentage 22 | func getMemInfo() (uint64, uint64, float64) { 23 | 24 | v, _ := mem.VirtualMemory() 25 | 26 | totalMemory := v.Total / 1000000 //in MB 27 | freeMemory := v.Free / 1000000 // in MB 28 | usedMemoryPercentage := v.UsedPercent // in % 29 | 30 | return totalMemory, freeMemory, usedMemoryPercentage 31 | } 32 | 33 | // get host info 34 | // It returns hostName, architecture, operationSystem 35 | func getHostInfo() (string, string, string) { 36 | 37 | architecture, _ := host.KernelArch() 38 | hostInfo, _ := host.Info() 39 | hostName := hostInfo.Hostname 40 | operationSystem := hostInfo.OS 41 | return hostName, architecture, operationSystem 42 | } 43 | 44 | // get cpu info 45 | // it returns cpuNumCores, cpuPercentage 46 | func getCpuInfo() (int, float64) { 47 | 48 | cpuNumCores, _ := cpu.Counts(true) 49 | 50 | cpuPercentage, _ := cpu.Percent(time.Second, false) 51 | 52 | return cpuNumCores, cpuPercentage[0] 53 | 54 | } 55 | -------------------------------------------------------------------------------- /06-system-monitor/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | ) 9 | 10 | // define port 11 | const PORT string = ":8080" 12 | 13 | // define system info type 14 | 15 | type systemInfo struct { 16 | HostName string `json:"hostname"` 17 | TotalMemory uint64 `json:"total_memory"` 18 | FreeMemory uint64 `json:"free_memory"` 19 | MemoryUsedPercentage float64 `json:"memory_used_percentage"` 20 | Architecture string `json:"architecture"` 21 | OperationSystem string `json:"os"` 22 | NumberOfCpuCores int `json:"number_of_cpu_cores"` 23 | CpuUsedPercentage float64 `json:"cpu_used_percentage"` 24 | } 25 | 26 | // main function starts here 27 | func main() { 28 | 29 | // http://localhost:8080 30 | http.HandleFunc("/", getSysInfo) 31 | 32 | fmt.Printf("App is listening on %v\n", PORT) 33 | err := http.ListenAndServe(PORT, nil) 34 | // stop the app is any error to start the server 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | } 39 | 40 | func getSysInfo(w http.ResponseWriter, r *http.Request) { 41 | 42 | totalMemory, freeMemory, usedMemoryPercentage := getMemInfo() 43 | hostName, architecture, operationSystem := getHostInfo() 44 | cpuNumCores, cpuPercentage := getCpuInfo() 45 | // all system info as systemInfo struct 46 | sysInfo := systemInfo{ 47 | hostName, 48 | totalMemory, 49 | freeMemory, 50 | usedMemoryPercentage, 51 | architecture, 52 | operationSystem, 53 | cpuNumCores, 54 | cpuPercentage, 55 | } 56 | 57 | // converting into bytes for writing as response 58 | sysInfoByte, err := json.Marshal(sysInfo) 59 | 60 | // capture error 61 | checkError(err) 62 | 63 | //write into response body 64 | w.Write(sysInfoByte) 65 | 66 | } 67 | -------------------------------------------------------------------------------- /07-ssh-sftp-agent/README.md: -------------------------------------------------------------------------------- 1 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fakilans%2Fgolang-mini-projects%2Ftree%2Fmain%2F07-ssh-sftp-agent&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 2 | 3 | # Execute command, Create, Upload and Download file using Golang 4 | 5 | It is a golang based application to execute, create, upload, and download a file to and from a remote server using ssh and sftp package 6 | 7 | This can improved by flag cli but my aim is to make you understand SSH and SFTP logic with golang 8 | 9 | ## Prerequisites 10 | 11 | - Go 12 | - SSH server with username and password authentication or key based authentication 13 | 14 | ## Demo 15 | 16 | ![Alt SSH and SFTP agent](https://raw.githubusercontent.com/akilans/golang-mini-projects/main/demos/golang-ssh-sftp.gif) 17 | 18 | ## Usage 19 | 20 | This program will do the following tasks 21 | 22 | - Execute a command on remote server 23 | - Create a file on remote server 24 | - Upload a local file to remote sever 25 | - Download a remote file to local 26 | 27 | ```bash 28 | 29 | # clone a repo 30 | git clone https://github.com/akilans/golang-mini-projects.git 31 | 32 | # go to the 07-ssh-agent dir 33 | cd 07-ssh-sftp-agent 34 | 35 | # build 36 | go build 37 | 38 | # run 39 | 40 | ./go-ssh-sftp 41 | 42 | 43 | ``` 44 | -------------------------------------------------------------------------------- /07-ssh-sftp-agent/go.mod: -------------------------------------------------------------------------------- 1 | module go-ssh-sftp 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/pkg/sftp v1.13.4 7 | golang.org/x/crypto v0.0.0-20211202192323-5770296d904e 8 | ) 9 | 10 | require ( 11 | github.com/kr/fs v0.1.0 // indirect 12 | golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect 13 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /07-ssh-sftp-agent/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= 4 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 5 | github.com/pkg/sftp v1.13.4 h1:Lb0RYJCmgUcBgZosfoi9Y9sbl6+LJgOIgk/2Y4YjMFg= 6 | github.com/pkg/sftp v1.13.4/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 10 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 11 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 12 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 13 | golang.org/x/crypto v0.0.0-20211202192323-5770296d904e h1:MUP6MR3rJ7Gk9LEia0LP2ytiH6MuCfs7qYz+47jGdD8= 14 | golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 15 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 16 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 17 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 18 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 19 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 20 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 21 | golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E= 22 | golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 23 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 24 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= 25 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 26 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 27 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 28 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 29 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 30 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 31 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 32 | -------------------------------------------------------------------------------- /07-ssh-sftp-agent/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | 11 | "github.com/pkg/sftp" 12 | "golang.org/x/crypto/ssh" 13 | ) 14 | 15 | // check for any error 16 | func check(err error) { 17 | if err != nil { 18 | fmt.Printf("Error Happened %s \n", err) 19 | os.Exit(1) 20 | } 21 | } 22 | 23 | // In case if you have private key authentication 24 | // uncomment the sshKeyPath variable 25 | // uncomment sshDemoWithPrivateKey() function 26 | // call the sshDemoWithPrivateKey() function instead of sshDemoWithPassword() 27 | var ( 28 | sshUserName = "vagrant" 29 | sshPassword = "vagrant" 30 | sshKeyPath = "/home/akilan/Desktop/linux-vagrant/.vagrant/machines/ubuntu_ssh_server/virtualbox/private_key" 31 | sshHostname = "192.168.33.10:22" 32 | commandToExec = "ls -la /home/vagrant" 33 | fileToUpload = "./upload.txt" 34 | fileUploadLocation = "/home/vagrant/upload.txt" 35 | fileToDownload = "/home/vagrant/download.txt" 36 | ) 37 | 38 | func main() { 39 | 40 | fmt.Println("....Golang SSH Demo......") 41 | 42 | //conf := sshDemoWithPassword() // username and password authentication 43 | conf := sshDemoWithPrivateKey() // username and private key authentication 44 | 45 | // open ssh connection 46 | sshClient, err := ssh.Dial("tcp", sshHostname, conf) 47 | check(err) 48 | session, err := sshClient.NewSession() 49 | check(err) 50 | defer session.Close() 51 | 52 | // execute command on remote server 53 | var b bytes.Buffer 54 | session.Stdout = &b 55 | err = session.Run(commandToExec) 56 | check(err) 57 | log.Printf("%s: %s", commandToExec, b.String()) 58 | 59 | // open sftp connection 60 | sftpClient, err := sftp.NewClient(sshClient) 61 | check(err) 62 | defer sftpClient.Close() 63 | 64 | // create a file 65 | createFile, err := sftpClient.Create(fileToDownload) 66 | check(err) 67 | text := "This file created by Golang SSH.\nThis will be downloaded by Golang SSH\n" 68 | _, err = createFile.Write([]byte(text)) 69 | check(err) 70 | fmt.Println("Created file ", fileToDownload) 71 | 72 | // Upload a file 73 | srcFile, err := os.Open(fileToUpload) 74 | check(err) 75 | defer srcFile.Close() 76 | 77 | dstFile, err := sftpClient.Create(fileUploadLocation) 78 | check(err) 79 | defer dstFile.Close() 80 | 81 | _, err = io.Copy(dstFile, srcFile) 82 | check(err) 83 | fmt.Println("File uploaded successfully ", fileUploadLocation) 84 | 85 | // Download a file 86 | remoteFile, err := sftpClient.Open(fileToDownload) 87 | if err != nil { 88 | fmt.Fprintf(os.Stderr, "Unable to open remote file: %v\n", err) 89 | return 90 | } 91 | defer remoteFile.Close() 92 | 93 | localFile, err := os.Create("./download.txt") 94 | check(err) 95 | defer localFile.Close() 96 | 97 | _, err = io.Copy(localFile, remoteFile) 98 | check(err) 99 | fmt.Println("File downloaded successfully") 100 | 101 | } 102 | 103 | func sshDemoWithPassword() *ssh.ClientConfig { 104 | 105 | // ssh config with password authentication 106 | conf := &ssh.ClientConfig{ 107 | User: sshUserName, 108 | Auth: []ssh.AuthMethod{ 109 | ssh.Password(sshPassword), 110 | }, 111 | HostKeyCallback: ssh.InsecureIgnoreHostKey(), 112 | } 113 | 114 | return conf 115 | 116 | } 117 | 118 | func sshDemoWithPrivateKey() *ssh.ClientConfig { 119 | keyByte, err := ioutil.ReadFile(sshKeyPath) 120 | check(err) 121 | key, err := ssh.ParsePrivateKey(keyByte) 122 | check(err) 123 | 124 | // ssh config 125 | conf := &ssh.ClientConfig{ 126 | User: sshUserName, 127 | Auth: []ssh.AuthMethod{ 128 | ssh.PublicKeys(key), 129 | }, 130 | HostKeyCallback: ssh.InsecureIgnoreHostKey(), 131 | } 132 | 133 | return conf 134 | } 135 | -------------------------------------------------------------------------------- /07-ssh-sftp-agent/upload.txt: -------------------------------------------------------------------------------- 1 | This file uploaded to remote ssh server by Golang SSH agent 2 | -------------------------------------------------------------------------------- /08-file-folder-zip/.gitignore: -------------------------------------------------------------------------------- 1 | parent/ 2 | *.zip -------------------------------------------------------------------------------- /08-file-folder-zip/README.md: -------------------------------------------------------------------------------- 1 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fakilans%2Fgolang-mini-projects%2Ftree%2Fmain%2F08-file-folder-zip&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 2 | 3 | # Golang - Deal with Folders, Files and Zip files 4 | 5 | This program create folders, files and zip file. 6 | 7 | ### Folder structure 8 | 9 | parent/ 10 | ├── child 11 | │ └── child.txt 12 | ├── empty-folder 13 | └── parent.txt 14 | 15 | ### Demo 16 | 17 | ![Alt Folder, File and Zip](https://raw.githubusercontent.com/akilans/golang-mini-projects/main/demos/golang-zip.gif) 18 | 19 | ### Usage 20 | 21 | This program will do the following tasks 22 | 23 | - Create folders 24 | - Create files 25 | - Create a zip with correct folder structure 26 | 27 | ```bash 28 | 29 | # clone a repo 30 | git clone https://github.com/akilans/golang-mini-projects.git 31 | 32 | # go to the 08-file-folder-zip 33 | cd 08-file-folder-zip 34 | 35 | # build 36 | go build 37 | 38 | # run 39 | 40 | ./file-folder-zip 41 | 42 | 43 | ``` 44 | -------------------------------------------------------------------------------- /08-file-folder-zip/go.mod: -------------------------------------------------------------------------------- 1 | module file-folder-zip 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /08-file-folder-zip/main-bk: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "archive/zip" 5 | "fmt" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | ) 10 | 11 | // define all the constant variable 12 | const ( 13 | MAIN_FOLDER_NAME = "parent" 14 | SUB_FOLDER_NAME = "child" 15 | EMPTY_FOLDER_NAME = "empty-folder" 16 | FILE_UNDER_MAIN_FOLDER = "parent.txt" 17 | FILE_UNDER_SUB_FOLDER = "child.txt" 18 | MAIN_FOLDER_FILE_CONTENT = "Hello this is from main folder file" 19 | SUB_FOLDER_FILE_CONTENT = "Hello this is from sub folder file" 20 | ZIP_FILE_NAME = "golang-folder.zip" 21 | ) 22 | 23 | // check for error and stop the execution 24 | func checkForError(err error) { 25 | if err != nil { 26 | fmt.Println("Error - ", err) 27 | os.Exit(1) 28 | } 29 | } 30 | 31 | // create folder if not exists 32 | func createFolder(folderPath string) { 33 | // check if folder exists 34 | _, err := os.Stat(folderPath) 35 | 36 | if os.IsNotExist(err) { 37 | // create folder if no folder 38 | err = os.Mkdir(folderPath, 0755) 39 | checkForError(err) 40 | fmt.Printf("%s created successfully\n", folderPath) 41 | } else { 42 | fmt.Printf("%s - already exists\n", folderPath) 43 | os.Exit(1) 44 | } 45 | } 46 | 47 | // create file if not exists 48 | 49 | func createFile(filePath, content string) { 50 | 51 | fmt.Println(filePath) 52 | // check if file exists 53 | _, err := os.Stat(filePath) 54 | 55 | if os.IsNotExist(err) { 56 | // create file if no file 57 | f, err := os.Create(filePath) 58 | checkForError(err) 59 | defer f.Close() 60 | 61 | _, err = f.WriteString(content) 62 | checkForError(err) 63 | fmt.Printf("%s created successfully\n", filePath) 64 | } else { 65 | fmt.Printf("%s - already exists\n", filePath) 66 | os.Exit(1) 67 | } 68 | } 69 | 70 | // Main function 71 | func main() { 72 | 73 | var targetFilePaths []string 74 | // create a main dir 75 | createFolder(MAIN_FOLDER_NAME) 76 | 77 | // create a sub folder 78 | createFolder(filepath.Join(MAIN_FOLDER_NAME, SUB_FOLDER_NAME)) 79 | 80 | // create a empty sub folder 81 | createFolder(filepath.Join(MAIN_FOLDER_NAME, EMPTY_FOLDER_NAME)) 82 | 83 | // create a file under main folder 84 | createFile(filepath.Join(MAIN_FOLDER_NAME, FILE_UNDER_MAIN_FOLDER), MAIN_FOLDER_FILE_CONTENT) 85 | 86 | // create a file under sub folder 87 | createFile(filepath.Join(MAIN_FOLDER_NAME, SUB_FOLDER_NAME, FILE_UNDER_SUB_FOLDER), SUB_FOLDER_FILE_CONTENT) 88 | 89 | // get filepaths in all folders 90 | err := filepath.Walk(MAIN_FOLDER_NAME, func(path string, info os.FileInfo, err error) error { 91 | //fmt.Println(path) 92 | if info.IsDir() { 93 | targetFilePaths = append(targetFilePaths, path+"/") 94 | return nil 95 | } 96 | targetFilePaths = append(targetFilePaths, path) 97 | return nil 98 | }) 99 | checkForError(err) 100 | 101 | // zip file logic starts here 102 | ZipFile, err := os.Create(ZIP_FILE_NAME) 103 | checkForError(err) 104 | defer ZipFile.Close() 105 | 106 | zipWriter := zip.NewWriter(ZipFile) 107 | defer zipWriter.Close() 108 | 109 | for _, targetFilePath := range targetFilePaths { 110 | 111 | //fmt.Println(targetFilePath) 112 | 113 | fileInfo, err := os.Stat(targetFilePath) 114 | checkForError(err) 115 | 116 | if fileInfo.IsDir() { 117 | _, err := zipWriter.Create(targetFilePath) 118 | checkForError(err) 119 | } else { 120 | file, err := os.Open(targetFilePath) 121 | checkForError(err) 122 | defer file.Close() 123 | 124 | w, err := zipWriter.Create(targetFilePath) 125 | checkForError(err) 126 | _, err = io.Copy(w, file) 127 | checkForError(err) 128 | } 129 | 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /08-file-folder-zip/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "archive/zip" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | ) 11 | 12 | const ( 13 | ZIP_FILE_NAME = "example.zip" 14 | MAIN_FOLDER_NAME = "parent" 15 | ) 16 | 17 | type fileMeta struct { 18 | Path string 19 | IsDir bool 20 | } 21 | 22 | func main() { 23 | var files []fileMeta 24 | err := filepath.Walk(MAIN_FOLDER_NAME, func(path string, info os.FileInfo, err error) error { 25 | files = append(files, fileMeta{Path: path, IsDir: info.IsDir()}) 26 | return nil 27 | }) 28 | if err != nil { 29 | log.Fatalln(err) 30 | } 31 | 32 | z, err := os.Create(ZIP_FILE_NAME) 33 | if err != nil { 34 | log.Fatalln(err) 35 | } 36 | defer z.Close() 37 | 38 | zw := zip.NewWriter(z) 39 | defer zw.Close() 40 | 41 | for _, f := range files { 42 | path := f.Path 43 | if f.IsDir { 44 | path = fmt.Sprintf("%s%c", path, os.PathSeparator) 45 | } 46 | 47 | w, err := zw.Create(path) 48 | if err != nil { 49 | log.Fatalln(err) 50 | } 51 | 52 | if !f.IsDir { 53 | file, err := os.Open(f.Path) 54 | if err != nil { 55 | log.Fatalln(err) 56 | } 57 | defer file.Close() 58 | 59 | if _, err = io.Copy(w, file); err != nil { 60 | log.Fatalln(err) 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /09-pack-mod-demo/README.md: -------------------------------------------------------------------------------- 1 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fakilans%2Fgolang-mini-projects%2Ftree%2Fmain%2F09-pack-mod-demo&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 2 | 3 | # Golang Package and Module demo 4 | 5 | This program uses package from [https://github.com/akilans/go-module-demo] 6 | 7 | ### Notes 8 | 9 | - Have you ever used any packages in simple go app?. Definitely yes 10 | - Below example import "fmt" package. Do you wonder why all the functions, variable follows Camelcase pattern? 11 | - It is because fmt package exports variables, functions to other packages. 12 | - If any variable or function not following Camelcase then it will not be exported. It will be used inside a parent package 13 | 14 | ```go 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | ) 20 | 21 | func main(){ 22 | fmt.Println("Hello Go!") 23 | fmt.Printf("Hello Go!\n") 24 | } 25 | 26 | ``` 27 | 28 | ### Reference Module 29 | 30 | - In this application we are using the below module 31 | - [Please refer the go-module-demo here ](https://github.com/akilans/go-module-demo) 32 | 33 | ### Run this app 34 | 35 | ```bash 36 | go mod tidy 37 | go run main.go 38 | ``` 39 | -------------------------------------------------------------------------------- /09-pack-mod-demo/go.mod: -------------------------------------------------------------------------------- 1 | module module-demo 2 | 3 | go 1.17 4 | 5 | require github.com/akilans/go-module-demo v0.0.0-20220423063714-3771281ef276 // indirect 6 | -------------------------------------------------------------------------------- /09-pack-mod-demo/go.sum: -------------------------------------------------------------------------------- 1 | github.com/akilans/go-module-demo v0.0.0-20220423063714-3771281ef276 h1:6vsF+7BBQkSEA4I0SrfQ2MAj29oTtblAkkSAgbqsrCY= 2 | github.com/akilans/go-module-demo v0.0.0-20220423063714-3771281ef276/go.mod h1:O8pgezsJ3SROs/IgAVnXJ85aWQroZxnorJLV/sqjt+g= 3 | -------------------------------------------------------------------------------- /09-pack-mod-demo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | // bye is a folder name and goodbye is a package name. 6 | // Best practice is use the same name for folder and package like hello package 7 | goodbye "github.com/akilans/go-module-demo/bye" 8 | "github.com/akilans/go-module-demo/hello" 9 | ) 10 | 11 | func main() { 12 | fmt.Println("Hello") 13 | hello.SayHello() // SayHello exported by default as it follows camelcase pattern 14 | goodbye.SayBye() // SayBye exported by default as it follows camelcase pattern 15 | } 16 | -------------------------------------------------------------------------------- /10-golang-ssh-concurrent-file-uploder/.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant/ 2 | golang-ssh-concurrent-file-uploder -------------------------------------------------------------------------------- /10-golang-ssh-concurrent-file-uploder/README.md: -------------------------------------------------------------------------------- 1 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fakilans%2Fgolang-mini-projects%2Ftree%2Fmain%2F10-golang-ssh-concurrent-file-uploder&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 2 | 3 | # Concurrent File Uploader to remote servers using Go Routine 4 | 5 | It is golang command line application to upload files to remote servers concurrently using golang's inbuilt go routine and waitgroup 6 | 7 | ## Demo 8 | 9 | ![Alt Concurrent SSH File Uploader](https://raw.githubusercontent.com/akilans/golang-mini-projects/main/demos/golang-ssh-concurrent-file-uploder.gif) 10 | 11 | ## Usage 12 | 13 | - Normal method will upload hello.txt file to all remote servers 14 | - Go routine will upload hi.txt file to all remote servers 15 | - hello.txt and hi.txt file size is same 16 | 17 | ```bash 18 | 19 | # clone a repo 20 | git clone https://github.com/akilans/golang-mini-projects.git 21 | 22 | # go to the 10-golang-ssh-concurrent-file-uploder dir 23 | cd 10-golang-ssh-concurrent-file-uploder 24 | 25 | # build 26 | go build 27 | 28 | # run 29 | 30 | ./golang-ssh-concurrent-file-uploder 31 | 32 | # sample oputput 33 | ./golang-ssh-concurrent-file-uploder 34 | File hello.txt uploaded successfully to 127.0.0.1:2222 35 | File hello.txt uploaded successfully to 127.0.0.1:2200 36 | File hello.txt uploaded successfully to 127.0.0.1:2201 37 | File hello.txt uploaded successfully to 127.0.0.1:2202 38 | Without Go Routine - Upload task took 4.13 seconds 39 | File hi.txt uploaded successfully to 127.0.0.1:2222 40 | File hi.txt uploaded successfully to 127.0.0.1:2202 41 | File hi.txt uploaded successfully to 127.0.0.1:2201 42 | File hi.txt uploaded successfully to 127.0.0.1:2200 43 | With Go Routine - Upload task took 2.19 seconds 44 | ``` 45 | 46 | ## Contributing 47 | 48 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 49 | -------------------------------------------------------------------------------- /10-golang-ssh-concurrent-file-uploder/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # All Vagrant configuration is done below. The "2" in Vagrant.configure 5 | # configures the configuration version (we support older styles for 6 | # backwards compatibility). Please don't change it unless you know what 7 | # you're doing. 8 | 9 | Vagrant.configure("2") do |config| 10 | 11 | # First VM 12 | config.vm.define "ubuntu_1" do |ubuntu_1| 13 | ubuntu_1.vm.box = "ubuntu/focal64" 14 | ubuntu_1.vm.network "private_network", ip: "192.168.56.1" 15 | ubuntu_1.vm.hostname = "ubuntu-1" 16 | 17 | ubuntu_1.vm.provider "virtualbox" do |vb| 18 | vb.memory = "1024" 19 | vb.name = "ubuntu_1" 20 | end 21 | end 22 | 23 | # Second VM 24 | config.vm.define "ubuntu_2" do |ubuntu_2| 25 | ubuntu_2.vm.box = "ubuntu/focal64" 26 | ubuntu_2.vm.network "private_network", ip: "192.168.56.2" 27 | ubuntu_2.vm.hostname = "ubuntu-2" 28 | 29 | ubuntu_2.vm.provider "virtualbox" do |vb| 30 | vb.memory = "1024" 31 | vb.name = "ubuntu_2" 32 | end 33 | end 34 | 35 | 36 | # Third VM 37 | config.vm.define "ubuntu_3" do |ubuntu_3| 38 | ubuntu_3.vm.box = "ubuntu/focal64" 39 | ubuntu_3.vm.network "private_network", ip: "192.168.56.3" 40 | ubuntu_3.vm.hostname = "ubuntu-3" 41 | 42 | ubuntu_3.vm.provider "virtualbox" do |vb| 43 | vb.memory = "1024" 44 | vb.name = "ubuntu_3" 45 | end 46 | end 47 | 48 | # Fourth VM 49 | config.vm.define "ubuntu_4" do |ubuntu_4| 50 | ubuntu_4.vm.box = "ubuntu/focal64" 51 | ubuntu_4.vm.network "private_network", ip: "192.168.56.4" 52 | ubuntu_4.vm.hostname = "ubuntu-4" 53 | 54 | ubuntu_4.vm.provider "virtualbox" do |vb| 55 | vb.memory = "1024" 56 | vb.name = "ubuntu_4" 57 | end 58 | end 59 | 60 | 61 | # The most common configuration options are documented and commented below. 62 | # For a complete reference, please see the online documentation at 63 | # https://docs.vagrantup.com. 64 | 65 | # Every Vagrant development environment requires a box. You can search for 66 | # boxes at https://vagrantcloud.com/search. 67 | # config.vm.box = "base" 68 | 69 | # Disable automatic box update checking. If you disable this, then 70 | # boxes will only be checked for updates when the user runs 71 | # `vagrant box outdated`. This is not recommended. 72 | # config.vm.box_check_update = false 73 | 74 | # Create a forwarded port mapping which allows access to a specific port 75 | # within the machine from a port on the host machine. In the example below, 76 | # accessing "localhost:8080" will access port 80 on the guest machine. 77 | # NOTE: This will enable public access to the opened port 78 | # config.vm.network "forwarded_port", guest: 80, host: 8080 79 | 80 | # Create a forwarded port mapping which allows access to a specific port 81 | # within the machine from a port on the host machine and only allow access 82 | # via 127.0.0.1 to disable public access 83 | # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" 84 | 85 | # Create a private network, which allows host-only access to the machine 86 | # using a specific IP. 87 | # config.vm.network "private_network", ip: "192.168.33.10" 88 | 89 | # Create a public network, which generally matched to bridged network. 90 | # Bridged networks make the machine appear as another physical device on 91 | # your network. 92 | # config.vm.network "public_network" 93 | 94 | # Share an additional folder to the guest VM. The first argument is 95 | # the path on the host to the actual folder. The second argument is 96 | # the path on the guest to mount the folder. And the optional third 97 | # argument is a set of non-required options. 98 | # config.vm.synced_folder "../data", "/vagrant_data" 99 | 100 | # Provider-specific configuration so you can fine-tune various 101 | # backing providers for Vagrant. These expose provider-specific options. 102 | # Example for VirtualBox: 103 | # 104 | # config.vm.provider "virtualbox" do |vb| 105 | # # Display the VirtualBox GUI when booting the machine 106 | # vb.gui = true 107 | # 108 | # # Customize the amount of memory on the VM: 109 | # vb.memory = "1024" 110 | # end 111 | # 112 | # View the documentation for the provider you are using for more 113 | # information on available options. 114 | 115 | # Enable provisioning with a shell script. Additional provisioners such as 116 | # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the 117 | # documentation for more information about their specific syntax and use. 118 | # config.vm.provision "shell", inline: <<-SHELL 119 | # apt-get update 120 | # apt-get install -y apache2 121 | # SHELL 122 | end 123 | -------------------------------------------------------------------------------- /10-golang-ssh-concurrent-file-uploder/go.mod: -------------------------------------------------------------------------------- 1 | module golang-ssh-concurrent-file-uploder 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/pkg/sftp v1.13.5 7 | golang.org/x/crypto v0.3.0 8 | ) 9 | 10 | require ( 11 | github.com/kr/fs v0.1.0 // indirect 12 | golang.org/x/sys v0.2.0 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /10-golang-ssh-concurrent-file-uploder/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= 4 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 5 | github.com/pkg/sftp v1.13.5 h1:a3RLUqkyjYRtBTZJZ1VRrKbN3zhuPLlUc3sphVz81go= 6 | github.com/pkg/sftp v1.13.5/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 10 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 11 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 12 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 13 | golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= 14 | golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= 15 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 16 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 17 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 18 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 19 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 20 | golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= 21 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 22 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 23 | golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM= 24 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 25 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 26 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 27 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 28 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 29 | -------------------------------------------------------------------------------- /10-golang-ssh-concurrent-file-uploder/hello.txt: -------------------------------------------------------------------------------- 1 | This file will be uploaded by Golang using normal way 2 | -------------------------------------------------------------------------------- /10-golang-ssh-concurrent-file-uploder/hi.txt: -------------------------------------------------------------------------------- 1 | This file will be uploaded by Golang using concurrent way 2 | -------------------------------------------------------------------------------- /10-golang-ssh-concurrent-file-uploder/image-go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | "path/filepath" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type Image struct { 14 | Name string 15 | URL string 16 | Category string 17 | } 18 | 19 | var totalDownloadSizebyNormal float64 = 0.0 20 | var totalDurationbyNormal float64 21 | 22 | var totalDownloadSizebyGoRoutine float64 = 0.0 23 | var totalDurationbyGoRoutine float64 24 | 25 | var wg sync.WaitGroup 26 | 27 | var images = []Image{ 28 | {"nature1.jpeg", "https://images.pexels.com/photos/7753054/pexels-photo-7753054.jpeg", "nature"}, 29 | {"nature2.jpeg", "https://images.pexels.com/photos/572688/pexels-photo-572688.jpeg", "nature"}, 30 | {"nature3.jpeg", "https://images.pexels.com/photos/1671324/pexels-photo-1671324.jpeg", "nature"}, 31 | {"nature4.jpeg", "https://images.pexels.com/photos/6004828/pexels-photo-6004828.jpeg", "nature"}, 32 | {"nature5.jpeg", "https://images.pexels.com/photos/7753054/pexels-photo-7753054.jpeg", "nature"}, 33 | } 34 | 35 | func (img Image) DownloadSaveImage(usingGoroutine bool) { 36 | var folderName string 37 | 38 | // check whether it is called by normal method or go routine 39 | // Create folder name based on normal method or go routine 40 | if usingGoroutine { 41 | folderName = "go-routine/" + img.Category 42 | defer wg.Done() 43 | } else { 44 | folderName = "normal/" + img.Category 45 | } 46 | 47 | res, err := http.Get(img.URL) 48 | 49 | // Check for server error 50 | if err != nil { 51 | fmt.Printf("Server Error - Failed to save %v from %v \n", img.Name, img.URL) 52 | return 53 | } 54 | 55 | defer res.Body.Close() 56 | 57 | // check for status code error 58 | if res.StatusCode != 200 { 59 | fmt.Printf("Status Code Error - Failed to save %v from %v. Server returns code %v \n", img.Name, img.URL, res.StatusCode) 60 | return 61 | } 62 | 63 | // Check if the category folder exists if not create one 64 | _, err = os.Stat(folderName) 65 | if os.IsNotExist(err) { 66 | err = os.MkdirAll(folderName, 0755) 67 | if err != nil { 68 | fmt.Printf("Failed to create folder for %v category \n", folderName) 69 | return 70 | } 71 | } 72 | 73 | // Create a file with image name 74 | imageFile, err := os.Create(filepath.Join(folderName, img.Name)) 75 | if err != nil { 76 | fmt.Printf("Failed to file for %v \n", img.Name) 77 | return 78 | } 79 | defer imageFile.Close() 80 | 81 | // Save the image to the created folder 82 | numberOfBytes, err := io.Copy(imageFile, res.Body) 83 | if err != nil { 84 | fmt.Printf("Failed to copy file from img.URL to %v \n", img.Name) 85 | return 86 | } 87 | 88 | if usingGoroutine { 89 | totalDownloadSizebyGoRoutine += float64(numberOfBytes) / 1000000.00 90 | } else { 91 | totalDownloadSizebyNormal += float64(numberOfBytes) / 1000000.00 92 | } 93 | 94 | fmt.Printf("Total download size of image %v is %.2fMB \n", img.Name, (float64(numberOfBytes) / 1000000.00)) 95 | 96 | } 97 | 98 | func download() { 99 | 100 | // using normal way 101 | startTime := time.Now() 102 | fmt.Println("##### Start - Normal Way #####") 103 | for _, image := range images { 104 | image.DownloadSaveImage(false) 105 | } 106 | endTime := time.Now() 107 | totalDurationbyNormal = endTime.Sub(startTime).Seconds() 108 | 109 | fmt.Printf("Downloaded %.2fMB of data in %.2f seconds\n", totalDownloadSizebyNormal, totalDurationbyNormal) 110 | fmt.Println("##### End - Normal Way #####") 111 | 112 | // Using concurrency way 113 | wg.Add(len(images)) 114 | startTime = time.Now() 115 | fmt.Println("##### Start - Go Routine Way #####") 116 | for _, image := range images { 117 | go image.DownloadSaveImage(true) 118 | } 119 | wg.Wait() 120 | 121 | endTime = time.Now() 122 | totalDurationbyGoRoutine = endTime.Sub(startTime).Seconds() 123 | 124 | fmt.Printf("Downloaded %.2fMB of data in %.2f seconds\n", totalDownloadSizebyGoRoutine, totalDurationbyGoRoutine) 125 | fmt.Println("##### End - Go Routine Way #####") 126 | 127 | } 128 | -------------------------------------------------------------------------------- /10-golang-ssh-concurrent-file-uploder/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "path/filepath" 8 | "sync" 9 | "time" 10 | 11 | "github.com/pkg/sftp" 12 | "golang.org/x/crypto/ssh" 13 | ) 14 | 15 | type Server struct { 16 | Hostname string 17 | Username string 18 | Password string 19 | PrivateKeyPath string 20 | FiletoUpload string 21 | FileUploadLocation string 22 | } 23 | 24 | var wg sync.WaitGroup 25 | 26 | // check for any error 27 | func check(err error) { 28 | if err != nil { 29 | fmt.Printf("Error Happened %s \n", err) 30 | return 31 | } 32 | } 33 | 34 | // Main function 35 | func main() { 36 | 37 | servers := []Server{ 38 | {"127.0.0.1:2222", "vagrant", "", "./.vagrant/machines/ubuntu_1/virtualbox/private_key", "hello.txt", "/home/vagrant/"}, 39 | {"127.0.0.1:2200", "vagrant", "", "./.vagrant/machines/ubuntu_2/virtualbox/private_key", "hello.txt", "/home/vagrant/"}, 40 | {"127.0.0.1:2201", "vagrant", "", "./.vagrant/machines/ubuntu_3/virtualbox/private_key", "hello.txt", "/home/vagrant/"}, 41 | {"127.0.0.1:2202", "vagrant", "", "./.vagrant/machines/ubuntu_4/virtualbox/private_key", "hello.txt", "/home/vagrant/"}, 42 | } 43 | 44 | // with out go routine 45 | startTime := time.Now() 46 | for _, server := range servers { 47 | server.UploadFileToServer(false) 48 | } 49 | endTime := time.Now() 50 | 51 | fmt.Printf("Without Go Routine - Upload task took %.2f seconds \n", endTime.Sub(startTime).Seconds()) 52 | 53 | startTime = time.Now() 54 | 55 | // with go routine 56 | wg.Add(len(servers)) 57 | for _, server := range servers { 58 | go server.UploadFileToServer(true) 59 | } 60 | wg.Wait() 61 | endTime = time.Now() 62 | fmt.Printf("With Go Routine - Upload task took %.2f seconds \n", endTime.Sub(startTime).Seconds()) 63 | 64 | } 65 | 66 | // Upload file to remote SSH server 67 | 68 | func (s Server) UploadFileToServer(usingGoRoutine bool) { 69 | 70 | if usingGoRoutine { 71 | defer wg.Done() 72 | s.FiletoUpload = "hi.txt" 73 | } 74 | 75 | if s.Password == "" && s.PrivateKeyPath == "" { 76 | fmt.Println("Please provide ssh password or ssh password") 77 | } 78 | 79 | var conf *ssh.ClientConfig 80 | 81 | // get ssh config 82 | 83 | if s.Password != "" { 84 | //fmt.Println("Login with password") 85 | conf = s.sshDemoWithPassword() 86 | } 87 | 88 | if s.PrivateKeyPath != "" { 89 | //fmt.Println("Login with private key") 90 | conf = s.sshDemoWithPrivateKey() 91 | } 92 | 93 | // open ssh connection 94 | sshClient, err := ssh.Dial("tcp", s.Hostname, conf) 95 | check(err) 96 | 97 | // open sftp connection 98 | //fmt.Println("open sftp connection") 99 | sftpClient, err := sftp.NewClient(sshClient) 100 | check(err) 101 | defer sftpClient.Close() 102 | 103 | // Upload a file 104 | //fmt.Println("Upload a file") 105 | srcFile, err := os.Open(s.FiletoUpload) 106 | check(err) 107 | defer srcFile.Close() 108 | 109 | //fmt.Println("Create a file in remote machine") 110 | dstFile, err := sftpClient.Create(filepath.Join(s.FileUploadLocation, s.FiletoUpload)) 111 | check(err) 112 | defer dstFile.Close() 113 | 114 | //fmt.Println("Uploading a file") 115 | _, err = io.Copy(dstFile, srcFile) 116 | check(err) 117 | fmt.Printf("File %v uploaded successfully to %v \n", s.FiletoUpload, s.Hostname) 118 | 119 | } 120 | 121 | // SSH auth with Password 122 | func (s Server) sshDemoWithPassword() *ssh.ClientConfig { 123 | 124 | // ssh config with password authentication 125 | conf := &ssh.ClientConfig{ 126 | User: s.Username, 127 | Auth: []ssh.AuthMethod{ 128 | ssh.Password(s.Password), 129 | }, 130 | HostKeyCallback: ssh.InsecureIgnoreHostKey(), 131 | } 132 | 133 | return conf 134 | 135 | } 136 | 137 | // SSH auth with Private Key 138 | func (s Server) sshDemoWithPrivateKey() *ssh.ClientConfig { 139 | keyByte, err := os.ReadFile(s.PrivateKeyPath) 140 | check(err) 141 | signer, err := ssh.ParsePrivateKey(keyByte) 142 | check(err) 143 | 144 | // ssh config 145 | conf := &ssh.ClientConfig{ 146 | User: s.Username, 147 | Auth: []ssh.AuthMethod{ 148 | ssh.PublicKeys(signer), 149 | }, 150 | HostKeyCallback: ssh.InsecureIgnoreHostKey(), 151 | } 152 | 153 | return conf 154 | } 155 | -------------------------------------------------------------------------------- /11-jwt-golang/Golang-JWT.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "f7f035c6-faaa-4cf2-967a-a218cd042335", 4 | "name": "Golang-JWT", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "Get open page", 10 | "request": { 11 | "method": "GET", 12 | "header": [], 13 | "url": { 14 | "raw": "http://localhost:4000", 15 | "protocol": "http", 16 | "host": [ 17 | "localhost" 18 | ], 19 | "port": "4000" 20 | } 21 | }, 22 | "response": [] 23 | }, 24 | { 25 | "name": "Get Secure page", 26 | "request": { 27 | "method": "GET", 28 | "header": [ 29 | { 30 | "key": "Token", 31 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJBa2lsYW4iLCJMb2dnZWRJblRpbWUiOiIxMC0xMi0yMDIyIDE2OjA0OjAzIiwiaXNzIjoiQWtpbGFuIiwiZXhwIjoxNjcwNjY4NTAzfQ.KoWrTBoKutRI8JeK6aPVQQ7AOuWs2eXIZUvMcgb5bIo", 32 | "type": "text" 33 | } 34 | ], 35 | "url": { 36 | "raw": "http://localhost:4000/secure", 37 | "protocol": "http", 38 | "host": [ 39 | "localhost" 40 | ], 41 | "port": "4000", 42 | "path": [ 43 | "secure" 44 | ] 45 | } 46 | }, 47 | "response": [] 48 | }, 49 | { 50 | "name": "Get JWT token", 51 | "request": { 52 | "method": "POST", 53 | "header": [], 54 | "body": { 55 | "mode": "raw", 56 | "raw": "{\n \"username\": \"admin\",\n \"password\": \"admin\"\n}", 57 | "options": { 58 | "raw": { 59 | "language": "json" 60 | } 61 | } 62 | }, 63 | "url": { 64 | "raw": "http://localhost:4000/login", 65 | "protocol": "http", 66 | "host": [ 67 | "localhost" 68 | ], 69 | "port": "4000", 70 | "path": [ 71 | "login" 72 | ] 73 | } 74 | }, 75 | "response": [] 76 | } 77 | ] 78 | } -------------------------------------------------------------------------------- /11-jwt-golang/README.md: -------------------------------------------------------------------------------- 1 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fakilans%2Fgolang-mini-projects%2Ftree%2Fmain%2F11-jwt-golang&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 2 | 3 | # JWT authentication with Golang 4 | 5 | - This application uses JWT based authorization method to protect REST api endpoint 6 | 7 | ### Run - URLs and sample responses 8 | 9 | - Run the application with the below commands and access the URLs 10 | 11 | ```bash 12 | go run main.go 13 | ``` 14 | 15 | - Import postman collection "Golang-JWT.postman_collection.json" and start testing 16 | 17 | - Access home route - http://localhost:4000 - No auth needed 18 | ```json 19 | { 20 | "status": "Success", 21 | "message": "Welcome to Golang with JWT authentication" 22 | } 23 | ``` 24 | - Access secure route - http://localhost:4000/secure - Expect auth error as we didn't pass JWT token 25 | 26 | ```json 27 | { 28 | "Status": "Failed", 29 | "Msg": "You are not authorized to view this page" 30 | } 31 | ``` 32 | 33 | - Generate JWT token - http://localhost:4000/login with payload with the below body 34 | 35 | ```json 36 | { 37 | "username": "admin", 38 | "password": "admin" 39 | } 40 | ``` 41 | 42 | ```json 43 | { 44 | "status": "Success", 45 | "message": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJBa2lsYW4iLCJMb2dnZWRJblRpbWUiOiIxMC0xMi0yMDIyIDIwOjU3OjMyIiwiaXNzIjoiQWtpbGFuIiwiZXhwIjoxNjcwNjg2MTEyfQ.E--k9nMc-uOHb6VWJCrTyzSgGQ6JGAT_m3J1z_z-Ohs" 46 | } 47 | ``` 48 | 49 | - Copy the JWT token from the above response and pass it in request header as Token value - http://localhost:4000/secure 50 | 51 | ```json 52 | { 53 | "status": "Success", 54 | "message": "Congrats and Welcome to the Secure page!. You gave me the correct JWT token!" 55 | } 56 | ``` 57 | 58 | - 59 | 60 | ### Demo 61 | 62 | ![Alt JWT authentication with Golang](https://raw.githubusercontent.com/akilans/golang-mini-projects/main/demos/jwt-with-golang.gif) 63 | 64 | ## Contributing 65 | 66 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 67 | -------------------------------------------------------------------------------- /11-jwt-golang/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/akilans/golang-mini-projects/11-jwt-golang 2 | 3 | go 1.19 4 | 5 | require github.com/golang-jwt/jwt/v4 v4.4.3 // indirect 6 | -------------------------------------------------------------------------------- /11-jwt-golang/go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= 2 | github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 3 | -------------------------------------------------------------------------------- /11-jwt-golang/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/golang-jwt/jwt/v4" 11 | ) 12 | 13 | // Store the SECRET KEY SECRETLY :) 14 | var SECRET_KEY string = "AwesomeGolangSecret" 15 | 16 | // To capture credentials from request which is needed to generate JWT 17 | type User struct { 18 | UserName string `json:"username"` 19 | Password string `json:"password"` 20 | } 21 | 22 | // message to send as json response 23 | type Message struct { 24 | Status string `json:"status"` 25 | Msg string `json:"message"` 26 | } 27 | 28 | // response message struct as json format 29 | func jsonMessageByte(status string, msg string) []byte { 30 | errrMessage := Message{status, msg} 31 | byteContent, _ := json.Marshal(errrMessage) 32 | return byteContent 33 | } 34 | 35 | // Custom claims needed for generating JWT token 36 | type MyCustomClaims struct { 37 | UserName string `json:"user_name"` 38 | LoggedInTime string 39 | jwt.RegisteredClaims 40 | } 41 | 42 | // Function to create JWT token 43 | func CreateJWT() (string, error) { 44 | currentTime := time.Now().Format("02-01-2006 15:04:05") 45 | 46 | // Storing user name and loggedin time 47 | // Token expires in 1 min. This is just for testing 48 | claims := MyCustomClaims{ 49 | "Akilan", 50 | currentTime, 51 | jwt.RegisteredClaims{ 52 | ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Minute)), 53 | Issuer: "Akilan", 54 | }, 55 | } 56 | 57 | // Generate token with HS256 algorithm and custom claims 58 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 59 | // Sign the token with our secret key 60 | signedToken, err := token.SignedString([]byte(SECRET_KEY)) 61 | 62 | return signedToken, err 63 | } 64 | 65 | // Function to validate JWT 66 | // Get the token from user and validate it 67 | func ValidateJWT(tokenString string) bool { 68 | token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) { 69 | return []byte(SECRET_KEY), nil 70 | }) 71 | 72 | if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid { 73 | log.Printf("%v - %v - %v \n", claims.UserName, claims.LoggedInTime, claims.RegisteredClaims.Issuer) 74 | return true 75 | } else { 76 | log.Println(err) 77 | return false 78 | } 79 | } 80 | 81 | // Middleware auth handler 82 | func Auth(handler func(w http.ResponseWriter, r *http.Request)) http.HandlerFunc { 83 | 84 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 85 | // Get the JWT token from request header 86 | if r.Header["Token"] != nil { 87 | providedToken := r.Header["Token"][0] 88 | if ValidateJWT(providedToken) { 89 | handler(w, r) 90 | } else { 91 | w.WriteHeader(401) 92 | w.Write(jsonMessageByte("Failed", "You are not authorized to view this page")) 93 | } 94 | } else { 95 | w.WriteHeader(401) 96 | w.Write(jsonMessageByte("Failed", "Please provide valid JWT token in request header as Token")) 97 | } 98 | 99 | }) 100 | } 101 | 102 | // Handle login 103 | func LoginHandler(w http.ResponseWriter, r *http.Request) { 104 | if r.Method != "POST" { 105 | w.WriteHeader(405) 106 | w.Write(jsonMessageByte("Failed", r.Method+" - Method not allowed")) 107 | } else { 108 | var userData User 109 | err := json.NewDecoder(r.Body).Decode(&userData) 110 | if err != nil { 111 | w.WriteHeader(400) 112 | w.Write(jsonMessageByte("Failed", "Bad Request - Failed to parse the payload ")) 113 | } else { 114 | log.Printf("User name - %v and Password is %v\n", userData.UserName, userData.Password) 115 | // user name and password is hard code 116 | // We can use DB 117 | if userData.UserName == "admin" && userData.Password == "admin" { 118 | token, _ := CreateJWT() 119 | w.Write(jsonMessageByte("Success", token)) 120 | } else { 121 | w.WriteHeader(401) 122 | w.Write(jsonMessageByte("Failed", "Invalid credentials")) 123 | } 124 | } 125 | } 126 | 127 | } 128 | 129 | // Handle home route 130 | func HomeHandler(w http.ResponseWriter, r *http.Request) { 131 | w.Write(jsonMessageByte("Success", "Welcome to Golang with JWT authentication")) 132 | } 133 | 134 | // Handle secure route 135 | func SecureHandler(w http.ResponseWriter, r *http.Request) { 136 | w.Write(jsonMessageByte("Success", "Congrats and Welcome to the Secure page!. You gave me the correct JWT token!")) 137 | } 138 | 139 | func main() { 140 | fmt.Println("JWT - authentication with Golang") 141 | 142 | // No auth needed 143 | http.HandleFunc("/", HomeHandler) 144 | 145 | // Generate JWT token by providing username and password in the payload 146 | http.HandleFunc("/login", LoginHandler) 147 | 148 | // Auth middleware added for restricing the direct acccess 149 | // Provide JWT token in header section as "Token" 150 | http.HandleFunc("/secure", Auth(SecureHandler)) 151 | 152 | http.ListenAndServe(":4000", nil) 153 | } 154 | -------------------------------------------------------------------------------- /12-fiber-book-rest/.env: -------------------------------------------------------------------------------- 1 | DB_DSN="root:root#123@tcp(127.0.0.1:3306)/bookstore?charset=utf8mb4&parseTime=True&loc=Local" 2 | PORT=":8080" 3 | SECRET_KEY="mysecret" -------------------------------------------------------------------------------- /12-fiber-book-rest/.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant/ -------------------------------------------------------------------------------- /12-fiber-book-rest/README.md: -------------------------------------------------------------------------------- 1 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fakilans%2Fgolang-mini-projects%2Ftree%2Fmain%2F12-fiber-book-rest&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 2 | 3 | # Bookstore REST API with MySQL, GORM, JWT and Fiber framework 4 | 5 | - This is REST based API to list, add, update and delete books 6 | 7 | ### Tools and Packages 8 | 9 | - Fiber - Golang web Framework 10 | - MySql - SQL Database 11 | - Gorm - ORM library for golang 12 | - JWT - For Authorization 13 | - Bycrypt - To hash passwords 14 | 15 | ### Prerequisites 16 | 17 | - Golang 18 | - Mysql 19 | 20 | ```bash 21 | # I used docker to run mysql 22 | docker container run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root#123 mysql 23 | docker exec -it mysql bash 24 | mysql -u root -p 25 | Enter password: root#123 26 | mysql> CREATE DATABASE bookstore; 27 | ``` 28 | 29 | - Update .env file with correct information 30 | 31 | ```bash 32 | DB_DSN="root:root#123@tcp(127.0.0.1:3306)/bookstore?charset=utf8mb4&parseTime=True&loc=Local" 33 | PORT=":8080" 34 | SECRET_KEY="mysecret" 35 | ``` 36 | 37 | ### Run the application 38 | 39 | ```bash 40 | go run main.go 41 | ``` 42 | 43 | ### URL endpoints 44 | 45 | - Users 46 | - `POST` - Register - /admin 47 | - `POST` - Login - /login 48 | - Books - JWT token needs to be passed in header 49 | - `GET` - Get all the books - `/` 50 | - `GET` - Get book by id - `/book/{id}` 51 | - `DELETE` - Delete book by id - `/book/{id}` 52 | - `POST` - Add a book - `/addbook` 53 | - `PUT` - Update book - `/book/{id}` 54 | 55 | ### Postman setup for testing 56 | 57 | - Import `fiber-rest-book.postman_collection.json` and start testing 58 | 59 | ### Demo 60 | 61 | ![Alt Bookstore API](https://raw.githubusercontent.com/akilans/golang-mini-projects/main/demos/fiber-book-rest.gif) 62 | -------------------------------------------------------------------------------- /12-fiber-book-rest/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # All Vagrant configuration is done below. The "2" in Vagrant.configure 5 | # configures the configuration version (we support older styles for 6 | # backwards compatibility). Please don't change it unless you know what 7 | # you're doing. 8 | Vagrant.configure("2") do |config| 9 | # The most common configuration options are documented and commented below. 10 | # For a complete reference, please see the online documentation at 11 | # https://docs.vagrantup.com. 12 | 13 | # Every Vagrant development environment requires a box. You can search for 14 | # boxes at https://vagrantcloud.com/search. 15 | config.vm.box = "ubuntu/focal64" 16 | config.vm.hostname = "ubuntu-docker" 17 | 18 | # Disable automatic box update checking. If you disable this, then 19 | # boxes will only be checked for updates when the user runs 20 | # `vagrant box outdated`. This is not recommended. 21 | # config.vm.box_check_update = false 22 | 23 | # Create a forwarded port mapping which allows access to a specific port 24 | # within the machine from a port on the host machine. In the example below, 25 | # accessing "localhost:8080" will access port 80 on the guest machine. 26 | # NOTE: This will enable public access to the opened port 27 | # config.vm.network "forwarded_port", guest: 80, host: 8080 28 | 29 | # Create a forwarded port mapping which allows access to a specific port 30 | # within the machine from a port on the host machine and only allow access 31 | # via 127.0.0.1 to disable public access 32 | # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" 33 | 34 | # Create a private network, which allows host-only access to the machine 35 | # using a specific IP. 36 | config.vm.network "private_network", ip: "192.168.33.10" 37 | 38 | # Create a public network, which generally matched to bridged network. 39 | # Bridged networks make the machine appear as another physical device on 40 | # your network. 41 | # config.vm.network "public_network" 42 | 43 | # Share an additional folder to the guest VM. The first argument is 44 | # the path on the host to the actual folder. The second argument is 45 | # the path on the guest to mount the folder. And the optional third 46 | # argument is a set of non-required options. 47 | # config.vm.synced_folder "../data", "/vagrant_data" 48 | 49 | # Provider-specific configuration so you can fine-tune various 50 | # backing providers for Vagrant. These expose provider-specific options. 51 | # Example for VirtualBox: 52 | # 53 | config.vm.provider "virtualbox" do |vb| 54 | # # Display the VirtualBox GUI when booting the machine 55 | # vb.gui = true 56 | # 57 | # # Customize the amount of memory on the VM: 58 | vb.memory = "2048" 59 | vb.name = "ubuntu-docker" 60 | end 61 | # 62 | # View the documentation for the provider you are using for more 63 | # information on available options. 64 | 65 | # Enable provisioning with a shell script. Additional provisioners such as 66 | # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the 67 | # documentation for more information about their specific syntax and use. 68 | config.vm.provision "shell", inline: <<-SHELL 69 | apt-get update 70 | sudo apt-get remove docker docker-engine docker.io containerd runc 71 | sudo apt-get install ca-certificates curl gnupg lsb-release -y 72 | sudo mkdir -p /etc/apt/keyrings 73 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg 74 | echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null 75 | sudo apt-get update 76 | sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y 77 | sudo usermod -aG docker vagrant 78 | SHELL 79 | end 80 | -------------------------------------------------------------------------------- /12-fiber-book-rest/controllers/booksController.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/akilans/fiber-book-rest/helpers" 7 | "github.com/akilans/fiber-book-rest/models" 8 | "github.com/go-playground/validator/v10" 9 | "github.com/gofiber/fiber/v2" 10 | ) 11 | 12 | // Payload validation response message 13 | type ErrorResponse struct { 14 | FailedField string `json:"failed_field"` 15 | Tag string `json:"tag"` 16 | Value string `json:"value"` 17 | } 18 | 19 | var validate = validator.New() 20 | 21 | // validate book payload 22 | 23 | func ValidateBookStruct(book models.Book) []ErrorResponse { 24 | var errors []ErrorResponse 25 | err := validate.Struct(book) 26 | if err != nil { 27 | for _, err := range err.(validator.ValidationErrors) { 28 | var element ErrorResponse 29 | element.FailedField = err.StructNamespace() 30 | element.Tag = err.Tag() 31 | element.Value = err.Param() 32 | errors = append(errors, element) 33 | } 34 | } 35 | return errors 36 | } 37 | 38 | // List books function 39 | func ListBooksHandler(c *fiber.Ctx) error { 40 | // Get list of books from DB 41 | books, err := models.GetBooks() 42 | if err != nil { 43 | helpers.LogError(err) 44 | return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ 45 | "message": "Failed to list books", 46 | }) 47 | } else { 48 | return c.Status(fiber.StatusOK).JSON(fiber.Map{ 49 | "data": books, 50 | }) 51 | } 52 | } 53 | 54 | // Add a new book function 55 | /* 56 | Get new book inputs from user 57 | validate it 58 | Store in the DB 59 | */ 60 | func AddBookHandler(c *fiber.Ctx) error { 61 | var newBook models.Book 62 | 63 | // Parse request body 64 | if err := c.BodyParser(&newBook); err != nil { 65 | helpers.LogError(err) 66 | return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ 67 | "message": "Please provide valid inputs", 68 | }) 69 | } else { 70 | // Validate user inputs 71 | errors := ValidateBookStruct(newBook) 72 | if errors != nil { 73 | return c.Status(fiber.StatusBadRequest).JSON(errors) 74 | } 75 | // Add new book into db 76 | newBookID, err := models.AddBook(newBook) 77 | if err != nil { 78 | helpers.LogError(err) 79 | return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ 80 | "message": "Failed to add a new book", 81 | }) 82 | } else { 83 | return c.Status(fiber.StatusOK).JSON(fiber.Map{ 84 | "message": "New book added successfully with id - " + strconv.Itoa(newBookID), 85 | }) 86 | } 87 | } 88 | } 89 | 90 | // Get book by id function 91 | /* 92 | Validate provided book id in the URL 93 | Get book details from DB 94 | If no book returned send book not found message 95 | else give book info 96 | */ 97 | func GetBookHandler(c *fiber.Ctx) error { 98 | // Validate provided book id 99 | bookId, err := c.ParamsInt("id") 100 | if err != nil { 101 | helpers.LogError(err) 102 | return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ 103 | "message": "Please provide a valid book id", 104 | }) 105 | } 106 | // Get book by id 107 | book := models.GetBookByID(bookId) 108 | 109 | // Check for empty result 110 | if (book == models.Book{}) { 111 | return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ 112 | "message": "Book doesn't exists", 113 | }) 114 | } else { 115 | return c.Status(fiber.StatusOK).JSON(fiber.Map{ 116 | "data": book, 117 | }) 118 | } 119 | 120 | } 121 | 122 | // Update a book by id function 123 | /* 124 | Validate provided book id in the URL 125 | Get book details from DB 126 | If no book returned send book not found message 127 | else update book details 128 | */ 129 | func UpdateBookHandler(c *fiber.Ctx) error { 130 | 131 | // Validate provided book id 132 | bookId, err := c.ParamsInt("id") 133 | 134 | if err != nil { 135 | helpers.LogError(err) 136 | return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ 137 | "message": "Please provide a valid book id", 138 | }) 139 | } 140 | 141 | // Get book by id 142 | book := models.GetBookByID(bookId) 143 | 144 | // Check for empty result 145 | if (book == models.Book{}) { 146 | return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ 147 | "message": "Book doesn't exists", 148 | }) 149 | } 150 | 151 | // parse the request body 152 | if err := c.BodyParser(&book); err != nil { 153 | helpers.LogError(err) 154 | return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ 155 | "message": "Failed to update a book", 156 | }) 157 | } else { 158 | // Update a book with new values 159 | updatedBookID, err := models.UpdateBook(book) 160 | if err != nil { 161 | helpers.LogError(err) 162 | return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ 163 | "message": "Failed to update a book", 164 | }) 165 | } else { 166 | return c.Status(fiber.StatusOK).JSON(fiber.Map{ 167 | "message": "Book updated successfully with id - " + strconv.Itoa(updatedBookID), 168 | }) 169 | } 170 | } 171 | } 172 | 173 | // Delete books function 174 | /* 175 | Validate provided book id in the URL 176 | If book doesn't exists send book not found message 177 | else delete a book 178 | */ 179 | func DeleteBookHandler(c *fiber.Ctx) error { 180 | // Validate provided book id 181 | bookId, err := c.ParamsInt("id") 182 | if err != nil { 183 | helpers.LogError(err) 184 | return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ 185 | "message": "Please provide a valid book id", 186 | }) 187 | } 188 | 189 | // Delete book by id 190 | err = models.DeleteBookByID(bookId) 191 | if err != nil { 192 | helpers.LogError(err) 193 | return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ 194 | "message": "Book doesn't exists", 195 | }) 196 | } else { 197 | return c.Status(fiber.StatusOK).JSON(fiber.Map{ 198 | "message": "Book deleted successfully", 199 | }) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /12-fiber-book-rest/controllers/usersController.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/akilans/fiber-book-rest/helpers" 9 | "github.com/akilans/fiber-book-rest/models" 10 | "github.com/go-playground/validator/v10" 11 | "github.com/gofiber/fiber/v2" 12 | "github.com/golang-jwt/jwt/v4" 13 | ) 14 | 15 | type Login struct { 16 | Email string `json:"email" validate:"required,email,min=6,max=100"` 17 | Password string `json:"password" validate:"required,min=6,max=15"` 18 | } 19 | 20 | // Custom claims needed for generating JWT token 21 | type MyCustomClaims struct { 22 | UserEmail string 23 | LoggedInTime string 24 | jwt.RegisteredClaims 25 | } 26 | 27 | // validate user payload 28 | func ValidateUserStruct(user models.User) []ErrorResponse { 29 | var errors []ErrorResponse 30 | err := validate.Struct(user) 31 | if err != nil { 32 | for _, err := range err.(validator.ValidationErrors) { 33 | var element ErrorResponse 34 | element.FailedField = err.StructNamespace() 35 | element.Tag = err.Tag() 36 | element.Value = err.Param() 37 | errors = append(errors, element) 38 | } 39 | } 40 | return errors 41 | } 42 | 43 | func ValidateLoginUserStruct(user Login) []ErrorResponse { 44 | var errors []ErrorResponse 45 | err := validate.Struct(user) 46 | if err != nil { 47 | for _, err := range err.(validator.ValidationErrors) { 48 | var element ErrorResponse 49 | element.FailedField = err.StructNamespace() 50 | element.Tag = err.Tag() 51 | element.Value = err.Param() 52 | errors = append(errors, element) 53 | } 54 | } 55 | return errors 56 | } 57 | 58 | // Add User Handler 59 | /* 60 | Get email and password from user 61 | Validate email and password based on Login struct rules 62 | hash the password using bcrypt 63 | Store email and hashedpassword in db 64 | */ 65 | func AddUserHandler(c *fiber.Ctx) error { 66 | var loginUser models.User 67 | 68 | //parse the request body and store the email and password inputs 69 | if err := c.BodyParser(&loginUser); err != nil { 70 | helpers.LogError(err) 71 | return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ 72 | "message": "Please provide valid inputs", 73 | }) 74 | } else { 75 | // validate user inputs 76 | errors := ValidateUserStruct(loginUser) 77 | if errors != nil { 78 | return c.Status(fiber.StatusBadRequest).JSON(errors) 79 | } 80 | 81 | var existingUser models.User 82 | 83 | existingUser, err = models.GetUserByEmail(loginUser.Email) 84 | 85 | if err != nil { 86 | helpers.LogError(err) 87 | return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ 88 | "message": "Failed get user info", 89 | }) 90 | } 91 | 92 | if (existingUser != models.User{}) { 93 | return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ 94 | "message": loginUser.Email + " - Email already exists", 95 | }) 96 | } 97 | 98 | // generate hash password 99 | hashedPassword, err := helpers.GenerateHashPassword(loginUser.Password) 100 | 101 | if err != nil { 102 | helpers.LogError(err) 103 | return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ 104 | "message": "Failed to Hash password", 105 | }) 106 | } 107 | loginUser.Password = hashedPassword 108 | // insert into table 109 | loginUserID, err := models.AddUser(loginUser) 110 | if err != nil { 111 | helpers.LogError(err) 112 | return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ 113 | "message": "Failed to add a new user", 114 | }) 115 | } else { 116 | return c.Status(fiber.StatusOK).JSON(fiber.Map{ 117 | "message": "New User added successfully with id - " + strconv.Itoa(loginUserID), 118 | }) 119 | } 120 | } 121 | } 122 | 123 | // Login Handler 124 | /* 125 | Get email and password from user 126 | Validate email and password based on Login struct rules 127 | get user details by provided email id if not found provide user not found message 128 | if found check the provided password match with stored password(hashed version) 129 | If password mismatch throw invalid credential message 130 | Provide a JWT token for valid credentials 131 | */ 132 | func LoginHandler(c *fiber.Ctx) error { 133 | var loginUser Login 134 | // parse request body 135 | if err := c.BodyParser(&loginUser); err != nil { 136 | helpers.LogError(err) 137 | return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ 138 | "message": "Please provide valid inputs", 139 | }) 140 | } else { 141 | // validate the user inputs 142 | errors := ValidateLoginUserStruct(loginUser) 143 | if errors != nil { 144 | return c.Status(fiber.StatusBadRequest).JSON(errors) 145 | } 146 | // Get user details by provided email id 147 | user, err := models.GetUserByEmail(loginUser.Email) 148 | if err != nil { 149 | helpers.LogError(err) 150 | return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ 151 | "message": "Failed to login a user", 152 | }) 153 | } else { 154 | // If no user found, provide user not found message 155 | if (user == models.User{}) { 156 | return c.Status(fiber.StatusOK).JSON(fiber.Map{ 157 | "message": "Login failed - user not found", 158 | }) 159 | } 160 | // Check for password matching 161 | result := helpers.CheckHashPassword(loginUser.Password, user.Password) 162 | if result { 163 | // generate a JWT token 164 | token, _ := CreateJWT(loginUser.Email) 165 | return c.Status(fiber.StatusOK).JSON(fiber.Map{ 166 | "message": "success", 167 | "token": token, 168 | }) 169 | } else { 170 | // throw invalid password message 171 | return c.Status(fiber.StatusOK).JSON(fiber.Map{ 172 | "message": "Login failed - invalid password", 173 | }) 174 | } 175 | } 176 | } 177 | } 178 | 179 | // Create JWT token 180 | // Function to create JWT token 181 | func CreateJWT(userEmail string) (string, error) { 182 | currentTime := time.Now().Format("02-01-2006 15:04:05") 183 | 184 | // Storing user name and loggedin time 185 | // Token expires in 1 hour. 186 | claims := MyCustomClaims{ 187 | userEmail, 188 | currentTime, 189 | jwt.RegisteredClaims{ 190 | ExpiresAt: jwt.NewNumericDate(time.Now().Add(60 * time.Minute)), 191 | Issuer: "Akilan", 192 | }, 193 | } 194 | 195 | // Generate token with HS256 algorithm and custom claims 196 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 197 | // Sign the token with our secret key 198 | signedToken, err := token.SignedString([]byte(os.Getenv("SECRET_KEY"))) 199 | 200 | return signedToken, err 201 | } 202 | 203 | // Custom error message for invalid JWT 204 | func JwtError(c *fiber.Ctx, err error) error { 205 | // Return status 401 and failed authentication error. 206 | if err.Error() == "Missing or malformed JWT" { 207 | return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ 208 | "status": "failed", 209 | "message": err.Error(), 210 | }) 211 | } 212 | 213 | // Return status 401 and failed authentication error. 214 | return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ 215 | "status": "failed", 216 | "message": err.Error(), 217 | }) 218 | } 219 | -------------------------------------------------------------------------------- /12-fiber-book-rest/fiber-rest-book.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "61ec977b-b2d8-4f48-a8da-c28e8404c4bc", 4 | "name": "fiber-rest-book", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "Add a book", 10 | "request": { 11 | "auth": { 12 | "type": "bearer", 13 | "bearer": [ 14 | { 15 | "key": "token", 16 | "value": "{{TOKEN}}", 17 | "type": "string" 18 | } 19 | ] 20 | }, 21 | "method": "POST", 22 | "header": [ 23 | { 24 | "key": "Content-Type", 25 | "value": "application/json", 26 | "type": "text" 27 | } 28 | ], 29 | "body": { 30 | "mode": "raw", 31 | "raw": "{\n \"title\": \"How to Win Friends and Influence People\",\n \"author\": \"Dale Carnegie\",\n \"price\": 600,\n \"image_url\": \"https://images-na.ssl-images-amazon.com/images/I/51C4Tpxn4KL._SX316_BO1,204,203,200_.jpg\"\n}", 32 | "options": { 33 | "raw": { 34 | "language": "json" 35 | } 36 | } 37 | }, 38 | "url": { 39 | "raw": "{{BASE_URL}}/addbook", 40 | "host": [ 41 | "{{BASE_URL}}" 42 | ], 43 | "path": [ 44 | "addbook" 45 | ] 46 | } 47 | }, 48 | "response": [] 49 | }, 50 | { 51 | "name": "Get Books", 52 | "request": { 53 | "auth": { 54 | "type": "noauth" 55 | }, 56 | "method": "GET", 57 | "header": [], 58 | "url": { 59 | "raw": "{{BASE_URL}}", 60 | "host": [ 61 | "{{BASE_URL}}" 62 | ] 63 | } 64 | }, 65 | "response": [] 66 | }, 67 | { 68 | "name": "Get Book by ID", 69 | "request": { 70 | "auth": { 71 | "type": "bearer", 72 | "bearer": [ 73 | { 74 | "key": "token", 75 | "value": "{{TOKEN}}", 76 | "type": "string" 77 | } 78 | ] 79 | }, 80 | "method": "GET", 81 | "header": [], 82 | "url": { 83 | "raw": "{{BASE_URL}}/book/2", 84 | "host": [ 85 | "{{BASE_URL}}" 86 | ], 87 | "path": [ 88 | "book", 89 | "2" 90 | ] 91 | } 92 | }, 93 | "response": [] 94 | }, 95 | { 96 | "name": "Delete Book by ID", 97 | "request": { 98 | "auth": { 99 | "type": "bearer", 100 | "bearer": [ 101 | { 102 | "key": "token", 103 | "value": "{{TOKEN}}", 104 | "type": "string" 105 | } 106 | ] 107 | }, 108 | "method": "DELETE", 109 | "header": [], 110 | "url": { 111 | "raw": "{{BASE_URL}}/book/3", 112 | "host": [ 113 | "{{BASE_URL}}" 114 | ], 115 | "path": [ 116 | "book", 117 | "3" 118 | ] 119 | } 120 | }, 121 | "response": [] 122 | }, 123 | { 124 | "name": "Update a book by ID", 125 | "request": { 126 | "auth": { 127 | "type": "bearer", 128 | "bearer": [ 129 | { 130 | "key": "token", 131 | "value": "{{TOKEN}}", 132 | "type": "string" 133 | } 134 | ] 135 | }, 136 | "method": "PUT", 137 | "header": [ 138 | { 139 | "key": "Content-Type", 140 | "value": "application/json", 141 | "type": "text" 142 | } 143 | ], 144 | "body": { 145 | "mode": "raw", 146 | "raw": "{\n \"title\": \"How to Win Friends and Influence People\",\n \"authorr\": \"Dale Carnegie\",\n \"price\": 600,\n \"image_url\": \"https://images-na.ssl-images-amazon.com/images/I/51C4Tpxn4KL._SX316_BO1,204,203,200_.jpg\"\n}", 147 | "options": { 148 | "raw": { 149 | "language": "json" 150 | } 151 | } 152 | }, 153 | "url": { 154 | "raw": "{{BASE_URL}}/book/2", 155 | "host": [ 156 | "{{BASE_URL}}" 157 | ], 158 | "path": [ 159 | "book", 160 | "2" 161 | ] 162 | } 163 | }, 164 | "response": [] 165 | }, 166 | { 167 | "name": "Add user", 168 | "request": { 169 | "method": "POST", 170 | "header": [ 171 | { 172 | "key": "Content-Type", 173 | "value": "application/json", 174 | "type": "text" 175 | } 176 | ], 177 | "body": { 178 | "mode": "raw", 179 | "raw": "{\n \"name\": \"Akilan\",\n \"email\": \"akilan.468@gmail.com\",\n \"password\": \"akilan\"\n}", 180 | "options": { 181 | "raw": { 182 | "language": "json" 183 | } 184 | } 185 | }, 186 | "url": { 187 | "raw": "{{BASE_URL}}/admin", 188 | "host": [ 189 | "{{BASE_URL}}" 190 | ], 191 | "path": [ 192 | "admin" 193 | ] 194 | } 195 | }, 196 | "response": [] 197 | }, 198 | { 199 | "name": "Login user", 200 | "request": { 201 | "method": "POST", 202 | "header": [ 203 | { 204 | "key": "Content-Type", 205 | "value": "application/json", 206 | "type": "text" 207 | } 208 | ], 209 | "body": { 210 | "mode": "raw", 211 | "raw": "{\n \"email\": \"akilan.468@gmail.com\",\n \"password\": \"akilan\"\n}", 212 | "options": { 213 | "raw": { 214 | "language": "json" 215 | } 216 | } 217 | }, 218 | "url": { 219 | "raw": "{{BASE_URL}}/login", 220 | "host": [ 221 | "{{BASE_URL}}" 222 | ], 223 | "path": [ 224 | "login" 225 | ] 226 | } 227 | }, 228 | "response": [] 229 | } 230 | ] 231 | } -------------------------------------------------------------------------------- /12-fiber-book-rest/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/akilans/fiber-book-rest 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/gofiber/fiber/v2 v2.40.1 7 | github.com/joho/godotenv v1.4.0 8 | golang.org/x/crypto v0.4.0 9 | gorm.io/driver/mysql v1.4.4 10 | gorm.io/gorm v1.24.2 11 | ) 12 | 13 | require ( 14 | github.com/andybalholm/brotli v1.0.4 // indirect 15 | github.com/go-playground/locales v0.14.0 // indirect 16 | github.com/go-playground/universal-translator v0.18.0 // indirect 17 | github.com/go-playground/validator/v10 v10.11.1 // indirect 18 | github.com/go-sql-driver/mysql v1.7.0 // indirect 19 | github.com/gofiber/jwt/v3 v3.3.4 // indirect 20 | github.com/golang-jwt/jwt/v4 v4.4.3 // indirect 21 | github.com/jinzhu/inflection v1.0.0 // indirect 22 | github.com/jinzhu/now v1.1.5 // indirect 23 | github.com/klauspost/compress v1.15.12 // indirect 24 | github.com/leodido/go-urn v1.2.1 // indirect 25 | github.com/mattn/go-colorable v0.1.13 // indirect 26 | github.com/mattn/go-isatty v0.0.16 // indirect 27 | github.com/mattn/go-runewidth v0.0.14 // indirect 28 | github.com/rivo/uniseg v0.4.3 // indirect 29 | github.com/valyala/bytebufferpool v1.0.0 // indirect 30 | github.com/valyala/fasthttp v1.43.0 // indirect 31 | github.com/valyala/tcplisten v1.0.0 // indirect 32 | golang.org/x/sys v0.3.0 // indirect 33 | golang.org/x/text v0.5.0 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /12-fiber-book-rest/go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 2 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 3 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 7 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= 8 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= 9 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= 10 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= 11 | github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= 12 | github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= 13 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 14 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= 15 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 16 | github.com/gofiber/fiber/v2 v2.40.1 h1:pc7n9VVpGIqNsvg9IPLQhyFEMJL8gCs1kneH5D1pIl4= 17 | github.com/gofiber/fiber/v2 v2.40.1/go.mod h1:Gko04sLksnHbzLSRBFWPFdzM9Ws9pRxvvIaohJK1dsk= 18 | github.com/gofiber/jwt/v3 v3.3.4 h1:x3sUJG0D/zsrjAz5QuVvbotyERy4/qN897S75tRXrfA= 19 | github.com/gofiber/jwt/v3 v3.3.4/go.mod h1:i8fUvsjTCPNcfdaGhvZo9etWqIjKmJajeTMYDWlFdd4= 20 | github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 21 | github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= 22 | github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 23 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 24 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 25 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 26 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 27 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 28 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= 29 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 30 | github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= 31 | github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= 32 | github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= 33 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 34 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 35 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 36 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 37 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 38 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 39 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 40 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 41 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 42 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 43 | github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= 44 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 45 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= 46 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 47 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 48 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 49 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 50 | github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= 51 | github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 52 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 53 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 54 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 55 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 56 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 57 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 58 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 59 | github.com/valyala/fasthttp v1.41.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= 60 | github.com/valyala/fasthttp v1.43.0 h1:Gy4sb32C98fbzVWZlTM1oTMdLWGyvxR03VhM6cBIU4g= 61 | github.com/valyala/fasthttp v1.43.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= 62 | github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= 63 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 64 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 65 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 66 | golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= 67 | golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= 68 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 69 | golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 70 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 71 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 72 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 73 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 74 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 75 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 76 | golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= 77 | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 78 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 79 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 80 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 81 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 82 | golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= 83 | golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 84 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 85 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 86 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 87 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 88 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 89 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 90 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 91 | gorm.io/driver/mysql v1.4.4 h1:MX0K9Qvy0Na4o7qSC/YI7XxqUw5KDw01umqgID+svdQ= 92 | gorm.io/driver/mysql v1.4.4/go.mod h1:BCg8cKI+R0j/rZRQxeKis/forqRwRSYOR8OM3Wo6hOM= 93 | gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 94 | gorm.io/gorm v1.24.2 h1:9wR6CFD+G8nOusLdvkZelOEhpJVwwHzpQOUM+REd6U0= 95 | gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= 96 | -------------------------------------------------------------------------------- /12-fiber-book-rest/helpers/utils.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "log" 5 | 6 | "golang.org/x/crypto/bcrypt" 7 | ) 8 | 9 | // Log erros 10 | func LogError(err error) { 11 | log.Printf("Error - %v \n", err.Error()) 12 | } 13 | 14 | // Generate Hash Password 15 | func GenerateHashPassword(plainPassword string) (string, error) { 16 | bytes, err := bcrypt.GenerateFromPassword([]byte(plainPassword), 14) 17 | return string(bytes), err 18 | } 19 | 20 | // Compare password 21 | func CheckHashPassword(password, hash string) bool { 22 | err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) 23 | return err == nil 24 | } 25 | -------------------------------------------------------------------------------- /12-fiber-book-rest/initializers/db.go: -------------------------------------------------------------------------------- 1 | package initializers 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "gorm.io/driver/mysql" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | var DB *gorm.DB 12 | 13 | // Connect to DB 14 | func ConnectDB() { 15 | var err error 16 | dsn := os.Getenv("DB_DSN") 17 | // store the DB connection in DB variable 18 | DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) 19 | 20 | if err != nil { 21 | log.Fatal("Error connecting to DB") 22 | } else { 23 | log.Println("Connected to DB successfully") 24 | } 25 | 26 | } 27 | 28 | // Get DB connection 29 | func GetDB() *gorm.DB { 30 | // return the DB connection 31 | return DB 32 | } 33 | -------------------------------------------------------------------------------- /12-fiber-book-rest/initializers/env.go: -------------------------------------------------------------------------------- 1 | package initializers 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/joho/godotenv" 7 | ) 8 | 9 | // Load ENV variables from .env file 10 | func LoadEnvs() { 11 | log.Println("Start env load") 12 | err := godotenv.Load() 13 | if err != nil { 14 | log.Fatal("Error loading .env file") 15 | } else { 16 | log.Println("Loaded env successfully") 17 | } 18 | log.Println("End env load") 19 | } 20 | -------------------------------------------------------------------------------- /12-fiber-book-rest/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/akilans/fiber-book-rest/routes" 9 | "github.com/gofiber/fiber/v2" 10 | ) 11 | 12 | // Main function 13 | func main() { 14 | fmt.Println("Bookstore REST API with MySQL, GORM, JWT, and Fiber") 15 | 16 | // Refer init function defined in models/booksModel.go 17 | // That loads env, Connects to DB and migrate tables 18 | 19 | // setup app 20 | app := fiber.New() 21 | 22 | // router config 23 | routes.Routes(app) 24 | 25 | PORT := os.Getenv("PORT") 26 | log.Println("Server started on port - ", PORT) 27 | // start app 28 | log.Fatal(app.Listen(PORT)) 29 | } 30 | -------------------------------------------------------------------------------- /12-fiber-book-rest/models/booksModel.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | 7 | "github.com/akilans/fiber-book-rest/initializers" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | // Book Type -> Books table 12 | type Book struct { 13 | ID int `json:"id"` 14 | Title string `json:"title" validate:"required,min=1,max=100" gorm:"unique"` 15 | Author string `json:"author" validate:"required,min=1,max=50"` 16 | Price float64 `json:"price" validate:"required,number"` 17 | ImageURL string `json:"image_url" validate:"required,min=1,max=350"` 18 | } 19 | 20 | var db *gorm.DB 21 | 22 | // Init function to connect to DB, get DB connection and migrate tables 23 | func init() { 24 | initializers.LoadEnvs() 25 | initializers.ConnectDB() 26 | db = initializers.GetDB() 27 | SyncDB() 28 | } 29 | 30 | // add a book 31 | // Get books 32 | func GetBooks() ([]Book, error) { 33 | var books []Book 34 | result := db.Find(&books) 35 | if result.Error != nil { 36 | return nil, result.Error 37 | } 38 | return books, nil 39 | } 40 | 41 | // add a book 42 | func AddBook(book Book) (id int, err error) { 43 | result := db.Create(&book) 44 | if result.Error != nil { 45 | return 0, result.Error 46 | } else { 47 | return book.ID, nil 48 | } 49 | } 50 | 51 | // Update a book 52 | func UpdateBook(book Book) (id int, err error) { 53 | result := db.Save(&book) 54 | if result.Error != nil { 55 | return 0, result.Error 56 | } else { 57 | return book.ID, nil 58 | } 59 | } 60 | 61 | // Get book details 62 | func GetBookByID(id int) Book { 63 | book, isExists := IsBookExists(id) 64 | if isExists { 65 | return book 66 | } 67 | return Book{} 68 | } 69 | 70 | // check book exits or not 71 | func IsBookExists(id int) (Book, bool) { 72 | var book = Book{ID: id} 73 | result := db.Limit(1).Find(&book) 74 | if result.RowsAffected > 0 { 75 | return book, true 76 | } 77 | return Book{}, false 78 | } 79 | 80 | // Delete a book by id 81 | func DeleteBookByID(id int) error { 82 | book, isExists := IsBookExists(id) 83 | if isExists { 84 | db.Delete(&book) 85 | return nil 86 | } 87 | return errors.New("Book doesn't exists") 88 | 89 | } 90 | 91 | // Migrate tables 92 | func SyncDB() { 93 | log.Println("Start of DB migration") 94 | err := db.AutoMigrate(&Book{}, &User{}) 95 | if err != nil { 96 | log.Fatal(err.Error()) 97 | } 98 | log.Println("End of DB migration") 99 | } 100 | -------------------------------------------------------------------------------- /12-fiber-book-rest/models/usersModel.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // User Type -> userss table 4 | type User struct { 5 | ID int `json:"id"` 6 | Name string `json:"name" validate:"required,min=1,max=100"` 7 | Email string `json:"email" validate:"required,email,min=6,max=100" gorm:"unique"` 8 | Password string `json:"password" validate:"required,min=6,max=15"` 9 | } 10 | 11 | // add user 12 | func AddUser(user User) (id int, err error) { 13 | result := db.Create(&user) 14 | if result.Error != nil { 15 | return 0, result.Error 16 | } else { 17 | return user.ID, nil 18 | } 19 | } 20 | 21 | // add user 22 | func GetUserByEmail(email string) (User, error) { 23 | var user User 24 | result := db.Where("email = ?", email).Find(&user) 25 | if result.Error != nil { 26 | return User{}, result.Error 27 | } else { 28 | return user, nil 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /12-fiber-book-rest/routes/routes.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/akilans/fiber-book-rest/controllers" 7 | "github.com/gofiber/fiber/v2" 8 | jwtware "github.com/gofiber/jwt/v3" 9 | ) 10 | 11 | // Define all routes and handlers call 12 | func Routes(app *fiber.App) { 13 | 14 | // No auth needed for this /admin and /login 15 | // Register Admin page 16 | app.Post("/admin", controllers.AddUserHandler) 17 | 18 | // Login admin 19 | app.Post("/login", controllers.LoginHandler) 20 | 21 | // Protected routes 22 | // JWT Middleware 23 | app.Use(jwtware.New(jwtware.Config{ 24 | SigningKey: []byte(os.Getenv("SECRET_KEY")), 25 | ErrorHandler: controllers.JwtError, 26 | })) 27 | 28 | // List all books 29 | app.Get("/", controllers.ListBooksHandler) 30 | 31 | // Add a new book 32 | app.Post("/addbook", controllers.AddBookHandler) 33 | 34 | // get a book by id 35 | app.Get("/book/:id", controllers.GetBookHandler) 36 | 37 | // update a book by id 38 | app.Put("/book/:id", controllers.UpdateBookHandler) 39 | 40 | // delete a book by id 41 | app.Delete("/book/:id", controllers.DeleteBookHandler) 42 | 43 | } 44 | -------------------------------------------------------------------------------- /13-k8s-client-go/README.md: -------------------------------------------------------------------------------- 1 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fakilans%2Fgolang-mini-projects%2Ftree%2Fmain%2F13-k8s-client-go&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 2 | 3 | # Kubernetes Client-go 4 | 5 | This program lists pods and deployments in the kubernetes cluster. 6 | Also creates/updates a deployment with k8s manifest yaml file 7 | [dep.yaml](https://github.com/akilans/golang-mini-projects/blob/main/13-k8s-client-go/dep.yaml) 8 | 9 | ## Prerequisites 10 | 11 | - Go 12 | - Running k8s cluster with kubeconfig file (v1.25.3) 13 | - client-go package (v0.25.3) 14 | - k8s API structure and basic understanding 15 | - [k8s-api-ref.md](https://github.com/akilans/golang-mini-projects/blob/main/13-k8s-client-go/k8s-api-ref.md) 16 | 17 | ### Demo 18 | 19 | ![List k8s pods, Deployment and create/update deployment](https://raw.githubusercontent.com/akilans/golang-mini-projects/main/demos/k8s-client-go.gif) 20 | 21 | ```bash 22 | 23 | # clone a repo 24 | git clone https://github.com/akilans/golang-mini-projects.git 25 | 26 | # go to the 13-k8s-client-go 27 | cd 13-k8s-client-go 28 | 29 | # build 30 | go build 31 | 32 | # run 33 | 34 | ./k8s-client-go 35 | # Sample output 36 | : ' 37 | Testing client go... 38 | Pod - test-dep-ffd6fccb7-4cc7g 39 | Deployment - test-dep 40 | test-dep is found in default namespace. So Updating.... 41 | Updated deployment successfully... 42 | ' 43 | ``` 44 | 45 | ## Credits and references 46 | 47 | 1. [Ivan Velichko](https://iximiuz.com/en/posts/kubernetes-api-structure-and-terminology/) 48 | 2. [dx13.co.uk](https://dx13.co.uk/articles/2021/01/15/kubernetes-types-using-go/) 49 | 3. [Client-go Examples](https://github.com/kubernetes/client-go/tree/master/examples) 50 | -------------------------------------------------------------------------------- /13-k8s-client-go/dep.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: test-dep 6 | name: test-dep 7 | spec: 8 | replicas: 5 9 | selector: 10 | matchLabels: 11 | app: test-dep 12 | template: 13 | metadata: 14 | labels: 15 | app: test-dep 16 | spec: 17 | containers: 18 | - image: httpd 19 | name: httpd -------------------------------------------------------------------------------- /13-k8s-client-go/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/akilans/k8s-client-go 2 | 3 | go 1.19 4 | 5 | require ( 6 | k8s.io/api v0.26.1 7 | k8s.io/apimachinery v0.26.1 8 | k8s.io/client-go v0.25.3 9 | ) 10 | 11 | require ( 12 | github.com/davecgh/go-spew v1.1.1 // indirect 13 | github.com/emicklei/go-restful/v3 v3.9.0 // indirect 14 | github.com/go-logr/logr v1.2.3 // indirect 15 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 16 | github.com/go-openapi/jsonreference v0.20.0 // indirect 17 | github.com/go-openapi/swag v0.19.14 // indirect 18 | github.com/gogo/protobuf v1.3.2 // indirect 19 | github.com/golang/protobuf v1.5.2 // indirect 20 | github.com/google/gnostic v0.5.7-v3refs // indirect 21 | github.com/google/go-cmp v0.5.9 // indirect 22 | github.com/google/gofuzz v1.1.0 // indirect 23 | github.com/imdario/mergo v0.3.6 // indirect 24 | github.com/josharian/intern v1.0.0 // indirect 25 | github.com/json-iterator/go v1.1.12 // indirect 26 | github.com/mailru/easyjson v0.7.6 // indirect 27 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 28 | github.com/modern-go/reflect2 v1.0.2 // indirect 29 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 30 | github.com/spf13/pflag v1.0.5 // indirect 31 | golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 // indirect 32 | golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect 33 | golang.org/x/sys v0.3.0 // indirect 34 | golang.org/x/term v0.3.0 // indirect 35 | golang.org/x/text v0.5.0 // indirect 36 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect 37 | google.golang.org/appengine v1.6.7 // indirect 38 | google.golang.org/protobuf v1.28.1 // indirect 39 | gopkg.in/inf.v0 v0.9.1 // indirect 40 | gopkg.in/yaml.v2 v2.4.0 // indirect 41 | gopkg.in/yaml.v3 v3.0.1 // indirect 42 | k8s.io/klog/v2 v2.80.1 // indirect 43 | k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect 44 | k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect 45 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 46 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 47 | sigs.k8s.io/yaml v1.3.0 // indirect 48 | ) 49 | -------------------------------------------------------------------------------- /13-k8s-client-go/k8s-api-ref.md: -------------------------------------------------------------------------------- 1 | # K8S API Reference 2 | 3 | ```bash 4 | # It gives k8s api information 5 | kubectl api-resources -o wide 6 | # it gives how kubectl calls k8s api vi url 7 | kubectl get pods -v 6 8 | #https://192.168.49.2:8443/api/v1/namespaces/default/pods?limit=500 9 | 10 | 11 | # Make Kubernetes API available on localhost:8080 12 | # to bypass the auth step in subsequent queries: 13 | kubectl proxy --port=8080 & 14 | 15 | # List all known API paths 16 | curl http://localhost:8080/ 17 | # List known versions of the `core` group 18 | curl http://localhost:8080/api 19 | # List known resources of the `core/v1` group 20 | curl http://localhost:8080/api/v1 21 | # Get a particular Pod resource 22 | curl http://localhost:8080/api/v1/namespaces/default/pods/sleep-7c7db887d8-dkkcg 23 | 24 | # List known groups (all but `core`) 25 | curl http://localhost:8080/apis 26 | # List known versions of the `apps` group 27 | curl http://localhost:8080/apis/apps 28 | # List known resources of the `apps/v1` group 29 | curl http://localhost:8080/apis/apps/v1 30 | # Get a particular Deployment resource 31 | curl http://localhost:8080/apis/apps/v1/namespaces/default/deployments/sleep 32 | 33 | # json response 34 | kubectl get --raw /api/v1/namespaces/default/pods/httpd | python3 -m json.tool 35 | 36 | ``` 37 | 38 | ### K8S - API 39 | 40 | - k8s.io/api and k8s.io/apimachinery modules - these are the two main dependencies of the official Go client. 41 | The api module defines Go structs for the Kubernetes Objects, and the apimachinery module brings lower-level building blocks and common API functionality like serialization, type conversion, or error handling 42 | 43 | ```bash 44 | # get k8s api endpoint url with port 45 | kubectl cluster-info 46 | kubectl config view 47 | 48 | # Find k8s version 49 | curl https://192.168.49.2:8443/version --insecure 50 | curl https://192.168.49.2:8443/version --cacert ~/.minikube/ca.crt 51 | 52 | # call api with authentication 53 | # With certificates 54 | # It throws forbidder error 55 | curl --cacert ~/.minikube/ca.crt https://192.168.49.2:8443/apis/apps/v1/deployments 56 | # with certs 57 | curl --cert ~/.minikube/profiles/minikube/client.crt --key ~/.minikube/profiles/minikube/client.key \ 58 | --cacert ~/.minikube/ca.crt https://192.168.49.2:8443/apis/apps/v1/deployments 59 | 60 | # With Token 61 | # generate token 62 | TOKEN=$(kubectl create token default) 63 | curl --cacert ~/.minikube/ca.crt https://192.168.49.2:8443/apis/apps/v1/deployments \ 64 | --header "Authorization: Bearer $TOKEN" 65 | 66 | # service account from kube-system namespace 67 | TOKEN=$(kubectl -n kube-system create token default) 68 | curl --cacert ~/.minikube/ca.crt https://192.168.49.2:8443/apis/apps/v1/deployments \ 69 | --header "Authorization: Bearer $TOKEN" 70 | 71 | # Create deployment 72 | curl --cert ~/.minikube/profiles/minikube/client.crt --key ~/.minikube/profiles/minikube/client.key \ 73 | --cacert ~/.minikube/ca.crt https://192.168.49.2:8443/apis/apps/v1/namespaces/default/deployments \ 74 | -X POST \ 75 | -H 'Content-Type: application/yaml' \ 76 | -d '--- 77 | apiVersion: apps/v1 78 | kind: Deployment 79 | metadata: 80 | labels: 81 | app: demo-app 82 | name: demo-app 83 | spec: 84 | replicas: 2 85 | selector: 86 | matchLabels: 87 | app: demo-app 88 | template: 89 | metadata: 90 | labels: 91 | app: demo-app 92 | spec: 93 | containers: 94 | - image: httpd 95 | name: httpd 96 | ' 97 | 98 | # Update deployment 99 | curl --cert ~/.minikube/profiles/minikube/client.crt --key ~/.minikube/profiles/minikube/client.key \ 100 | --cacert ~/.minikube/ca.crt https://192.168.49.2:8443/apis/apps/v1/namespaces/default/deployments/demo-app \ 101 | -X PUT \ 102 | -H 'Content-Type: application/yaml' \ 103 | -d '--- 104 | apiVersion: apps/v1 105 | kind: Deployment 106 | metadata: 107 | labels: 108 | app: demo-app 109 | name: demo-app 110 | spec: 111 | replicas: 4 112 | selector: 113 | matchLabels: 114 | app: demo-app 115 | template: 116 | metadata: 117 | labels: 118 | app: demo-app 119 | spec: 120 | containers: 121 | - image: httpd 122 | name: httpd 123 | ' 124 | 125 | #Patch deployment 126 | curl https://192.168.49.2:8443/apis/apps/v1/namespaces/default/deployments/demo-app \ 127 | --cacert ~/.minikube/ca.crt \ 128 | --cert ~/.minikube/profiles/minikube/client.crt \ 129 | --key ~/.minikube/profiles/minikube/client.key \ 130 | -X PATCH \ 131 | -H 'Content-Type: application/merge-patch+json' \ 132 | -d '{ 133 | "spec": { 134 | "template": { 135 | "spec": { 136 | "containers": [ 137 | { 138 | "name": "httpd", 139 | "image": "httpd" 140 | } 141 | ] 142 | } 143 | } 144 | } 145 | }' 146 | 147 | # update only replicas 148 | curl https://192.168.49.2:8443/apis/apps/v1/namespaces/default/deployments/demo-app \ 149 | --cacert ~/.minikube/ca.crt \ 150 | --cert ~/.minikube/profiles/minikube/client.crt \ 151 | --key ~/.minikube/profiles/minikube/client.key \ 152 | -X PATCH \ 153 | -H 'Content-Type: application/merge-patch+json' \ 154 | -d '{ 155 | "spec": { 156 | "replicas": 1 157 | } 158 | }' 159 | 160 | # Delete deployment 161 | curl https://192.168.49.2:8443/apis/apps/v1/namespaces/default/deployments/demo-app \ 162 | --cacert ~/.minikube/ca.crt \ 163 | --cert ~/.minikube/profiles/minikube/client.crt \ 164 | --key ~/.minikube/profiles/minikube/client.key \ 165 | -X DELETE 166 | ``` 167 | 168 | ### k8s.io/api, k8s.io/apimachinery 169 | 170 | - While the k8s.io/api module focuses on the concrete higher-level types like Deployments, Secrets, or Pods, the k8s.io/apimachinery is a home for lower-level but more universal data structures. 171 | s 172 | -------------------------------------------------------------------------------- /13-k8s-client-go/main.go: -------------------------------------------------------------------------------- 1 | // This application 2 | // Lists Pods, Deployments in the kubernetes cluster 3 | // Creates/Updates a deployment with k8s manifest yaml file 4 | 5 | package main 6 | 7 | import ( 8 | "context" 9 | "flag" 10 | "fmt" 11 | "log" 12 | "os" 13 | 14 | appsv1 "k8s.io/api/apps/v1" 15 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 | "k8s.io/client-go/kubernetes" 17 | "k8s.io/client-go/kubernetes/scheme" 18 | "k8s.io/client-go/tools/clientcmd" 19 | ) 20 | 21 | // Function to list all the pods in the specific namespace 22 | func listPods(clientset *kubernetes.Clientset, namespace string) { 23 | 24 | pods, err := clientset.CoreV1().Pods(namespace).List(context.Background(), v1.ListOptions{}) 25 | 26 | if err != nil { 27 | panic(err.Error()) 28 | } 29 | 30 | // print all the pod names in the specified namespace 31 | if len(pods.Items) > 0 { 32 | for _, pod := range pods.Items { 33 | fmt.Println("Pod -", pod.Name) 34 | } 35 | } else { 36 | fmt.Printf("No pods found in %s namespace\n", namespace) 37 | } 38 | } 39 | 40 | // Function to list all the Deployments in the specific namespace 41 | func listDeployments(clientset *kubernetes.Clientset, namespace string) { 42 | 43 | deployments, err := clientset.AppsV1().Deployments(namespace).List(context.Background(), v1.ListOptions{}) 44 | 45 | if err != nil { 46 | panic(err.Error()) 47 | } 48 | 49 | // print all the pod names in default namespace 50 | if len(deployments.Items) > 0 { 51 | for _, deployment := range deployments.Items { 52 | fmt.Println("Deployment -", deployment.Name) 53 | } 54 | } else { 55 | fmt.Printf("No deployments found in %s namespace\n", namespace) 56 | } 57 | } 58 | 59 | func main() { 60 | fmt.Println("Testing client go...") 61 | 62 | // Namespace to deploy 63 | namespace := "default" 64 | kubeConfigFilePath := "/home/akilan/.kube/config" 65 | 66 | kubeconfig := flag.String("kubeconfig", kubeConfigFilePath, "kubeconfig file location") 67 | 68 | // use the current context in kubeconfig 69 | config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) 70 | if err != nil { 71 | panic(err.Error()) 72 | } 73 | 74 | // create the clientset 75 | clientset, err := kubernetes.NewForConfig(config) 76 | if err != nil { 77 | panic(err.Error()) 78 | } 79 | 80 | // getting pods in default namespace 81 | listPods(clientset, namespace) 82 | // get all the deployments in the default namespace 83 | listDeployments(clientset, namespace) 84 | 85 | // Load the file into a buffer 86 | data, err := os.ReadFile("dep.yaml") 87 | if err != nil { 88 | log.Fatal(err) 89 | } 90 | 91 | // Universal Decoder for schema decoding for all kinds [ Pod, Deployment, Service etc...] 92 | decoder := scheme.Codecs.UniversalDeserializer() 93 | 94 | // Find GVK and decoding 95 | obj, groupVersionKind, err := decoder.Decode(data, nil, nil) 96 | if err != nil { 97 | fmt.Println(err) 98 | } 99 | 100 | // Supporting only deployment kind 101 | if groupVersionKind.Group == "apps" && 102 | groupVersionKind.Version == "v1" && 103 | groupVersionKind.Kind == "Deployment" { 104 | 105 | deploymentObj := obj.(*appsv1.Deployment) 106 | 107 | // check if deployment with same name exists 108 | existingDeployments, _ := clientset.AppsV1().Deployments(namespace).List(context.Background(), v1.ListOptions{}) 109 | if err != nil { 110 | fmt.Println(err) 111 | } 112 | 113 | isDeploymentExists := false 114 | 115 | for _, dep := range existingDeployments.Items { 116 | if dep.Name == deploymentObj.ObjectMeta.Name { 117 | isDeploymentExists = true 118 | } 119 | } 120 | 121 | if !isDeploymentExists { 122 | // create a deployment if empty 123 | fmt.Printf("%s is not found in %s namespace. So Creating...\n", deploymentObj.ObjectMeta.Name, namespace) 124 | _, err = clientset.AppsV1().Deployments(namespace).Create(context.Background(), deploymentObj, v1.CreateOptions{}) 125 | if err != nil { 126 | fmt.Println(err) 127 | } else { 128 | fmt.Println("Created deployment successfully...") 129 | } 130 | 131 | } else { 132 | // update the deployment if exists 133 | fmt.Printf("%s is found in %s namespace. So Updating.... \n", deploymentObj.ObjectMeta.Name, namespace) 134 | _, err = clientset.AppsV1().Deployments(namespace).Update(context.Background(), deploymentObj, v1.UpdateOptions{}) 135 | if err != nil { 136 | fmt.Println(err) 137 | } else { 138 | fmt.Println("Updated deployment successfully...") 139 | } 140 | } 141 | 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /14_akit-ops/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19.5 as builder 2 | # Define build env 3 | ENV GOOS linux 4 | ENV CGO_ENABLED 0 5 | # Add a work directory 6 | WORKDIR /app 7 | # Cache and install dependencies 8 | COPY go.mod go.sum ./ 9 | RUN go mod download 10 | # Copy app files 11 | COPY . . 12 | # Build app 13 | RUN go build -o akit-ops 14 | 15 | 16 | FROM alpine:3.17 as production 17 | # Add certificates 18 | RUN apk add --no-cache ca-certificates 19 | # Copy built binary from builder 20 | COPY --from=builder /app/akit-ops . 21 | 22 | # Exec built binary 23 | CMD ./akit-ops -------------------------------------------------------------------------------- /14_akit-ops/README.md: -------------------------------------------------------------------------------- 1 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fakilans%2Fgolang-mini-projects%2Ftree%2Fmain%2F14_akit-ops&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 2 | 3 | # Akit-Ops - simple gitops solution 4 | 5 | - This application detect any changes in the provided git repository and create/update k8s resources 6 | - Provide a repo url and pull interval as env variable 7 | - Commit a valid k8s manifest files in the provided git repository 8 | 9 | ### Prerequisites 10 | 11 | - Go 12 | - K8s cluster 13 | - Create a service account with Proper permissions and deploy this application - [sa-role-rb-akit-ops.yaml](https://github.com/akilans/golang-mini-projects/blob/main/14_akit-ops/sa-role-rb-akit-ops.yaml) 14 | - Public github repo with simple k8s manifest files 15 | - Understanding of k8s API architecture - [k8s-api-ref.md](https://github.com/akilans/golang-mini-projects/blob/main/14_akit-ops/k8s-api-ref.md) 16 | 17 | ### Flowchart 18 | 19 | ![Akit-Ops](https://raw.githubusercontent.com/akilans/golang-mini-projects/main/images/akit-ops.png?raw=true) 20 | 21 | ### Demo 22 | 23 | ![Akit-Ops Demo](https://raw.githubusercontent.com/akilans/golang-mini-projects/main/demos/akit-ops.gif) 24 | 25 | ### Usage 26 | 27 | ```bash 28 | # clone a repo 29 | git clone https://github.com/akilans/golang-mini-projects.git 30 | 31 | # go to the 14_akit-ops 32 | cd 14_akit-ops 33 | 34 | # create docker image for akit-ops and push it 35 | docker image build -t akilan/akit-ops:1 . 36 | docker image push akilan/akit-ops:1 37 | 38 | # create a service account,deployment with proper roles for deployment 39 | # update repo url and pull interval here 40 | kubectl apply -f sa-role-rb-akit-ops.yaml 41 | 42 | ``` 43 | 44 | ## Credits and references 45 | 46 | 1. [Ivan Velichko](https://iximiuz.com/en/posts/kubernetes-api-structure-and-terminology/) 47 | 2. [dx13.co.uk](https://dx13.co.uk/articles/2021/01/15/kubernetes-types-using-go/) 48 | 3. [Client-go Examples](https://github.com/kubernetes/client-go/tree/master/examples) 49 | -------------------------------------------------------------------------------- /14_akit-ops/akit-ops-ui-app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM httpd 2 | 3 | COPY . /usr/local/apache2/htdocs/ -------------------------------------------------------------------------------- /14_akit-ops/akit-ops-ui-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Akit-Ops-Demo 7 | 13 | 14 | 15 |

Akit-Ops - Demo -v3

16 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /14_akit-ops/createUpdate.go: -------------------------------------------------------------------------------- 1 | // It has function to create/update deployment and Service 2 | package main 3 | 4 | import ( 5 | "context" 6 | "fmt" 7 | 8 | appsv1 "k8s.io/api/apps/v1" 9 | corev1 "k8s.io/api/core/v1" 10 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/client-go/kubernetes" 12 | ) 13 | 14 | // Create / Update service function 15 | func createUpdateService(clientset *kubernetes.Clientset, serviceObj *corev1.Service, namespace string) { 16 | 17 | // check if service with same name exists 18 | existingservices, err := clientset.CoreV1().Services(namespace).List(context.Background(), v1.ListOptions{}) 19 | checkIfError(err) 20 | isServiceExists := false 21 | 22 | for _, dep := range existingservices.Items { 23 | if dep.Name == serviceObj.ObjectMeta.Name { 24 | isServiceExists = true 25 | } 26 | } 27 | 28 | if !isServiceExists { 29 | // create a service if empty 30 | fmt.Printf("%s is not found in %s namespace. So Creating...\n", serviceObj.ObjectMeta.Name, namespace) 31 | _, err := clientset.CoreV1().Services(namespace).Create(context.Background(), serviceObj, v1.CreateOptions{}) 32 | if err != nil { 33 | fmt.Println(err) 34 | } else { 35 | fmt.Println("Created service successfully...") 36 | } 37 | 38 | } else { 39 | // update the service if exists 40 | fmt.Printf("%s is found in %s namespace. So Updating.... \n", serviceObj.ObjectMeta.Name, namespace) 41 | _, err := clientset.CoreV1().Services(namespace).Update(context.Background(), serviceObj, v1.UpdateOptions{}) 42 | if err != nil { 43 | fmt.Println(err) 44 | } else { 45 | fmt.Println("Updated service successfully...") 46 | } 47 | } 48 | 49 | } 50 | 51 | // Create / Update deployment function 52 | func createUpdateDeployment(clientset *kubernetes.Clientset, deploymentObj *appsv1.Deployment, namespace string) { 53 | 54 | // check if deployment with same name exists 55 | existingDeployments, err := clientset.AppsV1().Deployments(namespace).List(context.Background(), v1.ListOptions{}) 56 | checkIfError(err) 57 | isDeploymentExists := false 58 | 59 | for _, dep := range existingDeployments.Items { 60 | if dep.Name == deploymentObj.ObjectMeta.Name { 61 | isDeploymentExists = true 62 | } 63 | } 64 | 65 | if !isDeploymentExists { 66 | // create a deployment if empty 67 | fmt.Printf("%s is not found in %s namespace. So Creating...\n", deploymentObj.ObjectMeta.Name, namespace) 68 | _, err = clientset.AppsV1().Deployments(namespace).Create(context.Background(), deploymentObj, v1.CreateOptions{}) 69 | if err != nil { 70 | fmt.Println(err) 71 | } else { 72 | fmt.Println("Created deployment successfully...") 73 | } 74 | 75 | } else { 76 | // update the deployment if exists 77 | fmt.Printf("%s is found in %s namespace. So Updating.... \n", deploymentObj.ObjectMeta.Name, namespace) 78 | _, err = clientset.AppsV1().Deployments(namespace).Update(context.Background(), deploymentObj, v1.UpdateOptions{}) 79 | if err != nil { 80 | fmt.Println(err) 81 | } else { 82 | fmt.Println("Updated deployment successfully...") 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /14_akit-ops/git.go: -------------------------------------------------------------------------------- 1 | // It has functions to clone a repo and detect any changes in the repo 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | 7 | "github.com/go-git/go-git/v5" 8 | ) 9 | 10 | // clone the provided repo 11 | func cloneRepo(url, repoPath string) *git.Repository { 12 | r, err := git.PlainClone(repoPath, false, &git.CloneOptions{ 13 | URL: url, 14 | RecurseSubmodules: git.DefaultSubmoduleRecursionDepth, 15 | }) 16 | checkIfError(err) 17 | return r 18 | } 19 | 20 | // Detect any changes in the repo 21 | func detectChanges(r *git.Repository) bool { 22 | 23 | isChanged := false 24 | 25 | ref, err := r.Head() 26 | 27 | checkIfError(err) 28 | 29 | currentHashId := ref.Hash() 30 | 31 | // Get the working directory for the repository 32 | w, err := r.Worktree() 33 | 34 | checkIfError(err) 35 | 36 | w.Pull(&git.PullOptions{RemoteName: "origin"}) 37 | 38 | ref, err = r.Head() 39 | 40 | checkIfError(err) 41 | newHashId := ref.Hash() 42 | 43 | if currentHashId != newHashId { 44 | fmt.Println(string("\033[32m"), "Change detected...Time to deploy", string("\033[0m")) 45 | isChanged = true 46 | } else { 47 | fmt.Println(string("\033[31m"), "No change detected...", string("\033[0m")) 48 | } 49 | 50 | return isChanged 51 | 52 | } 53 | -------------------------------------------------------------------------------- /14_akit-ops/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/akilans/akit-ops 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/go-git/go-git/v5 v5.6.1 7 | k8s.io/api v0.26.1 8 | k8s.io/apimachinery v0.26.1 9 | k8s.io/client-go v0.25.3 10 | ) 11 | 12 | require ( 13 | github.com/Microsoft/go-winio v0.5.2 // indirect 14 | github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect 15 | github.com/acomagu/bufpipe v1.0.4 // indirect 16 | github.com/cloudflare/circl v1.1.0 // indirect 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/emicklei/go-restful/v3 v3.9.0 // indirect 19 | github.com/emirpasic/gods v1.18.1 // indirect 20 | github.com/go-git/gcfg v1.5.0 // indirect 21 | github.com/go-git/go-billy/v5 v5.4.1 // indirect 22 | github.com/go-logr/logr v1.2.3 // indirect 23 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 24 | github.com/go-openapi/jsonreference v0.20.1 // indirect 25 | github.com/go-openapi/swag v0.22.3 // indirect 26 | github.com/gogo/protobuf v1.3.2 // indirect 27 | github.com/golang/protobuf v1.5.3 // indirect 28 | github.com/google/gnostic v0.5.7-v3refs // indirect 29 | github.com/google/go-cmp v0.5.9 // indirect 30 | github.com/google/gofuzz v1.1.0 // indirect 31 | github.com/google/uuid v1.3.0 // indirect 32 | github.com/imdario/mergo v0.3.13 // indirect 33 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 34 | github.com/josharian/intern v1.0.0 // indirect 35 | github.com/json-iterator/go v1.1.12 // indirect 36 | github.com/kevinburke/ssh_config v1.2.0 // indirect 37 | github.com/mailru/easyjson v0.7.7 // indirect 38 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 39 | github.com/modern-go/reflect2 v1.0.2 // indirect 40 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 41 | github.com/pjbgf/sha1cd v0.3.0 // indirect 42 | github.com/sergi/go-diff v1.1.0 // indirect 43 | github.com/skeema/knownhosts v1.1.0 // indirect 44 | github.com/spf13/pflag v1.0.5 // indirect 45 | github.com/xanzy/ssh-agent v0.3.3 // indirect 46 | golang.org/x/crypto v0.6.0 // indirect 47 | golang.org/x/net v0.8.0 // indirect 48 | golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect 49 | golang.org/x/sys v0.6.0 // indirect 50 | golang.org/x/term v0.6.0 // indirect 51 | golang.org/x/text v0.8.0 // indirect 52 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect 53 | google.golang.org/appengine v1.6.7 // indirect 54 | google.golang.org/protobuf v1.28.1 // indirect 55 | gopkg.in/inf.v0 v0.9.1 // indirect 56 | gopkg.in/warnings.v0 v0.1.2 // indirect 57 | gopkg.in/yaml.v2 v2.4.0 // indirect 58 | gopkg.in/yaml.v3 v3.0.1 // indirect 59 | k8s.io/klog/v2 v2.90.1 // indirect 60 | k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a // indirect 61 | k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect 62 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 63 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 64 | sigs.k8s.io/yaml v1.3.0 // indirect 65 | ) 66 | -------------------------------------------------------------------------------- /14_akit-ops/k8s-api-ref.md: -------------------------------------------------------------------------------- 1 | # K8S API Reference 2 | 3 | ```bash 4 | # It gives k8s api information 5 | kubectl api-resources -o wide 6 | # it gives how kubectl calls k8s api vi url 7 | kubectl get pods -v 6 8 | #https://192.168.49.2:8443/api/v1/namespaces/default/pods?limit=500 9 | 10 | 11 | # Make Kubernetes API available on localhost:8080 12 | # to bypass the auth step in subsequent queries: 13 | kubectl proxy --port=8080 & 14 | 15 | # List all known API paths 16 | curl http://localhost:8080/ 17 | # List known versions of the `core` group 18 | curl http://localhost:8080/api 19 | # List known resources of the `core/v1` group 20 | curl http://localhost:8080/api/v1 21 | # Get a particular Pod resource 22 | curl http://localhost:8080/api/v1/namespaces/default/pods/sleep-7c7db887d8-dkkcg 23 | 24 | # List known groups (all but `core`) 25 | curl http://localhost:8080/apis 26 | # List known versions of the `apps` group 27 | curl http://localhost:8080/apis/apps 28 | # List known resources of the `apps/v1` group 29 | curl http://localhost:8080/apis/apps/v1 30 | # Get a particular Deployment resource 31 | curl http://localhost:8080/apis/apps/v1/namespaces/default/deployments/sleep 32 | 33 | # json response 34 | kubectl get --raw /api/v1/namespaces/default/pods/httpd | python3 -m json.tool 35 | 36 | ``` 37 | 38 | ### K8S - API 39 | 40 | - k8s.io/api and k8s.io/apimachinery modules - these are the two main dependencies of the official Go client. 41 | The api module defines Go structs for the Kubernetes Objects, and the apimachinery module brings lower-level building blocks and common API functionality like serialization, type conversion, or error handling 42 | 43 | ```bash 44 | # get k8s api endpoint url with port 45 | kubectl cluster-info 46 | kubectl config view 47 | 48 | # Find k8s version 49 | curl https://192.168.49.2:8443/version --insecure 50 | curl https://192.168.49.2:8443/version --cacert ~/.minikube/ca.crt 51 | 52 | # call api with authentication 53 | # With certificates 54 | # It throws forbidder error 55 | curl --cacert ~/.minikube/ca.crt https://192.168.49.2:8443/apis/apps/v1/deployments 56 | # with certs 57 | curl --cert ~/.minikube/profiles/minikube/client.crt --key ~/.minikube/profiles/minikube/client.key \ 58 | --cacert ~/.minikube/ca.crt https://192.168.49.2:8443/apis/apps/v1/deployments 59 | 60 | # With Token 61 | # generate token 62 | TOKEN=$(kubectl create token default) 63 | curl --cacert ~/.minikube/ca.crt https://192.168.49.2:8443/apis/apps/v1/deployments \ 64 | --header "Authorization: Bearer $TOKEN" 65 | 66 | # service account from kube-system namespace 67 | TOKEN=$(kubectl -n kube-system create token default) 68 | curl --cacert ~/.minikube/ca.crt https://192.168.49.2:8443/apis/apps/v1/deployments \ 69 | --header "Authorization: Bearer $TOKEN" 70 | 71 | # Create deployment 72 | curl --cert ~/.minikube/profiles/minikube/client.crt --key ~/.minikube/profiles/minikube/client.key \ 73 | --cacert ~/.minikube/ca.crt https://192.168.49.2:8443/apis/apps/v1/namespaces/default/deployments \ 74 | -X POST \ 75 | -H 'Content-Type: application/yaml' \ 76 | -d '--- 77 | apiVersion: apps/v1 78 | kind: Deployment 79 | metadata: 80 | labels: 81 | app: demo-app 82 | name: demo-app 83 | spec: 84 | replicas: 2 85 | selector: 86 | matchLabels: 87 | app: demo-app 88 | template: 89 | metadata: 90 | labels: 91 | app: demo-app 92 | spec: 93 | containers: 94 | - image: httpd 95 | name: httpd 96 | ' 97 | 98 | # Update deployment 99 | curl --cert ~/.minikube/profiles/minikube/client.crt --key ~/.minikube/profiles/minikube/client.key \ 100 | --cacert ~/.minikube/ca.crt https://192.168.49.2:8443/apis/apps/v1/namespaces/default/deployments/demo-app \ 101 | -X PUT \ 102 | -H 'Content-Type: application/yaml' \ 103 | -d '--- 104 | apiVersion: apps/v1 105 | kind: Deployment 106 | metadata: 107 | labels: 108 | app: demo-app 109 | name: demo-app 110 | spec: 111 | replicas: 4 112 | selector: 113 | matchLabels: 114 | app: demo-app 115 | template: 116 | metadata: 117 | labels: 118 | app: demo-app 119 | spec: 120 | containers: 121 | - image: httpd 122 | name: httpd 123 | ' 124 | 125 | #Patch deployment 126 | curl https://192.168.49.2:8443/apis/apps/v1/namespaces/default/deployments/demo-app \ 127 | --cacert ~/.minikube/ca.crt \ 128 | --cert ~/.minikube/profiles/minikube/client.crt \ 129 | --key ~/.minikube/profiles/minikube/client.key \ 130 | -X PATCH \ 131 | -H 'Content-Type: application/merge-patch+json' \ 132 | -d '{ 133 | "spec": { 134 | "template": { 135 | "spec": { 136 | "containers": [ 137 | { 138 | "name": "httpd", 139 | "image": "httpd" 140 | } 141 | ] 142 | } 143 | } 144 | } 145 | }' 146 | 147 | # update only replicas 148 | curl https://192.168.49.2:8443/apis/apps/v1/namespaces/default/deployments/demo-app \ 149 | --cacert ~/.minikube/ca.crt \ 150 | --cert ~/.minikube/profiles/minikube/client.crt \ 151 | --key ~/.minikube/profiles/minikube/client.key \ 152 | -X PATCH \ 153 | -H 'Content-Type: application/merge-patch+json' \ 154 | -d '{ 155 | "spec": { 156 | "replicas": 1 157 | } 158 | }' 159 | 160 | # Delete deployment 161 | curl https://192.168.49.2:8443/apis/apps/v1/namespaces/default/deployments/demo-app \ 162 | --cacert ~/.minikube/ca.crt \ 163 | --cert ~/.minikube/profiles/minikube/client.crt \ 164 | --key ~/.minikube/profiles/minikube/client.key \ 165 | -X DELETE 166 | ``` 167 | 168 | ### k8s.io/api, k8s.io/apimachinery 169 | 170 | - While the k8s.io/api module focuses on the concrete higher-level types like Deployments, Secrets, or Pods, the k8s.io/apimachinery is a home for lower-level but more universal data structures. 171 | -------------------------------------------------------------------------------- /14_akit-ops/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "strconv" 9 | "strings" 10 | "time" 11 | 12 | appsv1 "k8s.io/api/apps/v1" 13 | corev1 "k8s.io/api/core/v1" 14 | "k8s.io/client-go/kubernetes" 15 | "k8s.io/client-go/kubernetes/scheme" 16 | "k8s.io/client-go/rest" 17 | "k8s.io/client-go/tools/clientcmd" 18 | ) 19 | 20 | // Common function to check any error 21 | func checkIfError(err error) { 22 | if err != nil { 23 | fmt.Printf("error: %s\n", err) 24 | } 25 | } 26 | 27 | func main() { 28 | 29 | fmt.Println("My own git ops - akit-ops") 30 | 31 | // Default values 32 | url := os.Getenv("REPO_URL") 33 | pullInterval, _ := strconv.Atoi(os.Getenv("REPO_PULL_INTERVAL")) 34 | repoPath := "./deployrepo" 35 | manifestFile := "manifest.yaml" 36 | namespace := "default" 37 | 38 | // check this app is running inside k8s cluster 39 | _, isRunningInsideK8sCluster := os.LookupEnv("KUBERNETES_SERVICE_HOST") 40 | 41 | // clientset variable 42 | var clientset *kubernetes.Clientset 43 | 44 | // create clientset based on where this app is running 45 | if isRunningInsideK8sCluster { 46 | // creates the in-cluster config 47 | config, err := rest.InClusterConfig() 48 | checkIfError(err) 49 | // creates the clientset 50 | clientset, err = kubernetes.NewForConfig(config) 51 | checkIfError(err) 52 | } else { 53 | currentUserHomeDir, _ := os.UserHomeDir() 54 | kubeConfigFilePath := filepath.Join(currentUserHomeDir, ".kube/config") 55 | kubeconfig := flag.String("kubeconfig", kubeConfigFilePath, "kubeconfig file location") 56 | 57 | // use the current context in kubeconfig 58 | config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) 59 | checkIfError(err) 60 | 61 | // create the clientset 62 | clientset, err = kubernetes.NewForConfig(config) 63 | checkIfError(err) 64 | } 65 | 66 | // call clone repo function 67 | r := cloneRepo(url, repoPath) 68 | 69 | // Loop to watch for any changes 70 | for { 71 | // check for any changes 72 | isChanged := detectChanges(r) 73 | 74 | if isChanged { 75 | 76 | // Load the file into a buffer 77 | fname := filepath.Join(repoPath, manifestFile) 78 | data, err := os.ReadFile(fname) 79 | checkIfError(err) 80 | 81 | // Universal Decoder for schema decoding for all kinds [ Pod, Deployment, Service etc...] 82 | decoder := scheme.Codecs.UniversalDeserializer() 83 | 84 | // loop through each kubernetes resources separated by --- 85 | for _, resourceYAML := range strings.Split(string(data), "---") { 86 | 87 | // skip empty documents 88 | if len(resourceYAML) == 0 { 89 | continue 90 | } 91 | 92 | // Find GVK and decoding. 93 | obj, groupVersionKind, err := decoder.Decode([]byte(resourceYAML), nil, nil) 94 | checkIfError(err) 95 | 96 | // check for k8s resource type 97 | switch groupVersionKind.Kind { 98 | // deployment 99 | case "Deployment": 100 | fmt.Println("Deployment") 101 | deploymentObj := obj.(*appsv1.Deployment) 102 | if deploymentObj.ObjectMeta.Namespace != "" { 103 | namespace = deploymentObj.ObjectMeta.Namespace 104 | } 105 | // call the function create/update deployment 106 | createUpdateDeployment(clientset, deploymentObj, namespace) 107 | // service 108 | case "Service": 109 | fmt.Println("Service") 110 | serviceObj := obj.(*corev1.Service) 111 | if serviceObj.ObjectMeta.Namespace != "" { 112 | namespace = serviceObj.ObjectMeta.Namespace 113 | } 114 | 115 | // call the function create/update service 116 | createUpdateService(clientset, serviceObj, namespace) 117 | 118 | default: 119 | fmt.Printf("Currently %s - resource type is not supported\n", groupVersionKind.Kind) 120 | } 121 | 122 | } 123 | 124 | } 125 | 126 | fmt.Println("Wait for ", pullInterval, " Seconds") 127 | time.Sleep(time.Duration(pullInterval) * time.Second) 128 | 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /14_akit-ops/sa-role-rb-akit-ops.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: pod-deployment-service-manager 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: ClusterRole 9 | metadata: 10 | name: pod-deployment-service-manager-role 11 | rules: 12 | - apiGroups: 13 | - "" 14 | resources: 15 | - pods 16 | - services 17 | verbs: 18 | - get 19 | - list 20 | - create 21 | - update 22 | - patch 23 | - apiGroups: 24 | - apps 25 | resources: 26 | - deployments 27 | verbs: 28 | - get 29 | - list 30 | - create 31 | - update 32 | - patch 33 | 34 | --- 35 | apiVersion: rbac.authorization.k8s.io/v1 36 | kind: ClusterRoleBinding 37 | metadata: 38 | name: pod-deployment-read-rb 39 | roleRef: 40 | apiGroup: rbac.authorization.k8s.io 41 | kind: ClusterRole 42 | name: pod-deployment-service-manager-role 43 | subjects: 44 | - kind: ServiceAccount 45 | name: pod-deployment-service-manager 46 | namespace: default 47 | 48 | --- 49 | apiVersion: apps/v1 50 | kind: Deployment 51 | metadata: 52 | labels: 53 | app: akit-ops 54 | name: akit-ops 55 | spec: 56 | replicas: 1 57 | selector: 58 | matchLabels: 59 | app: akit-ops 60 | template: 61 | metadata: 62 | labels: 63 | app: akit-ops 64 | spec: 65 | containers: 66 | - image: akilan/akit-ops:1 67 | name: akit-ops 68 | env: 69 | - name: REPO_URL 70 | value: "https://github.com/akilans/k8s-manifests.git" 71 | - name: REPO_PULL_INTERVAL 72 | value: "15" 73 | serviceAccount: pod-deployment-service-manager -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fakilans%2Fgolang-mini-projects&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 2 | 3 | # Golang Mini Projects for Beginners 4 | 5 | Here is the collection of beginner friendly golang projects 6 | 7 | ### Mini Projects 8 | 9 | - [Book Store cli app to list, add, update, delete books](https://github.com/akilans/golang-mini-projects/tree/main/01-bookstore-cli-flag-json) 10 | 11 | - [Organize folder based on file extensions](https://github.com/akilans/golang-mini-projects/tree/main/02-organize-folder) 12 | 13 | - [Website Monitoring and Alerting using Golang](https://github.com/akilans/golang-mini-projects/tree/main/03-web-monitor) 14 | 15 | - [Bookstore rest API to list, add, update, delete books](https://github.com/akilans/golang-mini-projects/tree/main/04-bookstore-api) 16 | 17 | - [Random password generator](https://github.com/akilans/golang-mini-projects/tree/main/05-random-password) 18 | 19 | - [System Metrics API](https://github.com/akilans/golang-mini-projects/tree/main/06-system-monitor) 20 | 21 | - [SSH and SFTP](https://github.com/akilans/golang-mini-projects/tree/main/07-ssh-sftp-agent) 22 | 23 | - [Folders, Files & Zip](https://github.com/akilans/golang-mini-projects/tree/main/08-file-folder-zip) 24 | 25 | - [Package and Module demo](https://github.com/akilans/golang-mini-projects/tree/main/09-pack-mod-demo) 26 | 27 | - [Concurrent File Uploader](https://github.com/akilans/golang-mini-projects/tree/main/10-golang-ssh-concurrent-file-uploder) 28 | 29 | - [Golang REST API with JWT auth](https://github.com/akilans/golang-mini-projects/tree/main/11-jwt-golang) 30 | 31 | - [Bookstore rest API with Fiber, Gorm, MySQL, JWT](https://github.com/akilans/golang-mini-projects/tree/main/12-fiber-book-rest) 32 | 33 | - [Kubernetes Client-go example](https://github.com/akilans/golang-mini-projects/tree/main/13-k8s-client-go) 34 | 35 | - [Akit-Ops - simple GitOps solution](https://github.com/akilans/golang-mini-projects/tree/main/14_akit-ops) 36 | 37 | ## Credits and references 38 | 39 | 1. [That DevOps Guy](https://www.youtube.com/c/MarcelDempers) 40 | 2. [Donald Feury](https://www.youtube.com/c/DonaldFeury) 41 | 3. [Coding with Robby](https://www.youtube.com/@codingwithrobby) 42 | 4. [Akhil Sharma](https://www.youtube.com/@AkhilSharmaTech) 43 | 5. [NerdCademy](https://www.youtube.com/@NerdCademyDev) 44 | 6. [Golang Dojo](https://www.youtube.com/@GolangDojo) 45 | 46 | ## Contributing 47 | 48 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 49 | 50 | ## Tools used 51 | 52 | - [Video - Openshot](https://www.openshot.org/) 53 | - [Gif - FFMPEG](https://www.ffmpeg.org/) 54 | - [Ubuntu OS](https://ubuntu.com/) 55 | - [VS code IDE](https://code.visualstudio.com/) 56 | - [Simple screen recorder](https://www.maartenbaert.be/simplescreenrecorder/) 57 | -------------------------------------------------------------------------------- /demos/akit-ops.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akilans/golang-mini-projects/073fdede0ffe5b0e288a891e1870aa316cc725c6/demos/akit-ops.gif -------------------------------------------------------------------------------- /demos/fiber-book-rest.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akilans/golang-mini-projects/073fdede0ffe5b0e288a891e1870aa316cc725c6/demos/fiber-book-rest.gif -------------------------------------------------------------------------------- /demos/golang-bookstore-api.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akilans/golang-mini-projects/073fdede0ffe5b0e288a891e1870aa316cc725c6/demos/golang-bookstore-api.gif -------------------------------------------------------------------------------- /demos/golang-bookstore-cli.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akilans/golang-mini-projects/073fdede0ffe5b0e288a891e1870aa316cc725c6/demos/golang-bookstore-cli.gif -------------------------------------------------------------------------------- /demos/golang-folder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akilans/golang-mini-projects/073fdede0ffe5b0e288a891e1870aa316cc725c6/demos/golang-folder.gif -------------------------------------------------------------------------------- /demos/golang-random-password-flag.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akilans/golang-mini-projects/073fdede0ffe5b0e288a891e1870aa316cc725c6/demos/golang-random-password-flag.gif -------------------------------------------------------------------------------- /demos/golang-random-password.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akilans/golang-mini-projects/073fdede0ffe5b0e288a891e1870aa316cc725c6/demos/golang-random-password.gif -------------------------------------------------------------------------------- /demos/golang-ssh-concurrent-file-uploder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akilans/golang-mini-projects/073fdede0ffe5b0e288a891e1870aa316cc725c6/demos/golang-ssh-concurrent-file-uploder.gif -------------------------------------------------------------------------------- /demos/golang-ssh-sftp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akilans/golang-mini-projects/073fdede0ffe5b0e288a891e1870aa316cc725c6/demos/golang-ssh-sftp.gif -------------------------------------------------------------------------------- /demos/golang-system-metrics.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akilans/golang-mini-projects/073fdede0ffe5b0e288a891e1870aa316cc725c6/demos/golang-system-metrics.gif -------------------------------------------------------------------------------- /demos/golang-web-monitor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akilans/golang-mini-projects/073fdede0ffe5b0e288a891e1870aa316cc725c6/demos/golang-web-monitor.gif -------------------------------------------------------------------------------- /demos/golang-zip.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akilans/golang-mini-projects/073fdede0ffe5b0e288a891e1870aa316cc725c6/demos/golang-zip.gif -------------------------------------------------------------------------------- /demos/jwt-with-golang.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akilans/golang-mini-projects/073fdede0ffe5b0e288a891e1870aa316cc725c6/demos/jwt-with-golang.gif -------------------------------------------------------------------------------- /demos/k8s-client-go.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akilans/golang-mini-projects/073fdede0ffe5b0e288a891e1870aa316cc725c6/demos/k8s-client-go.gif -------------------------------------------------------------------------------- /demos/rest-api-to-exec-shell.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akilans/golang-mini-projects/073fdede0ffe5b0e288a891e1870aa316cc725c6/demos/rest-api-to-exec-shell.gif -------------------------------------------------------------------------------- /images/akit-ops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akilans/golang-mini-projects/073fdede0ffe5b0e288a891e1870aa316cc725c6/images/akit-ops.png -------------------------------------------------------------------------------- /images/golang-system-metrics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akilans/golang-mini-projects/073fdede0ffe5b0e288a891e1870aa316cc725c6/images/golang-system-metrics.png -------------------------------------------------------------------------------- /images/web-monitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akilans/golang-mini-projects/073fdede0ffe5b0e288a891e1870aa316cc725c6/images/web-monitor.png -------------------------------------------------------------------------------- /images/web-monitor.xml: -------------------------------------------------------------------------------- 1 | 5Vpbc+MmFP41emxHCEm2H2Mnu9tO22knnW3ziCUi0ZWFB+HY3l9fkNAFUBzbsaxk85KBIziG73ycC8SBi9XuM0Pr9Hca48zx3HjnwFvH8yazUPyVgn0l8ENQCRJG4krUEdyT71gJXSXdkBgX2kBOacbJWhdGNM9xxDUZYoxu9WGPNNN/dY0SbAnuI5TZ0n9IzNNKOg3cVv4FkyStfxm46ssK1YOVoEhRTLcdEbxz4IJRyqvWarfAmcSuxqWa9+mZr83CGM75URP++vq0Bfvlrwn4TGY5mm8WX39SWp5QtlEb/o3StcSunLvFy4JwgX+1A76vYWF0k8dYanYdON+mYtD9GkXy61bwQMhSvspED4jmI8myBc0oK+fCx0ccRpGQF5zRb7jzJZ7Mlq5UqFaFGce7Z7cLGhAF+TBdYc72Yoia4E0V7jXxVHfbWhHUpkk7FgyVDCniJI3mFlvRUPCeALVnQU0eK4gduY4wEwuYL5loJbIluJI78JONfEpXy01xMupxgKex34f61FvCMBwGdXAs7M25uTjuoYW7hSnO4xvpK0QvylBRkEiHUmc73hH+r2z/HKjegxon27e7zrDbfd3JxVY6k2T3ofutnVb26nnVUnFsOSnDIGI7dMMifACH2nsilmD+Ek9tA3cMGPTYr5YxnCFOnvTl9tlU/cKflIiNNPyB0OCPSYtqm2pW19sZinzvBUUVDpaikmLNts9n3XQo1oEO51oG9rPuguwZlRWBaUzTSR/NCldXNDuOFMJMaN8ZtpYDihPWO3MPLsscrw8XjWoBFyXozCLoH9SiqAgEXCclykiSS8YKBmERQOYyXBCRL92oDysSx3L6nOGCfEfLUpUkowJN6A3mTnArdW04LaqMD1iBKac5NqKYEl0gTPkg0A00scMU7OGzN1SQqtPdDx+lxo0+vn4Om2zmVD/zoqKBow+w8/qHniz+Rz3eMDDgD+zj7V/1eNvJ/y9FWWUxjERpbGX/nKHomzzL768A0D1rw/xuAQB6sG+Elwcf9oOPV4hkh4xQ4PJMbwlPSS5TZ7LC8sfk0RDK3qNxjJrY6zHOdaszcPny7Mwgdk7AvGDgq13Ei/VZRefRQqR1rXJuKm5Ueo3PvlaInFjEu4nj8nhLK5T+V3gCNyMFt4/5m7/+8o0MBIIjj/pg91/ALon/ZiRJSpSVL373MPujXzMCu7B7rUMd9+YBjFoSmDcG8NySwDqQMLiqv6tJOC4v2uCsheY2Ul/hHhQeG2in4xJPT6T9s++8jEDrT64baBvDdHgm7Hivujhb0u1dKzBIRhlPaUJzlJUPUxXb/sOc79VboawydZa+BY5NX8mxo8nzuicpfyif8M5unepk8K37BE/3CTA0YsjRPmHiHlY0tE8ILOJ9oNtnODNce8/11FVvn73B3kj13MC7XtI47jkF+vHyzi6SjezTzAEu9F4VmlVMEBxcljk+PO15Sx8+zPOWN9gDrOuMUwaNeu0DTIaYSeSxjDavIAditOf1r/fZZY1BUbtS33+gRxrhZa4VBUW3/T+7ynztPyvCu/8B -------------------------------------------------------------------------------- /notes.md: -------------------------------------------------------------------------------- 1 | # Golang Notes for Beginners 2 | 3 | ### Install Golang on Linux 4 | 5 | ```bash 6 | # Download go1.17.3.linux-amd64.tar.gz 7 | rm -rf /usr/local/go && tar -C /usr/local -xzf go1.17.3.linux-amd64.tar.gz 8 | export PATH=$PATH:/usr/local/go/bin 9 | # Create workspace folder anywhere and update here 10 | ` 11 | go-workspace/ 12 | ├── bin 13 | ├── pkg 14 | └── src 15 | └── github.com 16 | └── akilans 17 | └── go-tutorial 18 | ├── 01-hello 19 | │   └── main.go 20 | └── readme.md 21 | ` 22 | export GOPATH=/home/akilan/go-workspace 23 | export PATH=$PATH:$GOPATH/bin 24 | go version 25 | ``` 26 | 27 | ### Go Formatting 28 | 29 | ```bash 30 | # instead of default name of binary give the custom one 31 | go build -o hello_world hello.go 32 | 33 | # go has no central repo like maven, npm. It depends on SCM 34 | # VS code installs default tools needed for golang syntax 35 | # But good to know how it works 36 | # fmt goimports 37 | go fmt . # automatically reformats your code to match the standard format. 38 | # Install 3rd party packages 39 | # check in /home/akilan/go-workspace/bin & pkg dir 40 | go install golang.org/x/tools/cmd/goimports@latest 41 | 42 | # cleans up your import statements. It puts them in 43 | #alphabetical order, removes unused imports, and attempts to guess 44 | #any unspecified imports 45 | goimports -l -w . 46 | #The -l flag tells goimports to print the files with incorrect formatting to the console. 47 | #The -w flag tells goimports to modify the files in-place. 48 | #The . specifies the files to be scanned: everything in the 49 | #current directory and all of its subdirectories. 50 | # golint and go vet 51 | # Refer https://golangci-lint.run/ for Golang CI 52 | ``` 53 | 54 | go lint - checks for any syntax error 55 | go vet - check for any unused variable, invalid function param etc. 56 | 57 | Always run go fmt or goimports before compiling your code! 58 | 59 | Rather than use separate tools, you can run 60 | multiple tools together with golangci-lint. It combines golint, go 61 | vet, and an ever-increasing set of other code quality tools. Once it is 62 | installed, you run golangci-lint with the command 63 | 64 | By default vs code uses staticcheck, Delve, gopls 65 | Delve is a debugger for the Go programming language. 66 | 67 | ## Useful commands 68 | 69 | ```bash 70 | # remove path from terminal display 71 | export PS1="\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\$ " 72 | ffmpeg -i ~/test.mp4 ~/test.gif 73 | ``` 74 | 75 | ## Notes 76 | 77 | - If you are referring to a character, use the rune type 78 | - When you are within a function, you can use the := operator to replace a var declaration 79 | 80 | ```go 81 | //different ways to declare and assign variable 82 | var x int = 10 83 | var x = 10 84 | var x int 85 | var x, y int = 10, 20 86 | var x, y int 87 | var x, y = 10, "hello" 88 | var ( 89 | x int 90 | y = 20 91 | z int = 30 92 | d, e = 40, "hello" 93 | f, g string 94 | ) 95 | ``` 96 | 97 | When you take a slice from a slice, you are not making a copy of the 98 | data. Instead, you now have two variables that are sharing memory. 99 | This means that changes to an element in a slice affect all slices that 100 | share that element. 101 | 102 | ```go 103 | x := [] int {1, 2, 3, 4} 104 | y := x[:2] 105 | z := x[1:]x[1] = 20 106 | y[0] = 10 107 | z[1] = 30 108 | fmt.Println("x:", x) 109 | fmt.Println("y:", y) 110 | fmt.Println("z:", z) 111 | 112 | x: [10 20 30 4] 113 | y: [10 20] 114 | z: [20 30 4] 115 | 116 | ``` 117 | 118 | Be very careful when taking a slice of a slice! Both slices share the 119 | same memory and changes to one are reflected in the other. Avoid 120 | modifying slices after they have been sliced or if they were produced by 121 | slicing. Use a three-part slice expression to prevent append from sharing 122 | capacity between slices. 123 | 124 | ```go 125 | // copy whole slice 126 | x := [] int {1, 2, 3, 4} 127 | y := make([] int , 4) 128 | num := copy(y, x) 129 | fmt.Println(y, num) 130 | 131 | // [1 2 3 4] 4 132 | 133 | // copy few elements from slice 134 | x := [] int {1, 2, 3, 4} 135 | y := make([] int , 2) 136 | copy(y, x[2:]) 137 | 138 | // another example 139 | x := [] int {1, 2, 3, 4} 140 | d := [4] int {5, 6, 7, 8} 141 | y := make([] int , 2) 142 | copy(y, d[:]) 143 | fmt.Println(y) 144 | copy(d[:], x) 145 | fmt.Println(d) 146 | // output 147 | [5 6] 148 | [1 2 3 4] 149 | ``` 150 | -------------------------------------------------------------------------------- /playground/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/akilans/golang-mini-projects/playground 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /playground/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var myname string 7 | var myAge int 8 | var myHeight float64 9 | var amIWorking bool 10 | fmt.Println(len(myname)) 11 | fmt.Println(myname) 12 | fmt.Println(myAge) 13 | fmt.Println(myHeight) 14 | fmt.Println(amIWorking) 15 | } 16 | -------------------------------------------------------------------------------- /rest-api-to-exec-shell/README.md: -------------------------------------------------------------------------------- 1 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fakilans%2Fgolang-mini-projects%2Ftree%2Fmain%2Frest-api-to-exec-shell&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 2 | 3 | # REST API to Execute Shell Commands 4 | 5 | - It is a golang REST API application to execute shell command from payload 6 | - It is not recommended - Just for learning purpose 7 | 8 | ## Demo 9 | 10 | ![Alt Execute Shell Command](https://raw.githubusercontent.com/akilans/golang-mini-projects/main/demos/rest-api-to-exec-shell.gif) 11 | 12 | ## Usage 13 | 14 | ```bash 15 | 16 | # clone a repo 17 | git clone https://github.com/akilans/golang-mini-projects.git 18 | 19 | # go rest-api-to-exec-shell dir 20 | cd rest-api-to-exec-shell 21 | 22 | # run 23 | 24 | go run main.go 25 | 26 | 27 | # sample payload to list files and folder under /home/akilan/Desktop dir 28 | { 29 | "command": "ls", 30 | "arguments": ["-l","/home/akilan/Desktop"] 31 | } 32 | 33 | # Sample response 34 | { 35 | "Msg": "total 4\ndrwxrwxr-x 35 akilan akilan 4096 Nov 18 19:55 devops-work\n" 36 | } 37 | ``` 38 | 39 | ## Contributing 40 | 41 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 42 | -------------------------------------------------------------------------------- /rest-api-to-exec-shell/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "os/exec" 9 | ) 10 | 11 | // Define port 12 | var PORT string = ":5000" 13 | 14 | // Payload struct 15 | type Payload struct { 16 | Command string `json:"command"` 17 | Arguments []string `json:"arguments"` 18 | } 19 | 20 | // message to send as json response 21 | type Message struct { 22 | Msg string 23 | } 24 | 25 | // response message struct as json format 26 | func jsonMessageByte(msg string) []byte { 27 | errrMessage := Message{msg} 28 | byteContent, _ := json.Marshal(errrMessage) 29 | return byteContent 30 | } 31 | 32 | // Allow only POST method 33 | // Validate Payload - It should contain command and argument 34 | // Parse the post data and execute the command in the payload 35 | 36 | func executeShell(w http.ResponseWriter, r *http.Request) { 37 | 38 | if r.Method != "POST" { 39 | w.WriteHeader(405) 40 | w.Write(jsonMessageByte(r.Method + " - Method not allowed")) 41 | } else { 42 | var payloadData Payload 43 | err := json.NewDecoder(r.Body).Decode(&payloadData) 44 | if err != nil { 45 | w.WriteHeader(400) 46 | w.Write(jsonMessageByte("Bad Request - Failed to parse the payload " + err.Error())) 47 | } else { 48 | if payloadData.Command != "" && len(payloadData.Arguments) != 0 { 49 | out, err := exec.Command(payloadData.Command, payloadData.Arguments...).Output() 50 | if err != nil { 51 | w.WriteHeader(500) 52 | w.Write(jsonMessageByte("Server Error - Failed to execute the command " + err.Error())) 53 | } else { 54 | w.Write(jsonMessageByte(string(out))) 55 | } 56 | } else { 57 | w.Write(jsonMessageByte("Please provide command and argument")) 58 | } 59 | 60 | } 61 | 62 | } 63 | 64 | } 65 | 66 | // Main function 67 | // Start the server 68 | func main() { 69 | http.HandleFunc("/", executeShell) 70 | 71 | fmt.Printf("App is listening on %v\n", PORT) 72 | 73 | err := http.ListenAndServe(PORT, nil) 74 | 75 | if err != nil { 76 | log.Fatal("Failed to start the application") 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /syllabus.md: -------------------------------------------------------------------------------- 1 | # Golang for Beginners 2 | 3 | ### Week 1 4 | 5 | - Day 1 6 | 7 | - Why Golang 8 | - Interpreted language vs Compiled Language 9 | - Install golang - Windows/Mac/Linux 10 | - Setup workspace - GOPATH, src, bin, pkg folder 11 | - Run First golang Program 12 | - Create hello world using notepad 13 | - Install VS code IDE 14 | - Create project in VS code and run golang program 15 | 16 | - Day 2 17 | - variables 18 | - Data types 19 | - Int, Float,String 20 | - Arrays, Slice, Struct 21 | - Getting user inputs 22 | - Arithmetic Operation 23 | - Type casting 24 | - Simple Calculator application 25 | 26 | ### Week 2 27 | 28 | - Day 1 29 | 30 | - conditional statements 31 | - operator 32 | - If else 33 | - For loop 34 | - While loop 35 | - break, continue 36 | - Grade system application 37 | 38 | - Day 2 39 | - File handling 40 | - Create file 41 | - Read file 42 | - Delete file 43 | - File creation application 44 | --------------------------------------------------------------------------------