diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml deleted file mode 100644 index 2683f72..0000000 --- a/.github/workflows/go.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: Go - -on: - push: - branches: [ main ] - pull_request: - types: [opened, reopened, synchronize] - -jobs: - build-test: - name: Build, test, and format - strategy: - matrix: - go-version: [1.17.x] - platform: [ubuntu-latest, macos-latest, windows-latest] - runs-on: ${{ matrix.platform }} - steps: - - uses: actions/checkout@v4 - - - name: setup go - uses: actions/setup-go@v4 - with: - go-version: ${{ matrix.go-version }} - - - name: Build - run: go build -v ./... - - - name: Test - run: go test -v -race ./... - - - name: Format - if: matrix.platform == 'ubuntu-latest' - run: if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then exit 1; fi - - - mysql-integration-test: - name: Integration tests for the MySQL backend. - strategy: - matrix: - go-version: [1.17.x] - platform: [ubuntu-latest] - mysql-version: ['8.0'] - runs-on: ${{ matrix.platform }} - services: - mysql: - image: mysql:8.0 - env: - MYSQL_RANDOM_ROOT_PASSWORD: yes - MYSQL_DATABASE: nanomdm - MYSQL_USER: nanomdm - MYSQL_PASSWORD: nanomdm - ports: - - 3800:3306 - options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 - defaults: - run: - shell: bash - env: - MYSQL_PWD: nanomdm - PORT: 3800 - steps: - - uses: actions/checkout@v4 - - - name: setup go - uses: actions/setup-go@v4 - with: - go-version: ${{ matrix.go-version }} - - - name: Verify MySQL connection - run: | - while ! mysqladmin ping --host=localhost --port=$PORT --protocol=TCP --silent; do - sleep 1 - done - - - name: Set up schema - run: | - mysql --version - mysql --user=nanomdm --host=localhost --port=$PORT --protocol=TCP nanomdm < ./storage/mysql/schema.sql - - - name: Test - run: go test -v --tags=integration ./storage/mysql/ -args -dsn "nanomdm:nanomdm@tcp(localhost:$PORT)/nanomdm" diff --git a/.github/workflows/on-push-pr.yml b/.github/workflows/on-push-pr.yml new file mode 100644 index 0000000..d692c82 --- /dev/null +++ b/.github/workflows/on-push-pr.yml @@ -0,0 +1,127 @@ +on: + push: + branches: [main] + tags: ["v*.*.*"] + pull_request: + types: [opened, reopened, synchronize] +jobs: + format-build-test: + strategy: + matrix: + go-version: ['1.19.x', '1.21.x'] + platform: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + + - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version: ${{ matrix.go-version }} + + - if: matrix.platform == 'ubuntu-latest' + run: if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then exit 1; fi + + - run: go build -v ./... + + - run: make test + docker-build-push: + if: github.event_name != 'pull_request' + needs: format-build-test + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + with: + fetch-depth: 0 + + - uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0 + + - uses: docker/metadata-action@818d4b7b91585d195f67373fd9cb0332e31a7175 # v4.6.0 + id: meta + with: + images: | + ghcr.io/${{ github.repository }} + tags: | + type=ref,event=branch + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha + + - uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 # v4.1.1 + with: + context: . + push: true + file: Dockerfile.buildx + platforms: linux/amd64,linux/arm64,linux/arm + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + release-zips: + if: github.event_name != 'pull_request' + needs: format-build-test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + with: + fetch-depth: 0 + + - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version: '1.19.x' + + - run: CGO_ENABLED=0 make release + + - uses: actions/upload-artifact@v3 + with: + name: release-zips + path: "*.zip" + mysql-test: + runs-on: 'ubuntu-latest' + needs: format-build-test + services: + mysql: + image: mysql:8.0 + env: + MYSQL_RANDOM_ROOT_PASSWORD: yes + MYSQL_DATABASE: testdb + MYSQL_USER: testuser + MYSQL_PASSWORD: testpw + ports: + - 3800:3306 + options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + defaults: + run: + shell: bash + env: + MYSQL_PWD: testpw + PORT: 3800 + steps: + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + + - uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0 + with: + go-version: '1.19.x' + + - name: verify mysql + run: | + while ! mysqladmin ping --host=localhost --port=$PORT --protocol=TCP --silent; do + sleep 1 + done + + - name: mysql schema + run: | + mysql --version + mysql --user=testuser --host=localhost --port=$PORT --protocol=TCP testdb < ./storage/mysql/schema.sql + + - name: set test dsn + run: echo "NANOMDM_MYSQL_STORAGE_TEST_DSN=testuser:testpw@tcp(localhost:$PORT)/testdb" >> $GITHUB_ENV + + - run: go test -v ./storage/mysql diff --git a/.github/workflows/on-release.yml b/.github/workflows/on-release.yml new file mode 100644 index 0000000..55b735b --- /dev/null +++ b/.github/workflows/on-release.yml @@ -0,0 +1,22 @@ +on: + release: + types: [published] +jobs: + release-zips: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + with: + fetch-depth: 0 + + - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version: '1.19.x' + + - run: CGO_ENABLED=0 make release + + - run: gh release upload ${{ github.event.release.tag_name }} *.zip + env: + GH_TOKEN: ${{ github.token }} diff --git a/Dockerfile b/Dockerfile index 4f49198..3eca44d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,14 @@ FROM gcr.io/distroless/static -COPY nanomdm-linux-amd64 /nanomdm -COPY nano2nano-linux-amd64 /nano2nano +ARG TARGETOS TARGETARCH + +COPY nanomdm-$TARGETOS-$TARGETARCH /app/nanomdm +COPY nano2nano-$TARGETOS-$TARGETARCH /app/nano2nano EXPOSE 9000 -VOLUME ["/db"] +VOLUME ["/app/db"] + +WORKDIR /app -ENTRYPOINT ["/nanomdm"] +ENTRYPOINT ["/app/nanomdm"] diff --git a/Dockerfile.buildx b/Dockerfile.buildx new file mode 100644 index 0000000..33b3c8a --- /dev/null +++ b/Dockerfile.buildx @@ -0,0 +1,28 @@ +FROM --platform=$BUILDPLATFORM golang:1.19 AS builder + +WORKDIR /go/app + +COPY . . + +ARG TARGETOS TARGETARCH + +RUN --mount=type=cache,target=/root/.cache/go-build \ + --mount=type=cache,target=/go/pkg \ + CGO_ENABLED=0 make \ + nanomdm-$TARGETOS-$TARGETARCH \ + nano2nano-$TARGETOS-$TARGETARCH + +FROM gcr.io/distroless/static + +ARG TARGETOS TARGETARCH + +COPY --from=builder /go/app/nanomdm-$TARGETOS-$TARGETARCH /app/nanomdm +COPY --from=builder /go/app/nano2nano-$TARGETOS-$TARGETARCH /app/nano2nano + +EXPOSE 9000 + +VOLUME ["/app/db"] + +WORKDIR /app + +ENTRYPOINT ["/app/nanomdm"] diff --git a/Makefile b/Makefile index be0a068..a892ebf 100644 --- a/Makefile +++ b/Makefile @@ -5,12 +5,18 @@ OSARCH=$(shell go env GOHOSTOS)-$(shell go env GOHOSTARCH) NANOMDM=\ nanomdm-darwin-amd64 \ nanomdm-darwin-arm64 \ - nanomdm-linux-amd64 + nanomdm-linux-amd64 \ + nanomdm-linux-arm64 \ + nanomdm-linux-arm \ + nanomdm-windows-amd64.exe NANO2NANO=\ nano2nano-darwin-amd64 \ nano2nano-darwin-arm64 \ - nano2nano-linux-amd64 + nano2nano-linux-amd64 \ + nano2nano-linux-arm64 \ + nano2nano-linux-arm \ + nano2nano-windows-amd64.exe SUPPLEMENTAL=\ tools/cmdr.py \ @@ -18,8 +24,6 @@ SUPPLEMENTAL=\ my: nanomdm-$(OSARCH) nano2nano-$(OSARCH) -docker: nanomdm-linux-amd64 nano2nano-linux-amd64 - $(NANOMDM): cmd/nanomdm GOOS=$(word 2,$(subst -, ,$@)) GOARCH=$(word 3,$(subst -, ,$(subst .exe,,$@))) go build $(LDFLAGS) -o $@ ./$< @@ -43,12 +47,9 @@ nanomdm-%-$(VERSION).zip: nanomdm-% nano2nano-% $(SUPPLEMENTAL) clean: rm -rf nanomdm-* nano2nano-* -release: \ - nanomdm-darwin-amd64-$(VERSION).zip \ - nanomdm-darwin-arm64-$(VERSION).zip \ - nanomdm-linux-amd64-$(VERSION).zip +release: $(foreach bin,$(NANOMDM),$(subst .exe,,$(bin))-$(VERSION).zip) test: go test -v -cover -race ./... -.PHONY: my docker $(NANOMDM) $(NANO2NANO) clean release test +.PHONY: my $(NANOMDM) $(NANO2NANO) clean release test diff --git a/README.md b/README.md index aa96378..1958814 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,15 @@ A quick guide to get NanoMDM up and running using ngrok. - [Operations Guide](docs/operations-guide.md) A brief overview of the various command-line switches and HTTP endpoints and APIs available to NanoMDM. +## Getting the latest version + +* Release `.zip` files containing the server and supplementals should be attached to every [GitHub release](https://github.com/micromdm/nanomdm/releases). + * Release zips are also [published](https://github.com/micromdm/nanomdm/actions) for every `main` branch commit. +* A Docker container is built and [published to the GHCR.io](http://ghcr.io/micromdm/nanomdm) registry for every release. + * `docker pull ghcr.io/micromdm/nanomdm:latest` — `docker run ghcr.io/micromdm/nanocmd:latest` + * A Docker container is also published for every `main` branch commit (and tagged with `:main`) +* If you have a [Go toolchain installed](https://go.dev/doc/install) you can checkout the source and simply run `make`. + ## Features - Horizontal scaling: zero/minimal local state. Persistence in storage layers. MySQL and PostgreSQL backends provided in the box. diff --git a/storage/mysql/bstoken_test.go b/storage/mysql/bstoken_test.go index 4ccea1e..9fbc801 100644 --- a/storage/mysql/bstoken_test.go +++ b/storage/mysql/bstoken_test.go @@ -1,23 +1,22 @@ -//go:build integration -// +build integration - package mysql import ( "bytes" "context" "encoding/base64" + "os" "testing" "github.com/micromdm/nanomdm/mdm" ) func TestBSToken(t *testing.T) { - if *flDSN == "" { - t.Fatal("MySQL DSN flag not provided to test") + testDSN := os.Getenv("NANOMDM_MYSQL_STORAGE_TEST_DSN") + if testDSN == "" { + t.Skip("NANOMDM_MYSQL_STORAGE_TEST_DSN not set") } - storage, err := New(WithDSN(*flDSN), WithDeleteCommands()) + storage, err := New(WithDSN(testDSN), WithDeleteCommands()) if err != nil { t.Fatal(err) } diff --git a/storage/mysql/common_test.go b/storage/mysql/common_test.go deleted file mode 100644 index 84e9a89..0000000 --- a/storage/mysql/common_test.go +++ /dev/null @@ -1,8 +0,0 @@ -//go:build integration -// +build integration - -package mysql - -import "flag" - -var flDSN = flag.String("dsn", "", "DSN of test MySQL instance") diff --git a/storage/mysql/device_test.go b/storage/mysql/device_test.go index a431196..12ac9a4 100644 --- a/storage/mysql/device_test.go +++ b/storage/mysql/device_test.go @@ -1,6 +1,3 @@ -//go:build integration -// +build integration - package mysql import ( diff --git a/storage/mysql/queue_test.go b/storage/mysql/queue_test.go index 08cf8d6..4022cad 100644 --- a/storage/mysql/queue_test.go +++ b/storage/mysql/queue_test.go @@ -1,9 +1,7 @@ -//go:build integration -// +build integration - package mysql import ( + "os" "testing" "github.com/micromdm/nanomdm/storage/internal/test" @@ -12,11 +10,12 @@ import ( ) func TestQueue(t *testing.T) { - if *flDSN == "" { - t.Fatal("MySQL DSN flag not provided to test") + testDSN := os.Getenv("NANOMDM_MYSQL_STORAGE_TEST_DSN") + if testDSN == "" { + t.Skip("NANOMDM_MYSQL_STORAGE_TEST_DSN not set") } - storage, err := New(WithDSN(*flDSN), WithDeleteCommands()) + storage, err := New(WithDSN(testDSN), WithDeleteCommands()) if err != nil { t.Fatal(err) } @@ -30,7 +29,7 @@ func TestQueue(t *testing.T) { test.TestQueue(t, d.UDID, storage) }) - storage, err = New(WithDSN(*flDSN)) + storage, err = New(WithDSN(testDSN)) if err != nil { t.Fatal(err) }