diff --git a/pkg/app/pipedv1/plugin/kubernetes/toolregistry/registry.go b/pkg/app/pipedv1/plugin/kubernetes/toolregistry/registry.go index d10ca33058..1674dc2459 100644 --- a/pkg/app/pipedv1/plugin/kubernetes/toolregistry/registry.go +++ b/pkg/app/pipedv1/plugin/kubernetes/toolregistry/registry.go @@ -18,35 +18,31 @@ package toolregistry import ( "context" - - "github.com/pipe-cd/pipecd/pkg/app/pipedv1/plugin/toolregistry" ) -// Registry provides functions to get path to the needed tools. -type Registry interface { - Kubectl(ctx context.Context, version string) (string, error) - Kustomize(ctx context.Context, version string) (string, error) - Helm(ctx context.Context, version string) (string, error) +type client interface { + InstallTool(ctx context.Context, name, version, script string) (string, error) } -func NewRegistry(client toolregistry.ToolRegistry) Registry { - return ®istry{ +func NewRegistry(client client) *Registry { + return &Registry{ client: client, } } -type registry struct { - client toolregistry.ToolRegistry +// Registry provides functions to get path to the needed tools. +type Registry struct { + client client } -func (r *registry) Kubectl(ctx context.Context, version string) (string, error) { +func (r *Registry) Kubectl(ctx context.Context, version string) (string, error) { return r.client.InstallTool(ctx, "kubectl", version, kubectlInstallScript) } -func (r *registry) Kustomize(ctx context.Context, version string) (string, error) { +func (r *Registry) Kustomize(ctx context.Context, version string) (string, error) { return r.client.InstallTool(ctx, "kustomize", version, kustomizeInstallScript) } -func (r *registry) Helm(ctx context.Context, version string) (string, error) { +func (r *Registry) Helm(ctx context.Context, version string) (string, error) { return r.client.InstallTool(ctx, "helm", version, helmInstallScript) } diff --git a/pkg/app/pipedv1/plugin/kubernetes/toolregistry/registry_test.go b/pkg/app/pipedv1/plugin/kubernetes/toolregistry/registry_test.go new file mode 100644 index 0000000000..92f75f5789 --- /dev/null +++ b/pkg/app/pipedv1/plugin/kubernetes/toolregistry/registry_test.go @@ -0,0 +1,93 @@ +// Copyright 2024 The PipeCD 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 toolregistry + +import ( + "context" + "os/exec" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/pipe-cd/pipecd/pkg/app/pipedv1/plugin/toolregistry/toolregistrytest" +) + +func TestRegistry_Kubectl(t *testing.T) { + t.Parallel() + + c, err := toolregistrytest.NewToolRegistry(t) + require.NoError(t, err) + + r := NewRegistry(c) + + t.Cleanup(func() { c.Close() }) + + p, err := r.Kubectl(context.Background(), "1.30.2") + require.NoError(t, err) + require.NotEmpty(t, p) + + out, err := exec.CommandContext(context.Background(), p, "version", "--client=true").CombinedOutput() + require.NoError(t, err) + + expected := "Client Version: v1.30.2\nKustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3" + + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(out))) +} + +func TestRegistry_Kustomize(t *testing.T) { + t.Parallel() + + c, err := toolregistrytest.NewToolRegistry(t) + require.NoError(t, err) + + r := NewRegistry(c) + + t.Cleanup(func() { c.Close() }) + + p, err := r.Kustomize(context.Background(), "5.4.3") + require.NoError(t, err) + require.NotEmpty(t, p) + + out, err := exec.CommandContext(context.Background(), p, "version").CombinedOutput() + require.NoError(t, err) + + expected := "v5.4.3" + + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(out))) +} + +func TestRegistry_Helm(t *testing.T) { + t.Parallel() + + c, err := toolregistrytest.NewToolRegistry(t) + require.NoError(t, err) + + r := NewRegistry(c) + + t.Cleanup(func() { c.Close() }) + + p, err := r.Helm(context.Background(), "3.16.1") + require.NoError(t, err) + require.NotEmpty(t, p) + + out, err := exec.CommandContext(context.Background(), p, "version").CombinedOutput() + require.NoError(t, err) + + expected := `version.BuildInfo{Version:"v3.16.1", GitCommit:"5a5449dc42be07001fd5771d56429132984ab3ab", GitTreeState:"clean", GoVersion:"go1.22.7"}` + + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(out))) +} diff --git a/pkg/app/pipedv1/plugin/toolregistry/toolregistrytest/toolregistrytest.go b/pkg/app/pipedv1/plugin/toolregistry/toolregistrytest/toolregistrytest.go new file mode 100644 index 0000000000..ef471c4d72 --- /dev/null +++ b/pkg/app/pipedv1/plugin/toolregistry/toolregistrytest/toolregistrytest.go @@ -0,0 +1,134 @@ +// Copyright 2024 The PipeCD 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 toolregistrytest + +import ( + "bytes" + "context" + "os" + "os/exec" + "runtime" + "testing" + "text/template" +) + +type ToolRegistry struct { + testingT *testing.T + tmpDir string +} + +type templateValues struct { + Name string + Version string + OutPath string + TmpDir string + Arch string + Os string +} + +func NewToolRegistry(t *testing.T) (*ToolRegistry, error) { + tmpDir, err := os.MkdirTemp("", "tool-registry-test") + if err != nil { + return nil, err + } + return &ToolRegistry{ + testingT: t, + tmpDir: tmpDir, + }, nil +} + +func (r *ToolRegistry) newTmpDir() (string, error) { + return os.MkdirTemp(r.tmpDir, "") +} + +func (r *ToolRegistry) binDir() (string, error) { + target := r.tmpDir + "/bin" + if err := os.MkdirAll(target, 0o755); err != nil { + return "", err + } + return target, nil +} + +func (r *ToolRegistry) outPath() (string, error) { + target, err := r.newTmpDir() + if err != nil { + return "", err + } + return target + "/out", nil +} + +func (r *ToolRegistry) InstallTool(ctx context.Context, name, version, script string) (path string, err error) { + outPath, err := r.outPath() + if err != nil { + return "", err + } + + tmpDir, err := r.newTmpDir() + if err != nil { + return "", err + } + + binDir, err := r.binDir() + if err != nil { + return "", err + } + + t, err := template.New("install script").Parse(script) + if err != nil { + return "", err + } + + vars := templateValues{ + Name: name, + Version: version, + OutPath: outPath, + TmpDir: tmpDir, + Arch: runtime.GOARCH, + Os: runtime.GOOS, + } + var buf bytes.Buffer + if err := t.Execute(&buf, vars); err != nil { + return "", err + } + + cmd := exec.CommandContext(ctx, "/bin/sh", "-c", buf.String()) + if out, err := cmd.CombinedOutput(); err != nil { + r.testingT.Log(string(out)) + return "", err + } + + if err := os.Chmod(outPath, 0o755); err != nil { + return "", err + } + + target := binDir + "/" + name + "-" + version + if out, err := exec.CommandContext(ctx, "/bin/sh", "-c", "mv "+outPath+" "+target).CombinedOutput(); err != nil { + r.testingT.Log(string(out)) + return "", err + } + + if err := os.RemoveAll(tmpDir); err != nil { + return "", err + } + + return target, nil +} + +func (r *ToolRegistry) Close() error { + if err := os.RemoveAll(r.tmpDir); err != nil { + return err + } + return nil +}