This document provides comprehensive guidelines for contributors working on the abm (Apple Business Manager) Go library.
This library provides a Go client for the Apple Business Manager (ABM) API, enabling authentication via JWT client assertions and retrieval of organizational device data through paginated API endpoints.
abm/
├── abm.go # Core client and device fetching logic
├── auth.go # OAuth2 authentication and JWT token generation
├── auth_test.go # Comprehensive authentication tests
├── pagination.go # Generic pagination iterator using Go 1.26 iterators
├── types.go # ABM API response types and constants
├── doc.go # Package documentation
├── examples/ # Usage examples
│ └── main.go
├── .github/ # GitHub configuration
│ ├── dependabot.yaml
│ └── renovate.json5
├── go.mod # Module dependencies
└── go.sum # Dependency checksums
- Authentication: JWT-based OAuth2 client credentials flow with ECDSA P-256 signing
- API Client: HTTP client wrapper with token management
- Pagination: Generic iterator pattern using Go 1.26's
iter.Seq2 - Types: Strongly-typed structs for ABM API resources
# Build the library (verify compilation)
go build ./...
# Build the example
go build ./examples# Run all tests
go test ./...
# Run tests with verbose output
go test -v ./...
# Run tests with race detector
go test -race ./...
# Run tests with coverage
go test -cover ./...
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out # View coverage in browser# Format code
gofmt -s -w .
# Run static analysis
go vet ./...
# Run linter (if golangci-lint is installed)
golangci-lint run# Update dependencies
go get -u ./...
go mod tidy
# Verify dependencies
go mod verify
# View dependency graph
go mod graphThis project strictly follows the Google Go Style Guide.
- Use Go 1.26+: This project requires Go 1.26 or higher and uses modern features like range-over-func iterators
- Use
anyinstead ofinterface{} - Use generics where appropriate (see
PageIteratorfor example) - Format with
gofmt -s: Always use simplified formatting - Godoc comments must end with a period
- Exported identifiers: Use
PascalCase(e.g.,Client,NewAssertion) - Unexported identifiers: Use
camelCase(e.g.,decodeOrgDevices,parseECDSAPrivateKeyFromPEM) - Constants: Use
PascalCasewith descriptive prefixes (e.g.,StatusAssigned,ProductFamilyIPhone) - Acronyms: Keep uppercase in names (e.g.,
ABM,JWT,ID,URL)
- Primitive types: Use non-pointer with
omitemptyColor string `json:"color,omitempty"`
- Struct types: Use pointer with
omitzeroLinks *PagedDocumentLinks `json:"links,omitzero"`
- Slices: Use
omitempty(slices of primitives or complex types)IMEI []string `json:"imei,omitempty"`
- Expand struct fields when it improves readability:
// GOOD HTTPOptions: genai.HTTPOptions{ Headers: http.Header{ "User-Agent": []string{version.UserAgent("genai")}, }, } // BAD (too compressed) HTTPOptions: genai.HTTPOptions{Headers: http.Header{"User-Agent": []string{version.UserAgent("genai")}}}
- Use
github.com/go-json-experiment/jsoninstead ofencoding/json - Use
github.com/golang-jwt/jwt/v5for JWT operations - Use
golang.org/x/oauth2for OAuth2 flows
- Use
testingpackage: Standard library testing only - Assertions: Use
github.com/google/go-cmp/cmpfor comparisons - No mocking frameworks: Make actual API calls when testing authentication
- Use
t.Context(): Always uset.Context()instead ofcontext.Background()
All tests must follow this pattern:
func TestFeatureName(t *testing.T) {
ctx := t.Context()
if err := ctx.Err(); err != nil {
t.Fatalf("context error: %v", err)
}
tests := map[string]struct {
input string
expected string
wantErr bool
}{
"success: basic case": {
input: "hello",
expected: "HELLO",
},
"error: empty input": {
input: "",
wantErr: true,
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
ctx := t.Context()
if err := ctx.Err(); err != nil {
t.Fatalf("context error: %v", err)
}
// Test logic here
// Do NOT use: tt := tt (copying variable is unneeded in modern Go)
})
}
}Test case names must follow the pattern: "<status>: <description>"
- Success cases:
"success: basic case","success: with pagination" - Error cases:
"error: missing parameter","error: invalid format"
- All public functions must have tests
- Cover error paths: Test both success and failure cases
- Test edge cases: Empty inputs, nil values, context cancellation
- Use table-driven tests: Map-based test structure (see example above)
Use github.com/google/go-cmp/cmp for all comparisons:
if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("mismatch (-want +got):\n%s", diff)
}Every test must check context errors:
ctx := t.Context()
if err := ctx.Err(); err != nil {
t.Fatalf("context error: %v", err)
}Use httptest for HTTP server mocking:
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Handler logic
}))
t.Cleanup(server.Close)Based on the repository's commit history, follow this format:
<scope>: <description>
Examples:
- abm: initial implements
- go.mod: init module
- github: add .github directory
- auth: add JWT token generation
- pagination: implement generic iterator
- types: add OrgDevice response types
- Scope prefix: Use the affected component as prefix (e.g.,
abm:,auth:,go.mod:) - Lowercase description: Keep descriptions lowercase and concise
- Imperative mood: Use imperative present tense ("add", not "added" or "adds")
- No period at end: Omit trailing periods in the subject line
All commits must be signed:
git commit --gpg-sign --signoff -m "scope: description"- Follow the PR template (
.github/PULL_REQUEST_TEMPLATE.md) - All tests must pass: Run
go test ./...before submitting - Code must be formatted: Run
gofmt -s -w . - Update documentation: If adding new public APIs, update godoc comments
- Add tests for new code: Maintain or improve test coverage
- Link related issues: Reference any related GitHub issues
Before submitting a PR, verify:
- All tests pass (
go test ./...) - Code is formatted (
gofmt -s -w .) - No linter warnings (
go vet ./...) - Godoc comments are complete and end with periods
- Test coverage is maintained or improved
- Commit messages follow the format
- No unnecessary dependencies added
- Error handling is comprehensive
- Context cancellation is handled properly
Never commit sensitive data:
- Private keys (
.pemfiles) - Client IDs or secrets
- Access tokens
- API credentials
The library requires ECDSA P-256 (ES256) private keys in PEM format:
- Supported formats:
EC PRIVATE KEYorPRIVATE KEY(PKCS8) - Required curve: P-256 (secp256r1)
- File permissions: Recommend
0600for private key files
JWT assertions must include:
- Issuer (
iss): Client ID - Subject (
sub): Client ID - Audience (
aud):https://account.apple.com/auth/oauth2/v2/token - Expiration (
exp): Maximum 180 days from issuance - JWT ID (
jti): Unique identifier (UUID recommended)
- Load ECDSA P-256 private key from PEM file
- Generate JWT with required claims and sign with private key
- Create OAuth2 token source using client credentials flow with JWT assertion
- Token source automatically refreshes tokens as needed
The library uses Go 1.26's range-over-func iterators for pagination:
for page, err := range PageIterator(ctx, client, decoder, baseURL) {
if err != nil {
return err
}
// Process page data
}Benefits:
- Automatic pagination handling
- Early termination with
break - Context cancellation support
- Generic implementation for any paginated endpoint
- Fail fast: Return errors immediately for invalid inputs
- Context-aware: Check
ctx.Err()at function entry and in loops - Wrapped errors: Use
fmt.Errorfwith%wfor error wrapping - Descriptive messages: Include context in error messages
Current dependencies (see go.mod):
github.com/go-json-experiment/json- Modern JSON librarygithub.com/golang-jwt/jwt/v5- JWT token generation and parsinggithub.com/google/go-cmp- Test assertionsgithub.com/google/uuid- UUID generation for JWT IDsgolang.org/x/oauth2- OAuth2 client credentials flow
Dependabot is configured to automatically update dependencies daily at 11:00 Asia/Tokyo time.
Manual updates:
go get -u ./...
go mod tidy
go test ./... # Verify updates don't break testsThis project is licensed under the Apache License 2.0. See the LICENSE file for details.
All source files must include the Apache 2.0 license header with SPDX identifier:
// Copyright 2026 The abm Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0