chore(clustertool): remove SCALE related features
This commit is contained in:
@@ -1,27 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var scaleLongHelp = strings.TrimSpace(`
|
||||
These are all commands that are exclusively intended to migrate away from TrueNAS SCALE Kubernetes Apps to a normal kubernets cluster.
|
||||
|
||||
They will all, at a later date, be removed.
|
||||
|
||||
`)
|
||||
|
||||
var scaleCmd = &cobra.Command{
|
||||
Use: "scale",
|
||||
Short: "Commands for handling TrueNAS SCALE",
|
||||
Example: "clustertool scale export",
|
||||
Long: advLongHelp,
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(scaleCmd)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/truecharts/public/clustertool/pkg/scale"
|
||||
)
|
||||
|
||||
var scaleExportLongHelp = strings.TrimSpace(`
|
||||
|
||||
`)
|
||||
|
||||
var scaleexport = &cobra.Command{
|
||||
Use: "export",
|
||||
Short: "Export SCALE Apps to file",
|
||||
Example: "clustertool scale export",
|
||||
Long: scaleExportLongHelp,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
scale.ExportApps()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
scaleCmd.AddCommand(scaleexport)
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/truecharts/public/clustertool/pkg/scale"
|
||||
)
|
||||
|
||||
var scaleMigrateLongHelp = strings.TrimSpace(`
|
||||
|
||||
`)
|
||||
|
||||
var scalemigrate = &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Migrate exported SCALE Apps to the Talos Cluster",
|
||||
Example: "clustertool scale migrate",
|
||||
Long: scaleMigrateLongHelp,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := scale.ProcessJSONFiles("./truenas_exports")
|
||||
if err != nil {
|
||||
log.Info().Msgf("Error: %v", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
scaleCmd.AddCommand(scalemigrate)
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package scale
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func ExportApps() {
|
||||
log.Info().Msg("Starting the ExportApps process")
|
||||
|
||||
// Execute the command and capture its output
|
||||
cmd := exec.Command("midclt", "call", "chart.release.query")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
log.Trace().Strs("command", cmd.Args).Msg("Executing command to fetch chart releases")
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Error().Err(err).Msgf("Error executing command: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
log.Info().Msg("Successfully executed command to fetch chart releases")
|
||||
|
||||
// Parse the JSON output
|
||||
var releases []map[string]interface{}
|
||||
log.Debug().Msg("Parsing JSON output from command")
|
||||
if err := json.Unmarshal(out.Bytes(), &releases); err != nil {
|
||||
log.Error().Err(err).Msgf("Error parsing JSON: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
log.Info().Int("release_count", len(releases)).Msg("Parsed releases from JSON output")
|
||||
|
||||
// Ensure the directory exists
|
||||
outputDir := "./truenas_exports"
|
||||
log.Trace().Str("directory", outputDir).Msg("Checking if output directory exists")
|
||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||
log.Error().Err(err).Msgf("Error creating directory: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
log.Info().Msg("Output directory ensured successfully")
|
||||
|
||||
// Save each release to a separate file
|
||||
for _, release := range releases {
|
||||
// Extract the release name
|
||||
name, ok := release["name"].(string)
|
||||
if !ok {
|
||||
log.Warn().Msg("Release does not have a name field or it is not a string")
|
||||
continue
|
||||
}
|
||||
|
||||
// Marshal the release data to JSON
|
||||
data, err := json.MarshalIndent(release, "", " ")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Error marshaling JSON for release %s: %v\n", name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Create the filename using the release name
|
||||
filename := filepath.Join(outputDir, fmt.Sprintf("%s.json", name))
|
||||
log.Debug().Str("filename", filename).Msg("Writing release data to file")
|
||||
|
||||
if err := ioutil.WriteFile(filename, data, 0644); err != nil {
|
||||
log.Error().Err(err).Msgf("Error writing file %s: %v\n", filename, err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Info().Str("release_name", name).Msg("Successfully wrote release data to file")
|
||||
}
|
||||
|
||||
log.Info().Msg("ExportApps process completed")
|
||||
}
|
||||
@@ -1,414 +0,0 @@
|
||||
package scale
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/truecharts/public/clustertool/pkg/fluxhandler"
|
||||
"github.com/truecharts/public/clustertool/pkg/helper"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type RadarrConfig struct {
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
Info map[string]interface{} `json:"info"`
|
||||
Config map[string]interface{} `json:"config"`
|
||||
ChartMetadata map[string]interface{} `json:"chart_metadata"`
|
||||
}
|
||||
|
||||
// removeKeysStartingWithIX recursively removes keys starting with "ix" or "IX" from a map
|
||||
func removeKeysStartingWithIX(data map[string]interface{}) {
|
||||
for key := range data {
|
||||
lowerKey := strings.ToLower(key)
|
||||
if strings.HasPrefix(lowerKey, "ix") {
|
||||
delete(data, key)
|
||||
} else {
|
||||
// If the value is a nested map, recursively remove keys
|
||||
if nestedMap, ok := data[key].(map[string]interface{}); ok {
|
||||
removeKeysStartingWithIX(nestedMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mergeLists merges "somethingList" with "something" based on the "name" key
|
||||
func mergeLists(config map[string]interface{}) {
|
||||
for key, value := range config {
|
||||
if strings.HasSuffix(key, "List") {
|
||||
baseKey := strings.TrimSuffix(key, "List")
|
||||
if baseMap, ok := config[baseKey].(map[string]interface{}); ok {
|
||||
if list, ok := value.([]interface{}); ok {
|
||||
for _, item := range list {
|
||||
if itemMap, ok := item.(map[string]interface{}); ok {
|
||||
if name, exists := itemMap["name"].(string); exists {
|
||||
baseMap[name] = itemMap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newMap := make(map[string]interface{})
|
||||
if list, ok := value.([]interface{}); ok {
|
||||
for _, item := range list {
|
||||
if itemMap, ok := item.(map[string]interface{}); ok {
|
||||
if name, exists := itemMap["name"].(string); exists {
|
||||
newMap[name] = itemMap
|
||||
}
|
||||
}
|
||||
}
|
||||
config[baseKey] = newMap
|
||||
}
|
||||
}
|
||||
delete(config, key)
|
||||
} else {
|
||||
// If the value is a nested map, recursively merge lists
|
||||
if nestedMap, ok := value.(map[string]interface{}); ok {
|
||||
mergeLists(nestedMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeSpecificKeys removes specific keys from the root-level of config/values
|
||||
func removeSpecificKeys(config map[string]interface{}) {
|
||||
specificKeys := []string{
|
||||
"donateNag",
|
||||
"docs",
|
||||
"image",
|
||||
"expertPodOpts",
|
||||
"advanced",
|
||||
"portal",
|
||||
"requests",
|
||||
"global",
|
||||
}
|
||||
|
||||
for _, key := range specificKeys {
|
||||
deleteKeyRecursive(config, key)
|
||||
}
|
||||
}
|
||||
|
||||
// deleteKeyRecursive recursively deletes a key from config and its nested maps
|
||||
func deleteKeyRecursive(config interface{}, key string) {
|
||||
switch c := config.(type) {
|
||||
case map[string]interface{}:
|
||||
if _, ok := c[key]; ok {
|
||||
delete(c, key)
|
||||
} else {
|
||||
for _, v := range c {
|
||||
deleteKeyRecursive(v, key)
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
for _, v := range c {
|
||||
deleteKeyRecursive(v, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeEmptyLists recursively removes empty lists from the config
|
||||
func removeEmptyLists(config map[string]interface{}) {
|
||||
for key, value := range config {
|
||||
if list, ok := value.([]interface{}); ok && len(list) == 0 {
|
||||
delete(config, key)
|
||||
} else if nestedMap, ok := value.(map[string]interface{}); ok {
|
||||
removeEmptyLists(nestedMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeEmptyDicts recursively removes empty dictionaries from the config
|
||||
func removeEmptyDicts(config map[string]interface{}) {
|
||||
for key, value := range config {
|
||||
if subMap, ok := value.(map[string]interface{}); ok {
|
||||
if len(subMap) == 0 {
|
||||
delete(config, key)
|
||||
} else {
|
||||
removeEmptyDicts(subMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeIXPrefix removes the "ix-" prefix from the namespace
|
||||
func removeIXPrefix(namespace string) string {
|
||||
if strings.HasPrefix(namespace, "ix-") {
|
||||
return namespace[3:]
|
||||
}
|
||||
return namespace
|
||||
}
|
||||
|
||||
// removePortalOpenDisabled removes portal.open.enabled: false if nothing else is defined under portal.open
|
||||
func removePortalOpenDisabled(config map[string]interface{}) {
|
||||
removeKeyIfValueRecursive(config, "portal.open.enabled", false)
|
||||
}
|
||||
|
||||
// removeAdvancedSettings removes instances of advanced: false or advanced: true
|
||||
func removeAdvancedSettings(config map[string]interface{}) {
|
||||
removeKeyIfValueRecursive(config, "advanced", false)
|
||||
removeKeyIfValueRecursive(config, "advanced", true)
|
||||
}
|
||||
|
||||
// removeKeyIfValueRecursive recursively deletes a key in config based on a dot-separated key path and value
|
||||
func removeKeyIfValueRecursive(config interface{}, keyPath string, value interface{}) {
|
||||
switch c := config.(type) {
|
||||
case map[string]interface{}:
|
||||
keys := strings.Split(keyPath, ".")
|
||||
if len(keys) == 1 {
|
||||
if c[keys[0]] == value {
|
||||
delete(c, keys[0])
|
||||
}
|
||||
} else {
|
||||
if v, ok := c[keys[0]]; ok {
|
||||
removeKeyIfValueRecursive(v, strings.Join(keys[1:], "."), value)
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
for _, v := range c {
|
||||
removeKeyIfValueRecursive(v, keyPath, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeSpecificKeyValues removes specific key-value pairs from the config
|
||||
func removeSpecificKeyValues(config map[string]interface{}) {
|
||||
specificKeyValues := map[string]interface{}{
|
||||
"amd.com/gpu": float64(0), // Use float64(0) to match JSON unmarshal output
|
||||
"cpu": "4000m",
|
||||
"gpu.intel.com/i915": float64(0), // Use float64(0) to match JSON unmarshal output
|
||||
"memory": "8Gi",
|
||||
"nvidia.com/gpu": float64(0), // Use float64(0) to match JSON unmarshal output
|
||||
"PUID": float64(568), // Use float64(568) to match JSON unmarshal output
|
||||
"UMASK": "\"0022\"",
|
||||
"fsGroup": float64(568), // Use float64(568) to match JSON unmarshal output
|
||||
"fsGroupChangePolicy": "Always",
|
||||
"readOnly": false,
|
||||
"size": "256Gi",
|
||||
"mode": "disabled",
|
||||
"type": "pvc",
|
||||
"runAsNonRoot": false,
|
||||
}
|
||||
|
||||
removeSpecificKeyValuesRecursive(config, specificKeyValues)
|
||||
}
|
||||
|
||||
// removeSpecificKeyValuesRecursive recursively deletes specific key-value pairs from the config
|
||||
func removeSpecificKeyValuesRecursive(config interface{}, specificKeyValues map[string]interface{}) {
|
||||
switch c := config.(type) {
|
||||
case map[string]interface{}:
|
||||
for key, value := range c {
|
||||
if expectedValue, ok := specificKeyValues[key]; ok {
|
||||
switch expectedValue := expectedValue.(type) {
|
||||
case float64:
|
||||
if actualValue, ok := value.(float64); ok && actualValue == expectedValue {
|
||||
delete(c, key)
|
||||
}
|
||||
case string:
|
||||
if actualValue, ok := value.(string); ok && actualValue == expectedValue {
|
||||
delete(c, key)
|
||||
}
|
||||
// Add more cases for other types if needed
|
||||
}
|
||||
} else {
|
||||
removeSpecificKeyValuesRecursive(value, specificKeyValues)
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
for _, v := range c {
|
||||
removeSpecificKeyValuesRecursive(v, specificKeyValues)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deleteNestedKeyRecursive recursively deletes a nested key in config based on a dot-separated key path
|
||||
func deleteNestedKeyRecursive(config map[string]interface{}, keyPath string) {
|
||||
keys := strings.Split(keyPath, ": ")
|
||||
if len(keys) != 2 {
|
||||
return
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(keys[0])
|
||||
value := strings.TrimSpace(keys[1])
|
||||
|
||||
if section, ok := config[key]; ok {
|
||||
if submap, ok := section.(map[string]interface{}); ok {
|
||||
if val, exists := submap[key]; exists {
|
||||
if val == value {
|
||||
delete(submap, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range config {
|
||||
if subMap, ok := v.(map[string]interface{}); ok {
|
||||
deleteNestedKeyRecursive(subMap, keyPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// processVolsyncEntries ensures that if "src.enabled" is true, "dest.enabled" is also true in "volsync" entries
|
||||
func processVolsyncEntries(config map[string]interface{}) {
|
||||
if persistence, ok := config["persistence"].(map[string]interface{}); ok {
|
||||
for _, value := range persistence {
|
||||
if volsync, ok := value.(map[string]interface{})["volsync"].(map[string]interface{}); ok {
|
||||
if src, ok := volsync["src"].(map[string]interface{}); ok {
|
||||
if enabled, ok := src["enabled"].(bool); ok && enabled {
|
||||
// TODO remove when we have snapshot compatible backend and are out of ALPHA
|
||||
src["enabled"] = false
|
||||
if dest, ok := volsync["dest"].(map[string]interface{}); ok {
|
||||
// TODO: enable when we have a snapshot-compatible backend
|
||||
dest["enabled"] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateHelmValuesFromJSON generates HelmRelease YAML from JSON configuration
|
||||
func GenerateHelmValuesFromJSON(inputFile string, outputFile string) error {
|
||||
// Read JSON file
|
||||
jsonFile, err := os.Open(inputFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open JSON file: %w", err)
|
||||
}
|
||||
defer jsonFile.Close()
|
||||
|
||||
byteValue, err := ioutil.ReadAll(jsonFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read JSON file: %w", err)
|
||||
}
|
||||
|
||||
var radarrConfig RadarrConfig
|
||||
err = json.Unmarshal(byteValue, &radarrConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal JSON: %w", err)
|
||||
}
|
||||
|
||||
// Transformations and deletions
|
||||
removeKeysStartingWithIX(radarrConfig.Config)
|
||||
removeSpecificKeys(radarrConfig.Config)
|
||||
mergeLists(radarrConfig.Config)
|
||||
removeEmptyLists(radarrConfig.Config)
|
||||
|
||||
radarrConfig.Namespace = removeIXPrefix(radarrConfig.Namespace)
|
||||
removePortalOpenDisabled(radarrConfig.Config)
|
||||
removeAdvancedSettings(radarrConfig.Config)
|
||||
removeSpecificKeyValues(radarrConfig.Config)
|
||||
removeEmptyDicts(radarrConfig.Config)
|
||||
processVolsyncEntries(radarrConfig.Config) // Add this line to call the new function
|
||||
|
||||
// Populate HelmRelease structure
|
||||
helmRelease := fluxhandler.HelmRelease{
|
||||
APIVersion: "helm.toolkit.fluxcd.io/v2", // default value
|
||||
Kind: "HelmRelease", // default value
|
||||
Metadata: fluxhandler.Metadata{
|
||||
Name: radarrConfig.Name,
|
||||
Namespace: radarrConfig.Namespace,
|
||||
},
|
||||
Spec: fluxhandler.Spec{
|
||||
Interval: "15m",
|
||||
ReleaseName: radarrConfig.Name, // Assuming release name should be same as the chart name
|
||||
Chart: fluxhandler.Chart{
|
||||
Spec: fluxhandler.ChartSpec{
|
||||
Chart: radarrConfig.ChartMetadata["name"].(string),
|
||||
Version: radarrConfig.ChartMetadata["version"].(string),
|
||||
SourceRef: fluxhandler.SourceRef{
|
||||
Kind: "HelmRepository",
|
||||
Name: "truecharts",
|
||||
Namespace: "flux-system",
|
||||
},
|
||||
},
|
||||
},
|
||||
Values: radarrConfig.Config, // Dynamic configuration
|
||||
},
|
||||
}
|
||||
|
||||
// Create directories if they do not exist
|
||||
outputDir := filepath.Dir(outputFile)
|
||||
err = os.MkdirAll(outputDir, 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directories: %w", err)
|
||||
}
|
||||
|
||||
// Write YAML file
|
||||
yamlData, err := yaml.Marshal(&helmRelease)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal YAML: %w", err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(outputFile, yamlData, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write YAML file: %w", err)
|
||||
}
|
||||
|
||||
log.Info().Msg("HelmRelease YAML file created successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetJSONFilesFromDir returns a list of JSON files in a directory
|
||||
func GetJSONFilesFromDir(dir string) ([]string, error) {
|
||||
var jsonFiles []string
|
||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() && strings.HasSuffix(info.Name(), ".json") {
|
||||
jsonFiles = append(jsonFiles, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to walk directory: %w", err)
|
||||
}
|
||||
return jsonFiles, nil
|
||||
}
|
||||
|
||||
// ProcessJSONFiles processes JSON files and generates HelmRelease YAML files
|
||||
func ProcessJSONFiles(inputDir string) error {
|
||||
jsonFiles, err := GetJSONFilesFromDir(inputDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, jsonFile := range jsonFiles {
|
||||
// Read JSON file to get the release name
|
||||
jsonData, err := ioutil.ReadFile(jsonFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read JSON file %s: %w", jsonFile, err)
|
||||
}
|
||||
|
||||
var radarrConfig RadarrConfig
|
||||
err = json.Unmarshal(jsonData, &radarrConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal JSON from file %s: %w", jsonFile, err)
|
||||
}
|
||||
|
||||
// Use release name for directory path
|
||||
outputDir := path.Join("./clusters/", helper.ClusterName, "/kubernetes/apps/", radarrConfig.Name, "/app/")
|
||||
|
||||
// Ensure output directory exists
|
||||
err = os.MkdirAll(outputDir, 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory %s: %w", outputDir, err)
|
||||
}
|
||||
|
||||
outputFile := filepath.Join(outputDir, "helm-release.yaml")
|
||||
|
||||
// Generate Helm values YAML
|
||||
err = GenerateHelmValuesFromJSON(jsonFile, outputFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate Helm values from file %s: %w", jsonFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user