Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
.git
.github
.cache
coverage
dist
bin
build
otelcol/bin
**/*.test
**/*.out
**/*.log
Expand Down
53 changes: 53 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ jobs:
version: latest
args: --timeout=5m

# otelcol/auth/sakauth is a separate Go module, so the root run above does
# not descend into it. It resolves the same root .golangci.yml.
- name: Run golangci-lint (sakauth module)
uses: golangci/golangci-lint-action@v8
with:
version: latest
working-directory: otelcol/auth/sakauth
args: --timeout=5m

- name: Check license headers in third_party
run: |
missing=$(find third_party -name "*.go" | xargs grep -rL "Copyright\|SPDX" 2>/dev/null)
Expand Down Expand Up @@ -180,6 +189,50 @@ jobs:
path: fleetint-${{ matrix.goos }}-${{ matrix.goarch }}
retention-days: 7

build_check_otelcol:
Comment thread
rsampaio marked this conversation as resolved.
name: Build Check (otelcol)
runs-on: ubuntu-latest
needs: [lint]
strategy:
matrix:
goarch: [amd64, arm64]
steps:
- name: Checkout code
uses: actions/checkout@v4
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with:
# This job installs and executes third-party tooling (the OTel
# Collector Builder and its transitive dependencies). Do not leave
# GITHUB_TOKEN in the git config where that tooling can read it.
persist-credentials: false

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}

- name: Install OTel Collector Builder
run: go install go.opentelemetry.io/collector/cmd/builder@v0.156.0
Comment thread
rsampaio marked this conversation as resolved.

- name: Build fleetint-otelcol
working-directory: otelcol
env:
GOOS: linux
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: 0
run: builder --config=otelcol-builder.yaml

# The sakauth unit and config tests run in the unit_test job, since
# `make test` now descends into the nested module. This step covers only
# the integration test, which needs the collector binary built above and
# self-skips without FLEETINT_OTELCOL_INTEGRATION=1.
# Only on amd64: the runner cannot execute arm64 test binaries.
- name: Run OTel gateway integration test
if: matrix.goarch == 'amd64'
working-directory: otelcol/auth/sakauth
env:
FLEETINT_OTELCOL_INTEGRATION: "1"
run: go test -race -run '^TestCollectorGatewayEndToEnd$' .

codeql:
name: CodeQL Analysis
runs-on: ubuntu-latest
Expand Down
27 changes: 27 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,21 @@ jobs:
fi
echo "EOF" >> "$GITHUB_OUTPUT"

- name: Prepare fleetint-otelcol image tags
id: otelcol_image
run: |
set -euo pipefail
VERSION="${{ steps.version.outputs.version }}"
OWNER_LC="$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')"
GHCR_IMAGE="ghcr.io/${OWNER_LC}/fleetint-otelcol"
echo "ghcr_image=${GHCR_IMAGE}" >> "$GITHUB_OUTPUT"
echo "tags<<EOF" >> "$GITHUB_OUTPUT"
echo "${GHCR_IMAGE}:${VERSION}" >> "$GITHUB_OUTPUT"
if [ "${{ steps.version.outputs.is_prerelease }}" = "false" ]; then
echo "${GHCR_IMAGE}:latest" >> "$GITHUB_OUTPUT"
fi
echo "EOF" >> "$GITHUB_OUTPUT"

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

Expand Down Expand Up @@ -124,6 +139,15 @@ jobs:
REVISION=${{ steps.version.outputs.commit }}
BUILD_TIMESTAMP=${{ steps.version.outputs.build_timestamp }}

- name: Build and push fleetint-otelcol image
uses: docker/build-push-action@v6
with:
context: .
file: ./otelcol/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.otelcol_image.outputs.tags }}

- name: Package Helm chart
run: |
helm package deployments/helm/fleet-intelligence-agent \
Expand Down Expand Up @@ -191,6 +215,9 @@ jobs:
echo "**fleet-intelligence-agent image tags:**" >> $GITHUB_STEP_SUMMARY
echo "${{ steps.agent_image_ghcr.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**fleetint-otelcol image tags:**" >> $GITHUB_STEP_SUMMARY
echo "${{ steps.otelcol_image.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Helm chart (GHCR OCI):**" >> $GITHUB_STEP_SUMMARY
echo "repo: oci://ghcr.io/${{ steps.agent_image_ghcr.outputs.owner_lc }}/charts" >> $GITHUB_STEP_SUMMARY
echo "chart: fleet-intelligence-agent:${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,4 @@ AGENTS.md

.worktrees/
docs/plans/
otelcol/bin/
24 changes: 20 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ ROOTDIR=$(dir $(abspath $(lastword $(MAKEFILE_LIST))))

BUILD_TIMESTAMP ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
VERSION ?= $(shell git describe --match 'v[0-9]*' --dirty='.m' --always)
REVISION=$(shell git rev-parse HEAD)$(shell if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi)
REVISION ?= $(shell git rev-parse HEAD)$(shell if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi)
PACKAGE=github.com/NVIDIA/fleet-intelligence-agent

# Nested Go module holding the OTel gateway SAK auth extension. It has its own
# go.mod, so root-module `./...` targets do not descend into it and it must be
# linted, vuln-checked and tested explicitly.
OTELCOL_MODULE ?= otelcol/auth/sakauth

ifneq "$(strip $(shell command -v $(GO) 2>/dev/null))" ""
GOOS ?= $(shell $(GO) env GOOS)
GOARCH ?= $(shell $(GO) env GOARCH)
Expand Down Expand Up @@ -118,7 +123,7 @@ docker-test: ## build test image and run tests in container
-t $(TEST_IMAGE) \
.
@echo "Running tests..."
@$(DOCKER) run --rm $(TEST_IMAGE)
@$(DOCKER) run --rm -e VERSION=$(VERSION) -e REVISION=$(REVISION) $(TEST_IMAGE)

# Specific target for fleetint (your main binary)
fleetint: bin/fleetint ## build fleetint binary
Expand All @@ -127,15 +132,18 @@ fleetint: bin/fleetint ## build fleetint binary
lint: ## run linting tools
@echo "Running linting..."
@if command -v $(GOLANGCI_LINT) >/dev/null 2>&1; then \
$(GOLANGCI_LINT) run ./...; \
$(GOLANGCI_LINT) run ./... && \
echo "Linting $(OTELCOL_MODULE)..." && \
( cd $(OTELCOL_MODULE) && $(GOLANGCI_LINT) run ./... ); \
else \
echo "golangci-lint not found, running basic checks..."; \
$(GOFMT) -l -s . | tee /tmp/gofmt.out; \
if [ -s /tmp/gofmt.out ]; then \
echo "Code formatting issues found. Run 'make fmt' to fix."; \
exit 1; \
fi; \
go vet ./...; \
go vet ./... && \
( cd $(OTELCOL_MODULE) && go vet ./... ); \
fi

fmt: ## format Go code
Expand All @@ -149,6 +157,12 @@ test: ## run tests with coverage
@$(GO) tool cover -html=coverage/coverage.out -o coverage/coverage.html
@echo "Coverage report generated: coverage/coverage.html"
@$(GO) tool cover -func=coverage/coverage.out | tail -1
@echo "Running tests in $(OTELCOL_MODULE)..."
# Coverage is not merged into coverage.out: it is a separate module and the
# codecov upload expects a single root profile.
# TestCollectorGatewayEndToEnd self-skips unless FLEETINT_OTELCOL_INTEGRATION=1
# and otelcol/bin/fleetint-otelcol exists; CI covers it in build_check_otelcol.
@( cd $(OTELCOL_MODULE) && $(GOTEST) $(GOFLAGS) -race ./... )

vuln: ## run vulnerability check
@echo "Running vulnerability check..."
Expand All @@ -157,6 +171,8 @@ vuln: ## run vulnerability check
$(GO) install golang.org/x/vuln/cmd/govulncheck@latest; \
fi
@govulncheck ./...
@echo "Running vulnerability check in $(OTELCOL_MODULE)..."
@( cd $(OTELCOL_MODULE) && govulncheck ./... )

clean: ## clean up binaries and build artifacts
@echo "Cleaning up..."
Expand Down
51 changes: 51 additions & 0 deletions cmd/fleetint/gateway_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
// 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 (
"testing"

pkgmetadata "github.com/NVIDIA/fleet-intelligence-sdk/pkg/metadata"
"github.com/stretchr/testify/require"

"github.com/NVIDIA/fleet-intelligence-agent/internal/config"
)

func TestConfigureHealthExporterFromEnvCollectorEndpoint(t *testing.T) {
t.Setenv("FLEETINT_COLLECTOR_ENDPOINT", "http://fleetint-otel-gateway:4318")
cfg := &config.Config{HealthExporter: &config.HealthExporterConfig{}}

require.NoError(t, configureHealthExporterFromEnv(cfg))
require.Equal(t, "http://fleetint-otel-gateway:4318", cfg.HealthExporter.CollectorEndpoint)
}

func TestConfigureHealthExporterFromEnvPreservesCollectorEndpointWhenUnset(t *testing.T) {
t.Setenv("FLEETINT_COLLECTOR_ENDPOINT", "")
cfg := &config.Config{HealthExporter: &config.HealthExporterConfig{
CollectorEndpoint: "https://collector.example",
}}

require.NoError(t, configureHealthExporterFromEnv(cfg))
require.Equal(t, "https://collector.example", cfg.HealthExporter.CollectorEndpoint)
}

func TestMaskMetadataValue(t *testing.T) {
const secret = "secret-token-value"

require.Equal(t, pkgmetadata.MaskToken(secret), maskMetadataValue(pkgmetadata.MetadataKeyToken, secret))
require.Equal(t, pkgmetadata.MaskToken(secret), maskMetadataValue("sak_token", secret))
require.Equal(t, secret, maskMetadataValue("backend_base_url", secret))
}
13 changes: 8 additions & 5 deletions cmd/fleetint/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,7 @@ func metadataCommand(cliContext *cli.Context) error {
log.Logger.Debugw("successfully read metadata")

for k, v := range metadata {
// Mask sensitive tokens (JWT and SAK)
if k == pkgmetadata.MetadataKeyToken || k == "sak_token" {
v = pkgmetadata.MaskToken(v)
}
fmt.Printf("%s: %s\n", k, v)
fmt.Printf("%s: %s\n", k, maskMetadataValue(k, v))
}

setKey := cliContext.String("set-key")
Expand Down Expand Up @@ -114,3 +110,10 @@ func metadataCommand(cliContext *cli.Context) error {
fmt.Printf("%s successfully updated metadata\n", cmdutil.CheckMark)
return nil
}

func maskMetadataValue(key, value string) string {
if key == pkgmetadata.MetadataKeyToken || key == "sak_token" {
return pkgmetadata.MaskToken(value)
}
return value
}
16 changes: 16 additions & 0 deletions cmd/fleetint/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"context"
"encoding/json"
"fmt"
"net/url"
"os"
"os/signal"
"path/filepath"
Expand Down Expand Up @@ -219,6 +220,21 @@ func configureHealthExporterFromEnv(cfg *config.Config) error {
return err
}

// OTel gateway collector mode: when set, metrics/logs are routed to the gateway
// instead of the backend directly. Enrollment remains independent for inventory
// and attestation; backend credentials are not forwarded to the gateway.
if val := os.Getenv("FLEETINT_COLLECTOR_ENDPOINT"); val != "" {
he.CollectorEndpoint = val
// Log only the host. The raw value is operator-supplied and may carry
// credentials in userinfo or a token in the path. Config.Validate
// rejects those, but it runs after this point.
host := "(unparseable)"
if parsed, err := url.Parse(val); err == nil && parsed.Host != "" {
host = parsed.Host
}
log.Logger.Infow("set OTel gateway collector endpoint from env", "collector_endpoint_host", host)
}

return nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ spec:
fieldPath: spec.nodeName
- name: IS_RUNTIME_K8S
value: "true"
{{- if .Values.otelGateway.enabled }}
- name: FLEETINT_COLLECTOR_ENDPOINT
value: {{ printf "http://%s-otel-gateway:%d" (include "fleet-intelligence-agent.fullname" .) (int .Values.otelGateway.service.port) | quote }}
{{- end }}
ports:
- name: http
containerPort: {{ .Values.ports.http }}
Expand Down
Loading
Loading