Skip to content

Commit 8ea9a12

Browse files
Merge pull request #30863 from smg247/non-payload-ote
TRT-2295: admit and extract non-payload OTE binaries
2 parents 7fd854e + d1e2115 commit 8ea9a12

13 files changed

Lines changed: 739 additions & 74 deletions

File tree

cmd/openshift-tests/openshift-tests.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/openshift/origin/pkg/cmd/openshift-tests/dev"
2424
"github.com/openshift/origin/pkg/cmd/openshift-tests/disruption"
2525
e2e_analysis "github.com/openshift/origin/pkg/cmd/openshift-tests/e2e-analysis"
26+
extensionadmission "github.com/openshift/origin/pkg/cmd/openshift-tests/extension-admission"
2627
"github.com/openshift/origin/pkg/cmd/openshift-tests/images"
2728
"github.com/openshift/origin/pkg/cmd/openshift-tests/list"
2829
"github.com/openshift/origin/pkg/cmd/openshift-tests/monitor"
@@ -112,6 +113,7 @@ func main() {
112113
timeline.NewTimelineCommand(ioStreams),
113114
run_disruption.NewRunInClusterDisruptionMonitorCommand(ioStreams),
114115
collectdiskcertificates.NewRunCollectDiskCertificatesCommand(ioStreams),
116+
extensionadmission.NewExtensionAdmissionCommand(ioStreams),
115117
versioncmd.NewVersionCommand(ioStreams),
116118
)
117119

pkg/apis/testextension/v1/doc.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// +k8s:deepcopy-gen=package
2+
3+
// Package v1 contains API Schema definitions for the testextension v1 API group
4+
// +groupName=testextension.redhat.io
5+
package v1
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package v1
2+
3+
import (
4+
"k8s.io/apimachinery/pkg/runtime/schema"
5+
)
6+
7+
const (
8+
GroupName = "testextension.redhat.io"
9+
Version = "v1"
10+
)
11+
12+
var (
13+
SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: Version}
14+
)

pkg/apis/testextension/v1/types.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package v1
2+
3+
import (
4+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
5+
)
6+
7+
// +genclient
8+
// +genclient:nonNamespaced
9+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
10+
11+
// TestExtensionAdmission controls which ImageStreams are permitted to provide extension test binaries
12+
type TestExtensionAdmission struct {
13+
metav1.TypeMeta `json:",inline"`
14+
metav1.ObjectMeta `json:"metadata,omitempty"`
15+
16+
Spec TestExtensionAdmissionSpec `json:"spec"`
17+
Status TestExtensionAdmissionStatus `json:"status,omitempty"`
18+
}
19+
20+
// TestExtensionAdmissionSpec defines the desired state of TestExtensionAdmission
21+
type TestExtensionAdmissionSpec struct {
22+
// Permit is a list of permitted ImageStream patterns in format "namespace/imagestream".
23+
// Supports wildcards like "openshift/*", "*/*", or specific "namespace/stream"
24+
// +kubebuilder:validation:Required
25+
// +kubebuilder:validation:MinItems=1
26+
Permit []string `json:"permit"`
27+
}
28+
29+
// TestExtensionAdmissionStatus defines the observed state of TestExtensionAdmission
30+
type TestExtensionAdmissionStatus struct {
31+
// Conditions represent the latest available observations of an object's state
32+
// +optional
33+
Conditions []metav1.Condition `json:"conditions,omitempty"`
34+
}
35+
36+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
37+
38+
// TestExtensionAdmissionList contains a list of TestExtensionAdmission
39+
type TestExtensionAdmissionList struct {
40+
metav1.TypeMeta `json:",inline"`
41+
metav1.ListMeta `json:"metadata,omitempty"`
42+
43+
Items []TestExtensionAdmission `json:"items"`
44+
}
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
package extensionadmission
2+
3+
import (
4+
_ "embed"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"path/filepath"
9+
"time"
10+
11+
"github.com/spf13/cobra"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/cli-runtime/pkg/genericclioptions"
14+
"k8s.io/kubectl/pkg/util/templates"
15+
"sigs.k8s.io/yaml"
16+
17+
testextensionv1 "github.com/openshift/origin/pkg/apis/testextension/v1"
18+
"github.com/openshift/origin/pkg/cmd"
19+
exutil "github.com/openshift/origin/test/extended/util"
20+
)
21+
22+
//go:embed testextensionadmission-crd.yaml
23+
var crdYAML []byte
24+
25+
func NewExtensionAdmissionCommand(ioStreams genericclioptions.IOStreams) *cobra.Command {
26+
o := &extensionAdmissionOptions{
27+
ioStreams: ioStreams,
28+
}
29+
30+
command := &cobra.Command{
31+
Use: "extension-admission",
32+
Short: "Manage TestExtensionAdmission resources",
33+
Long: templates.LongDesc(`
34+
Manage TestExtensionAdmission resources for controlling which ImageStreams
35+
are permitted to provide extension test binaries.
36+
37+
TestExtensionAdmission acts as an admission controller to determine which
38+
ImageStreams are permitted to provide test binaries outside the main
39+
OpenShift release payload.
40+
41+
To list or delete TestExtensionAdmission resources, use standard kubectl/oc commands:
42+
oc get testextensionadmissions
43+
oc delete testextensionadmission <name>
44+
`),
45+
PersistentPreRun: cmd.NoPrintVersion,
46+
}
47+
48+
command.AddCommand(
49+
newInstallCRDCommand(o),
50+
newCreateCommand(o),
51+
)
52+
53+
return command
54+
}
55+
56+
type extensionAdmissionOptions struct {
57+
ioStreams genericclioptions.IOStreams
58+
}
59+
60+
func newInstallCRDCommand(o *extensionAdmissionOptions) *cobra.Command {
61+
command := &cobra.Command{
62+
Use: "install-crd",
63+
Short: "Install the TestExtensionAdmission CRD",
64+
Long: templates.LongDesc(`
65+
Install the TestExtensionAdmission CustomResourceDefinition to the cluster.
66+
67+
This CRD must be installed before creating TestExtensionAdmission instances.
68+
`),
69+
RunE: func(cmd *cobra.Command, args []string) error {
70+
return o.installCRD()
71+
},
72+
}
73+
74+
return command
75+
}
76+
77+
func newCreateCommand(o *extensionAdmissionOptions) *cobra.Command {
78+
createOpts := &createOptions{
79+
extensionAdmissionOptions: o,
80+
}
81+
82+
command := &cobra.Command{
83+
Use: "create NAME --permit=PATTERN [--permit=PATTERN...]",
84+
Short: "Create a TestExtensionAdmission resource",
85+
Long: templates.LongDesc(`
86+
Create a TestExtensionAdmission resource with the specified permit patterns.
87+
88+
Permit patterns are in the format "namespace/imagestream" and support wildcards:
89+
- "openshift/*" - All ImageStreams in the openshift namespace
90+
- "test-extensions/*" - All ImageStreams in test-extensions namespace
91+
- "my-ns/my-stream" - Specific ImageStream
92+
- "*/*" - All ImageStreams in all namespaces (use with caution)
93+
94+
Example:
95+
openshift-tests extension-admission create my-admission \
96+
--permit=openshift/* \
97+
--permit=test-extensions/*
98+
`),
99+
Args: cobra.ExactArgs(1),
100+
RunE: func(cmd *cobra.Command, args []string) error {
101+
createOpts.name = args[0]
102+
return createOpts.create()
103+
},
104+
}
105+
106+
command.Flags().StringSliceVar(&createOpts.permits, "permit", nil, "Permit pattern(s) (can be specified multiple times)")
107+
command.MarkFlagRequired("permit")
108+
109+
return command
110+
}
111+
112+
type createOptions struct {
113+
*extensionAdmissionOptions
114+
name string
115+
permits []string
116+
}
117+
118+
func (o *createOptions) create() error {
119+
if len(o.permits) == 0 {
120+
return fmt.Errorf("at least one --permit pattern is required")
121+
}
122+
123+
// Create the TestExtensionAdmission object
124+
admission := &testextensionv1.TestExtensionAdmission{
125+
TypeMeta: metav1.TypeMeta{
126+
APIVersion: testextensionv1.SchemeGroupVersion.String(),
127+
Kind: "TestExtensionAdmission",
128+
},
129+
ObjectMeta: metav1.ObjectMeta{
130+
Name: o.name,
131+
},
132+
Spec: testextensionv1.TestExtensionAdmissionSpec{
133+
Permit: o.permits,
134+
},
135+
}
136+
137+
// Convert to YAML
138+
yamlBytes, err := yaml.Marshal(admission)
139+
if err != nil {
140+
return fmt.Errorf("failed to marshal TestExtensionAdmission to YAML: %w", err)
141+
}
142+
143+
// Apply using kubectl/oc
144+
artifactName := fmt.Sprintf("testextensionadmission-%s.yaml", o.name)
145+
if err := o.applyYAML(yamlBytes, artifactName); err != nil {
146+
return fmt.Errorf("failed to apply TestExtensionAdmission: %w", err)
147+
}
148+
149+
fmt.Fprintf(o.ioStreams.Out, "TestExtensionAdmission %q created successfully\n", o.name)
150+
return nil
151+
}
152+
153+
func (o *extensionAdmissionOptions) installCRD() error {
154+
if err := o.applyYAML(crdYAML, "testextensionadmission-crd.yaml"); err != nil {
155+
return fmt.Errorf("failed to install CRD: %w", err)
156+
}
157+
158+
fmt.Fprintln(o.ioStreams.Out, "TestExtensionAdmission CRD installed successfully")
159+
return nil
160+
}
161+
162+
// saveToArtifactDir saves the YAML content to ARTIFACT_DIR if the environment variable is set.
163+
// This helps with debugging by preserving the applied manifests.
164+
func saveToArtifactDir(yamlBytes []byte, basename string) {
165+
artifactDir := os.Getenv("ARTIFACT_DIR")
166+
if artifactDir == "" {
167+
return
168+
}
169+
170+
// Create a timestamped filename to avoid collisions
171+
timestamp := time.Now().UTC().Format("20060102-150405")
172+
filename := fmt.Sprintf("%s-%s", timestamp, basename)
173+
artifactPath := filepath.Join(artifactDir, filename)
174+
175+
if err := os.WriteFile(artifactPath, yamlBytes, 0644); err != nil {
176+
// Don't fail the operation, just log the error
177+
fmt.Fprintf(os.Stderr, "Warning: Failed to save artifact to %s: %v\n", artifactPath, err)
178+
return
179+
}
180+
181+
fmt.Fprintf(os.Stderr, "Saved artifact to %s\n", artifactPath)
182+
}
183+
184+
func (o *extensionAdmissionOptions) applyYAML(yamlBytes []byte, artifactName string) error {
185+
// Write YAML to a temporary file
186+
tmpFile, err := os.CreateTemp("", "testextensionadmission-*.yaml")
187+
if err != nil {
188+
return fmt.Errorf("failed to create temp file: %w", err)
189+
}
190+
defer os.Remove(tmpFile.Name())
191+
192+
if _, err := tmpFile.Write(yamlBytes); err != nil {
193+
tmpFile.Close()
194+
return fmt.Errorf("failed to write YAML to temp file: %w", err)
195+
}
196+
tmpFile.Close()
197+
198+
// Use oc apply via exec.Command
199+
ocPath := "oc"
200+
kubeconfig := exutil.KubeConfigPath()
201+
202+
var cmd *exec.Cmd
203+
if kubeconfig != "" {
204+
cmd = exec.Command(ocPath, "--kubeconfig="+kubeconfig, "apply", "-f", tmpFile.Name())
205+
} else {
206+
cmd = exec.Command(ocPath, "apply", "-f", tmpFile.Name())
207+
}
208+
209+
output, err := cmd.CombinedOutput()
210+
if err != nil {
211+
return fmt.Errorf("oc apply failed: %w\nOutput: %s", err, string(output))
212+
}
213+
214+
fmt.Fprintf(o.ioStreams.Out, "%s", string(output))
215+
saveToArtifactDir(yamlBytes, artifactName)
216+
return nil
217+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
apiVersion: apiextensions.k8s.io/v1
3+
kind: CustomResourceDefinition
4+
metadata:
5+
name: testextensionadmissions.testextension.redhat.io
6+
spec:
7+
group: testextension.redhat.io
8+
names:
9+
kind: TestExtensionAdmission
10+
listKind: TestExtensionAdmissionList
11+
plural: testextensionadmissions
12+
singular: testextensionadmission
13+
shortNames:
14+
- tea
15+
scope: Cluster
16+
versions:
17+
- name: v1
18+
served: true
19+
storage: true
20+
schema:
21+
openAPIV3Schema:
22+
type: object
23+
description: TestExtensionAdmission controls which ImageStreams are permitted to provide extension test binaries
24+
properties:
25+
spec:
26+
type: object
27+
description: Specification of permitted ImageStreams
28+
properties:
29+
permit:
30+
type: array
31+
description: List of permitted ImageStream patterns in format "namespace/imagestream". Each segment must be either "*" (wildcard) or a valid name (no embedded wildcards). Examples - "openshift/*", "*/*", "namespace/stream"
32+
minItems: 1
33+
items:
34+
type: string
35+
pattern: '^(\*|[a-zA-Z0-9]([-_a-zA-Z0-9]*[a-zA-Z0-9])?)/(\*|[a-zA-Z0-9]([-_a-zA-Z0-9]*[a-zA-Z0-9])?)$'
36+
required:
37+
- permit
38+
status:
39+
type: object
40+
description: Status of the TestExtensionAdmission
41+
x-kubernetes-preserve-unknown-fields: true
42+
subresources:
43+
status: {}

pkg/cmd/openshift-tests/images/images_command.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ func createImageMirrorForInternalImages(prefix string, ref reference.DockerImage
147147
// Extract all test binaries
148148
extractionContext, extractionContextCancel := context.WithTimeout(context.Background(), 30*time.Minute)
149149
defer extractionContextCancel()
150-
cleanUpFn, externalBinaries, err := extensions.ExtractAllTestBinaries(extractionContext, 10)
150+
cleanUpFn, externalBinaries, _, err := extensions.ExtractAllTestBinaries(extractionContext, 10)
151151
if err != nil {
152152
return nil, err
153153
}

pkg/cmd/openshift-tests/list/extensions.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func NewListExtensionsCommand(streams genericclioptions.IOStreams) *cobra.Comman
4747
}
4848

4949
// Extract all test binaries from the release payload
50-
cleanup, binaries, err := extensions.ExtractAllTestBinaries(ctx, 10)
50+
cleanup, binaries, _, err := extensions.ExtractAllTestBinaries(ctx, 10)
5151
if err != nil {
5252
return fmt.Errorf("failed to extract test binaries: %w", err)
5353
}

0 commit comments

Comments
 (0)