Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b28f5db
Issue 40: Unit-test for CRD
mrajagopal Aug 14, 2024
b1f649c
Issue 40: Unit-test for CRD
mrajagopal Aug 20, 2024
ee263e6
Merge branch 'main' into mrajagopal-unit-tests
mrajagopal Aug 15, 2025
7bb7833
feat: Create unit tests for NIC, NIM, NGF, and NGX
mrajagopal Sep 3, 2025
29de4ac
Merge branch 'main' into mrajagopal-unit-tests
mrajagopal Sep 3, 2025
33dabbd
Chore: Update to reflect changes in return parameters
mrajagopal Sep 3, 2025
d53e7ff
chore: replace deprecated routine filepath.HasPrefix with strings.Has…
mrajagopal Sep 3, 2025
2d9225d
Update pkg/jobs/ngf_job_list_test.go
mrajagopal Sep 4, 2025
ecb3c62
Update pkg/jobs/ngf_job_list_test.go
mrajagopal Sep 4, 2025
6d7b09d
Update pkg/data_collector/data_collector.go
mrajagopal Sep 4, 2025
6ef2f3e
Update pkg/jobs/nic_job_list_test.go
mrajagopal Sep 4, 2025
e124a26
Update pkg/jobs/job_test.go
mrajagopal Sep 4, 2025
1922e67
Update pkg/jobs/nic_job_list_test.go
mrajagopal Sep 4, 2025
2c2bd5c
Update pkg/jobs/common_job_list_test.go
mrajagopal Sep 4, 2025
3170f31
Fix: Address issues from common_job_list tests
mrajagopal Sep 15, 2025
d954b2e
Fix: Implement a mock data_collector
mrajagopal Sep 15, 2025
5e2bd87
Fix: Use the mock data_collector in various tests
mrajagopal Sep 15, 2025
6eef435
Fix merge-conflicts from main onto branch
mrajagopal Oct 13, 2025
625c0be
Fix: merge conflict from main
mrajagopal Oct 13, 2025
8e5c792
Fix: incorporate test coverage report
mrajagopal Oct 13, 2025
ba84c4e
Fix: Use library function for a pointer
mrajagopal Oct 13, 2025
0f1c9e6
Review fixes
mrajagopal Oct 14, 2025
7beddf9
Merge branch 'main' into mrajagopal-unit-tests
mrajagopal Oct 16, 2025
043d54d
Update pkg/mock/mock_data_collector.go
mrajagopal Oct 16, 2025
9e5a541
Update pkg/mock/mock_data_collector.go
mrajagopal Oct 16, 2025
eda9364
Review: Read mock CRD objects via a YAML declaration
mrajagopal Oct 17, 2025
7757474
Review: Read mock objects via a YAML declaration
mrajagopal Oct 17, 2025
b4dbe43
Review: Address comments
mrajagopal Oct 20, 2025
7c780b1
Review: Address comments
mrajagopal Oct 20, 2025
4e6d035
Fix: update go.mod following recent review commits
mrajagopal Oct 20, 2025
f757b84
Review: Add tests to confirm expected files are created for each job
mrajagopal Oct 24, 2025
c684282
Review: Add tests for failure paths when querying the k8s API for NGF…
mrajagopal Oct 24, 2025
1ccb031
Fix: collect-product-platform-info job was timing out on failure
mrajagopal Oct 28, 2025
4ff0cc7
Review: Add default case to all select block
mrajagopal Oct 28, 2025
70dbb48
Update pkg/jobs/common_job_list.go
mrajagopal Oct 28, 2025
2726bc3
Revert "Review: Add default case to all select block"
mrajagopal Oct 28, 2025
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
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,12 @@ nginx-utils:
docker buildx build --build-context project=nginx-utils --platform linux/amd64 -t nginx-utils -f nginx-utils/Dockerfile .

install: build
sudo cp cmd/kubectl-nginx_supportpkg /usr/local/bin
sudo cp cmd/kubectl-nginx_supportpkg /usr/local/bin

clean:
rm -f cmd/kubectl-nginx_supportpkg

test:
go clean -testcache && go test -v ./... -coverprofile=coverage.out
go tool cover -func=coverage.out
go tool cover -html=coverage.out -o coverage.html
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ module github.com/nginxinc/nginx-k8s-supportpkg
go 1.24.3

require (
github.com/mittwald/go-helm-client v0.12.17
github.com/mittwald/go-helm-client v0.12.18
github.com/spf13/cobra v1.10.1
go.uber.org/mock v0.5.0
helm.sh/helm/v3 v3.18.5
k8s.io/client-go v0.34.0
)

Expand Down Expand Up @@ -90,7 +92,6 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
google.golang.org/grpc v1.72.1 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
helm.sh/helm/v3 v3.18.5 // indirect
k8s.io/apiserver v0.34.0 // indirect
k8s.io/cli-runtime v0.33.3 // indirect
k8s.io/component-base v0.34.0 // indirect
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mittwald/go-helm-client v0.12.17 h1:PncoE1u3fXuHWLineNDQ4hI5J4uVbMW3JWrtdBR86TI=
github.com/mittwald/go-helm-client v0.12.17/go.mod h1:GQxuPspUcMsxWWDtYzjRdxOAjh3LKADIfgqtUf9mjHk=
github.com/mittwald/go-helm-client v0.12.18 h1:i9cJNv/YC3ZPKUKVNYTlrOO7ZO6YFKE/ak3J5TeYHPU=
github.com/mittwald/go-helm-client v0.12.18/go.mod h1:dLl5NkdKCvwKvLIdZzg4MDbxhSKmuimdmM3WpsAzS0I=
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
Expand Down Expand Up @@ -326,6 +326,8 @@ go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9f
go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
Expand Down
82 changes: 82 additions & 0 deletions pkg/crds/crd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package crds

import (
"reflect"
"testing"
)

func TestGetNICCRDList(t *testing.T) {
tests := []struct {
name string
want []Crd
}{
{
name: "Correct CRD list",
want: []Crd{
{
Resource: "apdoslogconfs",
Group: "appprotectdos.f5.com",
Version: "v1beta1",
},
{
Resource: "apdospolicies",
Group: "appprotectdos.f5.com",
Version: "v1beta1",
},
{
Resource: "dosprotectedresources",
Group: "appprotectdos.f5.com",
Version: "v1beta1",
},
{
Resource: "aplogconfs",
Group: "appprotect.f5.com",
Version: "v1beta1",
},
{
Resource: "appolicies",
Group: "appprotect.f5.com",
Version: "v1beta1",
},
{
Resource: "apusersigs",
Group: "appprotect.f5.com",
Version: "v1beta1",
},
{
Resource: "globalconfigurations",
Group: "k8s.nginx.org",
Version: "v1",
},
{
Resource: "policies",
Group: "k8s.nginx.org",
Version: "v1",
},
{
Resource: "transportservers",
Group: "k8s.nginx.org",
Version: "v1",
},
{
Resource: "virtualserverroutes",
Group: "k8s.nginx.org",
Version: "v1",
},
{
Resource: "virtualservers",
Group: "k8s.nginx.org",
Version: "v1",
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GetNICCRDList(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetNICCRDList() = %v, want %v", got, tt.want)
}
})
}
}
15 changes: 10 additions & 5 deletions pkg/data_collector/data_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
helmClient "github.com/mittwald/go-helm-client"
"github.com/nginxinc/nginx-k8s-supportpkg/pkg/crds"
corev1 "k8s.io/api/core/v1"
apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
crdClient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
Expand All @@ -53,12 +54,14 @@ type DataCollector struct {
Logger *log.Logger
LogFile *os.File
K8sRestConfig *rest.Config
K8sCoreClientSet *kubernetes.Clientset
K8sCrdClientSet *crdClient.Clientset
K8sMetricsClientSet *metricsClient.Clientset
K8sCoreClientSet kubernetes.Interface
K8sCrdClientSet apiextensionsclientset.Interface
K8sMetricsClientSet metricsClient.Interface
K8sHelmClientSet map[string]helmClient.Client
ExcludeDBData bool
ExcludeTimeSeriesData bool
PodExecutor func(namespace, pod, container string, command []string, ctx context.Context) ([]byte, error)
QueryCRD func(crd crds.Crd, namespace string, ctx context.Context) ([]byte, error)
}

type Manifest struct {
Expand Down Expand Up @@ -148,6 +151,8 @@ func NewDataCollector(collector *DataCollector) error {
collector.LogFile = logFile
collector.Logger = log.New(logFile, "", log.LstdFlags|log.LUTC|log.Lmicroseconds|log.Lshortfile)
collector.K8sHelmClientSet = make(map[string]helmClient.Client)
collector.PodExecutor = collector.RealPodExecutor
collector.QueryCRD = collector.RealQueryCRD

//Initialize clients
collector.K8sRestConfig = config
Expand Down Expand Up @@ -260,7 +265,7 @@ func (c *DataCollector) WrapUp(product string) (string, error) {
return tarballName, nil
}

func (c *DataCollector) PodExecutor(namespace string, pod string, container string, command []string, ctx context.Context) ([]byte, error) {
func (c *DataCollector) RealPodExecutor(namespace string, pod string, container string, command []string, ctx context.Context) ([]byte, error) {
req := c.K8sCoreClientSet.CoreV1().RESTClient().Post().
Namespace(namespace).
Resource("pods").
Expand Down Expand Up @@ -293,7 +298,7 @@ func (c *DataCollector) PodExecutor(namespace string, pod string, container stri
}
}

func (c *DataCollector) QueryCRD(crd crds.Crd, namespace string, ctx context.Context) ([]byte, error) {
func (c *DataCollector) RealQueryCRD(crd crds.Crd, namespace string, ctx context.Context) ([]byte, error) {

schemeGroupVersion := schema.GroupVersion{Group: crd.Group, Version: crd.Version}
negotiatedSerializer := scheme.Codecs.WithoutConversion()
Expand Down
133 changes: 133 additions & 0 deletions pkg/data_collector/data_collector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package data_collector

import (
"bytes"
"context"
"io"
"log"
"os"
"path/filepath"
"testing"

"github.com/nginxinc/nginx-k8s-supportpkg/pkg/crds"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/rest"
)

func TestNewDataCollector_Success(t *testing.T) {
dc := &DataCollector{Namespaces: []string{"default"}}
err := NewDataCollector(dc)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if dc.BaseDir == "" {
t.Error("BaseDir should be set")
}
if dc.Logger == nil {
t.Error("Logger should be set")
}
if dc.LogFile == nil {
t.Error("LogFile should be set")
}
if dc.K8sCoreClientSet == nil {
t.Error("K8sCoreClientSet should be set")
}
if dc.K8sCrdClientSet == nil {
t.Error("K8sCrdClientSet should be set")
}
if dc.K8sMetricsClientSet == nil {
t.Error("K8sMetricsClientSet should be set")
}
if dc.K8sHelmClientSet == nil {
t.Error("K8sHelmClientSet should be set")
}
}

func TestWrapUp_CreatesTarball(t *testing.T) {
tmpDir := t.TempDir()
logFile, _ := os.Create(filepath.Join(tmpDir, "supportpkg.log"))
dc := &DataCollector{
BaseDir: tmpDir,
LogFile: logFile,
Logger: log.New(io.Discard, "", 0),
}
product := "nginx"
tarball, err := dc.WrapUp(product)
if err != nil {
t.Fatalf("WrapUp failed: %v", err)
}
if _, err := os.Stat(tarball); err != nil {
t.Errorf("tarball not created: %v", err)
}
_ = os.Remove(tarball)
}

func TestRealPodExecutor_ReturnsOutput(t *testing.T) {
dc := &DataCollector{
K8sCoreClientSet: fake.NewSimpleClientset(),
K8sRestConfig: &rest.Config{},
}
// Replace RealPodExecutor with a mock for testing
dc.PodExecutor = func(namespace, pod, container string, command []string, ctx context.Context) ([]byte, error) {
return []byte("output"), nil
}
out, err := dc.PodExecutor("default", "pod", "container", []string{"echo", "hello"}, context.TODO())
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if !bytes.Contains(out, []byte("output")) {
t.Errorf("expected output, got %s", string(out))
}
}

func TestRealQueryCRD_ReturnsErrorOnInvalidConfig(t *testing.T) {
dc := &DataCollector{
K8sRestConfig: &rest.Config{},
}
crd := crds.Crd{Group: "test", Version: "v1", Resource: "foos"}
_, err := dc.RealQueryCRD(crd, "default", context.TODO())
if err == nil {
t.Error("expected error for invalid config")
}
}

func TestAllNamespacesExist_AllExist(t *testing.T) {
client := fake.NewSimpleClientset(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "default"}})
dc := &DataCollector{
Namespaces: []string{"default"},
K8sCoreClientSet: client,
Logger: log.New(io.Discard, "", 0),
}
if !dc.AllNamespacesExist() {
t.Error("expected all namespaces to exist")
}
}

func TestAllNamespacesExist_NotExist(t *testing.T) {
client := fake.NewSimpleClientset()
dc := &DataCollector{
Namespaces: []string{"missing"},
K8sCoreClientSet: client,
Logger: log.New(io.Discard, "", 0),
}
if dc.AllNamespacesExist() {
t.Error("expected namespaces to not exist")
}
}

func TestWrapUp_ErrorOnLogFileClose(t *testing.T) {
tmpDir := t.TempDir()
logFile, _ := os.Create(filepath.Join(tmpDir, "supportpkg.log"))
logFile.Close() // Already closed
dc := &DataCollector{
BaseDir: tmpDir,
LogFile: logFile,
Logger: log.New(io.Discard, "", 0),
}
_, err := dc.WrapUp("nginx")
if err == nil {
t.Error("expected error on closing already closed log file")
}
}
6 changes: 3 additions & 3 deletions pkg/jobs/common_job_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func CommonJobList() []Job {
Execute: func(dc *data_collector.DataCollector, ctx context.Context, ch chan JobResult) {
jobResult := JobResult{Files: make(map[string][]byte), Error: nil}
for _, namespace := range dc.Namespaces {
result, err := dc.K8sCoreClientSet.DiscoveryClient.ServerPreferredResources()
result, err := dc.K8sCoreClientSet.Discovery().ServerPreferredResources()
if err != nil {
dc.Logger.Printf("\tCould not retrieve API resources list %s: %v\n", namespace, err)
} else {
Expand All @@ -163,7 +163,7 @@ func CommonJobList() []Job {
Execute: func(dc *data_collector.DataCollector, ctx context.Context, ch chan JobResult) {
jobResult := JobResult{Files: make(map[string][]byte), Error: nil}
for _, namespace := range dc.Namespaces {
result, err := dc.K8sCoreClientSet.DiscoveryClient.ServerGroups()
result, err := dc.K8sCoreClientSet.Discovery().ServerGroups()
if err != nil {
dc.Logger.Printf("\tCould not retrieve API versions list %s: %v\n", namespace, err)
} else {
Expand Down Expand Up @@ -367,7 +367,7 @@ func CommonJobList() []Job {
Timeout: time.Second * 10,
Execute: func(dc *data_collector.DataCollector, ctx context.Context, ch chan JobResult) {
jobResult := JobResult{Files: make(map[string][]byte), Error: nil}
result, err := dc.K8sCoreClientSet.ServerVersion()
result, err := dc.K8sCoreClientSet.Discovery().ServerVersion()
if err != nil {
dc.Logger.Printf("\tCould not retrieve server version: %v\n", err)
} else {
Expand Down
Loading
Loading