Skip to content

converter for istio in-cluster operator config to sail operator config #616

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Feb 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ spec:
values:
pilot:
traceSampling: 0.1
version: v1.23.2
version: v1.24.3
```

Note that the only field that was added is the `spec.version` field. There are a few situations however where the APIs are different and require different approaches to achieve the same outcome.
Expand Down Expand Up @@ -294,7 +294,7 @@ spec:
pilot:
env:
PILOT_ENABLE_STATUS: "true"
version: v1.23.0
version: v1.24.3
namespace: istio-system
```

Expand All @@ -304,6 +304,37 @@ Note the quotes around the value of `spec.values.pilot.env.PILOT_ENABLE_STATUS`.

Sail Operator's Istio resource does not have a `spec.components` field. Instead, you can enable and disable components directly by setting `spec.values.<component>.enabled: true/false`. Other functionality exposed through `spec.components` like the k8s overlays is not currently available.

### Converter Script
This script is used to convert an Istio in-cluster operator configuration to a Sail Operator configuration. Upon execution, the script takes an input YAML file and istio version and generates a sail operator configuration file.

#### Usage
To run the configuration-converter.sh script, you need to provide four arguments, only input file is required other arguments are optional:

1. Input file path (<input>): The path to your Istio operator configuration YAML file (required).
2. Output file path (<output>): The path where the converted Sail configuration will be saved. If not provided, the script will save the output with -sail.yaml appended to the input file name.
3. Namespace (-n <namespace>): The Kubernetes namespace for the Istio deployment. Defaults to istio-system if not provided.
4. Version (-v <version>): The version of Istio to be used. If not provided, the `spec.version` field will be omitted from the output file and the operator will deploy the latest version when the YAML manifest is applied.

```bash
./tools/configuration-converter.sh </path/to/input.yaml> [/path/to/output.yaml] [-n namespace] [-v version]
```

##### Sample command only with input file:

```bash
./tools/configuration-converter.sh /home/user/istio_config.yaml
```

##### Sample command with custom output, namespace, and version:

```bash
./tools/configuration-converter.sh /home/user/input/istio_config.yaml /home/user/output/output.yaml -n custom-namespace -v v1.24.3
```

> [!WARNING]
> This script is still under development.
> Please verify the resulting configuration carefully after conversion to ensure it meets your expectations and requirements.

### CNI

The CNI plugin's lifecycle is managed separately from the control plane. You will have to create a [IstioCNI resource](#istiocni-resource) to use CNI.
Expand Down
150 changes: 150 additions & 0 deletions pkg/converter/converter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Copyright Istio 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 converter

import (
"fmt"
"os"
"path/filepath"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/istio-ecosystem/sail-operator/pkg/test/project"
"github.com/istio-ecosystem/sail-operator/tests/e2e/util/shell"
. "github.com/onsi/gomega"
"gopkg.in/yaml.v3"
)

var (
converter = filepath.Join(project.RootDir, "tools", "configuration-converter.sh")
istioFile = filepath.Join(project.RootDir, "tools", "istioConfig.yaml")
sailFile = filepath.Join(project.RootDir, "tools", "istioConfig-sail.yaml")
)

func TestConversion(t *testing.T) {
testcases := []struct {
name string
input string
args string
expectedOutput string
}{
{
name: "simple",
input: `apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
name: default
spec:`,
args: fmt.Sprintf("%s %s -n istio-system -v v1.24.3", istioFile, sailFile),
expectedOutput: `apiVersion: sailoperator.io/v1
kind: Istio
metadata:
name: default
spec:
namespace: istio-system
version: v1.24.3`,
},
{
name: "complex",
input: `apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
name: default
spec:
components:
base:
enabled: true
pilot:
enabled: false
values:
global:
externalIstiod: true
operatorManageWebhooks: true
configValidation: false
base:
enableCRDTemplates: true
pilot:
env:
PILOT_ENABLE_STATUS: true`,
args: fmt.Sprintf("-v v1.24.3 %s -n istio-system %s", istioFile, sailFile),
expectedOutput: `apiVersion: sailoperator.io/v1
kind: Istio
metadata:
name: default
spec:
values:
global:
externalIstiod: true
operatorManageWebhooks: true
configValidation: false
base:
enableCRDTemplates: true
enabled: true
pilot:
env:
PILOT_ENABLE_STATUS: "true"
enabled: false
namespace: istio-system
version: v1.24.3`,
},
{
name: "mandatory-arguments-only",
input: `apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
name: default
spec:`,
args: istioFile,
expectedOutput: `apiVersion: sailoperator.io/v1
kind: Istio
metadata:
name: default
spec:
namespace: istio-system`,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
g := NewWithT(t)
t.Cleanup(func() {
g.Expect(os.Remove(istioFile)).To(Succeed())
g.Expect(os.Remove(sailFile)).To(Succeed())
})

g.Expect(os.WriteFile(istioFile, []byte(tc.input), 0o644)).To(Succeed(), "failed to write YAML file")

_, err := shell.ExecuteCommand(converter + " " + tc.args)
g.Expect(err).NotTo(HaveOccurred(), "error in execution of ./configuration-converter.sh")

actualOutput, err := os.ReadFile(sailFile)
g.Expect(err).NotTo(HaveOccurred(), "Cannot read %s", sailFile)

actualData, err := parseYaml(actualOutput)
g.Expect(err).NotTo(HaveOccurred(), "Failed to parse sailFile")

expectedData, err := parseYaml([]byte(tc.expectedOutput))
g.Expect(err).NotTo(HaveOccurred(), "Failed to parse expected output")

g.Expect(cmp.Diff(actualData, expectedData)).To(Equal(""), "Conversion is not as expected")
})
}
}

// parseYaml takes a YAML string and unmarshals it into a map
func parseYaml(yamlContent []byte) (map[string]interface{}, error) {
var config map[string]interface{}
err := yaml.Unmarshal(yamlContent, &config)
return config, err
}
2 changes: 1 addition & 1 deletion tests/e2e/util/shell/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func ExecuteShell(command string, input string) (string, error) {
err := cmd.Run()
if err != nil {
// Return both stdout and stderr to help with debugging
return stdout.String() + stderr.String(), fmt.Errorf("error executing command: %s", stderr.String())
return stdout.String() + stderr.String(), fmt.Errorf("error executing command: %s: %s", cmd, stderr.String())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return stdout.String() + stderr.String(), fmt.Errorf("error executing command: %s: %s", cmd, stderr.String())
return stdout.String() + stderr.String(), fmt.Errorf("error executing command: %s: %s", cmd.String(), stderr.String())

}

return stdout.String(), nil
Expand Down
131 changes: 131 additions & 0 deletions tools/configuration-converter.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#!/bin/bash

# Copyright Istio 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.

# This script is used to convert istio configuration to sail operator configuration.
# In the end of the execution new yaml file will be created with "sail-ISTIO_CONFIG_YAML" name.
# Usage: ./configuration-converter.sh ISTIO_CONFIG_YAML_WITH_PATH, example: ./configuration-converter.sh sample_config.yaml"
set -e

# Function to show usage
usage() {
echo "Usage: $0 <input> [output] [-n <namespace>] [-v <version>]"
echo " <input> : Input file (required)"
echo " [output] : Output file (optional, defaults to input's file name with '-sail.yaml' suffix)"
echo " -n <namespace> : Namespace (optional, defaults to 'istio-system')"
echo " -v <version> : Istio Version (optional)"
exit 1
}

# Initialize variables
INPUT=""
OUTPUT=""
NAMESPACE="istio-system"
VERSION=""

while [[ $# -gt 0 ]]; do
case "$1" in
-n)
if [[ -z "$2" || "$2" == -* ]]; then
echo "Error: -n requires a non-empty argument."
exit 1
fi
NAMESPACE="$2"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:
If the user accidentally supplies "-n" without the value (similarly for version), it will not work as we are not checking the value of $2.

shift 2
;;
-v)
if [[ -z "$2" || "$2" == -* ]]; then
echo "Error: -v requires a non-empty argument."
exit 1
fi
VERSION="$2"
shift 2
;;
*)
if [[ -z "$INPUT" ]]; then
INPUT="$1" # First positional argument is the INPUT
elif [[ -z "$OUTPUT" ]]; then
OUTPUT="$1" # Second positional argument is the OUTPUT
fi
shift
;;
esac
done

# Ensure the input file is provided and is valid
if [[ -z "$INPUT" || ! -f "$INPUT" ]]; then
echo "Error: Input file is missing or invalid."
usage
fi

# If OUTPUT is not specified, generate a default output file name
if [[ -z "$OUTPUT" ]]; then
OUTPUT="$(dirname "$INPUT")/$(basename "$INPUT" .yaml)-sail.yaml"
elif [[ -d "$OUTPUT" ]]; then
echo "Error: OUTPUT must be a file, not a directory."
exit 1
fi

if ! command -v yq &>/dev/null; then
echo "Error: 'yq' is not installed. Please install it before running the script."
exit 1
fi

function add_mandatory_fields(){
yq -i eval ".apiVersion = \"sailoperator.io/v1\"
| .kind = \"Istio\"
| (select(.spec.meshConfig) | .spec.values.meshConfig) = .spec.meshConfig
| (select(.spec.values.istio_cni) | .spec.values.pilot.cni) = .spec.values.istio_cni
| .metadata.name = \"default\"
| .spec.namespace = \"$NAMESPACE\"
| del(.spec.values.istio_cni)
| del(.spec.meshConfig)
| del(.spec.hub)
| del(.spec.tag)
| del(.spec.values.gateways)" "$OUTPUT"

# If VERSION is not empty, add .spec.version
if [[ -n "$VERSION" ]]; then
yq -i ".spec.version = \"$VERSION\"" "$OUTPUT"
fi

}

function boolean_2_string(){
#Convert boolean values to string if they are under *.env
yq -i -e '
(.spec.values.[].env.[] | select(. == true)) |= "true" |
(.spec.values.[].env.[] | select(. == false)) |= "false" |
del(.. | select(length == 0))' "$OUTPUT"
}

# Note that if there is an entry except spec.components.<component>.enabled: true/false converter will delete them and warn user
function validate_spec_components(){
if [[ $(yq eval '.spec.components' "$OUTPUT") != "null" ]]; then
yq -i 'del(.spec.components.[] | keys[] | select(. != "enabled")) | .spec.values *= .spec.components | del (.spec.components)' "$OUTPUT"
echo "Only values in the format spec.components.<component>.enabled: true/false are supported for conversion. For more details, refer to the documentation: https://github.com/istio-ecosystem/sail-operator/tree/main/docs#components-field"
fi
}

# create output file
cp "$INPUT" "$OUTPUT"

# in-place edit created output file
add_mandatory_fields
boolean_2_string
validate_spec_components


echo "Sail configuration file created with name: $(realpath "$OUTPUT")"