diff --git a/README.md b/README.md
index ff8d74c..7d9882d 100644
--- a/README.md
+++ b/README.md
@@ -244,6 +244,7 @@ m - show all status messages saved in the history
s - auto-synchronize selected directories (local to DataPower)
S - save a running DataPower configuration
B - create and copy a secure backup of the appliance
+e - run exec command on current or selected cfg file(s)
0 - cycle between different DataPower view modes
filestore mode view / object mode view / status mode view
- when using SOMA access changed objects are marked and object
@@ -251,7 +252,7 @@ B - create and copy a secure backup of the appliance
? - show status information for the current DataPower object,
local directory or local file
P - show DataPower policy for the current DataPower object
- (can be used only on service, policy, matches,rules & actions)
+ (can be used only on service, policy, matches, rules & actions)
exports the current DataPower object, analyzes it and
shows service, policy, matches, rules and actions for
the object
diff --git a/config/config.go b/config/config.go
index 590eb16..ddaada4 100644
--- a/config/config.go
+++ b/config/config.go
@@ -417,7 +417,7 @@ func showVersion() {
// help prints in-program help information to console.
func showHelp(exitStatus int) {
- fmt.Println(help.Help)
+ fmt.Print(help.Help)
os.Exit(exitStatus)
}
diff --git a/help/help.go b/help/help.go
index a685274..e50f64b 100644
--- a/help/help.go
+++ b/help/help.go
@@ -71,6 +71,7 @@ m - show all status messages saved in the history
s - auto-synchronize selected directories (local to DataPower)
S - save a running DataPower configuration
B - create and copy a secure backup of the appliance
+e - run exec command on current or selected cfg file(s)
0 - cycle between different DataPower view modes
filestore mode view / object mode view / status mode view
- when using SOMA access changed objects are marked and object
@@ -78,7 +79,7 @@ B - create and copy a secure backup of the appliance
? - show status information for the current DataPower object,
local directory or local file
P - show DataPower policy for the current DataPower object
- (can be used only on service, policy, matches,rules & actions)
+ (can be used only on service, policy, matches, rules & actions)
exports the current DataPower object, analyzes it and
shows service, policy, matches, rules and actions for
the object
diff --git a/repo/dp/dp.go b/repo/dp/dp.go
index 4b554fa..1241779 100644
--- a/repo/dp/dp.go
+++ b/repo/dp/dp.go
@@ -2062,6 +2062,74 @@ func (r *dpRepo) FlushCache(
}
}
+// ExecConfig run exec dommand for a DataPower configuration script.
+func (r *dpRepo) ExecConfig(itemConfig *model.ItemConfig) error {
+ logging.LogDebugf("repo/dp/ExecConfig(%v)", itemConfig)
+
+ switch r.dataPowerAppliance.DpManagmentInterface() {
+ case config.DpInterfaceRest:
+ execConfigRequestJSON := fmt.Sprintf(`{"ExecConfig":{"URL":"%s"}}`, itemConfig.Path)
+ resultText, _, err := r.restPostForResult(
+ "/mgmt/actionqueue/"+itemConfig.DpDomain,
+ execConfigRequestJSON,
+ "/ExecConfig",
+ "Operation completed.",
+ "/ExecConfig")
+ if err != nil {
+ return err
+ }
+
+ logging.LogDebugf("repo/dp/ExecConfig(), resultText: '%s'", resultText)
+
+ return nil
+
+ case config.DpInterfaceSoma:
+ somaRequest := fmt.Sprintf(`
+
+
+ %s
+
+
+`, itemConfig.DpDomain, itemConfig.Path)
+ response, err := r.soma(somaRequest)
+ if err != nil {
+ return err
+ }
+ result, err := parseSOMAFindOne(response, "//*[local-name()='response']/*[local-name()='result']")
+ logging.LogDebugf("repo/dp/ExecConfig(), result: '%v'", result)
+ if result != "OK" {
+ return errs.Errorf("DataPower exec config error: '%v'", result)
+ }
+ return nil
+
+ default:
+ logging.LogDebug("repo/dp/ExecConfig(), using neither REST neither SOMA.")
+ return errs.Error("DataPower management interface not set.")
+ }
+
+ //
+ //
+ //
+ // 2023-02-06T10:52:04-05:00
+ //
+ //
+ // Unable to execute config:///default.cf - must be text file.
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ // 2023-02-06T11:06:01-05:00
+ // OK
+ //
+ //
+ //
+}
+
// GetManagementInterface returns current DataPower management interface used.
func (r *dpRepo) GetManagementInterface() string {
return r.dataPowerAppliance.DpManagmentInterface()
diff --git a/repo/dp/dp_test.go b/repo/dp/dp_test.go
index 29fc3fe..54bd592 100644
--- a/repo/dp/dp_test.go
+++ b/repo/dp/dp_test.go
@@ -2212,3 +2212,51 @@ func TestGetStatus(t *testing.T) {
assert.Equals(t, "GetStatus", string(statusBytes), wantStatus)
})
}
+
+func TestExecConfig(t *testing.T) {
+ itemToExec := model.ItemConfig{Type: model.ItemFile,
+ DpDomain: "MyExecDomain", DpFilestore: "local:", Path: "local:/config-ok.cfg"}
+
+ t.Run("ExecConfig no REST/SOMA", func(t *testing.T) {
+ clearRepo()
+
+ err := Repo.ExecConfig(&itemToExec)
+ assert.Equals(t, "ExecConfig", err, errs.Error("DataPower management interface not set."))
+ })
+
+ itemToExec.Path = "local:/config-err.cfg"
+ t.Run("ExecConfig REST Error", func(t *testing.T) {
+ clearRepo()
+ Repo.dataPowerAppliance.RestUrl = testRestURL
+
+ err := Repo.ExecConfig(&itemToExec)
+ assert.Equals(t, "ExecConfig", err, errs.Error("Unexpected response from server."))
+ })
+
+ itemToExec.Path = "local:/config-ok.cfg"
+ t.Run("ExecConfig REST OK", func(t *testing.T) {
+ clearRepo()
+ Repo.dataPowerAppliance.RestUrl = testRestURL
+
+ err := Repo.ExecConfig(&itemToExec)
+ assert.Nil(t, "ExecConfig", err)
+ })
+
+ itemToExec.Path = "local:/config-err.cfg"
+ t.Run("ExecConfig SOMA Error", func(t *testing.T) {
+ clearRepo()
+ Repo.dataPowerAppliance.SomaUrl = testSomaURL
+
+ err := Repo.ExecConfig(&itemToExec)
+ assert.Equals(t, "ExecConfig", err, errs.Error("DataPower exec config error: 'Unable to execute local:/config-err.cfg - must be text file.'"))
+ })
+
+ itemToExec.Path = "local:/config-ok.cfg"
+ t.Run("ExecConfig SOMA OK", func(t *testing.T) {
+ clearRepo()
+ Repo.dataPowerAppliance.SomaUrl = testSomaURL
+
+ err := Repo.ExecConfig(&itemToExec)
+ assert.Nil(t, "ExecConfig", err)
+ })
+}
diff --git a/repo/dp/dpmock_test.go b/repo/dp/dpmock_test.go
index 052ce53..944bbc3 100644
--- a/repo/dp/dpmock_test.go
+++ b/repo/dp/dpmock_test.go
@@ -4,6 +4,7 @@ import (
"fmt"
"io/ioutil"
"regexp"
+ "strings"
"github.com/croz-ltd/dpcmder/utils/errs"
)
@@ -68,6 +69,17 @@ func (nr mockRequester) httpRequest(dpa dpApplicance, urlFullPath, method, body
default:
return "", errs.Errorf("dpmock_test: Unrecognized method '%s'", method)
}
+ case "https://my_dp_host:5554/mgmt/actionqueue/MyExecDomain":
+ switch method {
+ case "POST":
+ if strings.Contains(body, "config-ok") {
+ content, err = ioutil.ReadFile("testdata/ExecConfig_ok.json")
+ } else {
+ content, err = ioutil.ReadFile("testdata/ExecConfig_error.json")
+ }
+ default:
+ return "", errs.Errorf("dpmock_test: Unrecognized method '%s'", method)
+ }
case "https://my_dp_host:5554/mgmt/actionqueue/tmp/pending/Export-20200228T061406Z-2":
content, err = ioutil.ReadFile("testdata/export-svc-pending-get.json")
case "https://my_dp_host:5554/mgmt/status/":
@@ -130,6 +142,14 @@ func (nr mockRequester) httpRequest(dpa dpApplicance, urlFullPath, method, body
if len(matches) == 1 {
opAction = "SecureBackup"
}
+
+ if len(matches) == 0 {
+ r = regexp.MustCompile(`.*.*`)
+ matches = r.FindStringSubmatch(body)
+ if len(matches) == 1 {
+ opAction = "ExecConfig"
+ }
+ }
}
}
@@ -180,6 +200,14 @@ func (nr mockRequester) httpRequest(dpa dpApplicance, urlFullPath, method, body
} else {
content, err = ioutil.ReadFile("testdata/SecureBackup_ok.xml")
}
+ case opTag == "do-action" && opAction == "ExecConfig":
+ r = regexp.MustCompile(`.*config-err.*`)
+ matches = r.FindStringSubmatch(body)
+ if len(matches) == 1 {
+ content, err = ioutil.ReadFile("testdata/ExecConfig_error.xml")
+ } else {
+ content, err = ioutil.ReadFile("testdata/ExecConfig_ok.xml")
+ }
default:
fmt.Printf("dpmock_test: Unrecognized SOMA request opTag: '%s', "+
"opClass: '%s', opObjClass: '%s', opLayoutOnly: '%s', opFilePath: '%s'.\n",
diff --git a/repo/dp/testdata/ExecConfig_error.json b/repo/dp/testdata/ExecConfig_error.json
new file mode 100644
index 0000000..d71331e
--- /dev/null
+++ b/repo/dp/testdata/ExecConfig_error.json
@@ -0,0 +1,12 @@
+{
+ "_links": {
+ "self": {
+ "href": "/mgmt/actionqueue/tmp"
+ }
+ },
+ "error": [
+ "Bad request.",
+ "Unable to execute local:/config-err.cfg - must be text file.",
+ " exec config:/matko23.cfg"
+ ]
+}
diff --git a/repo/dp/testdata/ExecConfig_error.xml b/repo/dp/testdata/ExecConfig_error.xml
new file mode 100644
index 0000000..89fdc26
--- /dev/null
+++ b/repo/dp/testdata/ExecConfig_error.xml
@@ -0,0 +1,12 @@
+
+
+
+ 2023-05-17T05:07:12-04:00
+
+
+ Unable to execute local:/config-err.cfg - must be text file.
+
+
+
+
+
\ No newline at end of file
diff --git a/repo/dp/testdata/ExecConfig_ok.json b/repo/dp/testdata/ExecConfig_ok.json
new file mode 100644
index 0000000..7cb52c8
--- /dev/null
+++ b/repo/dp/testdata/ExecConfig_ok.json
@@ -0,0 +1,12 @@
+{
+ "_links": {
+ "self": {
+ "href": "/mgmt/actionqueue/tmp"
+ },
+ "doc": {
+ "href": "/mgmt/docs/actionqueue"
+ }
+ },
+ "ExecConfig": "Operation completed.",
+ "script-log": ""
+}
diff --git a/repo/dp/testdata/ExecConfig_ok.xml b/repo/dp/testdata/ExecConfig_ok.xml
new file mode 100644
index 0000000..71a8b0f
--- /dev/null
+++ b/repo/dp/testdata/ExecConfig_ok.xml
@@ -0,0 +1,8 @@
+
+
+
+ 2023-05-17T05:07:29-04:00
+ OK
+
+
+
\ No newline at end of file
diff --git a/repo/localfs/localfs.go b/repo/localfs/localfs.go
index c64691b..ae8ece2 100644
--- a/repo/localfs/localfs.go
+++ b/repo/localfs/localfs.go
@@ -3,11 +3,6 @@ package localfs
import (
"fmt"
- "github.com/croz-ltd/dpcmder/config"
- "github.com/croz-ltd/dpcmder/model"
- "github.com/croz-ltd/dpcmder/utils/errs"
- "github.com/croz-ltd/dpcmder/utils/logging"
- "github.com/croz-ltd/dpcmder/utils/paths"
"io/ioutil"
"os"
"path/filepath"
@@ -15,6 +10,12 @@ import (
"strconv"
"strings"
"time"
+
+ "github.com/croz-ltd/dpcmder/config"
+ "github.com/croz-ltd/dpcmder/model"
+ "github.com/croz-ltd/dpcmder/utils/errs"
+ "github.com/croz-ltd/dpcmder/utils/logging"
+ "github.com/croz-ltd/dpcmder/utils/paths"
)
type localRepo struct {
@@ -211,6 +212,10 @@ file mode: %s`,
return []byte(result), nil
}
+func (r localRepo) ExecConfig(itemConfig *model.ItemConfig) error {
+ return errs.Error("Can't exec configuration on local machine.")
+}
+
// Tree represents file/dir with all it's children and modification time.
type Tree struct {
Dir bool
diff --git a/repo/repo.go b/repo/repo.go
index 103d01f..7e94f48 100644
--- a/repo/repo.go
+++ b/repo/repo.go
@@ -21,4 +21,5 @@ type Repo interface {
Delete(currentView *model.ItemConfig, itemType model.ItemType, parentPath, fileName string) (bool, error)
GetViewConfigByPath(currentView *model.ItemConfig, dirPath string) (*model.ItemConfig, error)
GetItemInfo(itemConfig *model.ItemConfig) ([]byte, error)
+ ExecConfig(itemConfig *model.ItemConfig) error
}
diff --git a/ui/worker.go b/ui/worker.go
index 3949bdf..b0da49e 100644
--- a/ui/worker.go
+++ b/ui/worker.go
@@ -240,6 +240,8 @@ func ProcessInputEvent(event tcell.Event) error {
err = saveDataPowerConfig(&workingModel)
case c == 'm':
err = showStatusMessages(workingModel.Statuses())
+ case c == 'e':
+ err = execConfigFile(&workingModel)
case c == '0':
err = toggleObjectMode(&workingModel)
case c == '?':
@@ -2167,6 +2169,57 @@ func syncLocalToDpLater(tree, treeOld *localfs.Tree) bool {
return changesMade
}
+// execConfigFile switches between (default) filestore mode, object mode and
+// status mode for the DataPower view.
+func execConfigFile(m *model.Model) error {
+ logging.LogDebugf("worker/execConfigFile(), dp.Repo.DpViewMode: %s", dp.Repo.DpViewMode)
+
+ side := m.CurrSide()
+ // viewConfig := m.ViewConfig(side)
+ switch side {
+ case model.Left:
+ itemsToExec := getSelectedOrCurrent(m)
+ pathsToExec := make([]string, len(itemsToExec))
+ for idx, item := range itemsToExec {
+ ci := item.Config
+ pathsToExec[idx] = ci.Path
+ switch item.Config.Type {
+ case model.ItemFile:
+ default:
+ return errs.Errorf("Can't exec item '%s' (%s)",
+ ci.Name, ci.Type.UserFriendlyString())
+ }
+ }
+
+ res := "n"
+ dialogResult := askUserInput(
+ fmt.Sprintf("Confirm exec files %v (y/n): ",
+ pathsToExec), "", false)
+ if dialogResult.dialogSubmitted {
+ res = dialogResult.inputAnswer
+ }
+ logging.LogDebugf("ui/execConfigFile(), confirm exec: '%s'", res)
+ if res == "y" {
+ for _, item := range itemsToExec {
+ showProgressDialogf("Running exec command '%s' file from DataPower...", item.Name)
+ err := repos[m.CurrSide()].ExecConfig(item.Config)
+ if err != nil {
+ hideProgressDialog()
+ return err
+ }
+ }
+ hideProgressDialog()
+ updateStatusf("Exec config files success: %v", pathsToExec)
+ }
+
+ default:
+ logging.LogDebug("worker/execConfigFile(), To exec config file select config file in the DataPower view.")
+ return errs.Error("To exec config file select config file in the DataPower view.")
+ }
+
+ return nil
+}
+
// toggleObjectMode switches between (default) filestore mode, object mode and
// status mode for the DataPower view.
func toggleObjectMode(m *model.Model) error {