Skip to content

Commit 0755156

Browse files
authored
feat(tests): Add tests for gitops-must-gather (#958)
* feat(tests): Add tests for gitops-must-gather Signed-off-by: Oliver Gondža <[email protected]> * feat(tests): validate_running_must_gather.go: permit image override Signed-off-by: Oliver Gondža <[email protected]> * fix(tests): 1-120_validate_running_must_gather.go: Do not read file to memory + please gosec Signed-off-by: Oliver Gondža <[email protected]> * chore(test): Static analysis fixes Signed-off-by: Oliver Gondža <[email protected]> * fix(tests): Suppress checking files not present in older ocp versions Signed-off-by: Oliver Gondža <[email protected]> --------- Signed-off-by: Oliver Gondža <[email protected]>
1 parent c5bfe84 commit 0755156

File tree

2 files changed

+198
-1
lines changed

2 files changed

+198
-1
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ require (
1919
github.com/stretchr/testify v1.11.1
2020
go.uber.org/zap v1.27.0
2121
golang.org/x/mod v0.29.0
22+
gopkg.in/yaml.v3 v3.0.1
2223
gotest.tools v2.2.0+incompatible
2324
k8s.io/api v0.33.2
2425
k8s.io/apiextensions-apiserver v0.33.1
@@ -158,7 +159,6 @@ require (
158159
gopkg.in/inf.v0 v0.9.1 // indirect
159160
gopkg.in/warnings.v0 v0.1.2 // indirect
160161
gopkg.in/yaml.v2 v2.4.0 // indirect
161-
gopkg.in/yaml.v3 v3.0.1 // indirect
162162
k8s.io/apiserver v0.33.2 // indirect
163163
k8s.io/cli-runtime v0.33.2 // indirect
164164
k8s.io/component-base v0.33.2 // indirect
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/*
2+
Copyright 2025.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package parallel
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"os"
23+
"path"
24+
"path/filepath"
25+
"strings"
26+
27+
osFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/os"
28+
"gopkg.in/yaml.v3"
29+
30+
argov1beta1api "github.com/argoproj-labs/argocd-operator/api/v1beta1"
31+
. "github.com/onsi/ginkgo/v2"
32+
. "github.com/onsi/gomega"
33+
"github.com/onsi/gomega/types"
34+
"github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture"
35+
argocdFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/argocd"
36+
fixtureUtils "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/utils"
37+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
38+
39+
"sigs.k8s.io/controller-runtime/pkg/client"
40+
)
41+
42+
// default used when E2E_MUST_GATHER_IMAGE is not set.
43+
// CI images:
44+
// - quay.io/redhat-user-workloads/rh-openshift-gitops-tenant/gitops-must-gather:on-pr-<GIT_COMMIT_SHA>
45+
// - quay.io/redhat-user-workloads/rh-openshift-gitops-tenant/gitops-must-gather:<GIT_COMMIT_SHA>
46+
// - quay.io/redhat-user-workloads/rh-openshift-gitops-tenant/gitops-must-gather:latest # For main branch.
47+
const defaultMustGatherImage = "quay.io/redhat-user-workloads/rh-openshift-gitops-tenant/gitops-must-gather:latest"
48+
49+
var _ = Describe("GitOps Operator Parallel E2E Tests", func() {
50+
51+
Context("1-120_validate_running_must_gather", func() {
52+
53+
var (
54+
k8sClient client.Client
55+
ctx context.Context
56+
)
57+
58+
BeforeEach(func() {
59+
fixture.EnsureParallelCleanSlate()
60+
k8sClient, _ = fixtureUtils.GetE2ETestKubeClient()
61+
ctx = context.Background()
62+
})
63+
64+
It("verified the files collected for must gather are valid", func() {
65+
By("creating namespace-scoped Argo CD instance")
66+
ns, nsCleanup := fixture.CreateRandomE2ETestNamespaceWithCleanupFunc()
67+
defer nsCleanup()
68+
69+
nsf, nsfCleanup := fixture.CreateManagedNamespaceWithCleanupFunc(ns.Name+"-follower", ns.Name)
70+
defer nsfCleanup()
71+
72+
argoCD := &argov1beta1api.ArgoCD{
73+
ObjectMeta: metav1.ObjectMeta{Name: "left-argocd", Namespace: ns.Name},
74+
Spec: argov1beta1api.ArgoCDSpec{},
75+
}
76+
Expect(k8sClient.Create(ctx, argoCD)).To(Succeed())
77+
78+
By("waiting for ArgoCD CRs to be reconciled and the instances to be ready in " + ns.Name)
79+
Eventually(argoCD, "5m", "5s").Should(argocdFixture.BeAvailable())
80+
81+
// TODO https://github.com/redhat-developer/gitops-must-gather/blob/135850b74b56b6fda9fc68ed4165a88b5c7dbeaf/gather_gitops.sh#L40
82+
// TODO https://github.com/redhat-developer/gitops-must-gather/blob/135850b74b56b6fda9fc68ed4165a88b5c7dbeaf/gather_gitops.sh#L61
83+
// TODO https://github.com/redhat-developer/gitops-must-gather/blob/135850b74b56b6fda9fc68ed4165a88b5c7dbeaf/gather_gitops.sh#L79
84+
85+
destDir := gather()
86+
defer os.RemoveAll(destDir)
87+
88+
// TODO: Not before 4.16: https://github.com/openshift/oc/commit/7d23cbb68dfed274b2821d91038f45c8ce12a249
89+
// Expect(path.Join(destDir, "must-gather.logs")).To(BeARegularFile())
90+
91+
Expect(path.Join(destDir, "event-filter.html")).To(BeARegularFile())
92+
Expect(path.Join(destDir, "timestamp")).To(BeARegularFile())
93+
94+
resources := resourcesDir(destDir)
95+
// TODO: Not before 4.16: https://github.com/openshift/oc/commit/6348e4a0484fce9b4151dbf39ca17bdd8a450053
96+
// Expect(path.Join(resources, "gather.logs")).To(BeARegularFile())
97+
csr := path.Join(resources, "cluster-scoped-resources")
98+
Expect(csr).To(BeADirectory())
99+
Expect(path.Join(csr, "apiextensions.k8s.io/customresourcedefinitions/applications.argoproj.io.yaml")).To(BeValidResourceFile())
100+
Expect(path.Join(csr, "argoproj.io/clusteranalysistemplates.yaml")).To(BeValidResourceFile())
101+
Expect(path.Join(csr, "config.openshift.io/clusterversions/version.yaml")).To(BeValidResourceFile())
102+
103+
n := path.Join(resources, "namespaces")
104+
Expect(n).To(BeADirectory())
105+
Expect(path.Join(n, "openshift-gitops/openshift-gitops.yaml")).To(BeValidResourceFile())
106+
Expect(path.Join(n, "openshift-gitops/route.openshift.io/routes.yaml")).To(BeValidResourceFile())
107+
Expect(path.Join(n, "openshift-gitops/argoproj.io/appprojects.yaml")).To(BeValidResourceFile())
108+
logs := path.Join(n, "openshift-gitops/pods/openshift-gitops-application-controller-0/argocd-application-controller/argocd-application-controller/logs/")
109+
Expect(path.Join(logs, "current.log")).To(BeARegularFile())
110+
Expect(path.Join(logs, "previous.log")).To(BeARegularFile())
111+
112+
Expect(path.Join(n, nsf.Name, "core/pods.yaml")).To(BeValidResourceFile())
113+
})
114+
})
115+
})
116+
117+
func gather() string {
118+
destDir, err := os.MkdirTemp("", "gitops-operator-e2e-must-gather-test-1-120_*")
119+
Expect(err).ToNot(HaveOccurred())
120+
121+
stdout, err := osFixture.ExecCommandWithOutputParam(
122+
true,
123+
"oc", "adm", "must-gather", "--image", mustGatherImage(), "--dest-dir", destDir,
124+
)
125+
Expect(err).ToNot(HaveOccurred())
126+
127+
errorLines := make([]string, 0)
128+
for _, line := range strings.Split(stdout, "\n") {
129+
if strings.Contains(line, "error:") {
130+
errorLines = append(errorLines, line)
131+
}
132+
}
133+
Expect(errorLines).To(BeEmpty(), "Errors found in must gather output")
134+
return destDir
135+
}
136+
137+
func resourcesDir(destDir string) string {
138+
// Find the only subdirectory which contains must-gather data
139+
entries, err := os.ReadDir(destDir)
140+
Expect(err).ToNot(HaveOccurred())
141+
142+
var subdirs []string
143+
for _, entry := range entries {
144+
if entry.IsDir() {
145+
subdirs = append(subdirs, entry.Name())
146+
}
147+
}
148+
149+
Expect(subdirs).To(HaveLen(1), "Expected exactly one subdirectory, found: %v", subdirs)
150+
return path.Join(destDir, subdirs[0])
151+
}
152+
153+
func mustGatherImage() string {
154+
injected := os.Getenv("E2E_MUST_GATHER_IMAGE")
155+
if injected == "" {
156+
return defaultMustGatherImage
157+
}
158+
return injected
159+
}
160+
161+
// BeValidResourceFile checks if the file exists and if it is a valid YAML file.
162+
func BeValidResourceFile() types.GomegaMatcher {
163+
return &validResourceFile{}
164+
}
165+
166+
type validResourceFile struct{}
167+
168+
func (matcher *validResourceFile) Match(actual any) (success bool, err error) {
169+
filePath, ok := actual.(string)
170+
if !ok {
171+
return false, fmt.Errorf("BeValidResourceFile matcher expects a string (file path)")
172+
}
173+
174+
filePath = filepath.Clean(filePath)
175+
f, err := os.Open(filePath)
176+
if err != nil {
177+
return false, fmt.Errorf("failed to read file: %v", err)
178+
}
179+
defer f.Close()
180+
181+
decoder := yaml.NewDecoder(f)
182+
var data map[string]any
183+
if err := decoder.Decode(&data); err != nil {
184+
return false, fmt.Errorf("failed parsing supposed YAML file: %v", err)
185+
}
186+
187+
_, exists := data["kind"]
188+
return exists, nil
189+
}
190+
191+
func (matcher *validResourceFile) FailureMessage(actual any) string {
192+
return fmt.Sprintf("Expected\n\t%v\nto be a valid YAML resource file", actual)
193+
}
194+
195+
func (matcher *validResourceFile) NegatedFailureMessage(actual any) string {
196+
return fmt.Sprintf("Expected\n\t%v\nnot to be a valid YAML resource file", actual)
197+
}

0 commit comments

Comments
 (0)