diff --git a/.gitlab/ci/.code-quality-ci.yml b/.gitlab/ci/.code-quality-ci.yml
index 92e6b501ef628540b527146643029252808c7ac9..acef63a432e83cde3dc89b12693333f78b1c843d 100644
--- a/.gitlab/ci/.code-quality-ci.yml
+++ b/.gitlab/ci/.code-quality-ci.yml
@@ -1,10 +1,9 @@
 code-quality:
-    image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/golangci/golangci-lint:v1.42-alpine
+    image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/golangci/golangci-lint:v1.45-alpine
     stage: analyze
     script:
         # writes golangci-lint output to gl-code-quality-report.json
         - apk add --update make jq
-        - cd controller/
         - make ci-lint
     artifacts:
         reports:
diff --git a/.gitlab/ci/.golangci-config/.runlint.sh b/.gitlab/ci/.golangci-config/.runlint.sh
deleted file mode 100755
index 774573b108c985a07f43d16ed44d0f63c1a7c8ca..0000000000000000000000000000000000000000
--- a/.gitlab/ci/.golangci-config/.runlint.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-golangci-lint run\
-  --config .ci/.golangci.yml\
-  --out-format code-climate |\
-  jq -r '.[] | "\(.location.path):\(.location.lines.begin) \(.description)"'
diff --git a/.gitlab/ci/.golangci-config/.golangci.yml b/.golangci.yml
similarity index 80%
rename from .gitlab/ci/.golangci-config/.golangci.yml
rename to .golangci.yml
index 53a29c482a1ee4adbed540965032b06c8c2af5ea..894ea22bf69db911b97f7093798094fdf22ba2d3 100644
--- a/.gitlab/ci/.golangci-config/.golangci.yml
+++ b/.golangci.yml
@@ -1,13 +1,18 @@
 run:
+    go: 1.18
+    concurrency: 4
     timeout: 10m
     issues-exit-code: 1
     # directories to be ignored by linters
     skip-dirs:
-        - forks
-        - test
+        - controller/forks
+        - controller/test
+        - controller/mocks
     skip-dirs-default: true
     skip-files:
         - http.go
+    modules-download-mode: readonly
+
 # output settings -> code-climate for GitLab
 output:
     format: code-climate
@@ -15,14 +20,16 @@ output:
     print-linter-name: true
     uniq-by-line: true
     path-prefix: ""
+
 # custom settings for linters
 linters-settings:
     gocyclo:
         min-complexity: 15
     golint:
         min-confidence: 0.8
-# enable the specific needed linters
+
 linters:
+    # enable the specific needed linters
     disable-all: true
     enable:
         - gofmt
diff --git a/Makefile b/Makefile
index 78fd8cf5d934718340d1a8d2d7a91e36649cb2dd..8b7569837704aa866a6d80de66d4d629e510e6ee 100644
--- a/Makefile
+++ b/Makefile
@@ -19,12 +19,21 @@ install-tools:
 	@echo Install development tooling
 	mkdir -p $(GOSDN_PRG)
 	go install gotest.tools/gotestsum@v1.7.0
-	go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.42
+	go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.45.1
 	@echo Finished installing development tooling
 
 ci-install-tools:
 	go install gotest.tools/gotestsum@v1.7.0
 
+ci-lint: ci-install-tools
+	golangci-lint run --config .golangci.yml --out-format code-climate | jq -r '.[] | "\(.location.path):\(.location.lines.begin) \(.description)"'
+
+lint: install-tools
+	./$(TOOLS_DIR)/golangci-lint run --config .golangci.yml | jq
+
+lint-fix: install-tools
+	./$(TOOLS_DIR)/golangci-lint run --config .golangci.yml --fix | jq
+
 build: pre
 	$(GOBUILD) -o $(BUILD_ARTIFACTS_PATH)/gosdn ./controller/cmd/gosdn
 	CGO_ENABLED=0 $(GOBUILD) -o $(BUILD_ARTIFACTS_PATH)/gosdnc ./cli/
@@ -43,7 +52,6 @@ containerlab-stop:
 containerlab-graph:
 	sudo containerlab graph --topo gosdn.clab.yaml
 
-
 shell-gosdn:
 	docker exec -it clab-gosdn_csbi_arista_base-gosdn bash
 shell-orchestrator:
diff --git a/cli/adapter/PndAdapter.go b/cli/adapter/PndAdapter.go
index b3147df614070d47df5ad72ea4334adb3a46853c..e593d40f47986cb0ac4287f5fbcc6a578b7c7fc2 100644
--- a/cli/adapter/PndAdapter.go
+++ b/cli/adapter/PndAdapter.go
@@ -23,7 +23,7 @@ type PndAdapter struct {
 	endpoint string
 }
 
-// NewAdapter creates a PND Adapter. It requires a valid PND UUID and a reachable
+// NewPndAdapter creates a PND Adapter. It requires a valid PND UUID and a reachable
 // goSDN endpoint.
 func NewPndAdapter(id, endpoint string) (*PndAdapter, error) {
 	pid, err := uuid.Parse(id)
@@ -56,6 +56,7 @@ func (p *PndAdapter) AddDevice(name string, opts *tpb.TransportOption, sid uuid.
 	return resp, nil
 }
 
+// GetSbiSchemaTree requests a sbi schema tree.
 func (p *PndAdapter) GetSbiSchemaTree(sid uuid.UUID) (map[string]*yang.Entry, error) {
 	resp, err := api.GetSbiSchemaTree(p.Endpoint(), p.ID(), sid)
 	if err != nil {
diff --git a/cli/cmd/deviceList.go b/cli/cmd/deviceList.go
index 02b0251f96cd86dde2cfdc97a1e27ef1ca57b249..9e73e82eb004be29114a2176b23584fd18777782 100644
--- a/cli/cmd/deviceList.go
+++ b/cli/cmd/deviceList.go
@@ -43,7 +43,7 @@ var deviceListCmd = &cobra.Command{
 	Use:     "list",
 	Aliases: []string{"ls"},
 	Short:   "list all devices in current PND",
-	Long:    `List all orchestrated network devices within the current PND.`,
+	Long:    "List all orchestrated network devices within the current PND.",
 
 	RunE: func(cmd *cobra.Command, args []string) error {
 		resp, err := pndAdapter.GetDevices()
@@ -60,7 +60,7 @@ var deviceListCmd = &cobra.Command{
 			if err != nil {
 				return err
 			}
-			log.Infof("   SchemaTree: ", tree)
+			log.Infof("   SchemaTree: %v", tree)
 		}
 		return nil
 	},
diff --git a/cli/cmd/prompt.go b/cli/cmd/prompt.go
index aa4c341f648583ad76d457b84145cd6efbdcbe0d..f4828889d8ec806ac12891a4a3f62aa91d68385d 100644
--- a/cli/cmd/prompt.go
+++ b/cli/cmd/prompt.go
@@ -46,19 +46,22 @@ import (
 
 var suggestions []prompt.Suggest
 
-type promptCompleter struct {
+// PromptCompleter provides completion for a device
+type PromptCompleter struct {
 	//NOTE: not too sure about this but for now it should be sufficient
-	deviceId               uuid.UUID
+	deviceID               uuid.UUID
 	yangSchemaCompleterMap map[uuid.UUID]*completer.YangSchemaCompleter
 }
 
-func NewPromptCompleter() *promptCompleter {
-	return &promptCompleter{
+// NewPromptCompleter returns a new promptCompleter
+func NewPromptCompleter() *PromptCompleter {
+	return &PromptCompleter{
 		yangSchemaCompleterMap: make(map[uuid.UUID]*completer.YangSchemaCompleter),
 	}
 }
 
-func (pc *promptCompleter) Run() {
+// Run starts the interactive completion
+func (pc *PromptCompleter) Run() {
 	fmt.Println("Welcome to the interactive mode of gosdnc.")
 	defer fmt.Println("Bye!")
 
@@ -99,7 +102,7 @@ func flagVisitor(f *pflag.Flag) {
 	}
 }
 
-func (pc *promptCompleter) cstmCompleter(d prompt.Document) []prompt.Suggest {
+func (pc *PromptCompleter) cstmCompleter(d prompt.Document) []prompt.Suggest {
 	// Start with the cobra 'rootCmd' and walk through it
 	// Reference: https://github.com/stromland/cobra-prompt
 	currCmd := rootCmd
@@ -109,7 +112,6 @@ func (pc *promptCompleter) cstmCompleter(d prompt.Document) []prompt.Suggest {
 	}
 
 	return completionBasedOnCmd(pc, currCmd, inputSplit, d)
-
 }
 
 func removeFlagsFromInputSlice(input []string) []string {
@@ -122,7 +124,7 @@ func removeFlagsFromInputSlice(input []string) []string {
 	return r
 }
 
-func deviceGetCompletion(c *promptCompleter, d prompt.Document, inputSplit []string) []prompt.Suggest {
+func deviceGetCompletion(c *PromptCompleter, d prompt.Document, inputSplit []string) []prompt.Suggest {
 	inputLen := len(inputSplit)
 	if inputLen == 2 || inputLen == 3 {
 		if id, err := uuid.Parse(inputSplit[inputLen-1]); err == nil {
@@ -143,7 +145,7 @@ func deviceGetCompletion(c *promptCompleter, d prompt.Document, inputSplit []str
 			}
 			c.yangSchemaCompleterMap[id] = completer.NewYangSchemaCompleter(schemaTree["Device"], true)
 			if yc, ok := c.yangSchemaCompleterMap[id]; ok {
-				c.deviceId = id
+				c.deviceID = id
 				return yc.Complete(d)
 			}
 
@@ -153,7 +155,7 @@ func deviceGetCompletion(c *promptCompleter, d prompt.Document, inputSplit []str
 			return prompt.FilterHasPrefix(getDevices(), d.GetWordBeforeCursor(), true)
 		}
 	} else {
-		if yc, ok := c.yangSchemaCompleterMap[c.deviceId]; ok {
+		if yc, ok := c.yangSchemaCompleterMap[c.deviceID]; ok {
 			return yc.Complete(d)
 		}
 		return []prompt.Suggest{}
@@ -177,10 +179,9 @@ func cobraCommandCompletion(currCmd *cobra.Command, d prompt.Document, loaded []
 		suggestions = append(suggestions, prompt.Suggest{Text: cmd.Name(), Description: cmd.Short})
 	}
 	return prompt.FilterHasPrefix(suggestions, d.GetWordBeforeCursor(), true)
-
 }
 
-func completionBasedOnCmd(c *promptCompleter, cmd *cobra.Command, inputSplit []string, d prompt.Document) []prompt.Suggest {
+func completionBasedOnCmd(c *PromptCompleter, cmd *cobra.Command, inputSplit []string, d prompt.Document) []prompt.Suggest {
 	switch cmd {
 	case pndUseCmd, pndGetCmd:
 		return cobraCommandCompletion(cmd, d, getPnds())
diff --git a/cli/completer/utils.go b/cli/completer/utils.go
index ab0f7affa82c659623b89209e2dccc9738396c9f..ab58c30fcf9c33ba2a1ccffbd20fe28bfb58904a 100644
--- a/cli/completer/utils.go
+++ b/cli/completer/utils.go
@@ -6,6 +6,8 @@ import (
 	"github.com/c-bata/go-prompt"
 )
 
+// SortSuggestionByText sorts the suggestion slice, otherwise the order of the slice will be
+// random.
 func SortSuggestionByText(suggestions []prompt.Suggest) []prompt.Suggest {
 	// sort the suggestion slice, otherwise the order of the slice will be
 	// random.
diff --git a/cli/completer/yangSchemaCompleter.go b/cli/completer/yangSchemaCompleter.go
index a40cbeab77c1dcbacf4ec2603448557e58852a09..392f17e68783b23de0897c5d74d8500fa9fe4993 100644
--- a/cli/completer/yangSchemaCompleter.go
+++ b/cli/completer/yangSchemaCompleter.go
@@ -12,13 +12,14 @@ import (
 )
 
 var (
+	// YangSchemaCompletionSeperator is the seperator for yang schemas
 	YangSchemaCompletionSeperator = string([]byte{' ', '/'})
 )
 
 // The idea of path suggestions for a SBI's YANG schema is heavily inspired by
 // gnmic (https://github.com/karimra/gnmic)
 
-// YgotSchemaPathCompleter
+// YangSchemaCompleter represtents the YangSchemaCompleter
 type YangSchemaCompleter struct {
 	Cache      map[string]*yang.Entry
 	Entry      *yang.Entry
@@ -105,6 +106,7 @@ func promptFromYangEntry(e *yang.Entry) []prompt.Suggest {
 	return []prompt.Suggest{}
 }
 
+// Complete provides the actual completion
 func (c *YangSchemaCompleter) Complete(d prompt.Document) []prompt.Suggest {
 	p := d.GetWordBeforeCursor()
 
diff --git a/controller/Makefile b/controller/Makefile
index dd0c17b7b4d9d78137faa3365578e3c5ef5f3ddb..1c6cc52a9fee363afbe97b28655a49b2ee42eb8d 100644
--- a/controller/Makefile
+++ b/controller/Makefile
@@ -15,7 +15,7 @@ install-tools:
 	@echo Install development tooling
 	mkdir -p $(GOSDN_PRG)
 	go install gotest.tools/gotestsum@v1.7.0
-	go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.42
+	go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.45.1
 	@echo Finished installing development tooling
 
 ci-install-tools:
diff --git a/controller/api/grpc.go b/controller/api/grpc.go
index 26b9dddc042cfcfd1f69b82fcc4c6859708d4b37..f416a259773eac0dc8b0a289713db006793ca7d6 100644
--- a/controller/api/grpc.go
+++ b/controller/api/grpc.go
@@ -59,6 +59,7 @@ func GetIds(addr string) ([]*ppb.PrincipalNetworkDomain, error) {
 	return resp.Pnd, nil
 }
 
+// GetAllCore requests all PNDs
 func GetAllCore(ctx context.Context, addr string) (*pb.GetPndListResponse, error) {
 	coreClient, err := nbi.CoreClient(addr, dialOptions...)
 	if err != nil {
@@ -211,6 +212,7 @@ func Confirm(addr, pnd string, cuids ...string) (*ppb.SetChangeListResponse, err
 	return CommitConfirm(addr, pnd, changes)
 }
 
+// CommitConfirm confirms a commit
 func CommitConfirm(addr, pnd string, changes []*ppb.SetChange) (*ppb.SetChangeListResponse, error) {
 	ctx := context.Background()
 	client, err := nbi.PndClient(addr, dialOptions...)
@@ -280,7 +282,7 @@ func GetDevice(addr, pid string, did ...string) (*ppb.GetOndResponse, error) {
 	return pndClient.GetOnd(ctx, req)
 }
 
-// GetSbiSchemaTree
+// GetSbiSchemaTree gets the sbi tree for a sbi
 func GetSbiSchemaTree(addr string, pid, sid uuid.UUID) (map[string]*yang.Entry, error) {
 	sbiClient, err := nbi.SbiClient(addr, dialOptions...)
 	if err != nil {
@@ -339,6 +341,7 @@ func GetDevices(addr, pid string) (*ppb.GetOndListResponse, error) {
 	return pndClient.GetOndList(ctx, req)
 }
 
+// GetPath requests a specific path
 func GetPath(addr, pid, did, path string) (*ppb.GetPathResponse, error) {
 	pndClient, err := nbi.PndClient(addr, dialOptions...)
 	if err != nil {
@@ -355,6 +358,7 @@ func GetPath(addr, pid, did, path string) (*ppb.GetPathResponse, error) {
 	return pndClient.GetPath(ctx, req)
 }
 
+// DeleteDevice deletes a device
 func DeleteDevice(addr, pid, did string) (*ppb.DeleteOndResponse, error) {
 	pndClient, err := nbi.PndClient(addr, dialOptions...)
 	if err != nil {
@@ -370,7 +374,7 @@ func DeleteDevice(addr, pid, did string) (*ppb.DeleteOndResponse, error) {
 	return pndClient.DeleteOnd(ctx, req)
 }
 
-// change creates a ChangeRequest for the specified OND. ApiOperations are
+// ChangeRequest change creates a ChangeRequest for the specified OND. ApiOperations are
 // used to specify the type of the change (update, replace, delete as specified
 // in https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#34-modifying-state)
 // For delete operations the value field needs to contain an empty string.
@@ -384,6 +388,7 @@ func ChangeRequest(addr, did, pid, path, value string, op ppb.ApiOperation) (*pp
 	return SendChangeRequest(addr, pid, req)
 }
 
+// SendChangeRequest sends a change request
 func SendChangeRequest(addr, pid string, req *ppb.ChangeRequest) (*ppb.SetPathListResponse, error) {
 	pndClient, err := nbi.PndClient(addr, dialOptions...)
 	if err != nil {
diff --git a/controller/nucleus/principalNetworkDomain.go b/controller/nucleus/principalNetworkDomain.go
index a4dbf0354200e9b4d2b12fd908d04284b26f1e64..716617f40af3dbff573704a57b0a10f698b81fa2 100644
--- a/controller/nucleus/principalNetworkDomain.go
+++ b/controller/nucleus/principalNetworkDomain.go
@@ -603,7 +603,7 @@ func (pnd *pndImplementation) requestPlugin(name string, opt *tpb.TransportOptio
 
 		return sbi, nil
 	}
-	return nil, fmt.Errorf("requestPlugin: received deployment slice was empty.")
+	return nil, fmt.Errorf("requestPlugin: received deployment slice was empty")
 }
 
 // GenericGrpcClient allows to distinguish between the different ygot
diff --git a/controller/nucleus/southbound.go b/controller/nucleus/southbound.go
index e9892a48109a37576e3be3a22010f57b10e923d4..abad487bb5c47b1fda418dde38d49c9743ee7ff6 100644
--- a/controller/nucleus/southbound.go
+++ b/controller/nucleus/southbound.go
@@ -95,7 +95,7 @@ func (oc *OpenConfig) Schema() *ytypes.Schema {
 	return schema
 }
 
-// SchemaTree returns the ygot generated SchemaTree compressed as gzip byte
+// SchemaTreeGzip returns the ygot generated SchemaTree compressed as gzip byte
 // slice.
 func (oc *OpenConfig) SchemaTreeGzip() []byte {
 	return openconfig.SchemaTreeGzip()
diff --git a/csbi/cmd/root.go b/csbi/cmd/root.go
index 939eb25ced0bb2667c8d77137d4542b27f7a0520..75eef6f39a737ff615850c5eea5e73ca79e6870a 100644
--- a/csbi/cmd/root.go
+++ b/csbi/cmd/root.go
@@ -77,7 +77,6 @@ func init() {
 	rootCmd.PersistentFlags().StringVar(&accessToken, "access-token", "", "access token for private repositories")
 	rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "", "log level 'debug' or 'trace'")
 	rootCmd.PersistentFlags().StringVar(&repoBasePath, "repository-base-path", "./models", "path to the repository base path (default is ./models)")
-
 }
 
 // initConfig reads in config file and ENV variables if set.
diff --git a/csbi/run.go b/csbi/run.go
index 96373dd0218260a11c8580a230dea5c86749ecf2..469899836c0d998b37cd14a739888127c89ff6bf 100644
--- a/csbi/run.go
+++ b/csbi/run.go
@@ -64,5 +64,4 @@ func Run(bindAddr string) {
 
 		o.Shutdown(ctx)
 	}()
-
 }
diff --git a/csbi/write.go b/csbi/write.go
index 7168431ff88e7decb4d751eed5759b0b8212db5a..b53e6dbb2baa547c1ed83694235b9e2625e1ac42 100644
--- a/csbi/write.go
+++ b/csbi/write.go
@@ -165,6 +165,7 @@ func writeCode(path string, code *ygen.GeneratedGoCode, t spb.Type) error {
 	return nil
 }
 
+// Manifest represents a csbi manifest
 type Manifest struct {
 	Name, Author, Version string
 }