Skip to content

Commit

Permalink
Support K8sApp by pipectl init in simplest way (#4759)
Browse files Browse the repository at this point in the history
* Add expected YAML for kustomize

Signed-off-by: t-kikuc <[email protected]>

* Add Kustomize pattern for pipectl init

Signed-off-by: t-kikuc <[email protected]>

* Add Helm pattern for pipectl init

Signed-off-by: t-kikuc <[email protected]>

* Add options for Helm

Signed-off-by: t-kikuc <[email protected]>

* fix typo

Signed-off-by: t-kikuc <[email protected]>

* Fix nits

Signed-off-by: t-kikuc <[email protected]>

---------

Signed-off-by: t-kikuc <[email protected]>
  • Loading branch information
t-kikuc authored Feb 24, 2024
1 parent 9e5ad3d commit 9a298a8
Show file tree
Hide file tree
Showing 9 changed files with 404 additions and 21 deletions.
2 changes: 1 addition & 1 deletion pkg/app/pipectl/cmd/initialize/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func generateConfig(ctx context.Context, input cli.Input, p prompt.Prompt) error
var cfg *genericConfig
switch platform {
case platformKubernetes:
panic("not implemented")
cfg, err = generateKubernetesConfig(p)
case platformECS:
cfg, err = generateECSConfig(p)
default:
Expand Down
212 changes: 212 additions & 0 deletions pkg/app/pipectl/cmd/initialize/kubernetes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// 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 initialize

import (
"fmt"

"github.com/pipe-cd/pipecd/pkg/app/pipectl/cmd/initialize/prompt"
"github.com/pipe-cd/pipecd/pkg/config"
)

// Use genericConfigs in order to simplify using the GenericApplicationSpec and keep the order as we want.
type genericKubernetesApplicationSpec struct {
Name string `json:"name"`
Input config.KubernetesDeploymentInput `json:"input"`
Description string `json:"description,omitempty"`
}

func generateKubernetesConfig(p prompt.Prompt) (*genericConfig, error) {
// inputs
var (
appName string
manifestManager string
)

const (
// select how to manage manifests
kustomize string = "0" // for Kustomize
helm string = "1" // for Helm
)

inputs := []prompt.Input{
{
Message: "Name of the application",
TargetPointer: &appName,
Required: true,
},
{
Message: fmt.Sprintf("How to manage manifests? [%s]Kustomize [%s]Helm", kustomize, helm),
TargetPointer: &manifestManager,
Required: true,
},
}

err := p.RunSlice(inputs)
if err != nil {
return nil, err
}

var deploymentInput *config.KubernetesDeploymentInput
switch manifestManager {
case kustomize:
deploymentInput, err = kustomizeInput(p)
case helm:
deploymentInput, err = helmInput(p)
default:
return nil, fmt.Errorf("invalid manifest manager: %s", manifestManager)
}
if err != nil {
return nil, err
}

spec := &genericKubernetesApplicationSpec{
Name: appName,
Input: *deploymentInput,
Description: "Generated by `pipectl init`. See https://pipecd.dev/docs/user-guide/configuration-reference/ for more.",
}

return &genericConfig{
Kind: config.KindKubernetesApp,
APIVersion: config.VersionV1Beta1,
ApplicationSpec: spec,
}, nil
}

// Genarate a KubernetesDeploymentInput for Kustomize
func kustomizeInput(p prompt.Prompt) (*config.KubernetesDeploymentInput, error) {
var (
kustomizeVersion string
)
input := prompt.Input{
Message: "Kustomize version",
TargetPointer: &kustomizeVersion,
Required: false,
}

err := p.Run(input)
if err != nil {
return nil, err
}

deploymentInput := &config.KubernetesDeploymentInput{
KustomizeVersion: kustomizeVersion,
}

return deploymentInput, nil
}

func helmInput(p prompt.Prompt) (*config.KubernetesDeploymentInput, error) {
var (
helmVersion string

// remote chart
chartRepository string
chartName string
chartVersion string
// local chart
chartPath string

// helm options
releaseName string
valueFiles []string
)

basicInputs := []prompt.Input{
{
Message: "Helm version",
TargetPointer: &helmVersion,
Required: false,
},
{
Message: "Helm Chart Repository (if empty, use local chart)",
TargetPointer: &chartRepository,
Required: false,
},
}

// Helm options
helmOptionsInputs := []prompt.Input{
{
Message: "Release name of helm deployment",
TargetPointer: &releaseName,
Required: false,
},
{
Message: "Value files (separated by space)",
TargetPointer: &valueFiles,
Required: false,
},
}

err := p.RunSlice(basicInputs)
if err != nil {
return nil, err
}

if chartRepository != "" {
// Use remote chart
remoteChartInputs := []prompt.Input{
{
Message: "Name of the Helm Chart",
TargetPointer: &chartName,
Required: true,
},
{
Message: "Version of the Helm Chart",
TargetPointer: &chartVersion,
Required: true,
},
}
err = p.RunSlice(remoteChartInputs)
if err != nil {
return nil, err
}
} else {
// Use local chart
localChartInputs := []prompt.Input{
{
Message: "Relative path from the repository root to the chart directory",
TargetPointer: &chartPath,
Required: true,
},
}
err = p.RunSlice(localChartInputs)
if err != nil {
return nil, err
}
}

err = p.RunSlice(helmOptionsInputs)
if err != nil {
return nil, err
}

deploymentInput := &config.KubernetesDeploymentInput{
HelmChart: &config.InputHelmChart{
Repository: chartRepository,
Name: chartName,
Version: chartVersion,
Path: chartPath,
},
HelmOptions: &config.InputHelmOptions{
ReleaseName: releaseName,
ValueFiles: valueFiles,
},
HelmVersion: helmVersion,
}

return deploymentInput, nil
}
122 changes: 122 additions & 0 deletions pkg/app/pipectl/cmd/initialize/kubernetes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// 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 initialize

import (
"os"
"strings"
"testing"

"github.com/goccy/go-yaml"
"github.com/stretchr/testify/assert"

"github.com/pipe-cd/pipecd/pkg/app/pipectl/cmd/initialize/prompt"
"github.com/pipe-cd/pipecd/pkg/config"
)

func TestGenerateKubernetesConfig(t *testing.T) {
t.Parallel()

testcases := []struct {
name string
inputs string // mock for user's input
expectedFile string
expectedErr bool
}{
// Kustomize
{
name: "valid inputs for Kustomize",
inputs: `myApp
0
5.3.0
`,
expectedFile: "testdata/k8s-app-kustomize.yaml",
expectedErr: false,
},
{
name: "Kustomize specific fields are all empty",
inputs: `myApp
0
`,
expectedFile: "testdata/k8s-app-kustomize-empty.yaml",
expectedErr: false,
},
// Helm
{
name: "valid inputs for Helm remote chart",
inputs: `myApp
1
3.13.1
oci://ghcr.io/pipe-cd
chart/helloworld
v0.30.0
helm-remote-chart
values1.yaml values2.yaml
`,
expectedFile: "testdata/k8s-app-helm-remote.yaml",
expectedErr: false,
},
{
name: "valid inputs for Helm local chart",
inputs: `myApp
1
3.13.1
../../local-modules/helm-charts/helloworld
helm-local-chart
values1.yaml values2.yaml
`,
expectedFile: "testdata/k8s-app-helm-local.yaml",
expectedErr: false,
},
// Common in Kustomize & Helm
{
name: "Kustomize specific fields are all empty",
inputs: `myApp
0
`,
expectedFile: "testdata/k8s-app-kustomize-empty.yaml",
expectedErr: false,
},
}

for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
reader := strings.NewReader(tc.inputs)
prompt := prompt.NewPrompt(reader)

// Generate the config
cfg, err := generateKubernetesConfig(prompt)
assert.Equal(t, tc.expectedErr, err != nil)

if err == nil {
// Compare the YAML output
yml, err := yaml.Marshal(cfg)
assert.NoError(t, err)
file, err := os.ReadFile(tc.expectedFile)
assert.NoError(t, err)
assert.Equal(t, string(file), string(yml))

// Check if the YAML output is compatible with the original Config model
_, err = config.DecodeYAML(yml)
assert.NoError(t, err)
}
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: pipecd.dev/v1beta1
kind: KubernetesApp
spec:
name: myApp
input: {}
description: Generated by `pipectl init`. See https://pipecd.dev/docs/user-guide/configuration-reference/ for more.
14 changes: 14 additions & 0 deletions pkg/app/pipectl/cmd/initialize/testdata/k8s-app-helm-local.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: pipecd.dev/v1beta1
kind: KubernetesApp
spec:
name: myApp
input:
helmVersion: 3.13.1
helmChart:
path: ../../local-modules/helm-charts/helloworld
helmOptions:
releaseName: helm-local-chart
valueFiles:
- values1.yaml
- values2.yaml
description: Generated by `pipectl init`. See https://pipecd.dev/docs/user-guide/configuration-reference/ for more.
16 changes: 16 additions & 0 deletions pkg/app/pipectl/cmd/initialize/testdata/k8s-app-helm-remote.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: pipecd.dev/v1beta1
kind: KubernetesApp
spec:
name: myApp
input:
helmVersion: 3.13.1
helmChart:
repository: oci://ghcr.io/pipe-cd
name: chart/helloworld
version: v0.30.0
helmOptions:
releaseName: helm-remote-chart
valueFiles:
- values1.yaml
- values2.yaml
description: Generated by `pipectl init`. See https://pipecd.dev/docs/user-guide/configuration-reference/ for more.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: pipecd.dev/v1beta1
kind: KubernetesApp
spec:
name: myApp
input: {}
description: Generated by `pipectl init`. See https://pipecd.dev/docs/user-guide/configuration-reference/ for more.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: pipecd.dev/v1beta1
kind: KubernetesApp
spec:
name: myApp
input:
kustomizeVersion: 5.3.0
description: Generated by `pipectl init`. See https://pipecd.dev/docs/user-guide/configuration-reference/ for more.
Loading

0 comments on commit 9a298a8

Please sign in to comment.