Skip to content

Commit

Permalink
provide a new install command to generate a helm chart
Browse files Browse the repository at this point in the history
this PR refactors the openshift-template generation logic from `install render` and introduces a helm chart generator on top of that.

helm is the preferred way of deploying services for ARO HCP related services.

https://issues.redhat.com/browse/ARO-11084

Signed-off-by: Gerd Oberlechner <goberlec@redhat.com>
  • Loading branch information
geoberle committed Oct 10, 2024
1 parent 809589e commit b49b387
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 71 deletions.
2 changes: 1 addition & 1 deletion cmd/install/assets/hypershift_operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ type HyperShiftOperatorDeployment struct {
ManagedService string
EnableSizeTagging bool
EnableEtcdRecovery bool
RegistryOverrides string
RegistryOverrides string
}

func (o HyperShiftOperatorDeployment) Build() *appsv1.Deployment {
Expand Down
6 changes: 3 additions & 3 deletions cmd/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ type Options struct {
ManagedService string
EnableSizeTagging bool
EnableEtcdRecovery bool
RegistryOverrides string
RenderNamespace bool
RegistryOverrides string
RenderNamespace bool
}

func (o *Options) Validate() error {
Expand Down Expand Up @@ -688,7 +688,7 @@ func setupOperatorResources(opts Options, userCABundleCM *corev1.ConfigMap, trus
ManagedService: opts.ManagedService,
EnableSizeTagging: opts.EnableSizeTagging,
EnableEtcdRecovery: opts.EnableEtcdRecovery,
RegistryOverrides: opts.RegistryOverrides,
RegistryOverrides: opts.RegistryOverrides,
}.Build()
operatorService := assets.HyperShiftOperatorService{
Namespace: operatorNamespace,
Expand Down
115 changes: 93 additions & 22 deletions cmd/install/install_helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,31 @@ import (
"os"
"strings"

"github.com/openshift/hypershift/pkg/version"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
crclient "sigs.k8s.io/controller-runtime/pkg/client"
)


var helmTemplateParams = TemplateParams{
HyperShiftImage: ".Values.operator.image",
HyperShiftImageTag: ".Values.operator.imageTag",
Namespace: ".Release.Namespace",
OIDCS3Name: ".Values.oidc.s3.name",
OIDCS3Region: ".Values.oidc.s3.region",
OIDCS3CredsSecret: ".Values.oidc.s3.credsSecret",
OIDCS3CredsSecretKey: ".Values.oidc.s3.credsSecretKey",
AWSPrivateRegion: ".Values.aws.private.region",
AWSPrivateCredsSecret: ".Values.aws.private.credsSecret",
AWSPrivateCredsSecretKey: ".Values.aws.private.credsSecretKey",
ExternalDNSCredsSecret: ".Values.externaldns.credsSecret",
ExternalDNSDomainFilter: ".Values.externaldns.domainFilter",
ExternalDNSTxtOwnerID: ".Values.externaldns.txtOwnerId",
ExternalDNSAzureWorkloadIdentity: ".Values.externaldns.azureWorkloadIdentity",
ExternalDNSImage: ".Values.externaldns.image",
RegistryOverrides: "registryOverrides",
TemplateNamespace: false,
Namespace: ".Release.Namespace",
HyperShiftImage: ".Values.image",
OIDCS3Name: ".Values.oidc.s3.name",
OIDCS3Region: ".Values.oidc.s3.region",
OIDCS3CredsSecret: ".Values.oidc.s3.credsSecret",
OIDCS3CredsSecretKey: ".Values.oidc.s3.credsSecretKey",
AWSPrivateRegion: ".Values.aws.private.region",
AWSPrivateCredsSecret: ".Values.aws.private.credsSecret",
AWSPrivateCredsSecretKey: ".Values.aws.private.credsSecretKey",
ExternalDNSCredsSecret: ".Values.externaldns.credsSecret",
ExternalDNSDomainFilter: ".Values.externaldns.domainFilter",
ExternalDNSTxtOwnerID: ".Values.externaldns.txtOwnerId",
ExternalDNSImage: ".Values.externaldns.image",
RegistryOverrides: ".Values.registryOverrides",
TemplateNamespace: false,
TemplateParamWrapper: func(name string) string {
return fmt.Sprintf("{{ %s }}", name)
},
},
}

func NewHelmRenderCommand(opts *Options) *cobra.Command {
Expand All @@ -53,15 +52,87 @@ func NewHelmRenderCommand(opts *Options) *cobra.Command {
if opts.OutputFile == "" {
opts.OutputFile = "./chart"
}
WriteManifestsToDir(crds, fmt.Sprintf("%s/crds", opts.OutputFile))
WriteManifestsToDir(manifests, fmt.Sprintf("%s/templates", opts.OutputFile))
err = writeManifestsToDir(crds, fmt.Sprintf("%s/crds", opts.OutputFile))
if err != nil {
return err
}
err = writeManifestsToDir(manifests, fmt.Sprintf("%s/templates", opts.OutputFile))
if err != nil {
return err
}
err = WriteChartYaml(opts.OutputFile)
if err != nil {
return err
}
err = WriteValuesFile(opts.OutputFile)
if err != nil {
return err
}
return nil
}

return cmd
}

func WriteManifestsToDir(manifests []crclient.Object, dir string) error {
func WriteChartYaml(dir string) error {
data := map[string]interface{}{
"apiVersion": "v2",
"name": "hypershift-operator",
"description": "A Helm chart for the HyperShift Operator",
"type": "application",
"version": "0.1.0",
"appVersion": version.GetRevision(),
}
return writeYamlFile(fmt.Sprintf("%s/Chart.yaml", dir), data)
}

func WriteValuesFile(dir string) error {
data := map[string]interface{}{
"image": "",
"registryOverrides": "",
"oidc": map[string]interface{}{
"s3": map[string]interface{}{
"name": "",
"region": "",
"credsSecret": "",
"credsSecretKey": "",
},
},
"aws": map[string]interface{}{
"private": map[string]interface{}{
"region": "",
"credsSecret": "",
"credsSecretKey": "",
},
},
"externaldns": map[string]interface{}{
"credsSecret": "",
"domainFilter": "",
"txtOwnerId": "",
"image": "",
},
}
return writeYamlFile(fmt.Sprintf("%s/values.yaml", dir), data)
}

func writeYamlFile(path string, data map[string]interface{}) error {
yamlData, err := yaml.Marshal(data)
if err != nil {
return err
}
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()
_, err = file.Write(yamlData)
if err != nil {
return err
}
return nil
}

func writeManifestsToDir(manifests []crclient.Object, dir string) error {
err := os.MkdirAll(dir, os.ModePerm)
if err != nil {
return err
Expand Down
70 changes: 70 additions & 0 deletions cmd/install/install_helm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package install

import (
"bytes"
"io"
"os"
"testing"
)

func ExecuteTestHelmCommand(args []string) ([]byte, error) {
// append helm to args
args = append([]string{"helm"}, args...)
cmd := NewCommand()
cmd.SetArgs(args)
b := bytes.NewBufferString("")
cmd.SetOut(b)
err := cmd.Execute()
if err != nil {
return []byte{}, err
}
return io.ReadAll(b)
}

func TestHelmCommand(t *testing.T) {
// create a folder to hold test data and delete it afterwards
tmpDir, err := os.MkdirTemp("", "test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)

_, err = ExecuteTestHelmCommand([]string{"--output-dir", tmpDir})
if err != nil {
t.Fatal(err)
}

// check if the output directory exists
if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
t.Fatalf("Output directory %s does not exist", tmpDir)
}

// check if the crds directory exists ...
for _, dir := range []string{"crds", "templates"} {
dirPath := tmpDir + "/" + dir
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
t.Fatalf("%s directory %s does not exist", dir, dirPath)
}
files, err := os.ReadDir(dirPath)
if err != nil {
t.Fatal(err)
}
if len(files) == 0 {
t.Fatalf("%s directory is empty", dirPath)
}
}

// check if the Chart.yaml file exists
chartPath := tmpDir + "/Chart.yaml"
if _, err := os.Stat(chartPath); os.IsNotExist(err) {
t.Fatalf("Chart.yaml file %s does not exist", chartPath)
}

// check if the values.yaml file exists
valuesPath := tmpDir + "/values.yaml"
if _, err := os.Stat(valuesPath); os.IsNotExist(err) {
t.Fatalf("values.yaml file %s does not exist", valuesPath)
}


}
79 changes: 56 additions & 23 deletions cmd/install/install_render.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
hyperapi "github.com/openshift/hypershift/support/api"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
crclient "sigs.k8s.io/controller-runtime/pkg/client"
)
Expand All @@ -27,26 +28,25 @@ var (
)

var openshiftTemplateParams = TemplateParams{
HyperShiftImage: "OPERATOR_IMG",
HyperShiftImageTag: "IMAGE_TAG",
Namespace: "NAMESPACE",
OIDCS3Name: "OIDC_S3_NAME",
OIDCS3Region: "OIDC_S3_REGION",
OIDCS3CredsSecret: "OIDC_S3_CREDS_SECRET",
OIDCS3CredsSecretKey: "OIDC_S3_CREDS_SECRET_KEY",
AWSPrivateRegion: "AWS_PRIVATE_REGION",
AWSPrivateCredsSecret: "AWS_PRIVATE_CREDS_SECRET",
AWSPrivateCredsSecretKey: "AWS_PRIVATE_CREDS_SECRET_KEY",
ExternalDNSCredsSecret: "EXTERNAL_DNS_CREDS_SECRET",
ExternalDNSDomainFilter: "EXTERNAL_DNS_DOMAIN_FILTER",
ExternalDNSTxtOwnerID: "EXTERNAL_DNS_TXT_OWNER_ID",
ExternalDNSAzureWorkloadIdentity: "EXTERNAL_DNS_AZURE_WORKLOAD_IDENTITY",
ExternalDNSImage: "EXTERNAL_DNS_IMAGE",
RegistryOverrides: "REGISTRY_OVERRIDES",
TemplateNamespace: true,
HyperShiftImage: "OPERATOR_IMG",
Namespace: "NAMESPACE",
HypershiftOperatorReplicas: "OPERATOR_REPLICAS",
OIDCS3Name: "OIDC_S3_NAME",
OIDCS3Region: "OIDC_S3_REGION",
OIDCS3CredsSecret: "OIDC_S3_CREDS_SECRET",
OIDCS3CredsSecretKey: "OIDC_S3_CREDS_SECRET_KEY",
AWSPrivateRegion: "AWS_PRIVATE_REGION",
AWSPrivateCredsSecret: "AWS_PRIVATE_CREDS_SECRET",
AWSPrivateCredsSecretKey: "AWS_PRIVATE_CREDS_SECRET_KEY",
ExternalDNSCredsSecret: "EXTERNAL_DNS_CREDS_SECRET",
ExternalDNSDomainFilter: "EXTERNAL_DNS_DOMAIN_FILTER",
ExternalDNSTxtOwnerID: "EXTERNAL_DNS_TXT_OWNER_ID",
ExternalDNSImage: "EXTERNAL_DNS_IMAGE",
RegistryOverrides: "REGISTRY_OVERRIDES",
TemplateNamespace: true,
TemplateParamWrapper: func(name string) string {
return fmt.Sprintf("${%s}", name)
},
},
}

func NewRenderCommand(opts *Options) *cobra.Command {
Expand Down Expand Up @@ -138,10 +138,12 @@ func openshiftTemplate(opts *Options) (crclient.Object, error) {
templateParameters := []map[string]string{}
templateParameters = append(
templateParameters,
map[string]string{"name": openshiftTemplateParams.HyperShiftImage, "value": version.HypershiftImageBase},
map[string]string{"name": openshiftTemplateParams.HyperShiftImageTag, "value": version.HypershiftImageTag},
map[string]string{"name": openshiftTemplateParams.HyperShiftImage, "value": fmt.Sprintf("%s:%s", version.HypershiftImageBase, version.HypershiftImageTag)},
map[string]string{"name": openshiftTemplateParams.HypershiftOperatorReplicas, "value": string(opts.HyperShiftOperatorReplicas)},
map[string]string{"name": openshiftTemplateParams.Namespace, "value": opts.Namespace},
)

// oidc S3 parameter
if opts.OIDCStorageProviderS3BucketName != "" {
templateParameters = append(
templateParameters,
Expand All @@ -151,6 +153,8 @@ func openshiftTemplate(opts *Options) (crclient.Object, error) {
map[string]string{"name": openshiftTemplateParams.OIDCS3CredsSecretKey, "value": opts.OIDCStorageProviderS3CredentialsSecretKey},
)
}

// aws private credentials
if opts.AWSPrivateCredentialsSecret != "" {
templateParameters = append(
templateParameters,
Expand All @@ -160,6 +164,7 @@ func openshiftTemplate(opts *Options) (crclient.Object, error) {
)
}

// external DNS
if opts.ExternalDNSProvider != "" && opts.ExternalDNSDomainFilter != "" && opts.ExternalDNSCredentialsSecret != "" {
templateParameters = append(
templateParameters,
Expand All @@ -174,8 +179,18 @@ func openshiftTemplate(opts *Options) (crclient.Object, error) {
}
}

crds, manifests, err := hyperShiftOperatorTemplateManifest(opts, openshiftTemplateParams)
manifests = append(manifests, crds...)
// create manifests
crds, objects, err := hyperShiftOperatorTemplateManifest(opts, openshiftTemplateParams)
objects = append(objects, crds...)
if err != nil {
return nil, err
}

// patch those manifests, where the template parameter placeholder was not injectable with opts (e.g. type mistmatch)
patches := []ObjectPatch{
{Kind: "Deployment", Name: "operator", Path: []string{"spec", "replicas"}, Value: openshiftTemplateParams.TemplateParamWrapper(openshiftTemplateParams.HypershiftOperatorReplicas)},
}
patchedObjects, err := applyPatchesToObjects(objects, patches)
if err != nil {
return nil, err
}
Expand All @@ -188,13 +203,31 @@ func openshiftTemplate(opts *Options) (crclient.Object, error) {
"metadata": map[string]interface{}{
"name": "hypershift-operator-template",
},
"objects": manifests,
"objects": patchedObjects,
"parameters": templateParameters,
},
}
return template, nil
}

func applyPatchesToObjects(objects []crclient.Object, patches []ObjectPatch) ([]crclient.Object, error) {
patchedObjects := make([]crclient.Object, len(objects))
for i, obj := range objects {
content, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return nil, err
}
patchedObject := &unstructured.Unstructured{Object: content}
for _, p := range patches {
if p.CanBeAppliedTo(patchedObject) {
unstructured.SetNestedField(patchedObject.Object, p.Value, p.Path...)
}
}
patchedObjects[i] = patchedObject
}
return patchedObjects, nil
}

func render(objects []crclient.Object, format string, out io.Writer) error {
switch format {
case RenderFormatYaml:
Expand Down
2 changes: 1 addition & 1 deletion cmd/install/install_render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func TestTemplateYamlRendering(t *testing.T) {
t.Fatal("no objects found in template")
}
params := []string{
"OPERATOR_REPLICAS", "OPERATOR_IMG", "IMAGE_TAG", "NAMESPACE",
"OPERATOR_REPLICAS", "OPERATOR_IMG", "NAMESPACE",
"OIDC_S3_NAME", "OIDC_S3_REGION", "OIDC_S3_CREDS_SECRET",
"OIDC_S3_CREDS_SECRET_KEY",
}
Expand Down
Loading

0 comments on commit b49b387

Please sign in to comment.