azure/acn-go-platform-abstraction
Go platform/OS-specific code patterns for cross-platform projects. Use when writing or reviewing code that must work on multiple OSes (Linux, Windows), when you see noop stubs in _<os>.go files, runtime.GOOS checks, or OS-specific build tags. Supersedes generic Go guidance on build tags and OS-specific code. Trigger on any code that branches by operating system.
npx skills add https://github.com/Azure/azure-container-networking --skill acn-go-platform-abstraction
Persona: You are a Go platform engineer who believes that if you need a noop stub, your abstraction is in the wrong place. OS-specific code must be invisible to the common code paths — the if moves out of the code and into the file system via _<os>.go files.
Modes:
_<os>.go files, (2) find runtime.GOOS checks in non-test code, (3) find OS-specific imports in common files.> Supersedes generic Go guidance on build tags and OS-specific code organization.
Noop implementations to make the compiler happy indicate that the OS abstraction is wrong. Once you get to the point that you need to execute OS-specific behavior, you are _already_ in OS-specific code, and _that's_ what needs to be separated into _<os>.go files.
addDefaultRoute on Linux because it's only needed on Windows, the abstraction boundary is too low. Move it up to where the calling code diverges.runtime.GOOS — any explicit GOOS check or noop stubbing of an interface in a *_<os>.go file indicates the abstraction is in the wrong place.config_<os>.go — don't hardcode paths or values that differ by OS in common code.// ❌ BAD — k8sSwiftV2_linux.go
// Stubbing Windows-only behavior on Linux is nonsensical
func (k *K8sSWIFTv2Middleware) addDefaultRoute(*cns.PodIpInfo, string) {}
// ❌ BAD — hnsclient_linux.go
// "hnsclient_linux" is nonsensical — HNS is a Windows concept
type linuxHNSClient struct{}
func (l *linuxHNSClient) DeleteEndpoint(id string) error { return nil }
This forces the reader to mentally track: "if GOOS=linux then this method call is noop." The if has moved out of code and into your head — that's worse, not better.
Find the last point of common code before OS divergence. That function gets OS-specific implementations:
// ❌ BAD — common code calls OS-specific stubs
func processEndpoint(info *EndpointInfo) error {
// ... common logic ...
if err := addDefaultRoute(info); err != nil { // noop on Linux!
return err
}
if err := configureHNS(info); err != nil { // noop on Linux!
return err
}
return nil
}
// ✅ GOOD — split at the last common boundary
// endpoint.go (common)
func processEndpoint(info *EndpointInfo) error {
// ... common logic ...
return configurePlatformNetworking(info)
}
// endpoint_linux.go
func configurePlatformNetworking(info *EndpointInfo) error {
return nil // nothing platform-specific needed
}
// endpoint_windows.go
func configurePlatformNetworking(info *EndpointInfo) error {
if err := addDefaultRoute(info); err != nil {
return err
}
return configureHNS(info)
}
The key insight: addDefaultRoute shouldn't exist in the common abstraction at all. The question is "what platform-specific networking setup is needed?" — and on Linux, the answer is "nothing."
When you encounter OS-specific behavior:
_linux.go and _windows.go versions of that function.| Signal | Problem | Fix |
| --- | --- | --- |
| Noop stub in _<os>.go | Abstraction too low | Move boundary up to caller |
| runtime.GOOS check | No abstraction at all | Extract to _<os>.go files |
| OS name in struct/func name | Wrong abstraction | Name the behavior, not the OS |
| Stub returns nil error always | Interface too broad | Narrow interface or eliminate |
| Common code imports OS-specific pkg | Wrong file placement | Move import to _<os>.go file |
// ❌ BAD — hardcoded paths with OS conditionals
const statePath = "/var/run/azure-cns"
// ✅ GOOD — config_<os>.go files
// config_linux.go
const defaultStatePath = "/var/run/azure-cns"
// config_windows.go
const defaultStatePath = `C:\ProgramData\azure-cns`
// config.go (common)
func statePath(configured string) string {
if configured != "" {
return configured
}
return defaultStatePath
}
// ❌ BAD — shelling out to PowerShell for registry/service operations
func setRegistryValue(key, value string) error {
cmd := exec.Command("powershell", "-Command",
fmt.Sprintf(`Set-ItemProperty -Path "%s" -Name "%s" -Value "%s"`, path, key, value))
return cmd.Run()
}
// ✅ GOOD — native Go Windows APIs
func setRegistryValue(key registry.Key, name string, value uint32) error {
return key.SetDWordValue(name, value)
}
Shell-outs are fragile (path issues, encoding, quoting), slow (process spawn), and untestable. Use golang.org/x/sys/windows/registry, golang.org/x/sys/windows/svc, etc. These are type-safe, testable with mocks, and context-aware.
| Mistake | Fix |
| --- | --- |
| func newLinuxRegistryClient returning windowsRegistryClient interface | Name the behavior, not the OS — this is nonsensical |
| Stubbing Windows-only behavior in Linux structs | The calling code is OS-specific — split there instead |
| hnsclient_linux.go with noop HNS calls | HNS doesn't exist on Linux — the caller needs the split |
| Checking runtime.GOOS to branch behavior | Use _<os>.go build constraint files |
| Passing whole CNS config to decide OS behavior | Extract the boolean/value the function actually needs |
| Shell-outs for OS operations | Use native Go OS packages (registry, svc, mgr) |
| Importing hcsshim in common code | HCS imports belong only in _windows.go files |
acn-go-design-boundaries skill for behavioral config vs scenario configacn-go-interfaces-dependencies skill for interface design at the consumersamber/cc-skills-golang@golang-code-style for general file organizationTake azure/acn-go-platform-abstraction from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.