Categorygithub.com/kylinsoong/golang
module
0.0.0-20241014013326-0cd9b86c656b
Repository: https://github.com/kylinsoong/golang.git
Documentation: pkg.go.dev

# README

= Golang Boot Camp :toc: manual

== Getting started

=== Check go version

[source, go]

go version

go version go1.21.5 linux/arm64

=== package, import, main

[source, go]

package main

import "fmt"

func main() { fmt.Println("Hello, World!") }

=== Run main methods

[source, go]

$ go run hello.go Hello, World!

=== Call External Methods

[source, go]

package main

import "fmt"

import "rsc.io/quote"

func main() { fmt.Println("Hello, World!") fmt.Println(quote.Glass()) fmt.Println(quote.Go()) fmt.Println(quote.Hello()) fmt.Println(quote.Opt()) }

== Getting started from real project

link:k8s-bigip-ctlr.adoc[project k8s-bigip-ctlr]

== Work with Go standard project

=== Project Structure

[source, go]

tree greetings/

greetings/ ├── cmd │   └── greetings │   └── main.go └── pkg └── greetings ├── greetings.go └── greetings_test.go

A standard Go project structure can vary depending on the size and nature of the project, but there are some common conventions that many Go developers follow:

  • cmd: The cmd directory is for the main applications of your project. Each application can have its own subdirectory. For example, myapp could be the main entry point for your application
  • internal: The internal directory is for packages that are only used within your project, not meant for external use.
  • pkg: The pkg directory contains libraries and packages that are meant to be used by other projects. Each package within pkg can have its own subdirectory.

=== Init and Setup Dependencies

[source, go]

go mod init github.com/kylinsoong/golang/greetings go mod tidy

=== Run

[source, go]

go run cmd/greetings/main.go

=== How main module call pkg module

[source, go]

package main

import ( "github.com/kylinsoong/golang/greetings/pkg/greetings" )

func main() { names := []string{"Gladys", "Samantha", "Darrin", "Kylin"} messages, err := greetings.Hellos(names) }

=== Run Unit Test

[source, go]

go test ./pkg/greetings/

=== Build

[source, go]

go build -o a.out cmd/greetings/*.go

=== Run Binary File

[source, go]

./a.out


== Data Struct

=== simple map

[source, go]

processedResources := make(map[string]bool)

processedResources["foo.yaml"] = true
processedResources["bar.yaml"] = false
processedResources["zoo.yaml"] = false

for key, value := range processedResources {
    fmt.Printf("%s: %v\n", key, value)
}

fmt.Println(processedResources["zoo.yaml"])

value, exists := processedResources["coo.yaml"]
if exists {
    fmt.Printf("coo.yaml: %v\n", value)
} else {
    fmt.Println("coo.yaml not exist")
}

=== simple struct

[source, go]

type WatchedNamespaces struct { Namespaces []string NamespaceLabel string }

func main() { watchedNamespaces := WatchedNamespaces{ Namespaces: []string{"namespace1", "namespace2"}, NamespaceLabel: "watched", }

fmt.Println(watchedNamespaces.Namespaces)
fmt.Println(watchedNamespaces.NamespaceLabel)

}

=== struct with func field

Using a Go struct with a function field offers flexibility and allows you to encapsulate behavior within the struct while enabling dynamic customization.

[source, go]

type Manager struct { queueLen int processAgentLabels func(map[string]string, string, string) bool }

func customProcessAgentLabels(labels map[string]string, namespace string, name string) bool { fmt.Printf("Custom Processing Agent Labels: %v, Namespace: %s, Name: %s\n", labels, namespace, name) return true }

func main() { appMgr := Manager{ queueLen: 10, processAgentLabels: customProcessAgentLabels, } appMgr.processAgentLabels(map[string]string{"key": "value"}, "exampleNamespace", "exampleName") }

== Fundamentals

=== object-oriented programming: interface composition

Go does not support traditional interface inheritance like some other object-oriented programming languages. Instead, Go uses a concept called "interface composition" or "embedding" to achieve similar goals without relying on classical inheritance.

In Go, you can embed one interface within another to create a new interface that includes the methods of the embedded interface.

[source, go] .Interface

type Interface interface { Add(item interface{}) Len() int Get() (item interface{}, shutdown bool) Done(item interface{}) ShutDown() ShutDownWithDrain() ShuttingDown() bool }

[source, go] .DelayingInterface

type DelayingInterface interface { Interface AddAfter(item interface{}, duration time.Duration) }

[source, go] .RateLimitingInterface

type RateLimitingInterface interface { DelayingInterface AddRateLimited(item interface{}) Forget(item interface{}) NumRequeues(item interface{}) int }

== Multi-threads

=== goroutine

The goroutine is a lightweight thread of execution managed by the Go runtime. Goroutines enable concurrent programming in a way that is more efficient and scalable compared to traditional threads.

[source, go]

package main

import ( "fmt" "time" )

func printNumbers() { for i := 1; i <= 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Printf("%d \n", i) } }

func main() { go printNumbers()

for i := 1; i <= 5; i++ {
    time.Sleep(100 * time.Millisecond)
    fmt.Printf("A%d \n", i)
}

}

=== channel: send and receive data

Channels are a typed conduit through which you can send and receive values with the channel operator <-:

  • ch <- v send v to channel
  • v := <-ch receive from channel, and assign value to v

[source, go]

func sum(s []int, c chan int) { sum := 0 for _, v := range s { sum += v } c <- sum // send sum to c }

func Test_Send_Receive(t *testing.T) { s := []int{7, 2, 8, -9, 4, 0} c := make(chan int) go sum(s[:len(s)/2], c) go sum(s[len(s)/2:], c) x, y := <-c, <-c fmt.Println(x, y, x+y) }

=== channel: communication and synchronization between goroutines

In Go, channels are a powerful mechanism for communication and synchronization between goroutines. They provide a way for one goroutine to send data to another goroutine.

[source, go]

func numberGenerator(ch chan int, wg *sync.WaitGroup) { defer wg.Done() for i := 1; i <= 5; i++ { ch <- i // Send numbers 1 to 5 to the channel } close(ch) // Close the channel to signal no more data will be sent }

func squareCalculator(ch chan int, resultCh chan int, wg *sync.WaitGroup) { defer wg.Done() for num := range ch { square := num * num resultCh <- square // Send squared result to the resultCh channel } close(resultCh) // Close the resultCh channel to signal no more results will be sent }

func resultPrinter(resultCh chan int, wg *sync.WaitGroup) { defer wg.Done() for result := range resultCh { fmt.Println("Squared Result:", result) } }

func main() { numberCh := make(chan int) resultCh := make(chan int) var wg sync.WaitGroup wg.Add(3) go numberGenerator(numberCh, &wg) go squareCalculator(numberCh, resultCh, &wg) go resultPrinter(resultCh, &wg) wg.Wait() }

=== channel: multiple chanels with select case statement

The select statement in Go is used to choose from multiple communication operations. It allows a goroutine to wait on multiple communication operations, blocking until one of them can proceed.

[source, go]

func simple_worker(c chan string) { c <- fmt.Sprintf("Hello from Channel %v", c) }

func Test_Multiple_Chan_With_Select(t *testing.T) { ch1 := make(chan string) ch2 := make(chan string) go simple_worker(ch1) go simple_worker(ch2) select { case msg1 := <-ch1: fmt.Println(msg1) case msg2 := <-ch2: fmt.Println(msg2) case <-time.After(3 * time.Second): fmt.Println("Timed out waiting for messages.") } }

=== channel: signal completion of sub goroutine

[source, go]

func worker(ch chan struct{}) { fmt.Println("Worker is starting...") time.Sleep(2 * time.Second) fmt.Println("Worker is done!") ch <- struct{}{} }

func main() { doneCh := make(chan struct{}) go worker(doneCh) <-doneCh fmt.Println("Main function exiting.") }

=== using signal to control application interruptiong and termination

In Go, the os/signal package provides a way to intercept signals sent to the program, such as termination signals (SIGINT for interrupt and SIGTERM for terminate). The signal usually wrapped with a channel that can be used to control application interruptiong and termination.

[source, go]

func main() { fmt.Println("Started to run tasks...") signals := make(chan os.Signal, 1) signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) sig := <-signals fmt.Printf("Received signal: %v\n", sig) }

=== defer statements a Last In, First Out (LIFO) execution order

[source, go]

func main() { defer fmt.Println("This will be executed third.") defer fmt.Println("This will be executed second.") defer fmt.Println("This will be executed first.") fmt.Println("Hello, Go!") }

=== thread-safe via defer and sync

[source, go]

type Counter struct { value int mu sync.Mutex }

func (c *Counter) increment() { c.mu.Lock() defer c.mu.Unlock() c.value++ }

func (c *Counter) getValue() int { c.mu.Lock() defer c.mu.Unlock() return c.value }

== K8S

=== How to repeatedly calls a provided function

The k8s.io/apimachinery/pkg/util/wait provides utilities for waiting and timing operations. Specifically, wait.Until is a function that repeatedly calls a provided function until the stop channel is closed or a timeout is reached.

[source, go] .wait.Until

func exampleWork() { fmt.Println("Doing some work...") time.Sleep(2 * time.Second) }

func main() { stopCh := make(chan struct{}) go wait.Until(exampleWork, time.Second, stopCh) time.Sleep(5 * time.Second) close(stopCh) time.Sleep(1 * time.Second) fmt.Println("Main goroutine exiting...") }

=== How to use kubernetes rate limited workqueue

Refer to link:#object-oriented-programming-interface-composition[object-oriented programming: interface composition] for more details about k8s.io/client-go/util/workqueue and RateLimitingInterface implemenration.

=== How to create kubernetes client

There are 2 stps necessary to create a kubeClient:

  • Create Kubernetes Rest Config, If your application run in Kubernetes, the use the certifications keys in Namespace default ServiceAccount, if your application run outside Kubernetes, then you need pass ~/.kube/config file to create Rest Config
  • Create Kubernetes Client via Kubernetes Rest Config

[source, go]

import "k8s.io/client-go/kubernetes" import "k8s.io/client-go/rest" import "k8s.io/client-go/tools/clientcmd"

var kubeClient kubernetes.Interface var config *rest.Config var err error

if *inCluster { config, err = rest.InClusterConfig() } else { config, err = clientcmd.BuildConfigFromFlags("", *kubeConfig) } if err != nil { log.Fatalf("[INIT] error creating configuration: %v", err) }

kubeClient, err = kubernetes.NewForConfig(config) if err != nil { log.Fatalf("[INIT] error connecting to the client: %v", err) }

=== How to use kubernetes client-side caching mechanism and cache api create a configmap watcher

k8s.io/client-go/tools/cache is a client-side caching mechanism. It is useful for reducing the number of server calls you'd otherwise need to make. Reflector watches a server and updates a Store. Two stores are provided; one that simply caches objects (for example, to allow a scheduler to list currently available nodes), and one that additionally acts as a FIFO queue (for example, to allow a scheduler to process incoming pods).

[source, go]

cfgMapInformer = cache.NewSharedIndexInformer(
	cache.NewFilteredListWatchFromClient(
		restClientv1,
		Configmaps,
		namespace,
		everything,
	),
	&v1.ConfigMap{},
	resyncPeriod,
	cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
)

cfgMapInformer.AddEventHandlerWithResyncPeriod(
	&cache.ResourceEventHandlerFuncs{
		AddFunc:    func(obj interface{}) { enqueueConfigMap(obj, OprTypeCreate) },
		UpdateFunc: func(old, cur interface{}) { enqueueConfigMap(cur, OprTypeUpdate) },
		DeleteFunc: func(obj interface{}) { enqueueConfigMap(obj, OprTypeDelete) },
	},
	resyncPeriod,
)

# Packages

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author