diff --git a/internal/plugin/runtime_extismv1_test.go b/internal/plugin/runtime_extismv1_test.go index 8d9c55195..583f31318 100644 --- a/internal/plugin/runtime_extismv1_test.go +++ b/internal/plugin/runtime_extismv1_test.go @@ -16,9 +16,6 @@ limitations under the License. package plugin import ( - "os" - "os/exec" - "path/filepath" "testing" extism "github.com/extism/go-sdk" @@ -29,34 +26,6 @@ import ( "github.com/stretchr/testify/require" ) -type pluginRaw struct { - Metadata Metadata - Dir string -} - -func buildLoadExtismPlugin(t *testing.T, dir string) pluginRaw { - t.Helper() - - pluginFile := filepath.Join(dir, PluginFileName) - - metadataData, err := os.ReadFile(pluginFile) - require.NoError(t, err) - - m, err := loadMetadata(metadataData) - require.NoError(t, err) - require.Equal(t, "extism/v1", m.Runtime, "expected plugin runtime to be extism/v1") - - cmd := exec.Command("make", "-C", dir) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - require.NoError(t, cmd.Run(), "failed to build plugin in %q", dir) - - return pluginRaw{ - Metadata: *m, - Dir: dir, - } -} - func TestRuntimeConfigExtismV1Validate(t *testing.T) { rc := RuntimeConfigExtismV1{} err := rc.Validate() @@ -66,7 +35,7 @@ func TestRuntimeConfigExtismV1Validate(t *testing.T) { func TestRuntimeExtismV1InvokePlugin(t *testing.T) { r := RuntimeExtismV1{} - pr := buildLoadExtismPlugin(t, "testdata/src/extismv1-test") + pr := BuildLoadExtismPlugin(t, "testdata/src/extismv1-test") require.Equal(t, "test/v1", pr.Metadata.Type) p, err := r.CreatePlugin(pr.Dir, &pr.Metadata) diff --git a/internal/plugin/runtime_extismv1_test_helpers.go b/internal/plugin/runtime_extismv1_test_helpers.go new file mode 100644 index 000000000..3bdfadec0 --- /dev/null +++ b/internal/plugin/runtime_extismv1_test_helpers.go @@ -0,0 +1,53 @@ +/* +Copyright The Helm Authors. +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 plugin + +import ( + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +type RawPlugin struct { + Metadata Metadata + Dir string +} + +func BuildLoadExtismPlugin(t *testing.T, dir string) RawPlugin { + t.Helper() + + pluginFile := filepath.Join(dir, PluginFileName) + + metadataData, err := os.ReadFile(pluginFile) + require.NoError(t, err) + + m, err := loadMetadata(metadataData) + require.NoError(t, err) + require.Equal(t, "extism/v1", m.Runtime, "expected plugin runtime to be extism/v1") + + cmd := exec.Command("make", "-C", dir) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + require.NoError(t, cmd.Run(), "failed to build plugin in %q", dir) + + return RawPlugin{ + Metadata: *m, + Dir: dir, + } +} diff --git a/internal/plugin/runtime_subprocess.go b/internal/plugin/runtime_subprocess.go index 802732b14..fde147851 100644 --- a/internal/plugin/runtime_subprocess.go +++ b/internal/plugin/runtime_subprocess.go @@ -255,7 +255,7 @@ func (r *SubprocessPluginRuntime) runPostrenderer(input *Input) (*Output, error) go func() { defer stdin.Close() - io.Copy(stdin, msg.Manifests) + io.Copy(stdin, bytes.NewBufferString(msg.Manifests)) }() postRendered := &bytes.Buffer{} @@ -272,7 +272,7 @@ func (r *SubprocessPluginRuntime) runPostrenderer(input *Input) (*Output, error) return &Output{ Message: schema.OutputMessagePostRendererV1{ - Manifests: postRendered, + Manifests: postRendered.String(), }, }, nil } diff --git a/internal/plugin/schema/postrenderer.go b/internal/plugin/schema/postrenderer.go index ef51a8a61..9533b328e 100644 --- a/internal/plugin/schema/postrenderer.go +++ b/internal/plugin/schema/postrenderer.go @@ -16,19 +16,15 @@ limitations under the License. package schema -import ( - "bytes" -) - // InputMessagePostRendererV1 implements Input.Message type InputMessagePostRendererV1 struct { - Manifests *bytes.Buffer `json:"manifests"` + Manifests string `json:"manifests"` // from CLI --post-renderer-args ExtraArgs []string `json:"extraArgs"` } type OutputMessagePostRendererV1 struct { - Manifests *bytes.Buffer `json:"manifests"` + Manifests string `json:"manifests"` } type ConfigPostRendererV1 struct{} diff --git a/pkg/postrenderer/postrenderer.go b/pkg/postrenderer/postrenderer.go index 55e6d3adf..881148a5b 100644 --- a/pkg/postrenderer/postrenderer.go +++ b/pkg/postrenderer/postrenderer.go @@ -18,6 +18,7 @@ import ( "context" "fmt" "path/filepath" + "strings" "helm.sh/helm/v4/internal/plugin/schema" @@ -64,9 +65,10 @@ func (r *postRendererPlugin) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer input := &plugin.Input{ Message: schema.InputMessagePostRendererV1{ ExtraArgs: r.args, - Manifests: renderedManifests, + Manifests: renderedManifests.String(), }, } + output, err := r.plugin.Invoke(context.Background(), input) if err != nil { return nil, fmt.Errorf("failed to invoke post-renderer plugin %q: %w", r.plugin.Metadata().Name, err) @@ -76,9 +78,9 @@ func (r *postRendererPlugin) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer // If the binary returned almost nothing, it's likely that it didn't // successfully render anything - if len(bytes.TrimSpace(outputMessage.Manifests.Bytes())) == 0 { + if len(strings.TrimSpace(outputMessage.Manifests)) == 0 { return nil, fmt.Errorf("post-renderer %q produced empty output", r.plugin.Metadata().Name) } - return outputMessage.Manifests, nil + return bytes.NewBufferString(outputMessage.Manifests), nil } diff --git a/pkg/postrenderer/postrenderer_test.go b/pkg/postrenderer/postrenderer_test.go index 824a1d179..5b0875803 100644 --- a/pkg/postrenderer/postrenderer_test.go +++ b/pkg/postrenderer/postrenderer_test.go @@ -18,15 +18,37 @@ package postrenderer import ( "bytes" + "path" "runtime" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "helm.sh/helm/v4/internal/plugin" "helm.sh/helm/v4/pkg/cli" ) +func buildLoadExtismPostRendererPlugin(t *testing.T, dir string, args ...string) PostRenderer { + t.Helper() + r := plugin.RuntimeExtismV1{} + + pr := plugin.BuildLoadExtismPlugin(t, dir) + require.Equal(t, "postrenderer/v1", pr.Metadata.Type) + + p, err := r.CreatePlugin(pr.Dir, &pr.Metadata) + + assert.NoError(t, err, "expected no error creating plugin") + assert.NotNil(t, p, "expected plugin to be created") + + s := cli.New() + s.PluginsDirectory = path.Dir(p.Dir()) + + renderer, err := NewPostRendererPlugin(s, p.Metadata().Name, args...) + require.NoError(t, err) + return renderer +} + func TestNewPostRenderPluginRunWithNoOutput(t *testing.T) { if runtime.GOOS == "windows" { // the actual Run test uses a basic sed example, so skip this test on windows @@ -59,7 +81,7 @@ func TestNewPostRenderPluginWithOneArgsRun(t *testing.T) { output, err := renderer.Run(bytes.NewBufferString("FOOTEST")) is.NoError(err) - is.Contains(output.String(), "ARG1") + is.Contains(output, "ARG1") } func TestNewPostRenderPluginWithTwoArgsRun(t *testing.T) { @@ -77,5 +99,32 @@ func TestNewPostRenderPluginWithTwoArgsRun(t *testing.T) { output, err := renderer.Run(bytes.NewBufferString("FOOTEST")) is.NoError(err) - is.Contains(output.String(), "ARG1 ARG2") + is.Contains(output, "ARG1 ARG2") +} + +func TestExtismPostRenderPluginRunWithNoArgsRun(t *testing.T) { + is := assert.New(t) + renderer := buildLoadExtismPostRendererPlugin(t, "testdata/plugins/extismv1-str-replace") + + output, err := renderer.Run(bytes.NewBufferString("FOOTEST")) + is.NoError(err) + is.Equal(output, "BARTEST") +} + +func TestExtismPostRenderPluginWithOneArgsRun(t *testing.T) { + is := assert.New(t) + renderer := buildLoadExtismPostRendererPlugin(t, "testdata/plugins/extismv1-str-replace", "ARG1") + + output, err := renderer.Run(bytes.NewBufferString("FOOTEST")) + is.NoError(err) + is.Equal(output, "ARG1") +} + +func TestExtismPostRenderPluginWithTwoArgsRun(t *testing.T) { + is := assert.New(t) + renderer := buildLoadExtismPostRendererPlugin(t, "testdata/plugins/extismv1-str-replace", "ARG1", "ARG2") + + output, err := renderer.Run(bytes.NewBufferString("FOOTEST")) + is.NoError(err) + is.Equal(output, "ARG1 ARG2") } diff --git a/pkg/postrenderer/testdata/plugins/extismv1-str-replace/.gitignore b/pkg/postrenderer/testdata/plugins/extismv1-str-replace/.gitignore new file mode 100644 index 000000000..ef7d91fbb --- /dev/null +++ b/pkg/postrenderer/testdata/plugins/extismv1-str-replace/.gitignore @@ -0,0 +1 @@ +plugin.wasm diff --git a/pkg/postrenderer/testdata/plugins/extismv1-str-replace/Makefile b/pkg/postrenderer/testdata/plugins/extismv1-str-replace/Makefile new file mode 100644 index 000000000..df8a98421 --- /dev/null +++ b/pkg/postrenderer/testdata/plugins/extismv1-str-replace/Makefile @@ -0,0 +1,11 @@ +.DEFAULT: build +.PHONY: build test vet + +.PHONY: plugin.wasm +plugin.wasm: + GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o plugin.wasm . + +build: plugin.wasm + +vet: + GOOS=wasip1 GOARCH=wasm go vet ./... diff --git a/pkg/postrenderer/testdata/plugins/extismv1-str-replace/go.mod b/pkg/postrenderer/testdata/plugins/extismv1-str-replace/go.mod new file mode 100644 index 000000000..49165704c --- /dev/null +++ b/pkg/postrenderer/testdata/plugins/extismv1-str-replace/go.mod @@ -0,0 +1,10 @@ +module helm.sh/helm/v4/pkg/postrenderer/testdata/plugins/postrenderer-v1-extism + +go 1.25.0 + +require ( + github.com/extism/go-pdk v1.1.3 + helm.sh/helm/v4 v4.0.0 +) + +replace helm.sh/helm/v4 => ../../../../.. diff --git a/pkg/postrenderer/testdata/plugins/extismv1-str-replace/go.sum b/pkg/postrenderer/testdata/plugins/extismv1-str-replace/go.sum new file mode 100644 index 000000000..c15d38292 --- /dev/null +++ b/pkg/postrenderer/testdata/plugins/extismv1-str-replace/go.sum @@ -0,0 +1,2 @@ +github.com/extism/go-pdk v1.1.3 h1:hfViMPWrqjN6u67cIYRALZTZLk/enSPpNKa+rZ9X2SQ= +github.com/extism/go-pdk v1.1.3/go.mod h1:Gz+LIU/YCKnKXhgge8yo5Yu1F/lbv7KtKFkiCSzW/P4= diff --git a/pkg/postrenderer/testdata/plugins/extismv1-str-replace/main.go b/pkg/postrenderer/testdata/plugins/extismv1-str-replace/main.go new file mode 100644 index 000000000..e73d04b92 --- /dev/null +++ b/pkg/postrenderer/testdata/plugins/extismv1-str-replace/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "strings" + + pdk "github.com/extism/go-pdk" + + "helm.sh/helm/v4/internal/plugin/schema" +) + +func RunPlugin() error { + var input schema.InputMessagePostRendererV1 + + if err := pdk.InputJSON(&input); err != nil { + return fmt.Errorf("failed to parse input json: %w", err) + } + + replacement := "BARTEST" + + if len(input.ExtraArgs) > 0 { + replacement = strings.Join(input.ExtraArgs, " ") + } + + updatedManifests := strings.ReplaceAll(input.Manifests, "FOOTEST", replacement) + + result := schema.OutputMessagePostRendererV1{ + Manifests: updatedManifests, + } + + if err := pdk.OutputJSON(&result); err != nil { + return fmt.Errorf("failed to write output json: %w", err) + } + + return nil +} + +//go:wasmexport helm_plugin_main +func HelmChartRenderer() uint64 { + pdk.Log(pdk.LogDebug, "running postrenderer-v1-extism plugin") + + if err := RunPlugin(); err != nil { + pdk.Log(pdk.LogError, err.Error()) + pdk.SetError(err) + return 1 + } + + return 0 +} + +func main() {} diff --git a/pkg/postrenderer/testdata/plugins/extismv1-str-replace/plugin.yaml b/pkg/postrenderer/testdata/plugins/extismv1-str-replace/plugin.yaml new file mode 100644 index 000000000..f6fade9ca --- /dev/null +++ b/pkg/postrenderer/testdata/plugins/extismv1-str-replace/plugin.yaml @@ -0,0 +1,5 @@ +name: extismv1-str-replace +version: 1.2.3 +type: postrenderer/v1 +apiVersion: v1 +runtime: extism/v1