Skip to content

Commit

Permalink
Merge pull request #3 from andygeorge/andygeorge/injection-go
Browse files Browse the repository at this point in the history
Rewrite in Golang
  • Loading branch information
andygeorge authored Sep 27, 2022
2 parents c8b4ace + a70e49e commit 9ff3510
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 29 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
example_files/
output/
/go/build/*
42 changes: 13 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,42 +21,26 @@ Or, manually:

## Example

- Create a Butane config file, `hello_world.bu`:

```yaml
variant: fcos
version: 1.4.0

storage:
directories:
- path: /tmp/example
mode: 0755

files:
- path: /tmp/example/hello_world.txt
mode: 0755
contents:
inline: |
Hello, world!
- Use the example Butane config file:
```shell
cp example_butane.bu example_files/example.bu
```

- The file `example_files/example.bu` should be populated

- Create an Ignition config using Butane:
```shell
butane --strict example_files/example.bu > example_files/example.ign
```

```bash
$ butane --strict hello_world.bu > hello_world.ign
$ cat hello_world.ign
{"ignition":{"version":"3.3.0"},"storage":{"directories":[{"path":"/tmp/example","mode":493}],"files":[{"path":"/tmp/example/hello_world.txt","contents":{"compression":"","source":"data:,Hello%2C%20world!%0A"},"mode":493}]}}
- The file `example_files/example.ign` should be populated and look like this:
```json
{"ignition":{"version":"3.3.0"},"storage":{"directories":[{"path":"/tmp/example","mode":493}],"files":[{"path":"/tmp/example/hello_world.txt","contents":{"compression":"","source":"data:,Hello%2C%20world!%0A"},"mode":420},{"path":"/tmp/example/hello_world_gzip.txt","contents":{"compression":"gzip","source":"data:;base64,H4sIAAAAAAAC/zSQQc4UOwyE9+8UdYBRn+IhgcQSxNoknu6Skjjj2MOI06MwP7soicv1fd8uLnBB0LNcaOKnOj5ra4Yf5q3izqaISwK/2BpyKc7fnCjWp+tatAEOxKX4cg4GbRz4aq4dnCs7qjVzLAaka9xQbCwtoZEOqZxcLBwntDFuWFpRDcpc3SpC+zQHR2FlzRHIQJOf5gqNd7aiyzkE0vhIOfA9oIMdUtG5D08dlH7DI7kwbIVnhb7UC0N2YWRr0ou9k/cnLu5NfyM5oS+obORu1d4Ej5Q48P+OlAwFPV0/YDngOl0vHVWdsS+e1nKGhOK5SaFrKQpb+6dIoYl7npTA2IUwxSmRfuDTq+gMze1xBKwU0SKBkpNVYk/YwHRj1bEtblMcKNmmbG7Y/c5CQdWlvl+7tV1DtiBW6Prwmv34708AAAD//9sohz4WAgAA"},"mode":420}]},"systemd":{"units":[{"contents":"[Unit]\nDescription=Hello world service\n\n[Service]\nType=oneshot\nExecStart=/usr/bin/echo \"hello world\"\nStandardOutput=journal\n\n[Install]\nWantedBy=multi-user.target default.target\n","enabled":true,"name":"hello-world.service"}]}}
```

- Run Injection:

```bash
$ sudo injection hello_world.ign
Creating directory: /tmp/example
Creating file: /tmp/example/hello_world.txt

$ cat /tmp/example/hello_world.txt
Hello, world!
```shell
# TODO: injection example
```

## Ignition support
Expand Down
47 changes: 47 additions & 0 deletions example_butane.bu
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
variant: fcos
version: 1.4.0

storage:
directories:
- path: /tmp/example
mode: 0755

- path: /tmp/example2

- path: /tmp/example3/example4
mode: 0744

files:
- path: /tmp/example/hello_world.txt
contents:
inline: |
Hello, world!

- path: /tmp/example/hello_world_gzip.txt
mode: 0644
contents:
inline: |
This is a much larger Hello World file that will use gzip compression
in the Ignition. Lorem ipsum dolor sit amet, consectetur adipisicing
elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.

systemd:
units:
- name: hello-world.service
enabled: true
contents: |
[Unit]
Description=Hello world service

[Service]
Type=oneshot
ExecStart=/usr/bin/echo "hello world"
StandardOutput=journal

[Install]
WantedBy=multi-user.target default.target
13 changes: 13 additions & 0 deletions go/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# injection-go
`injection` built in Go.

```shell
# generate ignition
butane --strict ../example_files/example.bu > ../example_files/example.ign

# build injection
go mod tidy && go fmt *.go && go build -o build/

# run injection
./build/injection ../example_files/example.ign
```
3 changes: 3 additions & 0 deletions go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module injection

go 1.19
200 changes: 200 additions & 0 deletions go/injection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package main

import (
"compress/gzip"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/url"
"os"
"os/exec"
"strings"
)

const (
DefaultDirectoryMode os.FileMode = 0755
DefaultFileMode os.FileMode = 0644
DefaultSystemdUnitFileMode os.FileMode = 0644
DefaultSystemdUnitPath string = "/etc/systemd/system"
)

type IgnitionConfig struct {
Storage struct {
Directories []struct {
Path string `json:"path"`
Mode int `json:"mode"`
} `json:"directories"`
Files []struct {
Path string `json:"path"`
Mode int `json:"mode"`
Contents struct {
Compression string `json:"compression"`
Source string `json:"source"`
} `json:"contents"`
} `json:"files"`
} `json:"storage"`
Systemd struct {
Units []struct {
Name string `json:"name"`
Enabled bool `json:"enabled"`
Contents string `json:"contents"`
} `json:"units"`
} `json:"systemd"`
}

func main() {
var filename string
var ignitionConfig IgnitionConfig
var err error

args := os.Args[1:]

if len(args) < 1 {
fmt.Println("Specify a filename!")
os.Exit(1)
} else {
filename = args[0]
}

readFile, err := os.ReadFile(filename)
check(err)

err = json.Unmarshal([]byte(string(readFile)), &ignitionConfig)
check(err)

err = WriteDirectories(ignitionConfig)
check(err)

err = WriteFiles(ignitionConfig)
check(err)

err = WriteUnits(ignitionConfig)
check(err)
}

func OpenFile(path string, mode os.FileMode) (*os.File, error) {
options := os.O_WRONLY | os.O_TRUNC | os.O_CREATE

return os.OpenFile(path, options, mode)
}

func WriteDirectories(ignitionConfig IgnitionConfig) error {
var err error

for _, directoryConfig := range ignitionConfig.Storage.Directories {
path := directoryConfig.Path
mode := DefaultDirectoryMode
fmt.Println(path)

if directoryConfig.Mode != 0 {
mode = os.FileMode(directoryConfig.Mode)
}

if _, err = os.Stat(path); os.IsNotExist(err) {
err = os.MkdirAll(path, mode)
} else {
err = os.Chmod(path, mode)
}
}

return err
}

func WriteFiles(ignitionConfig IgnitionConfig) error {
var err error
var targetFile *os.File

for _, fileConfig := range ignitionConfig.Storage.Files {
path := fileConfig.Path
mode := DefaultFileMode
compression := fileConfig.Contents.Compression
source := fileConfig.Contents.Source
var rawData, unescapedData string
var decodedGzipData []byte
fmt.Println(path)

if fileConfig.Mode != 0 {
mode = os.FileMode(fileConfig.Mode)
}

if compression == "gzip" {
idx := strings.Index(source, ";base64,")
rawData = source[idx+8:]

gz, err := decodeBase64Data(rawData)
decodedGzipData, err = decodeGzipData(string(gz))
targetFile, err = OpenFile(path, mode)
if err != nil {
return err
}
defer targetFile.Close()
fmt.Fprintf(targetFile, "%s", string(decodedGzipData))
} else {
idx := strings.Index(source, ",")
rawData = source[idx+1:]

targetFile, err = OpenFile(path, mode)
if err != nil {
return err
}
defer targetFile.Close()
unescapedData, err = url.QueryUnescape(rawData)
fmt.Fprintf(targetFile, "%s", unescapedData)
}
}

return err
}

func WriteUnits(ignitionConfig IgnitionConfig) error {
var err error
var targetFile *os.File

for _, unitConfig := range ignitionConfig.Systemd.Units {
path := DefaultSystemdUnitPath + "/" + unitConfig.Name
mode := DefaultSystemdUnitFileMode
fmt.Println(path)

unitEnabledString := "disable"
if unitConfig.Enabled {
unitEnabledString = "enable"
}

targetFile, err = OpenFile(path, mode)
fmt.Fprintf(targetFile, "%s", unitConfig.Contents)
defer targetFile.Close()
check(err)

cmd := exec.Command("systemctl", unitEnabledString, unitConfig.Name)
err = cmd.Run()
check(err)
}

return err
}

func decodeBase64Data(data string) ([]byte, error) {
decodedData, err := base64.StdEncoding.DecodeString(data)
if err != nil {
return nil, fmt.Errorf("unable to decode base64: %q", err)
}

return decodedData, nil
}

func decodeGzipData(data string) ([]byte, error) {
reader, err := gzip.NewReader(strings.NewReader(data))
if err != nil {
return nil, err
}
defer reader.Close()

return io.ReadAll(reader)
}

func check(err error) {
if err != nil {
panic(err)
}
}
5 changes: 5 additions & 0 deletions go/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
/usr/bin/butane --strict ../example_files/example.bu > ../example_files/example.ign
rm -rf /tmp/example/
/usr/local/go/bin/go mod tidy && /usr/local/go/bin/go fmt *.go && /usr/local/go/bin/go build -o build/
./build/injection ../example_files/example.ign

0 comments on commit 9ff3510

Please sign in to comment.