filewatcher

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 11, 2026 License: MIT Imports: 8 Imported by: 0

README

k8s-filewatcher

A Go library for watching files with proper support for Kubernetes ConfigMap and Secret volume updates.

Why This Library?

Kubernetes mounts ConfigMaps and Secrets using atomic symlink swaps via the ..data directory pattern. When a ConfigMap or Secret is updated, kubelet:

  1. Creates a new timestamped directory (e.g., ..data_tmp)
  2. Atomically renames it to ..data
  3. Deletes the old directory

This causes standard file watchers to miss updates or break. This library handles these symlink changes correctly using both fsnotify events and periodic polling for reliability.

Installation

go get github.com/hadrienk/k8s-filewatcher

Usage

Basic Example
package main

import (
    "context"
    "log"
    
    "github.com/hadrienk/k8s-filewatcher"
)

func main() {
    watcher, err := filewatcher.New("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt")
    if err != nil {
        log.Fatal(err)
    }

    ctx := context.Background()
    go watcher.Start(ctx)

    // Read current content
    caCert := watcher.Get()
    log.Printf("CA cert: %d bytes", len(caCert))
}
With Callback
watcher, err := filewatcher.New("/path/to/config",
    filewatcher.WithInterval(5*time.Second),
    filewatcher.WithOnChange(func(content []byte) {
        log.Printf("File changed: %d bytes", len(content))
        // Reload your configuration here
    }),
)
With File Info
content, info, err := watcher.GetFS()
if err != nil {
    log.Fatal(err)
}
log.Printf("File: %s, Size: %d, Modified: %v", 
    info.Name(), info.Size(), info.ModTime())

How It Works

The watcher uses a dual approach for maximum reliability:

  1. fsnotify events: Detects Write, Create, Chmod, and Remove events. When a Remove or Chmod event occurs (indicating a symlink swap), the watch is re-added to follow the new symlink target.

  2. Periodic polling: Every 10 seconds by default (configurable), the file is checked for changes. This catches any missed events and provides eventual consistency.

Thread safety is ensured using sync.RWMutex with read locks for Get() operations and write locks for updates.

API

Types
type Watcher struct { /* ... */ }
type Option func(*Watcher)
Functions
// New creates a new file watcher
func New(path string, opts ...Option) (*Watcher, error)

// Options
func WithInterval(d time.Duration) Option
func WithOnChange(fn func(content []byte)) Option
Methods
// Start begins watching (blocks until context cancelled)
func (w *Watcher) Start(ctx context.Context) error

// Get returns current file content (thread-safe)
func (w *Watcher) Get() []byte

// GetFS returns content and file info (thread-safe)
func (w *Watcher) GetFS() ([]byte, fs.FileInfo, error)

Testing

go test -v

The test suite includes:

  • Basic file watching
  • Callback invocation
  • Thread safety
  • Kubernetes symlink pattern simulation
  • Context cancellation

License

MIT License - see LICENSE file for details

Documentation

Overview

Package filewatcher provides a generic file watcher that handles Kubernetes ConfigMap and Secret volume symlink updates correctly.

Kubernetes mounts ConfigMaps and Secrets using atomic symlink swaps via the ..data directory pattern. This watcher detects those changes using both fsnotify events and periodic polling for reliability.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Option

type Option func(*Watcher)

func WithInterval

func WithInterval(d time.Duration) Option

func WithOnChange

func WithOnChange(fn func(content []byte)) Option

type Watcher

type Watcher struct {
	sync.RWMutex
	// contains filtered or unexported fields
}
Example
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	filewatcher "github.com/hadrienk/k8s-filewatcher"
)

func main() {
	watcher, err := filewatcher.New("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
		filewatcher.WithInterval(5*time.Second),
		filewatcher.WithOnChange(func(content []byte) {
			log.Printf("CA certificate reloaded, %d bytes", len(content))
		}),
	)
	if err != nil {
		log.Fatal(err)
	}

	ctx := context.Background()
	go func() { _ = watcher.Start(ctx) }()

	caCert := watcher.Get()
	fmt.Printf("Current CA cert size: %d bytes\n", len(caCert))
}

func New

func New(path string, opts ...Option) (*Watcher, error)

func (*Watcher) Get

func (w *Watcher) Get() []byte

func (*Watcher) GetFS

func (w *Watcher) GetFS() ([]byte, fs.FileInfo, error)

func (*Watcher) Start

func (w *Watcher) Start(ctx context.Context) error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL