sts-sponsors team mailing list archive
-
sts-sponsors team
-
Mailing list archive
-
Message #07183
[Merge] ~troyanov/maas:maas-agent into maas:master
Anton Troyanov has proposed merging ~troyanov/maas:maas-agent into maas:master.
Commit message:
feat: add maas agent project bootstrap
Requested reviews:
Christian Grabowski (cgrabowski)
For more details, see:
https://code.launchpad.net/~troyanov/maas/+git/maas/+merge/441357
--
Your team MAAS Committers is subscribed to branch maas:master.
diff --git a/.golangci.yaml b/.golangci.yaml
new file mode 100644
index 0000000..6855546
--- /dev/null
+++ b/.golangci.yaml
@@ -0,0 +1,188 @@
+# This file contains all available configuration options
+# https://golangci-lint.run/usage/linters/
+
+run:
+ timeout: 5m
+ issues-exit-code: 1
+ tests: true
+ # Enables skipping of directories:
+ # - vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
+ skip-dirs-use-default: true
+
+ # Which files to skip: they will be analyzed, but issues from them won't be reported.
+ # Default value is empty list,
+ # but there is no need to include all autogenerated files,
+ # we confidently recognize autogenerated files.
+ # If it's not please let us know.
+ # "/" will be replaced by current OS file path separator to properly work on Windows.
+ skip-files:
+ - ".*\\.my\\.go$"
+ - lib/bad.go
+
+ # If set we pass it to "go list -mod={option}". From "go help modules":
+ # If invoked with -mod=readonly, the go command is disallowed from the implicit
+ # automatic updating of go.mod described above. Instead, it fails when any changes
+ # to go.mod are needed. This setting is most useful to check that go.mod does
+ # not need updates, such as in a continuous integration and testing system.
+ # If invoked with -mod=vendor, the go command assumes that the vendor
+ # directory holds the correct copies of dependencies and ignores
+ # the dependency descriptions in go.mod.
+ #
+ # Allowed values: readonly|vendor|mod
+ # By default, it isn't set.
+ modules-download-mode: readonly
+
+ # Allow multiple parallel golangci-lint instances running.
+ # If false (default) - golangci-lint acquires file lock on start.
+ allow-parallel-runners: false
+
+
+# output configuration options
+output:
+ # Format: colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions|teamcity
+ #
+ # Multiple can be specified by separating them by comma, output can be provided
+ # for each of them by separating format name and path by colon symbol.
+ # Output path can be either `stdout`, `stderr` or path to the file to write to.
+ # Example: "checkstyle:report.xml,json:stdout,colored-line-number"
+ #
+ # Default: colored-line-number
+ format: colored-line-number
+
+ # Print lines of code with issue.
+ # Default: true
+ print-issued-lines: true
+
+ # Print linter name in the end of issue text.
+ # Default: true
+ print-linter-name: true
+
+ # Make issues output unique by line.
+ # Default: true
+ uniq-by-line: true
+
+ # Add a prefix to the output file references.
+ # Default is no prefix.
+ path-prefix: ""
+
+ # Sort results by: filepath, line and column.
+ sort-results: false
+
+
+linters:
+ disable-all: true
+ enable:
+ - dupl
+ - errcheck
+ - exportloopref
+ - gocritic
+ - godot
+ - gofmt
+ - goimports
+ - gosec
+ - gosimple
+ - govet
+ # - loggercheck
+ - nilerr
+ - nilnil
+ - noctx
+ - nolintlint
+ - nonamedreturns
+ - revive
+ - staticcheck
+ - tagliatelle
+ - unused
+ - whitespace
+ - wsl
+
+linters-settings:
+
+
+issues:
+ # List of regexps of issue texts to exclude.
+ #
+ # But independently of this option we use default exclude patterns,
+ # it can be disabled by `exclude-use-default: false`.
+ # To list all excluded by default patterns execute `golangci-lint run --help`
+ #
+ # Default: https://golangci-lint.run/usage/false-positives/#default-exclusions
+ exclude: []
+
+ # Excluding configuration per-path, per-linter, per-text and per-source
+ exclude-rules:
+ # Exclude some linters from running on tests files.
+ - path: _test\.go
+ linters:
+ - gocyclo
+ - errcheck
+ - dupl
+ - gosec
+
+ # Exclude known linters from partially hard-vendored code,
+ # which is impossible to exclude via `nolint` comments.
+ # `/` will be replaced by current OS file path separator to properly work on Windows.
+
+ # Exclude `lll` issues for long lines with `go:generate`.
+ - linters:
+ - lll
+ source: "^//go:generate "
+
+ # Independently of option `exclude` we use default exclude patterns,
+ # it can be disabled by this option.
+ # To list all excluded by default patterns execute `golangci-lint run --help`.
+ # Default: true.
+ exclude-use-default: false
+
+ # If set to true exclude and exclude-rules regular expressions become case-sensitive.
+ # Default: false
+ exclude-case-sensitive: false
+
+ max-same-issues: 0
+
+ # Show only new issues: if there are unstaged changes or untracked files,
+ # only those changes are analyzed, else only changes in HEAD~ are analyzed.
+ # It's a super-useful option for integration of golangci-lint into existing large codebase.
+ # It's not practical to fix all existing issues at the moment of integration:
+ # much better don't allow issues in new code.
+ #
+ # Default: false.
+ new: false
+
+ # Show only new issues created after git revision `REV`.
+ new-from-rev: HEAD
+
+ # Show only new issues created in git patch with set file path.
+ # new-from-patch: path/to/patch/file
+
+ # Fix found issues (if it's supported by the linter).
+ fix: true
+
+
+severity:
+ # Set the default severity for issues.
+ #
+ # If severity rules are defined and the issues do not match or no severity is provided to the rule
+ # this will be the default severity applied.
+ # Severities should match the supported severity names of the selected out format.
+ # - Code climate: https://docs.codeclimate.com/docs/issues#issue-severity
+ # - Checkstyle: https://checkstyle.sourceforge.io/property_types.html#SeverityLevel
+ # - GitHub: https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
+ # - TeamCity: https://www.jetbrains.com/help/teamcity/service-messages.html#Inspection+Instance
+ #
+ # Default value is an empty string.
+ default-severity: error
+
+ # If set to true `severity-rules` regular expressions become case-sensitive.
+ # Default: false
+ case-sensitive: true
+
+ # When a list of severity rules are provided, severity information will be added to lint issues.
+ # Severity rules have the same filtering capability as exclude rules
+ # except you are allowed to specify one matcher per severity rule.
+ # Only affects out formats that support setting severity information.
+ #
+ # Default: []
+ rules:
+ - linters:
+ - dupl
+ severity: info
diff --git a/Makefile b/Makefile
index 73144fe..9e3b161 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,8 @@
python := python3
snapcraft := SNAPCRAFT_BUILD_INFO=1 snapcraft -v
+BIN_DIR := bin
+CURRENT_DIR = $(shell pwd)
VENV := .ve
# PPA used by MAAS dependencies. It can be overridden by the env.
@@ -11,6 +13,8 @@ ifeq ($(MAAS_PPA),)
MAAS_PPA = ppa:maas-committers/latest-deps
endif
+export PATH := $(CURRENT_DIR)/$(BIN_DIR):$(PATH)
+
# pkg_resources makes some incredible noise about version numbers. They
# are not indications of bugs in MAAS so we silence them everywhere.
export PYTHONWARNINGS = \
@@ -128,9 +132,10 @@ swagger-css: $(swagger-dist)
go-bins:
$(MAKE) -j -C src/host-info build
+ $(MAKE) -j -C src/maasagent build
.PHONY: go-bins
-test: test-missing-migrations test-py lint-oapi
+test: test-missing-migrations test-py lint-oapi test-go
.PHONY: test
test-missing-migrations: bin/database bin/maas-region
@@ -141,6 +146,10 @@ test-py: bin/test.parallel bin/subunit-1to2 bin/subunit2junitxml bin/subunit2pyu
@utilities/run-py-tests-ci
.PHONY: test-py
+test-go:
+ @find src/ -type f -name go.mod -maxdepth 3 -execdir make test \;
+.PHONY: test-go
+
test-perf: bin/pytest
GIT_BRANCH=$(shell git rev-parse --abbrev-ref HEAD) \
GIT_HASH=$(shell git rev-parse HEAD) \
@@ -182,9 +191,9 @@ lint-oapi: openapi.yaml
# Go fmt
lint-go:
- @find src/ \( -name pkg -o -name vendor \) -prune -o -name '*.go' -exec gofmt -l {} + | \
- tee /tmp/gofmt.lint
- @test ! -s /tmp/gofmt.lint
+ @find src -type f -name go.mod -maxdepth 3 -execdir golangci-lint run -v ./... \; | \
+ tee /tmp/golangci-lint.lint
+ @test ! -s /tmp/golangci-lint.lint
.PHONY: lint-go
lint-shell:
@@ -424,3 +433,9 @@ snap-tree-sync: $(UI_BUILD) go-bins $(SNAP_UNPACKED_DIR_MARKER)
src/host-info/bin/ \
$(SNAP_UNPACKED_DIR)/usr/share/maas/machine-resources/
.PHONY: snap-tree-sync
+
+$(BIN_DIR)/golangci-lint: GOLANGCI_VERSION=1.51.2
+$(BIN_DIR)/golangci-lint: scripts/get_golangci-lint | $(BIN_DIR)
+ GOBIN="$(realpath $(dir $@))"
+ sh scripts/get_golangci-lint.sh "v${GOLANGCI_VERSION}"
+ touch $@
diff --git a/scripts/get_golangci-lint b/scripts/get_golangci-lint
new file mode 100644
index 0000000..291c47a
--- /dev/null
+++ b/scripts/get_golangci-lint
@@ -0,0 +1,412 @@
+#!/bin/sh
+set -e
+
+usage() {
+ this=$1
+ cat <<EOF
+$this: download go binaries for golangci/golangci-lint
+
+Usage: $this [-b] bindir [-d] [tag]
+ -b sets bindir or installation directory, Defaults to ./bin
+ -d turns on debug logging
+ [tag] is a tag from
+ https://github.com/golangci/golangci-lint/releases
+ If tag is missing, then the latest will be used.
+
+ Generated by godownloader
+ https://github.com/goreleaser/godownloader
+
+EOF
+ exit 2
+}
+
+parse_args() {
+ #BINDIR is ./bin unless set be ENV
+ # over-ridden by flag below
+
+ BINDIR=${BINDIR:-./bin}
+ while getopts "b:dh?x" arg; do
+ case "$arg" in
+ b) BINDIR="$OPTARG" ;;
+ d) log_set_priority 10 ;;
+ h | \?) usage "$0" ;;
+ x) set -x ;;
+ esac
+ done
+ shift $((OPTIND - 1))
+ TAG=$1
+}
+# this function wraps all the destructive operations
+# if a curl|bash cuts off the end of the script due to
+# network, either nothing will happen or will syntax error
+# out preventing half-done work
+execute() {
+ tmpdir=$(mktemp -d)
+ log_debug "downloading files into ${tmpdir}"
+ http_download "${tmpdir}/${TARBALL}" "${TARBALL_URL}"
+ http_download "${tmpdir}/${CHECKSUM}" "${CHECKSUM_URL}"
+ hash_sha256_verify "${tmpdir}/${TARBALL}" "${tmpdir}/${CHECKSUM}"
+ srcdir="${tmpdir}/${NAME}"
+ rm -rf "${srcdir}"
+ (cd "${tmpdir}" && untar "${TARBALL}")
+ test ! -d "${BINDIR}" && install -d "${BINDIR}"
+ for binexe in $BINARIES; do
+ if [ "$OS" = "windows" ]; then
+ binexe="${binexe}.exe"
+ fi
+ install "${srcdir}/${binexe}" "${BINDIR}/"
+ log_info "installed ${BINDIR}/${binexe}"
+ done
+ rm -rf "${tmpdir}"
+}
+get_binaries() {
+ case "$PLATFORM" in
+ darwin/amd64) BINARIES="golangci-lint" ;;
+ darwin/arm64) BINARIES="golangci-lint" ;;
+ darwin/armv6) BINARIES="golangci-lint" ;;
+ darwin/armv7) BINARIES="golangci-lint" ;;
+ darwin/mips64) BINARIES="golangci-lint" ;;
+ darwin/mips64le) BINARIES="golangci-lint" ;;
+ darwin/ppc64le) BINARIES="golangci-lint" ;;
+ darwin/s390x) BINARIES="golangci-lint" ;;
+ freebsd/386) BINARIES="golangci-lint" ;;
+ freebsd/amd64) BINARIES="golangci-lint" ;;
+ freebsd/armv6) BINARIES="golangci-lint" ;;
+ freebsd/armv7) BINARIES="golangci-lint" ;;
+ freebsd/mips64) BINARIES="golangci-lint" ;;
+ freebsd/mips64le) BINARIES="golangci-lint" ;;
+ freebsd/ppc64le) BINARIES="golangci-lint" ;;
+ freebsd/s390x) BINARIES="golangci-lint" ;;
+ linux/386) BINARIES="golangci-lint" ;;
+ linux/amd64) BINARIES="golangci-lint" ;;
+ linux/arm64) BINARIES="golangci-lint" ;;
+ linux/armv6) BINARIES="golangci-lint" ;;
+ linux/armv7) BINARIES="golangci-lint" ;;
+ linux/mips64) BINARIES="golangci-lint" ;;
+ linux/mips64le) BINARIES="golangci-lint" ;;
+ linux/ppc64le) BINARIES="golangci-lint" ;;
+ linux/s390x) BINARIES="golangci-lint" ;;
+ linux/riscv64) BINARIES="golangci-lint" ;;
+ netbsd/386) BINARIES="golangci-lint" ;;
+ netbsd/amd64) BINARIES="golangci-lint" ;;
+ netbsd/armv6) BINARIES="golangci-lint" ;;
+ netbsd/armv7) BINARIES="golangci-lint" ;;
+ windows/386) BINARIES="golangci-lint" ;;
+ windows/amd64) BINARIES="golangci-lint" ;;
+ windows/arm64) BINARIES="golangci-lint" ;;
+ windows/armv6) BINARIES="golangci-lint" ;;
+ windows/armv7) BINARIES="golangci-lint" ;;
+ windows/mips64) BINARIES="golangci-lint" ;;
+ windows/mips64le) BINARIES="golangci-lint" ;;
+ windows/ppc64le) BINARIES="golangci-lint" ;;
+ windows/s390x) BINARIES="golangci-lint" ;;
+ *)
+ log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
+ exit 1
+ ;;
+ esac
+}
+tag_to_version() {
+ if [ -z "${TAG}" ]; then
+ log_info "checking GitHub for latest tag"
+ else
+ log_info "checking GitHub for tag '${TAG}'"
+ fi
+ REALTAG=$(github_release "$OWNER/$REPO" "${TAG}") && true
+ if test -z "$REALTAG"; then
+ log_crit "unable to find '${TAG}' - use 'latest' or see https://github.com/${PREFIX}/releases for details"
+ exit 1
+ fi
+ # if version starts with 'v', remove it
+ TAG="$REALTAG"
+ VERSION=${TAG#v}
+}
+adjust_format() {
+ # change format (tar.gz or zip) based on OS
+ case ${OS} in
+ windows) FORMAT=zip ;;
+ esac
+ true
+}
+adjust_os() {
+ # adjust archive name based on OS
+ true
+}
+adjust_arch() {
+ # adjust archive name based on ARCH
+ true
+}
+
+cat /dev/null <<EOF
+------------------------------------------------------------------------
+https://github.com/client9/shlib - portable posix shell functions
+Public domain - http://unlicense.org
+https://github.com/client9/shlib/blob/master/LICENSE.md
+but credit (and pull requests) appreciated.
+------------------------------------------------------------------------
+EOF
+is_command() {
+ command -v "$1" >/dev/null
+}
+echoerr() {
+ echo "$@" 1>&2
+}
+log_prefix() {
+ echo "$0"
+}
+_logp=6
+log_set_priority() {
+ _logp="$1"
+}
+log_priority() {
+ if test -z "$1"; then
+ echo "$_logp"
+ return
+ fi
+ [ "$1" -le "$_logp" ]
+}
+log_tag() {
+ case $1 in
+ 0) echo "emerg" ;;
+ 1) echo "alert" ;;
+ 2) echo "crit" ;;
+ 3) echo "err" ;;
+ 4) echo "warning" ;;
+ 5) echo "notice" ;;
+ 6) echo "info" ;;
+ 7) echo "debug" ;;
+ *) echo "$1" ;;
+ esac
+}
+log_debug() {
+ log_priority 7 || return 0
+ echoerr "$(log_prefix)" "$(log_tag 7)" "$@"
+}
+log_info() {
+ log_priority 6 || return 0
+ echoerr "$(log_prefix)" "$(log_tag 6)" "$@"
+}
+log_err() {
+ log_priority 3 || return 0
+ echoerr "$(log_prefix)" "$(log_tag 3)" "$@"
+}
+log_crit() {
+ log_priority 2 || return 0
+ echoerr "$(log_prefix)" "$(log_tag 2)" "$@"
+}
+uname_os() {
+ os=$(uname -s | tr '[:upper:]' '[:lower:]')
+ case "$os" in
+ msys*) os="windows" ;;
+ mingw*) os="windows" ;;
+ cygwin*) os="windows" ;;
+ win*) os="windows" ;;
+ esac
+ echo "$os"
+}
+uname_arch() {
+ arch=$(uname -m)
+ case $arch in
+ x86_64) arch="amd64" ;;
+ x86) arch="386" ;;
+ i686) arch="386" ;;
+ i386) arch="386" ;;
+ aarch64) arch="arm64" ;;
+ armv5*) arch="armv5" ;;
+ armv6*) arch="armv6" ;;
+ armv7*) arch="armv7" ;;
+ esac
+ echo ${arch}
+}
+uname_os_check() {
+ os=$(uname_os)
+ case "$os" in
+ darwin) return 0 ;;
+ dragonfly) return 0 ;;
+ freebsd) return 0 ;;
+ linux) return 0 ;;
+ android) return 0 ;;
+ nacl) return 0 ;;
+ netbsd) return 0 ;;
+ openbsd) return 0 ;;
+ plan9) return 0 ;;
+ solaris) return 0 ;;
+ windows) return 0 ;;
+ esac
+ log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value."
+ return 1
+}
+uname_arch_check() {
+ arch=$(uname_arch)
+ case "$arch" in
+ 386) return 0 ;;
+ amd64) return 0 ;;
+ arm64) return 0 ;;
+ armv5) return 0 ;;
+ armv6) return 0 ;;
+ armv7) return 0 ;;
+ ppc64) return 0 ;;
+ ppc64le) return 0 ;;
+ mips) return 0 ;;
+ mipsle) return 0 ;;
+ mips64) return 0 ;;
+ mips64le) return 0 ;;
+ s390x) return 0 ;;
+ riscv64) return 0 ;;
+ amd64p32) return 0 ;;
+ esac
+ log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value."
+ return 1
+}
+untar() {
+ tarball=$1
+ case "${tarball}" in
+ *.tar.gz | *.tgz) tar --no-same-owner -xzf "${tarball}" ;;
+ *.tar) tar --no-same-owner -xf "${tarball}" ;;
+ *.zip) unzip "${tarball}" ;;
+ *)
+ log_err "untar unknown archive format for ${tarball}"
+ return 1
+ ;;
+ esac
+}
+http_download_curl() {
+ local_file=$1
+ source_url=$2
+ header=$3
+ if [ -z "$header" ]; then
+ code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url")
+ else
+ code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url")
+ fi
+ if [ "$code" != "200" ]; then
+ log_debug "http_download_curl received HTTP status $code"
+ return 1
+ fi
+ return 0
+}
+http_download_wget() {
+ local_file=$1
+ source_url=$2
+ header=$3
+ if [ -z "$header" ]; then
+ wget -q -O "$local_file" "$source_url"
+ else
+ wget -q --header "$header" -O "$local_file" "$source_url"
+ fi
+}
+http_download() {
+ log_debug "http_download $2"
+ if is_command curl; then
+ http_download_curl "$@"
+ return
+ elif is_command wget; then
+ http_download_wget "$@"
+ return
+ fi
+ log_crit "http_download unable to find wget or curl"
+ return 1
+}
+http_copy() {
+ tmp=$(mktemp)
+ http_download "${tmp}" "$1" "$2" || return 1
+ body=$(cat "$tmp")
+ rm -f "${tmp}"
+ echo "$body"
+}
+github_release() {
+ owner_repo=$1
+ version=$2
+ test -z "$version" && version="latest"
+ giturl="https://github.com/${owner_repo}/releases/${version}"
+ json=$(http_copy "$giturl" "Accept:application/json")
+ test -z "$json" && return 1
+ version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//')
+ test -z "$version" && return 1
+ echo "$version"
+}
+hash_sha256() {
+ TARGET=${1:-/dev/stdin}
+ if is_command gsha256sum; then
+ hash=$(gsha256sum "$TARGET") || return 1
+ echo "$hash" | cut -d ' ' -f 1
+ elif is_command sha256sum; then
+ hash=$(sha256sum "$TARGET") || return 1
+ echo "$hash" | cut -d ' ' -f 1
+ elif is_command shasum; then
+ hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1
+ echo "$hash" | cut -d ' ' -f 1
+ elif is_command openssl; then
+ hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1
+ echo "$hash" | cut -d ' ' -f a
+ else
+ log_crit "hash_sha256 unable to find command to compute sha-256 hash"
+ return 1
+ fi
+}
+hash_sha256_verify() {
+ TARGET=$1
+ checksums=$2
+ if [ -z "$checksums" ]; then
+ log_err "hash_sha256_verify checksum file not specified in arg2"
+ return 1
+ fi
+ BASENAME=${TARGET##*/}
+ want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1)
+ if [ -z "$want" ]; then
+ log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'"
+ return 1
+ fi
+ got=$(hash_sha256 "$TARGET")
+ if [ "$want" != "$got" ]; then
+ log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got"
+ return 1
+ fi
+}
+cat /dev/null <<EOF
+------------------------------------------------------------------------
+End of functions from https://github.com/client9/shlib
+------------------------------------------------------------------------
+EOF
+
+PROJECT_NAME="golangci-lint"
+OWNER=golangci
+REPO="golangci-lint"
+BINARY=golangci-lint
+FORMAT=tar.gz
+OS=$(uname_os)
+ARCH=$(uname_arch)
+PREFIX="$OWNER/$REPO"
+
+# use in logging routines
+log_prefix() {
+ echo "$PREFIX"
+}
+PLATFORM="${OS}/${ARCH}"
+GITHUB_DOWNLOAD=https://github.com/${OWNER}/${REPO}/releases/download
+
+uname_os_check "$OS"
+uname_arch_check "$ARCH"
+
+parse_args "$@"
+
+get_binaries
+
+tag_to_version
+
+adjust_format
+
+adjust_os
+
+adjust_arch
+
+log_info "found version: ${VERSION} for ${TAG}/${OS}/${ARCH}"
+
+NAME=${BINARY}-${VERSION}-${OS}-${ARCH}
+TARBALL=${NAME}.${FORMAT}
+TARBALL_URL=${GITHUB_DOWNLOAD}/${TAG}/${TARBALL}
+CHECKSUM=${PROJECT_NAME}-${VERSION}-checksums.txt
+CHECKSUM_URL=${GITHUB_DOWNLOAD}/${TAG}/${CHECKSUM}
+
+
+execute
diff --git a/src/maasagent/.gitignore b/src/maasagent/.gitignore
new file mode 100644
index 0000000..c2b1d37
--- /dev/null
+++ b/src/maasagent/.gitignore
@@ -0,0 +1,24 @@
+### Go ###
+go.work
+
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool
+*.out
+
+# Dependency directories
+vendor/
+
+# BINDIR used for various tools
+bin/
+
+# DISTDIR
+dist/
diff --git a/src/maasagent/Makefile b/src/maasagent/Makefile
new file mode 100644
index 0000000..12bfd07
--- /dev/null
+++ b/src/maasagent/Makefile
@@ -0,0 +1,51 @@
+SHELL := /bin/bash
+
+BIN_DIR := bin
+BUILD_DIR := build
+CMD_DIR := cmd
+
+GO := go # override to use alternative Go binary
+VENDOR_DIR := vendor
+
+LDFLAGS := -ldflags '-s -w -extldflags "-static"'
+
+# Explicitly set cache dirs to avoid situations where we can't mkdir under $HOME (e.g. Launchpad builds)
+export GOCACHE := $(shell [ -d $(HOME)/.cache ] && echo $(HOME)/.cache/go-cache || mktemp --tmpdir -d tmp.go-cacheXXX)
+export GOMODCACHE := $(shell [ -d $(HOME)/go ] && echo $(HOME)/go/pkg/mod || mktemp --tmpdir -d tmp.go-mod-cacheXXX)
+export CGO_ENABLED := $(if $(CGO_ENABLED),$(CGO_ENABLED),0)
+export GOFLAGS := -mod=vendor
+
+default: build test lint
+
+$(BIN_DIR): ; mkdir -p $@
+
+ARTIFACTS := $(subst /,,$(subst cmd/,,$(wildcard cmd/*/)))
+build: vendor $(addprefix $(BUILD_DIR)/,$(ARTIFACTS))
+
+$(BUILD_DIR)/%:
+ $(GO) build -o $(BUILD_DIR)/$* $(LDFLAGS) cmd/$*/*.go
+
+.PHONY: test
+test:
+ CGO_ENABLED=1 $(GO) test -race ./...
+
+.PHONY: generate
+generate: $(BIN_DIR)/mockgen
+ $(GO) generate ./...
+
+.PHONY: tools
+tools: $(BIN_DIR)/mockgen
+
+.PHONY: vendor
+vendor: $(VENDOR_DIR)/modules.txt
+
+$(VENDOR_DIR)/modules.txt: go.mod
+ $(GO) mod vendor
+
+$(BIN_DIR)/mockgen: MOCKGEN_VERSION=1.6.0
+$(BIN_DIR)/mockgen: | $(BIN_DIR)
+ GOFLAGS="" GOBIN="$(realpath $(dir $@))" $(GO) install github.com/golang/mock/mockgen@v${MOCKGEN_VERSION}
+
+.PHONY: clean
+clean:
+ rm -rf $(VENDOR_DIR) $(BIN_DIR) $(BUILD_DIR)
diff --git a/src/maasagent/cmd/README.md b/src/maasagent/cmd/README.md
new file mode 100644
index 0000000..8ddd037
--- /dev/null
+++ b/src/maasagent/cmd/README.md
@@ -0,0 +1,5 @@
+# `/cmd`
+
+The directory name for each application should match the name of the executable you want to have (e.g., /cmd/myapp).
+
+It's common to have a small `main` function that imports and invokes the code from the `/internal` and `/pkg` directories and nothing else.
diff --git a/src/maasagent/cmd/agent/README.md b/src/maasagent/cmd/agent/README.md
new file mode 100644
index 0000000..afd1b6a
--- /dev/null
+++ b/src/maasagent/cmd/agent/README.md
@@ -0,0 +1,3 @@
+# agent
+
+MAAS Agent.
diff --git a/src/maasagent/cmd/agent/main.go b/src/maasagent/cmd/agent/main.go
new file mode 100644
index 0000000..7905807
--- /dev/null
+++ b/src/maasagent/cmd/agent/main.go
@@ -0,0 +1,5 @@
+package main
+
+func main() {
+
+}
diff --git a/src/maasagent/cmd/netmon/README.md b/src/maasagent/cmd/netmon/README.md
new file mode 100644
index 0000000..efd6623
--- /dev/null
+++ b/src/maasagent/cmd/netmon/README.md
@@ -0,0 +1,3 @@
+# netmon
+
+MAAS Agent Network monitor that can be used as a standalone binary.
diff --git a/src/maasagent/cmd/netmon/main.go b/src/maasagent/cmd/netmon/main.go
new file mode 100644
index 0000000..e83655f
--- /dev/null
+++ b/src/maasagent/cmd/netmon/main.go
@@ -0,0 +1,9 @@
+package main
+
+import (
+ "launchpad.net/maas/maas/src/maasagent/internal/netmon"
+)
+
+func main() {
+ netmon.NewService()
+}
diff --git a/src/maasagent/go.mod b/src/maasagent/go.mod
new file mode 100644
index 0000000..9bee24c
--- /dev/null
+++ b/src/maasagent/go.mod
@@ -0,0 +1,3 @@
+module launchpad.net/maas/maas/src/maasagent
+
+go 1.18
diff --git a/src/maasagent/go.sum b/src/maasagent/go.sum
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/maasagent/go.sum
diff --git a/src/maasagent/internal/README.md b/src/maasagent/internal/README.md
new file mode 100644
index 0000000..3332284
--- /dev/null
+++ b/src/maasagent/internal/README.md
@@ -0,0 +1,3 @@
+# `/internal`
+
+Private application and library code. This is the code you don't want others importing in their applications or libraries. Note that this layout pattern is enforced by the Go compiler itself.
diff --git a/src/maasagent/internal/netmon/service.go b/src/maasagent/internal/netmon/service.go
new file mode 100644
index 0000000..14ebdc6
--- /dev/null
+++ b/src/maasagent/internal/netmon/service.go
@@ -0,0 +1,3 @@
+package netmon
+
+func NewService() {}
Follow ups