Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
6 changes: 6 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@ reviews:
profile: chill
auto_review:
enabled: true
path_filters:
- "!api/gen/**" # Ignore generated protobuf bindings
- "!api/**/zz_generated.*" # Ignore generated deepcopy and conversion code
- "!client-go/client/**" # Ignore generated clientset
- "!client-go/listers/**" # Ignore generated listers
- "!client-go/informers/**" # Ignore generated informers
16 changes: 16 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# ==============================================================================
# GIT ATTRIBUTES
# ==============================================================================
# Use 'linguist-generated=true' to hide generated code from GitHub PR diffs.
# ==============================================================================

# Hide Kubernetes generated helpers (DeepCopy, Defaults, Conversions, OpenAPI, etc)
zz_generated.*.go linguist-generated=true

# Hide generated Protobuf Go bindings
*.pb.go linguist-generated=true

# Hide the generated client library
client-go/client/** linguist-generated=true
client-go/listers/** linguist-generated=true
client-go/informers/** linguist-generated=true
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*.so
*.dylib
api/bin/*
client-go/bin/*

# Test binary, built with `go test -c`
*.test
Expand Down
2 changes: 2 additions & 0 deletions .versions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ go_tools:
setup_envtest: 'latest'
goimports: 'v0.30.0'
crane: 'v0.20.2'
goverter: 'v1.9.2'
kubernetes_code_gen: 'v0.34.1'

# Protocol Buffers / gRPC
protobuf:
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ license-headers-lint: ## Check license headers in source files
-ignore '**/*.toml' \
-ignore '**/*lock.hcl' \
-ignore '**/*pb2*' \
-ignore '**/hack/**' \
.

# Check go.mod files for proper replace directives
Expand Down
149 changes: 120 additions & 29 deletions api/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,32 @@
# Configuration
# ==============================================================================

# Helpers for string manipulation
COMMA := ,
EMPTY :=
SPACE := $(EMPTY) $(EMPTY)

# Modules
MODULE_NAME := github.com/nvidia/nvsentinel/api

# API Versions (space-separated)
API_VERSIONS := device/v1alpha1

# Helper for tools that need comma-separated Group/Versions
API_VERSIONS_COMMA := $(subst $(SPACE),$(COMMA),$(API_VERSIONS))

# Helper for tools that need full package paths
API_PKGS := $(foreach version,$(API_VERSIONS),$(MODULE_NAME)/$(version))

#Helper for tools that need comma-separated package paths
API_PKGS_COMMA := $(subst $(SPACE),$(COMMA),$(API_PKGS))

# Common
BOILERPLATE := hack/boilerplate.go.txt

# Directories
PROTO_DIR := proto
GEN_DIR := gen/go
PROTO_DIR := proto
GEN_DIR := gen/go

# Shell setup
SHELL = /usr/bin/env bash -o pipefail
Expand All @@ -29,9 +52,17 @@ LOCALBIN ?= $(shell pwd)/bin
$(LOCALBIN):
mkdir -p $(LOCALBIN)

# Go Version
GO_VERSION := $(shell grep "^toolchain " go.mod | awk '{print $$2}')
ifeq ($(GO_VERSION),)
GO_VERSION := $(shell go env GOVERSION)
endif

# Tool Binaries
PROTOC_GEN_GO ?= $(LOCALBIN)/protoc-gen-go
PROTOC_GEN_GO_GRPC ?= $(LOCALBIN)/protoc-gen-go-grpc
PROTOC_GEN_GO ?= $(LOCALBIN)/protoc-gen-go
PROTOC_GEN_GO_GRPC ?= $(LOCALBIN)/protoc-gen-go-grpc
DEEPCOPY_GEN ?= $(LOCALBIN)/deepcopy-gen
GOVERTER ?= $(LOCALBIN)/goverter

# Tool Versions (load from ../.versions.yaml)
# Requires yq to be installed: brew install yq (macOS) or see https://github.com/mikefarah/yq
Expand All @@ -42,15 +73,17 @@ endif

VERSIONS_FILE := ../.versions.yaml

PROTOC_GEN_GO_VERSION := $(shell $(YQ) '.protobuf.protoc_gen_go' $(VERSIONS_FILE))
PROTOC_GEN_GO_GRPC_VERSION := $(shell $(YQ) '.protobuf.protoc_gen_go_grpc' $(VERSIONS_FILE))
PROTOC_GEN_GO_VERSION := $(shell $(YQ) '.protobuf.protoc_gen_go' $(VERSIONS_FILE))
PROTOC_GEN_GO_GRPC_VERSION := $(shell $(YQ) '.protobuf.protoc_gen_go_grpc' $(VERSIONS_FILE))
KUBERNETES_CODEGEN_VERSION := $(shell $(YQ) '.go_tools.kubernetes_code_gen' $(VERSIONS_FILE))
GOVERTER_VERSION := $(shell $(YQ) '.go_tools.goverter' $(VERSIONS_FILE))

# ==============================================================================
# Targets
# ==============================================================================

.PHONY: all
all: protos-generate build
all: code-gen test build ## Run code generation, compile all code, and execute tests.

##@ General

Expand All @@ -60,42 +93,100 @@ help: ## Display this help.

##@ Development

.PHONY: code-gen
code-gen: install-tools protos-generate deepcopy-gen conversion-gen ## Run all code generation targets.
@echo "Synchronizing module dependencies..."
go mod tidy

.PHONY: build
build:
build: code-gen ## Compile all Go code after generation to verify type safety.
@echo "Compiling..."
go build -v ./...

.PHONY: test
test: code-gen ## Run unit tests and generate coverage after code generation.
@echo "Testing..."
go test -v $$(go list ./... | grep -v /gen/) -coverprofile cover.out

.PHONY: protos-generate
protos-generate: $(PROTOC_GEN_GO) $(PROTOC_GEN_GO_GRPC) protos-clean ## Generate Go code from Proto definitions.
@echo "Generating Proto code..."
protos-generate: $(PROTOC_GEN_GO) $(PROTOC_GEN_GO_GRPC) clean-protos ## Generate Protobuf Go bindings.
@echo "Generating protobuf bindings for $(API_VERSIONS_COMMA)..."
@mkdir -p $(GEN_DIR)
cd proto && protoc \
-I . \
-I ../$(THIRD_PARTY_DIR) \
--plugin="protoc-gen-go=$(PROTOC_GEN_GO)" \
--plugin="protoc-gen-go-grpc=$(PROTOC_GEN_GO_GRPC)" \
--go_out=../$(GEN_DIR) \
--go_opt=paths=source_relative \
--go-grpc_out=../$(GEN_DIR) \
--go-grpc_opt=paths=source_relative \
device/v1alpha1/*.proto
@echo "Cleaning up dependencies..."
go mod tidy
@echo "Done."

.PHONY: protos-clean
protos-clean: ## Remove generated code.
@echo "Cleaning old generated Proto code..."
@$(foreach version,$(API_VERSIONS), \
cd $(PROTO_DIR) && protoc \
-I . \
--plugin="protoc-gen-go=$(PROTOC_GEN_GO)" \
--plugin="protoc-gen-go-grpc=$(PROTOC_GEN_GO_GRPC)" \
--go_out="../$(GEN_DIR)" \
--go_opt=paths="source_relative" \
--go-grpc_out="../$(GEN_DIR)" \
--go-grpc_opt=paths="source_relative" \
$(version)/*.proto; \
)

.PHONY: deepcopy-gen
deepcopy-gen: $(DEEPCOPY_GEN) clean-deepcopy ## Generate deepcopy code.
@echo "Generating deepcopy code for $(API_VERSIONS_COMMA)..."
$(DEEPCOPY_GEN) \
--bounding-dirs="$(API_PKGS_COMMA)" \
--go-header-file="$(BOILERPLATE)" \
--output-file="zz_generated.deepcopy.go" \
$(API_PKGS)

.PHONY: conversion-gen
conversion-gen: $(GOVERTER) clean-conversion ## Generate conversion code.
@echo "Generating conversion code for $(API_VERSIONS_COMMA)..."
@$(foreach version,$(API_VERSIONS), \
$(GOVERTER) gen ./$(version); \
)

.PHONY: clean
clean: clean-protos clean-conversion clean-deepcopy ## Remove all generated code.

.PHONY: purge
purge: clean clean-bin ## Remove all generated code and downloaded tools.

.PHONY: clean-protos
clean-protos: ## Remove generated protobuf Go bindings.
@echo "Removing generated protobuf Go bindings..."
rm -rf $(GEN_DIR)
@echo "Done."

.PHONY: clean-deepcopy
clean-deepcopy: ## Remove generated deepcopy code.
@echo "Removing generated deepcopy code..."
find . -name "zz_generated.deepcopy.go" -delete

.PHONY: clean-conversion
clean-conversion: ## Remove generated conversion code.
@echo "Removing generated conversion code..."
find . -name "zz_generated.conversion.go" -delete

.PHONY: clean-bin
clean-bin: ## Remove all downloaded tools in localbin.
@echo "Removing downloaded build tools from $(LOCALBIN)..."
rm -rf $(LOCALBIN)

##@ Build Dependencies

.PHONY: install-tools
install-tools: install-tools-echo $(PROTOC_GEN_GO) $(PROTOC_GEN_GO_GRPC) $(DEEPCOPY_GEN) $(GOVERTER) ## Ensure all necessary build tools are downloaded and installed.

.PHONY: install-tools-echo
install-tools-echo:
@echo "Installing dependencies..."

$(PROTOC_GEN_GO):
$(call go-install-tool,$(PROTOC_GEN_GO),google.golang.org/protobuf/cmd/protoc-gen-go,$(PROTOC_GEN_GO_VERSION))

$(PROTOC_GEN_GO_GRPC):
$(call go-install-tool,$(PROTOC_GEN_GO_GRPC),google.golang.org/grpc/cmd/protoc-gen-go-grpc,$(PROTOC_GEN_GO_GRPC_VERSION))

$(DEEPCOPY_GEN):
$(call go-install-tool,$(DEEPCOPY_GEN),k8s.io/code-generator/cmd/deepcopy-gen,$(KUBERNETES_CODEGEN_VERSION))

$(GOVERTER):
$(call go-install-tool,$(GOVERTER),github.com/jmattheis/goverter/cmd/goverter,$(GOVERTER_VERSION))

# go-install-tool macro
# $1 - target path with name of binary
# $2 - package url which can be installed
Expand All @@ -107,7 +198,7 @@ mkdir -p $(LOCALBIN); \
package=$(2)@$(3) ;\
echo "Downloading $${package}" ;\
rm -f $(1) || true ;\
GOBIN=$(LOCALBIN) go install $${package} ;\
GOBIN=$(LOCALBIN) GOTOOLCHAIN=$(GO_VERSION) go install $${package} ;\
mv $(1) $(1)-$(3) ;\
} ;\
ln -sf $(1)-$(3) $(1)
Expand Down
133 changes: 133 additions & 0 deletions api/device/v1alpha1/converter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
//
// 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 v1alpha1

import (
pb "github.com/nvidia/nvsentinel/api/gen/go/device/v1alpha1"
"google.golang.org/protobuf/types/known/timestamppb"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// Converter is the interface used to generate type conversion methods between the
// Kubernetes Resource Model structs and the Protobuf message structs.
//
// goverter:converter
// goverter:output:file ./zz_generated.conversion.go
// goverter:extend FromProtobufTypeMeta FromProtobufListTypeMeta FromProtobufTimestamp ToProtobufTimestamp
// goverter:useZeroValueOnPointerInconsistency
type Converter interface {
// FromProtobuf converts a protobuf Gpu message into a GPU object.
//
// goverter:map . TypeMeta | FromProtobufTypeMeta
// goverter:map Metadata ObjectMeta
FromProtobuf(source *pb.Gpu) GPU

// ToProtobuf converts a GPU object into a protobuf Gpu message.
//
// goverter:map ObjectMeta Metadata
// goverter:ignore state sizeCache unknownFields
ToProtobuf(source GPU) *pb.Gpu

// FromProtobufList converts a protobuf GpuList message into a GPUList object.
//
// goverter:map . TypeMeta | FromProtobufListTypeMeta
// goverter:map Metadata ListMeta
FromProtobufList(source *pb.GpuList) *GPUList

// ToProtobufList converts a GPUList object into a protobuf GpuList message.
//
// goverter:map ListMeta Metadata
// goverter:ignore state sizeCache unknownFields
ToProtobufList(source *GPUList) *pb.GpuList

// FromProtobufObjectMeta converts a protobuf ObjectMeta into a metav1.ObjectMeta object.
//
// goverter:ignoreMissing
FromProtobufObjectMeta(source *pb.ObjectMeta) metav1.ObjectMeta

// ToProtobufObjectMeta converts a metav1.ObjectMeta into a protobuf Object message.
//
// goverter:ignore state sizeCache unknownFields
ToProtobufObjectMeta(source metav1.ObjectMeta) *pb.ObjectMeta

// FromProtobufListMeta converts a protobuf ListMeta into a metav1.ListMeta object.
//
// goverter:ignore SelfLink Continue RemainingItemCount
FromProtobufListMeta(source *pb.ListMeta) metav1.ListMeta

// ToProtobufListMeta converts a metav1.ListMeta into a protobuf ListMeta message.
//
// goverter:ignore state sizeCache unknownFields
ToProtobufListMeta(source metav1.ListMeta) *pb.ListMeta

// FromProtobufSpec converts a protobuf GpuSpec message into a GPUSpec object.
//
// goverter:map Uuid UUID
FromProtobufSpec(source *pb.GpuSpec) GPUSpec

// ToProtobufSpec converts a GPUSpec object into a protobuf GpuSpec message.
//
// goverter:map UUID Uuid
// goverter:ignore state sizeCache unknownFields
ToProtobufSpec(source GPUSpec) *pb.GpuSpec

// FromProtobufStatus converts a protobuf GpuStatus message into a GPUStatus object.
FromProtobufStatus(source *pb.GpuStatus) GPUStatus

// ToProtobufStatus converts a GPUStatus object into a protobuf GpuStatus message.
//
// goverter:ignore state sizeCache unknownFields
ToProtobufStatus(source GPUStatus) *pb.GpuStatus

// FromProtobufCondition converts a protobuf Condition message into a metav1.Condition object.
//
// goverter:ignore ObservedGeneration
// Note: ObservedGeneration is specific to k8s and not found in the protobuf Condition message.
FromProtobufCondition(source *pb.Condition) metav1.Condition

// ToProtobufCondition converts a metav1.Condition object into a protobuf Condition message.
//
// goverter:ignore state sizeCache unknownFields
ToProtobufCondition(source metav1.Condition) *pb.Condition
}

// FromProtobufTypeMeta generates the standard TypeMeta for the root GPU resource.
func FromProtobufTypeMeta(_ *pb.Gpu) metav1.TypeMeta {
return metav1.TypeMeta{
Kind: "GPU",
APIVersion: SchemeGroupVersion.String(),
}
}

// FromProtobufListTypeMeta generates the standard TypeMeta for the GPUList resource.
func FromProtobufListTypeMeta(_ *pb.GpuList) metav1.TypeMeta {
return metav1.TypeMeta{
Kind: "GPUList",
APIVersion: SchemeGroupVersion.String(),
}
}

// FromProtobufTimestamp converts a protobuf Timestamp message to a metav1.Time.
func FromProtobufTimestamp(source *timestamppb.Timestamp) metav1.Time {
if source == nil {
return metav1.Time{}
}
return metav1.NewTime(source.AsTime())
}

// ToProtobufTimestamp converts a metav1.Time to a protobuf Timestamp message.
func ToProtobufTimestamp(source metav1.Time) *timestamppb.Timestamp {
return timestamppb.New(source.Time)
}
Loading
Loading