Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
036f215
feat: implement foundation orchestrator, CLI carriage, and structured…
karthunni Jun 17, 2026
70d2b55
build: add copyright license headers to go files
karthunni Jun 17, 2026
105cfdc
refactor: change logging and cli harness
karthunni Jun 18, 2026
051ded3
refactor(main): use slog for CLI entrypoint logging
karthunni Jun 22, 2026
69eb392
refactor: resolve logger data races and remove global hijacking
karthunni Jun 22, 2026
f6b49d0
feat: log errors for skipped unsupported prometheus operator resources
karthunni Jun 22, 2026
c0959e9
feat: implement nil pointer safety checks
karthunni Jun 23, 2026
eb2594a
refactor: restructure parsing checks
karthunni Jun 23, 2026
7aaa3ad
fix: kubernetes resource validation
karthunni Jun 23, 2026
37e8b5e
fix: make logger private and parser error pipeline
karthunni Jun 23, 2026
ffe27fd
fix: default streams and print errors
karthunni Jun 23, 2026
4a78f0f
fix: made resource level map thread-safe (future proofing)
karthunni Jun 23, 2026
e5e93ac
fix: namespace default in get
karthunni Jun 23, 2026
a36ffaa
fix: more defensive initialization
karthunni Jun 24, 2026
e9b4758
fix: remove unnecessary test
karthunni Jun 24, 2026
c17f8eb
fix: remove defaulting comment and slog resolve
karthunni Jun 24, 2026
b380a49
fix: add skipped outcome in report/logs
karthunni Jun 24, 2026
a8ccb1f
fix: hidden directory edge case
karthunni Jun 24, 2026
fc9d074
feat: CLI support for multiple files and strict arguments
karthunni Jun 24, 2026
bdc0f31
fix: process List inputs
karthunni Jun 24, 2026
c5440d0
fix: implement consistent parse errors
karthunni Jun 24, 2026
46489b6
fix: cache initialization and cli suggestion
karthunni Jun 24, 2026
a17481a
fix: print summary in main
karthunni Jun 24, 2026
a673119
fix: output writing in CLI
karthunni Jun 24, 2026
9966902
fix: print manifests only if successful
karthunni Jun 24, 2026
eebdbbe
fix: remove custom log levels
karthunni Jun 26, 2026
627d1d0
fix: lint errors
karthunni Jun 26, 2026
ad15fd3
feat: readme added
karthunni Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions cmd/gmp-migrate/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright 2024 Google LLC
#
# 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.

FROM --platform=$BUILDPLATFORM google-go.pkg.dev/golang:1.26.4@sha256:3444149d0a7e3f7cfb9c2db65f0f75676fe6ad04de3ce72674efb120c08dd1c1 AS buildbase
ARG TARGETOS
ARG TARGETARCH
ARG BUILDARCH
WORKDIR /app
COPY charts/values.global.yaml charts/values.global.yaml
COPY go.mod go.mod
COPY go.sum go.sum
COPY tools tools
# Copy the Go vendor directory only if it exists. Vendor folder will automatically
# cause 'go build' to use -mod=vendor flag (otherwise -mod=mod is used).
COPY vendor* vendor
COPY cmd cmd
COPY pkg pkg

ENV GOEXPERIMENT=boringcrypto
ENV CGO_ENABLED=1
ENV GOFIPS140=off
ENV GOTOOLCHAIN=local
ENV GOOS=${TARGETOS}
ENV GOARCH=${TARGETARCH}
RUN if [ "${TARGETARCH}" = "arm64" ] && [ "${BUILDARCH}" != "arm64" ]; then \
apt-get update && apt-get install -y --no-install-recommends \
gcc-aarch64-linux-gnu libc6-dev-arm64-cross; \
export CC=aarch64-linux-gnu-gcc; \
elif [ "${TARGETARCH}" = "amd64" ] && [ "${BUILDARCH}" != "amd64" ]; then \
apt-get update && apt-get install -y --no-install-recommends \
gcc-x86-64-linux-gnu libc6-dev-amd64-cross; \
export CC=x86_64-linux-gnu-gcc; \
fi && \
GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build \
-ldflags="-X github.com/prometheus/common/version.Version=$(cat charts/values.global.yaml | go tool -modfile="tools/go.mod" yq '.version' ) \
-X github.com/prometheus/common/version.BuildDate=$(date --iso-8601=seconds)" \
Comment thread
bernot-dev marked this conversation as resolved.
-o gmp-migrate \
cmd/gmp-migrate/*.go
Comment thread
karthunni marked this conversation as resolved.


FROM gke.gcr.io/gke-distroless/libc:gke_distroless_20260307.00_p0@sha256:d5c073079125b887158bb1dd0ee4da49b39a08203c3c96124ee310962dd5aae2
COPY --from=buildbase /app/gmp-migrate /bin/gmp-migrate
ENTRYPOINT ["/bin/gmp-migrate"]
55 changes: 55 additions & 0 deletions cmd/gmp-migrate/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright 2026 Google LLC
//
// 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.

package main

import (
"flag"
"fmt"
"os"

"github.com/GoogleCloudPlatform/prometheus-engine/pkg/migrate"
)

func main() {
var inputFile string
flag.StringVar(&inputFile, "file", "", "Input source (YAML file, directory, or '-' for stdin) (Required)")
flag.StringVar(&inputFile, "f", "", "Input source (YAML file, directory, or '-' for stdin) (Required)")

flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
fmt.Fprint(os.Stderr, "Migrate Prometheus Operator configurations to Google Managed Prometheus (GMP).\n\n")
flag.PrintDefaults()
}
flag.Parse()
Comment thread
karthunni marked this conversation as resolved.

if inputFile == "" {
Comment thread
karthunni marked this conversation as resolved.
Outdated
fmt.Fprintln(os.Stderr, "[ERROR] Flag -f / --file is required.")
Comment thread
bernot-dev marked this conversation as resolved.
Outdated
flag.Usage()
os.Exit(1)
}

migrator := migrate.NewMigrator()
report, err := migrator.Run(inputFile)
if err != nil {
fmt.Fprintf(os.Stderr, "[ERROR] Migration failed: %v\n", err)
os.Exit(1)
}
Comment thread
karthunni marked this conversation as resolved.

// If any resource failed to migrate, exit with a non-zero code.
if report.FailedCount > 0 {
fmt.Fprintf(os.Stderr, "\n[ERROR] Migration completed with %d failures.\n", report.FailedCount)
os.Exit(1)
}
}
10 changes: 6 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ require (
sigs.k8s.io/controller-runtime v0.18.7
)

require github.com/efficientgo/e2e v0.14.1-0.20230710114240-c316eb95ae5b
require (
github.com/efficientgo/e2e v0.14.1-0.20230710114240-c316eb95ae5b
sigs.k8s.io/yaml v1.6.0
)

require (
cloud.google.com/go/auth v0.16.5 // indirect
Expand Down Expand Up @@ -83,7 +86,7 @@ require (
github.com/edsrzf/mmap-go v1.2.0 // indirect
github.com/efficientgo/core v1.0.0-rc.3 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/evanphx/json-patch v5.9.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect
github.com/fatih/color v1.18.0 // indirect
Expand Down Expand Up @@ -114,7 +117,7 @@ require (
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/imdario/mergo v0.3.6 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
Expand Down Expand Up @@ -176,7 +179,6 @@ require (
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)

replace (
Expand Down
8 changes: 4 additions & 4 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

147 changes: 147 additions & 0 deletions pkg/migrate/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright 2026 Google LLC
//
// 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.

package migrate

import (
"context"
"fmt"
"io"
"log/slog"
"strings"
"sync"
)

// LevelSuccess defines a custom slog.Level for Success (standard Info is 0, Warn is 4).
const LevelSuccess slog.Level = slog.LevelInfo + 1 // Level 1

// ConsoleHandler is a thread-safe slog.Handler that formats logs for the console (Stderr)
// and tracks the highest log level seen per resource (for statistics).
type ConsoleHandler struct {
mu sync.Mutex
out io.Writer
resourceLevels map[string]slog.Level
attrs []slog.Attr
}
Comment thread
karthunni marked this conversation as resolved.

// NewConsoleHandler creates a new ConsoleHandler.
func NewConsoleHandler(out io.Writer) *ConsoleHandler {
return &ConsoleHandler{
out: out,
resourceLevels: make(map[string]slog.Level),
}
}
Comment thread
karthunni marked this conversation as resolved.
Comment thread
karthunni marked this conversation as resolved.

func (h *ConsoleHandler) Enabled(_ context.Context, _ slog.Level) bool {
return true // Log everything
}

func (h *ConsoleHandler) Handle(_ context.Context, r slog.Record) error {
h.mu.Lock()
defer h.mu.Unlock()
Comment thread
karthunni marked this conversation as resolved.
Outdated

var kind, namespace, name, file string
var extraAttrs []string

// Helper to process and categorize attributes
processAttr := func(a slog.Attr) {
switch a.Key {
case "kind":
kind = a.Value.String()
case "namespace":
namespace = a.Value.String()
case "name":
name = a.Value.String()
case "file":
file = a.Value.String()
default:
// Collect all other attributes to print at the end of the line
extraAttrs = append(extraAttrs, fmt.Sprintf("%s=%v", a.Key, a.Value.Any()))
}
}
Comment thread
karthunni marked this conversation as resolved.

// Extract attributes bound to the logger instance
for _, a := range h.attrs {
processAttr(a)
}

// Extract attributes passed in the individual log call
r.Attrs(func(a slog.Attr) bool {
processAttr(a)
return true
})

// Map slog.Level to string for console output.
var levelStr string
switch r.Level {
case slog.LevelDebug:
levelStr = "DEBUG"
case slog.LevelInfo:
levelStr = "INFO"
case LevelSuccess:
levelStr = "SUCCESS"
case slog.LevelWarn:
levelStr = "WARNING"
case slog.LevelError:
levelStr = "ERROR"
default:
levelStr = r.Level.String()
}

// Format prefix cleanly
var prefix string
if file != "" {
prefix = fmt.Sprintf("[%s] ", file)
} else if kind != "" && name != "" {
prefix = fmt.Sprintf("[%s:%s/%s] ", kind, namespace, name)
}

// 1. Write formatted log to Stderr (console), appending extra attributes if any.
var suffix string
if len(extraAttrs) > 0 {
suffix = " " + strings.Join(extraAttrs, " ")
}
consoleLine := fmt.Sprintf("[%s] %s%s%s\n", levelStr, prefix, r.Message, suffix)
if _, err := io.WriteString(h.out, consoleLine); err != nil {
return err
}

// 2. Track the highest log level seen for this resource (for final report)
if kind != "" && name != "" {
key := fmt.Sprintf("%s/%s/%s", kind, namespace, name)
if r.Level > h.resourceLevels[key] {
h.resourceLevels[key] = r.Level
}
} else if file != "" {
// Track file-level log severity under the file path key
if r.Level > h.resourceLevels[file] {
h.resourceLevels[file] = r.Level
}
}
Comment thread
karthunni marked this conversation as resolved.
Outdated
Comment thread
karthunni marked this conversation as resolved.
Comment thread
karthunni marked this conversation as resolved.

return nil
}

func (h *ConsoleHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
newAttrs := append(h.attrs, attrs...)
return &ConsoleHandler{
out: h.out,
resourceLevels: h.resourceLevels,
attrs: newAttrs,
}
}
Comment thread
karthunni marked this conversation as resolved.
Comment thread
karthunni marked this conversation as resolved.
Comment thread
karthunni marked this conversation as resolved.

func (h *ConsoleHandler) WithGroup(_ string) slog.Handler {
return h
}
Comment thread
karthunni marked this conversation as resolved.
Loading
Loading