diff --git a/pkg/app/pipedv1/plugin/kubernetes/provider/kustomize.go b/pkg/app/pipedv1/plugin/kubernetes/provider/kustomize.go new file mode 100644 index 0000000000..819ce13c91 --- /dev/null +++ b/pkg/app/pipedv1/plugin/kubernetes/provider/kustomize.go @@ -0,0 +1,65 @@ +// 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 provider + +import ( + "bytes" + "context" + "fmt" + "os/exec" + + "go.uber.org/zap" +) + +type Kustomize struct { + execPath string + logger *zap.Logger +} + +func NewKustomize(path string, logger *zap.Logger) *Kustomize { + return &Kustomize{ + execPath: path, + logger: logger, + } +} + +func (c *Kustomize) Template(ctx context.Context, appName, appDir string, opts map[string]string) (string, error) { + args := []string{ + "build", + ".", + } + + for k, v := range opts { + args = append(args, fmt.Sprintf("--%s", k)) + if v != "" { + args = append(args, v) + } + } + + var stdout, stderr bytes.Buffer + cmd := exec.CommandContext(ctx, c.execPath, args...) + cmd.Dir = appDir + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + c.logger.Info(fmt.Sprintf("start templating a Kustomize application %s", appName), + zap.Any("args", args), + ) + + if err := cmd.Run(); err != nil { + return stdout.String(), fmt.Errorf("%w: %s", err, stderr.String()) + } + return stdout.String(), nil +} diff --git a/pkg/app/pipedv1/plugin/kubernetes/provider/kustomize_test.go b/pkg/app/pipedv1/plugin/kubernetes/provider/kustomize_test.go new file mode 100644 index 0000000000..1ec350d999 --- /dev/null +++ b/pkg/app/pipedv1/plugin/kubernetes/provider/kustomize_test.go @@ -0,0 +1,55 @@ +// 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 provider + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "github.com/pipe-cd/pipecd/pkg/app/pipedv1/plugin/kubernetes/toolregistry" + "github.com/pipe-cd/pipecd/pkg/app/pipedv1/plugin/toolregistry/toolregistrytest" +) + +func TestKustomizeTemplate(t *testing.T) { + t.Parallel() + + var ( + ctx = context.TODO() + appName = "testapp" + appDir = "testdata/testkustomize" + ) + + c, err := toolregistrytest.NewToolRegistry(t) + require.NoError(t, err) + + r := toolregistry.NewRegistry(c) + + t.Cleanup(func() { c.Close() }) + + kustomizePath, err := r.Kustomize(context.Background(), "5.4.3") + require.NoError(t, err) + require.NotEmpty(t, kustomizePath) + + kustomize := NewKustomize(kustomizePath, zap.NewNop()) + out, err := kustomize.Template(ctx, appName, appDir, map[string]string{ + "load-restrictor": "LoadRestrictionsNone", + }) + require.NoError(t, err) + assert.True(t, len(out) > 0) +} diff --git a/pkg/app/pipedv1/plugin/kubernetes/provider/loader.go b/pkg/app/pipedv1/plugin/kubernetes/provider/loader.go index 87511789e8..54853a6970 100644 --- a/pkg/app/pipedv1/plugin/kubernetes/provider/loader.go +++ b/pkg/app/pipedv1/plugin/kubernetes/provider/loader.go @@ -15,6 +15,7 @@ package provider import ( + "context" "errors" "fmt" "io/fs" @@ -24,6 +25,7 @@ import ( "strconv" "strings" + "go.uber.org/zap" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/yaml" @@ -39,6 +41,7 @@ const ( ) type LoaderInput struct { + AppName string AppDir string ConfigFilename string Manifests []string @@ -46,13 +49,22 @@ type LoaderInput struct { Namespace string TemplatingMethod TemplatingMethod + KustomizeVersion string + KustomizeOptions map[string]string + // TODO: define fields for LoaderInput. } type Loader struct { + toolRegistry ToolRegistry +} + +type ToolRegistry interface { + Kustomize(ctx context.Context, version string) (string, error) + Helm(ctx context.Context, version string) (string, error) } -func (l *Loader) LoadManifests(input LoaderInput) (manifests []Manifest, err error) { +func (l *Loader) LoadManifests(ctx context.Context, input LoaderInput) (manifests []Manifest, err error) { defer func() { // Override namespace if set because ParseManifests does not parse it // if namespace is not explicitly specified in the manifests. @@ -64,7 +76,18 @@ func (l *Loader) LoadManifests(input LoaderInput) (manifests []Manifest, err err case TemplatingMethodHelm: return nil, errors.New("not implemented yet") case TemplatingMethodKustomize: - return nil, errors.New("not implemented yet") + kustomizePath, err := l.toolRegistry.Kustomize(ctx, input.KustomizeVersion) + if err != nil { + return nil, fmt.Errorf("failed to get kustomize tool: %w", err) + } + + k := NewKustomize(kustomizePath, zap.NewNop()) // TODO: pass logger + data, err := k.Template(ctx, input.AppName, input.AppDir, input.KustomizeOptions) + if err != nil { + return nil, fmt.Errorf("failed to template kustomize manifests: %w", err) + } + + return ParseManifests(data) case TemplatingMethodNone: return LoadPlainYAMLManifests(input.AppDir, input.Manifests, input.ConfigFilename) default: diff --git a/pkg/app/pipedv1/plugin/kubernetes/provider/testdata/testkustomize/deployment.yaml b/pkg/app/pipedv1/plugin/kubernetes/provider/testdata/testkustomize/deployment.yaml new file mode 100644 index 0000000000..904afe8742 --- /dev/null +++ b/pkg/app/pipedv1/plugin/kubernetes/provider/testdata/testkustomize/deployment.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: the-deployment +spec: + replicas: 3 + selector: + matchLabels: + deployment: hello + template: + metadata: + labels: + deployment: hello + spec: + containers: + - name: helloworld + image: ghcr.io/pipe-cd/helloworld:v0.49.2 + args: + - server + ports: + - containerPort: 9085 diff --git a/pkg/app/pipedv1/plugin/kubernetes/provider/testdata/testkustomize/kustomization.yaml b/pkg/app/pipedv1/plugin/kubernetes/provider/testdata/testkustomize/kustomization.yaml new file mode 100644 index 0000000000..c7cf5bb89a --- /dev/null +++ b/pkg/app/pipedv1/plugin/kubernetes/provider/testdata/testkustomize/kustomization.yaml @@ -0,0 +1,5 @@ +commonLabels: + app: hello + +resources: + - deployment.yaml \ No newline at end of file