|
| 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 | +} |
0 commit comments