Skip to content

Commit

Permalink
import and export
Browse files Browse the repository at this point in the history
  • Loading branch information
mihakralj committed Sep 26, 2023
1 parent ba1b5da commit 0a49963
Show file tree
Hide file tree
Showing 36 changed files with 1,286 additions and 458 deletions.
8 changes: 8 additions & 0 deletions clearversion.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<opnsense>
<virtualip del:version="1.0.0"/>
<vlans del:version="1.0.0"/>
<staticroutes del:version="1.0.0"/>
<laggs del:version="1.0.0"/>
<ifgroups del:version="1.0.0"/>
</opnsense>
85 changes: 85 additions & 0 deletions cmd/backup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
Copyright © 2023 Miha [email protected]
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 cmd

import (
"fmt"
"strings"

"github.com/beevik/etree"
"github.com/mihakralj/opnsense/internal"
"github.com/spf13/cobra"
)

// backupCmd represents the backup command
var backupCmd = &cobra.Command{
Use: "backup",
Short: `List available backup configurations in '/conf/backup' directory`,
Long: `The 'backup' command provides functionalities for managing and viewing backup XML configurations within your OPNsense firewall system. You can list all backup configurations or get details about a specific one.`,
Example: ` show backup Lists all backup XML configurations.
show backup <config> Show details of a specific backup XML configuration`,
Run: func(cmd *cobra.Command, args []string) {

backupdir := "/conf/backup/"
path := "backups"
filename := ""

if len(args) > 0 {
filename = strings.TrimPrefix(args[0], "/")
if !strings.HasSuffix(filename, ".xml") {
filename = filename + ".xml"
}
path = path + "/" + filename
}

internal.Checkos()
rootdoc := etree.NewDocument()

bash := fmt.Sprintf(`echo -n '<?xml version="1.0" encoding="UTF-8"?>' && echo -n '<backups count="' && find %s -type f | wc -l | awk '{$1=$1};1' | tr -d '\n' && echo -n '">' | sed 's/##/"/g'`, backupdir)
bash = bash + fmt.Sprintf(` && find %s -type f -exec sh -c 'echo $(stat -f "%%m" "$1") $(basename "$1") $(stat -f "%%z" "$1") $(md5sum "$1")' sh {} \; | sort -nr -k1`, backupdir)
bash = bash + `| awk '{ date = strftime("%Y-%m-%dT%H:%M:%S", $1); delta = systime() - $1; days = int(delta / 86400); hours = int((delta % 86400) / 3600); minutes = int((delta % 3600) / 60); seconds = int(delta % 60); age = days "d " hours "h " minutes "m " seconds "s"; print " <" $2 " age=\"" age "\"><date>" date "</date><size>" $3 "</size><md5>" $4 "</md5></" $2 ">"; } END { print "</backups>"; }'`

backups := internal.ExecuteCmd(bash, host)
err := rootdoc.ReadFromString(backups)
if err != nil {
internal.Log(1, "format is not XML")
}
if len(args) > 0 {
internal.FullDepth()

configdoc := internal.LoadXMLFile(configfile, host, false)
backupdoc := internal.LoadXMLFile(backupdir+filename, host, false)

deltadoc := internal.DiffXML(backupdoc, configdoc, false)


// append all differences to the rootdoc
diffEl := rootdoc.FindElement(path).CreateElement("diff")
for _, child := range deltadoc.Root().ChildElements() {
diffEl.AddChild(child.Copy())
}

}
fmt.Println()
internal.PrintDocument(rootdoc, path)

},
}

func init() {
backupCmd.Flags().IntVarP(&depth, "depth", "d", 1, "Specifies number of depth levels of returned tree (default: 1)")
rootCmd.AddCommand(backupCmd)
}
57 changes: 0 additions & 57 deletions cmd/backups.go

This file was deleted.

46 changes: 26 additions & 20 deletions cmd/commit.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright © 2023 MihaK mihak09@gmail.com
Copyright © 2023 Miha miha.kralj@outlook.com
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -30,36 +30,42 @@ var commitCmd = &cobra.Command{
Long: `The 'commit' command finalizes the staged changes made to the 'staging.xml' file, making them the active configuration for the OPNsense firewall system. This operation is the last step in a sequence that typically involves the 'set' and optionally 'discard' commands. The 'commit' action creates a backup of the active 'config.xml', moves 'staging.xml' to 'config.xml', and reloads the 'configd' service.
`,

Example: ` opnsense commit Commit the changes in 'staging.xml' to become the active 'config.xml'
Example: ` opnsense commit Commit the changes in 'staging.xml' to become the active 'config.xml'
opnsense commit --force Commit the changes without requiring interactive confirmation.`,
Run: func(cmd *cobra.Command, args []string) {

// check if staging.xml exists
internal.Checkos()
bash := `if [ -f "` + stagingfile + `" ]; then echo "exists"; fi`
bash := `test -f "` + stagingfile + `" && echo "exists" || echo "missing"`
fileexists := internal.ExecuteCmd(bash, host)
if strings.TrimSpace(fileexists) != "exists" {
internal.Log(1, "no staging.xml detected - nothing to commit.")
fmt.Println("no staging.xml detected - nothing to commit.")
return
}
internal.Log(2,"modifying "+configfile)
bash = `diff -q "` + configfile + `" "` + stagingfile + `" >& /dev/null && echo "same" || echo "diff"`
filesame := internal.ExecuteCmd(bash, host)
if strings.TrimSpace(filesame) != "diff" {
fmt.Println("staging.xml and config.xml are the same - nothing to commit.")
}

configdoc := internal.LoadXMLFile(configfile, host, false)
stagingdoc := internal.LoadXMLFile(stagingfile, host, false)

deltadoc := internal.DiffXML(configdoc, stagingdoc, false)
fmt.Println("\nChanges to be commited:")
internal.PrintDocument(deltadoc, "opnsense")

internal.Log(2, "commiting %s to %s", stagingfile, configfile)

// copy config.xml to /conf/backup dir
backupname := generateBackupFilename()
bash = `sudo cp -f ` + configfile + ` /conf/backup/` + backupname + ` && sudo mv -f /conf/staging.xml `+configfile
//internal.ExecuteCmd(bash, host)
fmt.Println(bash)

bash = `if [ -f "` + configfile + `" ]; then echo "ok"; else echo "error"; fi`
fileexists = internal.ExecuteCmd(bash, host)
if fileexists == "ok" {
bash = ``
} else {
//error
bash = ``
}
// config reload - full or partial?
backupname := internal.GenerateBackupFilename()
bash = `sudo cp -f ` + configfile + ` /conf/backup/` + backupname + ` && sudo mv -f /conf/staging.xml ` + configfile
internal.ExecuteCmd(bash, host)

fmt.Println("time to reload OPNSense!")

//TODO: run php /usr/local/etc/rc.reload_all

fmt.Println(fileexists)
},
}

Expand Down
19 changes: 10 additions & 9 deletions cmd/compare.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright © 2023 MihaK mihak09@gmail.com
Copyright © 2023 Miha miha.kralj@outlook.com
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -22,11 +22,13 @@ import (
"github.com/spf13/cobra"
)

var compact bool

// compareCmd represents the compare command
var compareCmd = &cobra.Command{
Use: "compare [<original.xml>] [<modified.xml>]",
Short: `Compare differences between two XML configuration files`,
Long: `The 'compare' command identifies differences between two XML configuration files for the OPNsense firewall system. When only one filename is provided, it shows the differences between that file and the current 'config.xml'. When no filenames are provided, it compares the current 'config.xml' with 'staging.xml', akin to the 'show' command.`,
Long: `The 'compare' command identifies differences between two XML configuration files for the OPNsense firewall system. When only one filename is provided, it shows the differences between that file and the current 'config.xml'. When no filenames are provided, it compares the current 'config.xml' with 'staging.xml', akin to the 'show' command.`,
Example: ` opnsense compare b1.xml b2.xml Compare differences from 'b1.xml' to 'b2.xml'
opnsense compare backup.xml Compare differences from 'backup.xml' to 'config.xml'
opnsense compare Compare differences from 'config.xml' to 'staging.xml'`,
Expand Down Expand Up @@ -71,21 +73,20 @@ var compareCmd = &cobra.Command{
}

internal.Checkos()
olddoc := internal.LoadXMLFile(oldconfig, host)
if olddoc == nil {
internal.Log(1,"failed to get data from %s",oldconfig)
}
newdoc := internal.LoadXMLFile(newconfig, host)
olddoc := internal.LoadXMLFile(oldconfig, host, false)
newdoc := internal.LoadXMLFile(newconfig, host, true)
if newdoc == nil {
newdoc = olddoc
}
deltadoc := internal.DiffXML(olddoc, newdoc, true)

deltadoc := internal.DiffXML(olddoc, newdoc, !compact)
internal.PrintDocument(deltadoc, path)

},
}

func init() {
compareCmd.Flags().IntVarP(&depth, "depth", "d", 1, "Specifies number of levels of returned tree (1-5)")
compareCmd.Flags().IntVarP(&depth, "depth", "d", 1, "Specifies number of depth levels of returned tree (default: 1)")
compareCmd.Flags().BoolVarP(&compact, "compact", "c", false, "Show only the net changes between configurations")
rootCmd.AddCommand(compareCmd)
}
2 changes: 1 addition & 1 deletion cmd/delete.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright © 2023 MihaK mihak09@gmail.com
Copyright © 2023 Miha miha.kralj@outlook.com
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
83 changes: 58 additions & 25 deletions cmd/discard.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright © 2023 MihaK mihak09@gmail.com
Copyright © 2023 Miha miha.kralj@outlook.com
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -39,11 +39,8 @@ Use the 'discard' command cautiously to avoid losing uncommitted changes.`,

internal.Checkos()

configdoc := internal.LoadXMLFile(configfile, host)
if configdoc == nil {
internal.Log(1, "failed to get data from %s", configfile)
}
stagingdoc := internal.LoadXMLFile(stagingfile, host)
configdoc := internal.LoadXMLFile(configfile, host, false)
stagingdoc := internal.LoadXMLFile(stagingfile, host, true)
if stagingdoc == nil {
stagingdoc = configdoc
}
Expand All @@ -53,36 +50,72 @@ Use the 'discard' command cautiously to avoid losing uncommitted changes.`,
internal.Log(2, "Discarding all staged configuration changes.")
stagingdoc = configdoc
} else {
trimmedArg := strings.Trim(args[0], "/")
if matched, _ := regexp.MatchString(`\[0\]`, trimmedArg); matched {

if matched, _ := regexp.MatchString(`\[0\]`, args[0]); matched {
internal.Log(1, "XPath indexing of elements starts with 1, not 0")
}
if trimmedArg != "" {
path = trimmedArg
if args[0] != "" {
path = args[0]
}

parts := strings.Split(path, "/")
if parts[0] != "opnsense" {
path = "opnsense/" + path
}
configElement := configdoc.FindElement(path)
stagingElement := stagingdoc.FindElement(path)

if stagingElement != nil {
if configElement != nil {
stagingParent := stagingElement.Parent()
stagingParent.RemoveChild(stagingElement)
stagingParent.AddChild(configElement.Copy())
} else {
stagingParent := stagingElement.Parent()
stagingParent.RemoveChild(stagingElement)
if !strings.HasPrefix(path, "/") {
path = "/" + path
}

stagingEl := stagingdoc.FindElement(path)
configEl := configdoc.FindElement(path)

if configEl == nil && stagingEl != nil {
// Element is new in staging, remove it
parent := stagingEl.Parent()
parent.RemoveChild(stagingEl)

// Remove the last part of the path
lastSlash := strings.LastIndex(path, "/")
if lastSlash != -1 {
path = path[:lastSlash]
}
} else if configEl != nil && stagingEl != nil {
// Element exists in both configdoc and stagingdoc, restore it
parent := stagingEl.Parent()
parent.RemoveChild(stagingEl)
parent.AddChild(configEl.Copy())

// Restore attributes
configAttrs := configEl.Attr
stagingEl = parent.FindElement(configEl.Tag)
if stagingEl != nil {
for _, attr := range configAttrs {
stagingEl.CreateAttr(attr.Key, attr.Value)
}
}
} else if configEl != nil && stagingEl == nil {
// Element exists in configdoc but not in stagingdoc, add it to stagingdoc
stagingdoc.Root().AddChild(configEl.Copy())

// Copy attributes
configAttrs := configEl.Attr
stagingEl = stagingdoc.Root().FindElement(configEl.Tag)
if stagingEl != nil {
for _, attr := range configAttrs {
stagingEl.CreateAttr(attr.Key, attr.Value)
}
}
} else {
stagingdoc.Root().AddChild(configElement.Copy())
}
}
internal.SaveXMLFile(stagingfile, stagingdoc, host, true)
fmt.Printf("Discarded all staged changes in %s\n", path)

if len(args) < 1 {
fmt.Printf("Discarded all staged configuration changes")
} else {
fmt.Printf("Discarded staged changes in node %s:\n\n", path)
deltadoc := internal.DiffXML(configdoc, stagingdoc, true)
internal.PrintDocument(deltadoc, path)
}
internal.SaveXMLFile(stagingfile, stagingdoc, host, true)
},
}

Expand Down
Loading

0 comments on commit 0a49963

Please sign in to comment.