diff --git a/ReadMe.md b/ReadMe.md
index 688887c..f8e2638 100644
--- a/ReadMe.md
+++ b/ReadMe.md
@@ -2,6 +2,21 @@
server.garden Privileged Automation Agent
+```
+mkdir -p ssh
+
+ssh-keygen -t ed25519 -N '' -f ./ssh/severgarden_builtin_ed22519
+
+go build -o ansible-wrapper/ansible-playbook-wrapper ansible-wrapper/main.go
+go build -o host-key-poller/host-key-poller host-key-poller/main.go
+
+# you will have to provide a complete config file. normally this would be provideded by seedpacket
+nano config.json
+
+go run *.go
+```
+
+
-----------------------------
Rootsystem is the entrypoint & most highly privileged part of the server.garden automation system, hence "root" in the name.
diff --git a/automation/terraformActions.go b/automation/terraformActions.go
index 18551f3..f086ba9 100644
--- a/automation/terraformActions.go
+++ b/automation/terraformActions.go
@@ -4,7 +4,9 @@ import (
"bufio"
"bytes"
"encoding/json"
+ "encoding/xml"
"fmt"
+ "html"
"io"
"io/ioutil"
"os"
@@ -121,6 +123,20 @@ type TerraformApplyResult struct {
Status *SimplifiedTerraformStatus
}
+type XMLNode struct {
+ XMLName xml.Name
+ Attrs []xml.Attr `xml:"-"`
+ Content []byte `xml:",innerxml"`
+ Children []XMLNode `xml:",any"`
+}
+
+func (n *XMLNode) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ n.Attrs = start.Attr
+ type node XMLNode
+
+ return d.DecodeElement((*node)(n), &start)
+}
+
const data_template_file = "data.template_file"
func TerraformPlanAndApply(
@@ -233,6 +249,7 @@ func TerraformPlanAndApply(
if err != nil {
return "", nil, errors.Wrap(err, "can't TerraformPlanAndApply because can't makeSVGFromSimpleStatus")
}
+ return "", nil, errors.New("TODO remove this")
for moduleName, module := range simpleStatus.Modules {
if module.IsAnsible {
@@ -806,10 +823,42 @@ func makeSVGFromSimpleStatus(simpleStatus *SimplifiedTerraformStatus) (string, e
return "", err
}
- svgString := strings.ReplaceAll(string(dotStdout), `fill="none"`, `fill="#ffffff"`)
- matchTitleTag := regexp.MustCompile(`
[^<]*`)
- svgString = matchTitleTag.ReplaceAllString(svgString, "")
- svgString = strings.ReplaceAll(svgString, `Times,serif`, `-apple-system,system-ui,BlinkMacSystemFont,Ubuntu,Roboto,Segoe UI,sans-serif`)
+ svgString := string(dotStdout)
+
+ // the svg output from dot has html comments that contain metadata preceeding each entity.
+ // for example:
+ //
+ //
+ // We will attach that metadata to the svg elements as a data property
+ // Note that this will not work perfectly for the "cluster"s (aka modules)
+ // they are handled separately after the SVG has been inserted.
+ // Yes parsing XML with regex is "evil" but keep in mind, this XML was generated by a program, not written by hand.
+ // So it is a lot more predictable. Also, I couldn't figure out how to parse the comments with the go std lib XML parser.
+ commentRegex := regexp.MustCompile(`\s*\n\s*(<[a-z]+\s)`)
+ svgString = commentRegex.ReplaceAllStringFunc(svgString, func(matched string) string {
+ matches := commentRegex.FindStringSubmatch(matched)
+ return fmt.Sprintf("%s data-dot=\"%s\"", matches[2], html.UnescapeString(matches[1]))
+ })
+
+ ioutil.WriteFile("./test-gen.svg", []byte(svgString), 0777)
+
+ var svgXMLDocument XMLNode
+
+ err = xml.Unmarshal(dotStdout, &svgXMLDocument)
+ if err != nil {
+ return "", err
+ }
+
+ // svgString := strings.ReplaceAll(string(dotStdout), `fill="none"`, `fill="#ffffff"`)
+ // matchTitleTag := regexp.MustCompile(`[^<]*`)
+ // svgString = matchTitleTag.ReplaceAllString(svgString, "")
+ // svgString = strings.ReplaceAll(svgString, `Times,serif`, `-apple-system,system-ui,BlinkMacSystemFont,Ubuntu,Roboto,Segoe UI,sans-serif`)
+
+ testJson, _ := json.MarshalIndent(simpleStatus, "", " ")
+ ioutil.WriteFile("./test-status.json", testJson, 0777)
+ testJson2, _ := json.MarshalIndent(svgXMLDocument, "", " ")
+ ioutil.WriteFile("./test-xml.json", testJson2, 0777)
+ ioutil.WriteFile("./test-dot.txt", []byte(dot), 0777)
return svgString, nil
}
diff --git a/main.go b/main.go
index 50e20c6..abd13c0 100644
--- a/main.go
+++ b/main.go
@@ -6,12 +6,9 @@ import (
"log"
"net/http"
- errors "git.sequentialread.com/forest/pkg-errors"
-
"git.sequentialread.com/forest/rootsystem/automation"
"git.sequentialread.com/forest/rootsystem/configuration"
"git.sequentialread.com/forest/rootsystem/objectStorage"
- "git.sequentialread.com/forest/rootsystem/pki"
)
type applicationState struct {
@@ -21,85 +18,87 @@ type applicationState struct {
var global applicationState
-func main() {
- config, workingDirectory, err := configuration.LoadConfiguration()
- if err != nil {
- panic(errors.Wrap(err, "rootsystem can't start because loadConfiguration() returned"))
- }
- global.workingDirectory = workingDirectory
-
- storage, err := objectStorage.InitializeObjectStorage(config, true)
- if err != nil {
- panic(errors.Wrap(err, "rootsystem can't start because failed to initialize object storage"))
- }
- global.storage = storage
-
- go terraformStateServer()
-
- // This creates an access key that the gateway cloud instance can use to upload its SSH public key
- // to our object storage. the host-key-poller will download this SSH host public key and add it to our known_hosts
- // so that we can SSH to the gateway instance securely
- hostKeysAccessSpec := objectStorage.ObjectStorageKey{
- Name: "rootsystem-known-hosts",
- PathPrefix: "rootsystem/known-hosts",
- Read: true,
- Write: true,
- Delete: false,
- List: false,
- }
- knownHostsCredentials, err := global.storage.CreateAccessKeyIfNotExists(hostKeysAccessSpec)
- if err != nil {
- panic(err)
- }
-
- // BuildTLSCertsForThreshold fills in the CAs, Keys, and Certificates in the Threshold ansible roles.
- // So when terraform invokes ansible to install threshold client/server, it will install working
- // certificates and keys
- err = pki.BuildTLSCertsForThreshold(
- global.workingDirectory,
- config.Terraform.Variables["domain_name"],
- config.Host.Name,
- global.storage,
- )
- if err != nil {
- panic(err)
- }
-
- // First, run the terraform build for the GLOBAL components, meaning the components
- // that exist in the cloud, independent of how many server.garden nodes are being used.
- outputVariables, err := terraformBuild(
- config,
- automation.TerraformConfiguration{
- TargetedModules: config.Terraform.GlobalModules,
- TerraformProject: configuration.GLOBAL_TERRAFORM_PROJECT,
- HostKeysObjectStorageCredentials: knownHostsCredentials,
- },
- )
- if err != nil {
- panic(err)
- }
-
- // Next, we run a separate LOCAL terraform build which is specific to THIS server.garden node,
- // this build will be responsible for installing software on this node & registering this node with the
- // cloud resources
- _, err = terraformBuild(
- config,
- automation.TerraformConfiguration{
- TargetedModules: config.Terraform.LocalModules,
- TerraformProject: fmt.Sprintf("%s-%s", configuration.LOCAL_TERRAFORM_PROJECT, config.Host.Name),
- RemoteState: configuration.GLOBAL_TERRAFORM_PROJECT,
- RemoteStateVariables: outputVariables,
- },
- )
- if err != nil {
- panic(err)
- }
-
- a := make(chan bool)
-
- <-a
-
-}
+// func main() {
+// config, workingDirectory, err := configuration.LoadConfiguration()
+// if err != nil {
+// panic(errors.Wrap(err, "rootsystem can't start because loadConfiguration() returned"))
+// }
+// global.workingDirectory = workingDirectory
+
+// storage, err := objectStorage.InitializeObjectStorage(config, true)
+// if err != nil {
+// panic(errors.Wrap(err, "rootsystem can't start because failed to initialize object storage"))
+// }
+// global.storage = storage
+
+// go terraformStateServer()
+
+// // This creates an access key that the gateway cloud instance can use to upload its SSH public key
+// // to our object storage. the host-key-poller will download this SSH host public key and add it to our known_hosts
+// // so that we can SSH to the gateway instance securely
+// hostKeysAccessSpec := objectStorage.ObjectStorageKey{
+// Name: "rootsystem-known-hosts",
+// PathPrefix: "rootsystem/known-hosts",
+// Read: true,
+// Write: true,
+// Delete: false,
+// List: false,
+// }
+// knownHostsCredentials, err := global.storage.CreateAccessKeyIfNotExists(hostKeysAccessSpec)
+// if err != nil {
+// panic(err)
+// }
+
+// // BuildTLSCertsForThreshold fills in the CAs, Keys, and Certificates in the Threshold ansible roles.
+// // So when terraform invokes ansible to install threshold client/server, it will install working
+// // certificates and keys
+// err = pki.BuildTLSCertsForThreshold(
+// global.workingDirectory,
+// config.Terraform.Variables["domain_name"],
+// config.Host.Name,
+// global.storage,
+// )
+// if err != nil {
+// panic(err)
+// }
+
+// // First, run the terraform build for the GLOBAL components, meaning the components
+// // that exist in the cloud, independent of how many server.garden nodes are being used.
+// outputVariables, err := terraformBuild(
+// config,
+// automation.TerraformConfiguration{
+// TargetedModules: config.Terraform.GlobalModules,
+// TerraformProject: configuration.GLOBAL_TERRAFORM_PROJECT,
+// HostKeysObjectStorageCredentials: knownHostsCredentials,
+// },
+// )
+// if err != nil {
+// panic(err)
+// }
+
+// os.Exit(0)
+
+// // Next, we run a separate LOCAL terraform build which is specific to THIS server.garden node,
+// // this build will be responsible for installing software on this node & registering this node with the
+// // cloud resources
+// _, err = terraformBuild(
+// config,
+// automation.TerraformConfiguration{
+// TargetedModules: config.Terraform.LocalModules,
+// TerraformProject: fmt.Sprintf("%s-%s", configuration.LOCAL_TERRAFORM_PROJECT, config.Host.Name),
+// RemoteState: configuration.GLOBAL_TERRAFORM_PROJECT,
+// RemoteStateVariables: outputVariables,
+// },
+// )
+// if err != nil {
+// panic(err)
+// }
+
+// a := make(chan bool)
+
+// <-a
+
+// }
func terraformBuild(
config *configuration.Configuration,
diff --git a/pki/pki.go b/pki/pki.go
index 2eb9cee..e21b06a 100644
--- a/pki/pki.go
+++ b/pki/pki.go
@@ -10,9 +10,9 @@ import (
"path/filepath"
"time"
- "git.sequentialread.com/forest/easypki/pkg/certificate"
- "git.sequentialread.com/forest/easypki/pkg/easypki"
- "git.sequentialread.com/forest/easypki/pkg/store"
+ "git.sequentialread.com/forest/easypki.git/pkg/certificate"
+ "git.sequentialread.com/forest/easypki.git/pkg/easypki"
+ "git.sequentialread.com/forest/easypki.git/pkg/store"
"git.sequentialread.com/forest/rootsystem/configuration"
"git.sequentialread.com/forest/rootsystem/objectStorage"
"github.com/pkg/errors"
@@ -240,6 +240,7 @@ func saveBundle(pki *easypki.EasyPKI, caName, bundleName string, savePrivateKey
if caName == "" {
caName = bundleName
}
+
bundle, err := pki.GetBundle(caName, bundleName)
if err != nil {
return errors.Wrapf(err, "Failed getting bundle %v within CA %v: %v", bundleName, caName)
diff --git a/pull.sh b/pull.sh
index a95f579..209f69c 100644
--- a/pull.sh
+++ b/pull.sh
@@ -28,6 +28,7 @@ if [ "$TERRAFORM_VERSION" != "$DESIRED_TERRAFORM_VERSION" ]; then
if [ "$TERRAFORM_VERSION" != "missing" ]; then
rm /usr/bin/terraform
fi
+ # curl "https://releases.hashicorp.com/terraform/0.12.29/terraform_0.12.29_linux_amd64.zip" > "terraform_0.12.29_linux_amd64.zip"
curl "https://releases.hashicorp.com/terraform/$DESIRED_TERRAFORM_VERSION/terraform_$DESIRED_TERRAFORM_VERSION""_linux_$TERRAFORM_ARCH.zip" > "terraform_$DESIRED_TERRAFORM_VERSION""_linux_$TERRAFORM_ARCH.zip"
unzip "terraform_$DESIRED_TERRAFORM_VERSION""_linux_$TERRAFORM_ARCH.zip"
diff --git a/terraform-modules/gateway-instance-digitalocean/main.tf b/terraform-modules/gateway-instance-digitalocean/main.tf
index c2e495c..d0a5bba 100644
--- a/terraform-modules/gateway-instance-digitalocean/main.tf
+++ b/terraform-modules/gateway-instance-digitalocean/main.tf
@@ -8,10 +8,6 @@ variable "domain_name" {
type = string
}
-variable "node_id" {
- type = string
-}
-
variable "digitalocean_ssh_key_fingerprints" {
type = list(string)
}
diff --git a/test.go b/test.go
new file mode 100644
index 0000000..7215d38
--- /dev/null
+++ b/test.go
@@ -0,0 +1,453 @@
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "encoding/xml"
+ "fmt"
+ "html"
+ "io"
+ "io/ioutil"
+ "os/exec"
+ "regexp"
+ "strings"
+
+ errors "git.sequentialread.com/forest/pkg-errors"
+)
+
+type XMLAttr struct {
+ Name string
+ Value string
+ Namespace string
+ Local string
+}
+
+type XMLNode struct {
+ XMLName xml.Name
+ Attrs []*XMLAttr `xml:"-"`
+ Content []byte `xml:",innerxml"`
+ Children []*XMLNode `xml:",any"`
+ Parent *XMLNode `xml:"-"`
+}
+
+type XMLQuery struct {
+ DirectChild bool
+ NodeType string
+ Class string
+ Attr string
+}
+
+func (node *XMLNode) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ attrPointers := []*XMLAttr{}
+ for _, attr := range start.Attr {
+ // I dont really give a shit about XML namespaces but I do have to search for xlink:title which is separate from normal title
+ // hardcoded that this is happening inside the svg namespace so all non svg namespaces attrs will be specified like ns:attr
+ name := attr.Name.Local
+ if attr.Name.Space != "" {
+ // attr.Name.Space is a url like http://www.w3.org/1999/xlink or ttp://www.w3.org/2000/svg so we are just grabbing the last part
+ split := strings.Split(attr.Name.Space, "/")
+ ns := split[len(split)-1]
+ if ns != "svg" {
+ name = fmt.Sprintf("%s:%s", ns, name)
+ }
+ }
+ myAttr := XMLAttr{
+ Name: name,
+ Value: attr.Value,
+ Namespace: attr.Name.Space,
+ Local: attr.Name.Local,
+ }
+ attrPointers = append(attrPointers, &myAttr)
+ }
+ node.Attrs = attrPointers
+
+ // this little derived type wont directly have this UnmarshalXML method,
+ // thus prevent the decoder from thinking it has to invoke our custom UnmarshalXML again...
+ type dontCauseInfiniteLoop XMLNode
+
+ return d.DecodeElement((*dontCauseInfiniteLoop)(node), &start)
+}
+
+//https://stackoverflow.com/questions/39780433/golang-xml-customize-output
+
+// // MarshalXML generate XML output for PrecsontructedInfo
+// func (preconstructed PreconstructedInfo) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
+// if (PreconstructedInfo{} == preconstructed) {
+// return nil
+// }
+// if preconstructed.Decks > 0 {
+// start.Attr = []xml.Attr{xml.Attr{Name: xml.Name{Local: "decks"}, Value: strconv.Itoa(preconstructed.Size)}}
+// }
+// if strings.Compare(preconstructed.Type, "") != 0 {
+// start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "type"}, Value: preconstructed.Type})
+// }
+
+// err = e.EncodeToken(start)
+// e.EncodeElement(preconstructed.Size, xml.StartElement{Name: xml.Name{Local: "size"}})
+// return e.EncodeToken(xml.EndElement{Name: start.Name})
+// }
+
+func (node XMLNode) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+
+ attrsToEncode := []xml.Attr{}
+ for _, attr := range node.Attrs {
+ // this encoder will print some stupid shit if you give it an xmlns attr:
+ // for example: