diff --git a/cli/README.md b/cli/README.md index 99957de..1bf6204 100644 --- a/cli/README.md +++ b/cli/README.md @@ -46,4 +46,35 @@ Running reads the connection and session details needed for connecting to a server from `connection-session-config.yaml` and sends a config TX. It creates directories in `local/config` with the respective certificates, a yaml file, named shared_cluster_config.yml, that includes the cluster configuration -and a yaml file, named version.yml, that includes the version. \ No newline at end of file +and a yaml file, named version.yml, that includes the version. + + + +#### Set Config Command +1. Run from 'orion-sdk' root folder. +2. For Set Config Run: + 2.1 `bin/bcdbadmin config get [args]`. + 2.2 `bin/bcdbadmin config set [args]`. + + Replace `[args]` with corresponding flags. The flags for config get are detailed in the table above. + +### +##### Flags +| Flags | Description | +|-----------------------------------|--------------------------------------------------------------------------| +| `-d, --db-connection-config-path` | the absolute or relative path of CLI connection configuration file | +| `-c, --cluster-config-path` | the absolute or relative path to the new cluster configuration yaml file | + +Both flags are necessary flags. If any flag is missing, the cli will raise an error. + +NOTE: the new cluster configuration yaml file should be named as: "new_cluster_config.yml" and should be located in the same directory as the directory given as flag via the GET command. + + +### +##### Example: + +Running +`bin/bcdbadmin config set -d "connection-session-config.yaml" -c "local/new_cluster_config.yml"` +reads the connection and session details needed for connecting to a server from `connection-session-config.yaml` and +sends a config TX. +It reads the `local/new_cluster_config.yml` to fetch the new cluster configuration and set it. \ No newline at end of file diff --git a/cli/commands/cli_utils.go b/cli/commands/cli_utils.go index 7bd98e6..c9b6633 100644 --- a/cli/commands/cli_utils.go +++ b/cli/commands/cli_utils.go @@ -24,7 +24,7 @@ type cliConfigParams struct { func (c *cliConfigParams) CreateDbAndOpenSession() error { var err error if err = c.cliConfig.ReadAndConstructCliConnConfig(c.cliConfigPath); err != nil { - return errors.Wrapf(err, "failed to read CLI configuration file") + return errors.Wrapf(err, "failed to read CLI connection configuration file") } c.db, err = bcdb.Create(&c.cliConfig.ConnectionConfig) @@ -43,18 +43,18 @@ func (c *cliConfigParams) CreateDbAndOpenSession() error { // ReadAndConstructCliConnConfig read unmarshal the yaml config file into a cliConnectionConfig object func (c *cliConnectionConfig) ReadAndConstructCliConnConfig(filePath string) error { if filePath == "" { - return errors.New("path to the shared configuration file is empty") + return errors.New("path to the connection configuration file is empty") } v := viper.New() v.SetConfigFile(filePath) if err := v.ReadInConfig(); err != nil { - return errors.Wrapf(err, "error reading shared config file: %s", filePath) + return errors.Wrapf(err, "error reading connection config file: %s", filePath) } if err := v.UnmarshalExact(c); err != nil { - return errors.Wrapf(err, "unable to unmarshal shared config file: '%s' into struct", filePath) + return errors.Wrapf(err, "unable to unmarshal connection config file: '%s' into struct", filePath) } clientLogger, err := logger.New( diff --git a/cli/commands/config.go b/cli/commands/config.go index 201b710..32f1948 100644 --- a/cli/commands/config.go +++ b/cli/commands/config.go @@ -11,6 +11,7 @@ import ( "github.com/hyperledger-labs/orion-server/pkg/types" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/viper" "gopkg.in/yaml.v2" ) @@ -50,12 +51,17 @@ func getConfigCmd() *cobra.Command { func setConfigCmd() *cobra.Command { setConfigCmd := &cobra.Command{ - Use: "set", - Short: "Set cluster configuration", - RunE: func(cmd *cobra.Command, args []string) error { - return errors.New("not implemented yet") - }, + Use: "set", + Short: "Set cluster configuration", + Example: "cli config set -d -c ", + RunE: setConfig, + } + + setConfigCmd.PersistentFlags().StringP("cluster-config-path", "c", "", "set the absolute or relative path of the new server configuration file") + if err := setConfigCmd.MarkPersistentFlagRequired("cluster-config-path"); err != nil { + panic(err.Error()) } + return setConfigCmd } @@ -106,6 +112,75 @@ func getConfig(cmd *cobra.Command, args []string) error { return nil } +func setConfig(cmd *cobra.Command, args []string) error { + cliConfigPath, err := cmd.Flags().GetString("db-connection-config-path") + if err != nil { + return errors.Wrapf(err, "failed to fetch the path of CLI connection configuration file") + } + + newClusterConfigPath, err := cmd.Flags().GetString("cluster-config-path") + if err != nil { + return errors.Wrapf(err, "failed to fetch the new server configuration path") + } + + lastVersion, err := readVersionYaml(path.Join(newClusterConfigPath, "version.yml")) + if err != nil { + return errors.Wrapf(err, "failed to read the version yaml file") + } + + params := cliConfigParams{ + cliConfigPath: cliConfigPath, + cliConfig: cliConnectionConfig{}, + db: nil, + session: nil, + } + + err = params.CreateDbAndOpenSession() + if err != nil { + return err + } + + tx, err := params.session.ConfigTx() + if err != nil { + return errors.Wrapf(err, "failed to instanciate a config TX") + } + defer abort(tx) + + _, version, err := tx.GetClusterConfig() + if err != nil { + return errors.Wrapf(err, "failed to fetch cluster config") + } + + // Check that the version in the version.yaml file is the same version as recieved by GetClusterConfig TX + if version != lastVersion { + errors.New("Cluster configuration cannot be updated since the version is not up to date") + } + + // Read the new shared configuration and build the new cluster config + newSharedConfig, err := readSharedConfigYaml(path.Join(newClusterConfigPath, "new_cluster_config.yml")) + if err != nil { + return errors.Wrapf(err, "failed to read the new cluster configuration file") + } + + newConfig, err := buildClusterConfig(newSharedConfig) + if err != nil { + return errors.Wrapf(err, "failed to build the cluster configuration from the shared configuration") + } + + // Set cluster configuration to the new one + err = tx.SetClusterConfig(newConfig) + if err != nil { + return errors.Wrapf(err, "failed to set the cluster config") + } + + _, _, err = tx.Commit(true) + if err != nil { + return errors.Wrapf(err, "failed to commit tx") + } + + return nil +} + func abort(tx bcdb.TxContext) { _ = tx.Abort() } @@ -124,6 +199,10 @@ func WriteClusterConfigToYaml(clusterConfig *types.ClusterConfig, version *types } err = os.WriteFile(path.Join(configYamlFilePath, "shared_cluster_config.yml"), c, 0644) + if err != nil { + return err + } + err = os.WriteFile(path.Join(configYamlFilePath, "version.yml"), v, 0644) if err != nil { return err @@ -292,3 +371,146 @@ func buildSharedClusterConfig(clusterConfig *types.ClusterConfig, configYamlFile return sharedConfiguration } + +// readVersionYaml reads the version from the file and returns it. +func readVersionYaml(versionFile string) (*types.Version, error) { + if versionFile == "" { + return nil, errors.New("path to the shared configuration file is empty") + } + + v := viper.New() + v.SetConfigFile(versionFile) + + if err := v.ReadInConfig(); err != nil { + return nil, errors.Wrapf(err, "error reading version file: %s", versionFile) + } + + version := &types.Version{} + if err := v.UnmarshalExact(version); err != nil { + return nil, errors.Wrapf(err, "unable to unmarshal version file: '%s' into struct", version) + } + return version, nil +} + +// readSharedConfigYaml reads the shared config from the file and returns it. +func readSharedConfigYaml(sharedConfigFile string) (*SharedConfiguration, error) { + if sharedConfigFile == "" { + return nil, errors.New("path to the shared configuration file is empty") + } + + v := viper.New() + v.SetConfigFile(sharedConfigFile) + + if err := v.ReadInConfig(); err != nil { + return nil, errors.Wrapf(err, "error reading shared config file: %s", sharedConfigFile) + } + + sharedConf := &SharedConfiguration{} + if err := v.UnmarshalExact(sharedConf); err != nil { + return nil, errors.Wrapf(err, "unable to unmarshal shared config file: '%s' into struct", sharedConfigFile) + } + return sharedConf, nil +} + +// buildClusterConfig builds a cluster configuration from a shared configuration +func buildClusterConfig(sharedConfig *SharedConfiguration) (*types.ClusterConfig, error) { + var nodesConfig []*types.NodeConfig + for _, node := range sharedConfig.Nodes { + cert, err := os.ReadFile(node.CertificatePath) + pemBlock, _ := pem.Decode(cert) + cert = pemBlock.Bytes + if err != nil { + return nil, errors.Wrapf(err, "unable read the certificate of node '%s'", node.NodeID) + } + nodeConfig := &types.NodeConfig{ + Id: node.NodeID, + Address: node.Host, + Port: node.Port, + Certificate: cert, + } + nodesConfig = append(nodesConfig, nodeConfig) + } + + var adminsConfig []*types.Admin + for _, admin := range sharedConfig.Admin { + cert, err := os.ReadFile(admin.CertificatePath) + pemBlock, _ := pem.Decode(cert) + cert = pemBlock.Bytes + if err != nil { + return nil, errors.Wrapf(err, "unable read the certificate of admin '%s'", admin.ID) + } + adminConfig := &types.Admin{ + Id: admin.ID, + Certificate: cert, + } + adminsConfig = append(adminsConfig, adminConfig) + } + + var rootCACertsConfig [][]byte + for i, rootCACertPath := range sharedConfig.CAConfig.RootCACertsPath { + rootCACertConfig, err := os.ReadFile(rootCACertPath) + pemBlock, _ := pem.Decode(rootCACertConfig) + rootCACertConfig = pemBlock.Bytes + if err != nil { + return nil, errors.Wrapf(err, "unable read the certificate of rootCA '%d'", i) + } + rootCACertsConfig = append(rootCACertsConfig, rootCACertConfig) + } + + var intermediateCACertsConfig [][]byte + for i, intermediateCACertPath := range sharedConfig.CAConfig.IntermediateCACertsPath { + intermediateCACertConfig, err := os.ReadFile(intermediateCACertPath) + pemBlock, _ := pem.Decode(intermediateCACertConfig) + intermediateCACertConfig = pemBlock.Bytes + if err != nil { + return nil, errors.Wrapf(err, "unable read the certificate of intermediateCA '%d'", i) + } + intermediateCACertsConfig = append(intermediateCACertsConfig, intermediateCACertConfig) + } + + var membersConfig []*types.PeerConfig + for _, member := range sharedConfig.Consensus.Members { + memberConfig := &types.PeerConfig{ + NodeId: member.NodeId, + RaftId: member.RaftId, + PeerHost: member.PeerHost, + PeerPort: member.PeerPort, + } + membersConfig = append(membersConfig, memberConfig) + } + + var observersConfig []*types.PeerConfig + for _, observer := range sharedConfig.Consensus.Observers { + observerConfig := &types.PeerConfig{ + NodeId: observer.NodeId, + RaftId: observer.RaftId, + PeerHost: observer.PeerHost, + PeerPort: observer.PeerPort, + } + observersConfig = append(observersConfig, observerConfig) + } + + clusterConfig := &types.ClusterConfig{ + Nodes: nodesConfig, + Admins: adminsConfig, + CertAuthConfig: &types.CAConfig{ + Roots: rootCACertsConfig, + Intermediates: intermediateCACertsConfig, + }, + ConsensusConfig: &types.ConsensusConfig{ + Algorithm: sharedConfig.Consensus.Algorithm, + Members: membersConfig, + Observers: observersConfig, + RaftConfig: &types.RaftConfig{ + TickInterval: sharedConfig.Consensus.RaftConfig.TickInterval, + ElectionTicks: sharedConfig.Consensus.RaftConfig.ElectionTicks, + HeartbeatTicks: sharedConfig.Consensus.RaftConfig.HeartbeatTicks, + MaxInflightBlocks: sharedConfig.Consensus.RaftConfig.MaxInflightBlocks, + SnapshotIntervalSize: sharedConfig.Consensus.RaftConfig.SnapshotIntervalSize, + }, + }, + LedgerConfig: &types.LedgerConfig{StateMerklePatriciaTrieDisabled: sharedConfig.Ledger.StateMerklePatriciaTrieDisabled}, + } + + return clusterConfig, nil +} diff --git a/cli/commands/config_test.go b/cli/commands/config_test.go index 908bc0b..c550a4d 100644 --- a/cli/commands/config_test.go +++ b/cli/commands/config_test.go @@ -9,12 +9,118 @@ import ( "testing" "github.com/hyperledger-labs/orion-sdk-go/examples/util" + "github.com/hyperledger-labs/orion-sdk-go/pkg/bcdb" "github.com/hyperledger-labs/orion-server/pkg/types" "github.com/pkg/errors" "github.com/spf13/viper" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v2" ) +func TestCheckCertsEncoderIsValid(t *testing.T) { + // 1. Create crypto material and start server + tempDir, err := os.MkdirTemp(os.TempDir(), "Cli-Check-Certs-Test") + require.NoError(t, err) + + testServer, _, _, err := util.SetupTestEnv(t, tempDir, uint32(6003)) + require.NoError(t, err) + defer testServer.Stop() + util.StartTestServer(t, testServer) + + // 2. read connection configuration, create db and open session + c, err := readConnConfig(path.Join(tempDir, "config.yml")) + require.NoError(t, err) + + bcdb, err := bcdb.Create(&c.ConnectionConfig) + require.NoError(t, err) + + session, err := bcdb.Session(&c.SessionConfig) + require.NoError(t, err) + + // 3. get cluster configration + tx, err := session.ConfigTx() + require.NoError(t, err) + + clusterConfig, version, err := tx.GetClusterConfig() + require.NoError(t, err) + require.NotNil(t, clusterConfig) + require.NotNil(t, version) + + // 4. parse the certificates from the cluster configuration and save it in a folder structure + parsedCertsDir, err := os.MkdirTemp(os.TempDir(), "certsTest") + defer os.RemoveAll("certsTest") + require.NoError(t, err) + + err = parseAndSaveCerts(clusterConfig, parsedCertsDir) + require.NoError(t, err) + + // 5. compare the generated certs with the certs recieved from the tx + err = compareFiles(path.Join(tempDir, "crypto", "admin", "admin.pem"), path.Join(parsedCertsDir, "admins", "admin.pem")) + require.NoError(t, err) + err = compareFiles(path.Join(tempDir, "crypto", "node", "node.pem"), path.Join(parsedCertsDir, "nodes", "server1.pem")) + require.NoError(t, err) + err = compareFiles(path.Join(tempDir, "crypto", "CA", "CA.pem"), path.Join(parsedCertsDir, "rootCAs", "rootCA0.pem")) + require.NoError(t, err) +} + +func TestCheckCertsDecoderIsValid(t *testing.T) { + // 1. Create crypto material and start server + tempDir, err := os.MkdirTemp(os.TempDir(), "Cli-Check-Certs-Test") + require.NoError(t, err) + + testServer, _, _, err := util.SetupTestEnv(t, tempDir, uint32(6003)) + require.NoError(t, err) + defer testServer.Stop() + util.StartTestServer(t, testServer) + + // 2. read connection configuration, create db and open session + c, err := readConnConfig(path.Join(tempDir, "config.yml")) + require.NoError(t, err) + + bcdb, err := bcdb.Create(&c.ConnectionConfig) + require.NoError(t, err) + + session, err := bcdb.Session(&c.SessionConfig) + require.NoError(t, err) + + // 3. get cluster configration + tx, err := session.ConfigTx() + require.NoError(t, err) + + clusterConfig, version, err := tx.GetClusterConfig() + require.NoError(t, err) + require.NotNil(t, clusterConfig) + require.NotNil(t, version) + + // 4. Get current cluster configuration by the get command to output a shared_cluster_config.yaml file with the paths for the certs + rootCmd := InitializeOrionCli() + + pwd, err := os.Getwd() + require.NoError(t, err) + testConnConfigFilePath := path.Join(tempDir, "config.yml") + getConfigDirPath, err := os.MkdirTemp(os.TempDir(), "configTest") + defer os.RemoveAll(getConfigDirPath) + require.NoError(t, err) + getConfigDirRelativePath := path.Join("..", "..", getConfigDirPath) + + rootCmd.SetArgs([]string{"config", "get", "-d", testConnConfigFilePath, "-c", path.Join(pwd, getConfigDirRelativePath)}) + err = rootCmd.Execute() + require.NoError(t, err) + + // 5. read the shared configuration created by the get command to create a sharedConfig object + sharedConfig, err := readSharedConfigYaml(path.Join(pwd, getConfigDirRelativePath, "shared_cluster_config.yml")) + require.NoError(t, err) + + // 6. build the cluster config from the shared configuration and compare the certificate bytes with those received by the tx + recievedClusterConfig, err := buildClusterConfig(sharedConfig) + require.NoError(t, err) + + require.Equal(t, clusterConfig.Admins[0].Certificate, recievedClusterConfig.Admins[0].Certificate) + require.Equal(t, clusterConfig.CertAuthConfig.Roots[0], recievedClusterConfig.CertAuthConfig.Roots[0]) + require.Equal(t, clusterConfig.CertAuthConfig.Intermediates, recievedClusterConfig.CertAuthConfig.Intermediates) + require.Equal(t, clusterConfig.Nodes[0].Certificate, recievedClusterConfig.Nodes[0].Certificate) +} + func TestInvalidFlagsGetConfigCommand(t *testing.T) { tests := []struct { name string @@ -39,7 +145,7 @@ func TestInvalidFlagsGetConfigCommand(t *testing.T) { { name: "File path not found", args: []string{"config", "get", "-d", "/path/to/cli-db-connection-config.yaml", "-c", "/path/to/cluster-config.yaml"}, - expectedErrMsg: "failed to read CLI configuration file", + expectedErrMsg: "failed to read CLI connection configuration file", }, } @@ -113,9 +219,43 @@ func TestGetConfigCommand(t *testing.T) { require.Equal(t, version.GetTxNum(), uint64(0)) } -func TestSetConfigCommand(t *testing.T) { +func TestInvalidFlagsSetConfigCommand(t *testing.T) { + tests := []struct { + name string + args []string + expectedErrMsg string + }{ + { + name: "No Flags", + args: []string{"config", "set"}, + expectedErrMsg: "required flag(s) \"cluster-config-path\", \"db-connection-config-path\" not set", + }, + { + name: "Missing Cli Connection Config Flag", + args: []string{"config", "set", "-c", "/path/to/cluster-config.yaml"}, + expectedErrMsg: "required flag(s) \"db-connection-config-path\" not set", + }, + { + name: "Missing Cluster Config Flag", + args: []string{"config", "set", "-d", "/path/to/cli-config.yaml"}, + expectedErrMsg: "required flag(s) \"cluster-config-path\" not set", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rootCmd := InitializeOrionCli() + rootCmd.SetArgs(tt.args) + err := rootCmd.Execute() + require.Error(t, err) + require.Contains(t, err.Error(), tt.expectedErrMsg) + }) + } +} + +func TestSetConfigCommand_UpdateParam(t *testing.T) { // 1. Create crypto material and start server - tempDir, err := os.MkdirTemp(os.TempDir(), "Cli-Set-Config-Test") + tempDir, err := os.MkdirTemp(os.TempDir(), "Cli-Set-Config-Test-Update-Param") require.NoError(t, err) testServer, _, _, err := util.SetupTestEnv(t, tempDir, uint32(6003)) @@ -123,70 +263,86 @@ func TestSetConfigCommand(t *testing.T) { defer testServer.Stop() util.StartTestServer(t, testServer) - // 2. Check cas command response + // 2. Get current cluster configuration rootCmd := InitializeOrionCli() - testDbConnectionConfigFilePath := path.Join(tempDir, "config.yml") - rootCmd.SetArgs([]string{"config", "set", "-d", testDbConnectionConfigFilePath}) + + pwd, err := os.Getwd() + require.NoError(t, err) + testConnConfigFilePath := path.Join(tempDir, "config.yml") + getConfigDirPath, err := os.MkdirTemp(os.TempDir(), "configTest-1") + defer os.RemoveAll(getConfigDirPath) + require.NoError(t, err) + getConfigDirRelativePath := path.Join("..", "..", getConfigDirPath) + + rootCmd.SetArgs([]string{"config", "get", "-d", testConnConfigFilePath, "-c", path.Join(pwd, getConfigDirRelativePath)}) err = rootCmd.Execute() - require.Error(t, err) - require.Equal(t, err.Error(), "not implemented yet") -} + require.NoError(t, err) -func readConnConfig(localConfigFile string) (*cliConnectionConfig, error) { - if localConfigFile == "" { - return nil, errors.New("path to the local configuration file is empty") - } + // 3. Prepare the new cluster configuration + sharedConfigYaml, err := readSharedConfigYaml(path.Join(getConfigDirRelativePath, "shared_cluster_config.yml")) + require.NoError(t, err) - v := viper.New() - v.SetConfigFile(localConfigFile) + clusterConfig, err := buildClusterConfig(sharedConfigYaml) + require.NoError(t, err) - if err := v.ReadInConfig(); err != nil { - return nil, errors.Wrapf(err, "error reading local config file: %s", localConfigFile) - } + clusterConfig.ConsensusConfig.RaftConfig.SnapshotIntervalSize++ + lastSnapshotIntervalSize := clusterConfig.ConsensusConfig.RaftConfig.SnapshotIntervalSize - localConf := &cliConnectionConfig{} - if err := v.UnmarshalExact(localConf); err != nil { - return nil, errors.Wrapf(err, "unable to unmarshal local config file: '%s' into struct", localConfigFile) - } - return localConf, nil -} + err = buildSharedClusterConfigAndWriteConfigToYaml(clusterConfig, getConfigDirRelativePath, "new_cluster_config.yml") + require.NoError(t, err) -func readSharedConfigYaml(sharedConfigFile string) (*SharedConfiguration, error) { - if sharedConfigFile == "" { - return nil, errors.New("path to the shared configuration file is empty") - } + // 4. Set cluster configuration + rootCmd.SetArgs([]string{"config", "set", "-d", testConnConfigFilePath, "-c", filepath.Join(pwd, getConfigDirRelativePath)}) + err = rootCmd.Execute() + require.NoError(t, err) - v := viper.New() - v.SetConfigFile(sharedConfigFile) + // 5. Restart the server to apply the new config + require.NoError(t, testServer.Start()) - if err := v.ReadInConfig(); err != nil { - return nil, errors.Wrapf(err, "error reading shared config file: %s", sharedConfigFile) - } + // 6. Get cluster configuration again and check the version and the parameter + getConfigDirPath, err = os.MkdirTemp(os.TempDir(), "configTest-2") + defer os.RemoveAll(getConfigDirPath) + require.NoError(t, err) + getConfigDirRelativePath = path.Join("..", "..", getConfigDirPath) - sharedConf := &SharedConfiguration{} - if err := v.UnmarshalExact(sharedConf); err != nil { - return nil, errors.Wrapf(err, "unable to unmarshal shared config file: '%s' into struct", sharedConfigFile) + rootCmd.SetArgs([]string{"config", "get", "-d", testConnConfigFilePath, "-c", path.Join(pwd, getConfigDirRelativePath)}) + err = rootCmd.Execute() + require.NoError(t, err) + + // 7. check the version + version, err := readVersionYaml(path.Join(getConfigDirRelativePath, "version.yml")) + if err != nil { + errors.Wrapf(err, "failed to read version file") } - return sharedConf, nil + require.Equal(t, version.GetBlockNum(), uint64(2)) + require.Equal(t, version.GetTxNum(), uint64(0)) + + // 8. check the parameter + newSharedConfigYaml, err := readSharedConfigYaml(path.Join(getConfigDirRelativePath, "shared_cluster_config.yml")) + require.NoError(t, err) + newClusterConfig, err := buildClusterConfig(newSharedConfigYaml) + require.NoError(t, err) + + require.Equal(t, newClusterConfig.ConsensusConfig.RaftConfig.SnapshotIntervalSize, lastSnapshotIntervalSize) } -func readVersionYaml(versionFile string) (*types.Version, error) { - if versionFile == "" { - return nil, errors.New("path to the shared configuration file is empty") +func readConnConfig(localConfigFile string) (*cliConnectionConfig, error) { + if localConfigFile == "" { + return nil, errors.New("path to the local configuration file is empty") } v := viper.New() - v.SetConfigFile(versionFile) + v.SetConfigFile(localConfigFile) if err := v.ReadInConfig(); err != nil { - return nil, errors.Wrapf(err, "error reading version file: %s", versionFile) + return nil, errors.Wrapf(err, "error reading local config file: %s", localConfigFile) } - version := &types.Version{} - if err := v.UnmarshalExact(version); err != nil { - return nil, errors.Wrapf(err, "unable to unmarshal version file: '%s' into struct", version) + localConf := &cliConnectionConfig{} + if err := v.UnmarshalExact(localConf); err != nil { + return nil, errors.Wrapf(err, "unable to unmarshal local config file: '%s' into struct", localConfigFile) } - return version, nil + return localConf, nil } func compareFiles(filepath1 string, filepath2 string) error { @@ -203,3 +359,16 @@ func compareFiles(filepath1 string, filepath2 string) error { } return nil } + +func buildSharedClusterConfigAndWriteConfigToYaml(clusterConfig *types.ClusterConfig, configYamlFilePath string, filename string) error { + sharedConfiguration := buildSharedClusterConfig(clusterConfig, configYamlFilePath) + c, err := yaml.Marshal(sharedConfiguration) + if err != nil { + return err + } + err = os.WriteFile(path.Join(configYamlFilePath, filename), c, 0644) + if err != nil { + return err + } + return nil +}