Skip to content

Commit

Permalink
Add json output (#10)
Browse files Browse the repository at this point in the history
* Add flags and docs to the main file

* Add json output style
  • Loading branch information
porrige51122 committed Aug 22, 2023
1 parent 67fe9e1 commit 8ba1121
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 24 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
*.so
*.dylib

# Ignore output json files
*.json

# Test binary, built with `go test -c`
*.test

Expand Down
37 changes: 37 additions & 0 deletions display/json/display.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// # Display/JSON
//
// This package contains the code for the JSON display of the AWS accounts and
// OUs. It uses the tree structure generated in the generation package to
// create a JSON representation of the accounts and OUs.
package json

import (
"os"

"github.com/CentricaDevOps/aws-organizations-visualiser/generation"
)

// Create is a function that takes in the tree structure and creates a JSON
// representation of it.
func Create(tree *generation.OU) ([]byte, error) {
// Use the existing structure to create a JSON representation of the tree.
return tree.ToJSON()
}

// OutputToFile is a function that takes in the json representaiton of the tree and
// outputs it to the given file
func OutputToFile(json []byte, filename string) error {
// Open the file
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()

// Write the JSON to the file
_, err = file.Write(json)
if err != nil {
return err
}
return nil
}
14 changes: 10 additions & 4 deletions generation/ou_struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package generation

import (
"context"
"encoding/json"

"github.com/aws/aws-sdk-go-v2/service/organizations"
"github.com/aws/aws-sdk-go-v2/service/organizations/types"
Expand All @@ -12,10 +13,10 @@ import (
// It can be used to represent the entire structure or a substructure in the
// style of a tree.
type OU struct {
Id string
Name string
Children []*OU
Accounts []types.Account
Id string `json:"id"`
Name string `json:"name"`
Children []*OU `json:"children"`
Accounts []types.Account `json:"accounts"`
}

// addChildren adds the given OUs to the OU's children slice.
Expand Down Expand Up @@ -43,6 +44,11 @@ func (o *OU) GetAccounts() []types.Account {
return o.Accounts
}

// ToJSON returns a JSON representation of the OU.
func (o *OU) ToJSON() ([]byte, error) {
return json.MarshalIndent(o, "", " ")
}

func (parent *OU) fillOuTree(ctx context.Context, api ListOrganizationalUnitsForParent) error {
// Get the OUs for the parent OU.
ous, err := getOUsForParent(ctx, api, parent.Id)
Expand Down
92 changes: 72 additions & 20 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,40 @@
/*
# AWS Organizations Visualiser
TODO: Add description
*/
// # AWS Organizations Visualiser
//
// This is a tool that can be used to visualise the structure of an AWS
// Organizations structure. It can be used to generate a JSON representation of the
// structure or to display the structure in the CLI.
//
// ## Usage
//
// This tool generates a structure that represents the AWS Organizations structure
// and then, based on the flags passed in, either displays the structure in the CLI
// or outputs the structure to a JSON file or both.
//
// ### Flags
//
// Usage:
//
// aws-organizations-visualiser [flags]
//
// Flags:
//
// -include-json
// Include the JSON representation of the AWS Organizations structure in the output (default true)
// -include-visual
// Include the visual representation of the AWS Organizations structure in the output (default true)
// -o string
// The output file for the JSON representation of the AWS Organizations structure (default "output.json")
package main

import (
"context"
"flag"
"fmt"
"os"

"log"
"strconv"

"github.com/CentricaDevOps/aws-organizations-visualiser/display/cli"
"github.com/CentricaDevOps/aws-organizations-visualiser/display/json"
"github.com/CentricaDevOps/aws-organizations-visualiser/generation"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/organizations"
Expand All @@ -27,7 +49,7 @@ type Logs struct {
// for the application
func (l *Logs) Println(v ...interface{}) {
if l.Enabled {
log.Println(v...)
fmt.Println(v...)
}
}

Expand All @@ -37,14 +59,13 @@ var logs Logs

// init is called before main and is used to check the permissions of the user
// running the application and to set up the logging configuration
func setupLogging() {
func setupLogging(logging string) {
// Set up logging
logs := Logs{Enabled: false}
ll := os.Getenv("LOGS_ENABLED")
if ll == "true" {
logs.Enabled = true
logs.Println("Logging initialised")
value, err := strconv.ParseBool(logging)
if err != nil {
value = false
}
logs = Logs{Enabled: value}
}

// checkPermissions is a function that checks the permissions of the user running
Expand Down Expand Up @@ -74,17 +95,22 @@ func checkPermissions() (context.Context, *organizations.Client, error) {
// main is the entry point of the application, it is called when the application
// is executed and is used to call the main logic of the application.
func main() {
// STAGE 1: Set up the logging and check permissions
setupLogging()
// STAGE 1: Sort out the input flags
jsonPtr := flag.Bool("include-json", true, "Include the JSON representation of the AWS Organizations structure in the output")
visualPtr := flag.Bool("include-visual", true, "Include the visual representation of the AWS Organizations structure in the output")
outputPtr := flag.String("o", "output.json", "The output file for the JSON representation of the AWS Organizations structure")
flag.Parse()

// STAGE 2: Set up the logging and check permissions
ll := os.Getenv("LOGS_ENABLED")
setupLogging(ll)
ctx, cfg, err := checkPermissions()
if err != nil {
fmt.Println("Error checking permissions")
logs.Println(err)
return
}

// STAGE 2: Sort out the input flags
// TODO: Decide on what flags to use and sort them out
logs.Println("Output file:", *outputPtr)

// STAGE 3: Run the main logic of the application to generate the data
// structure
Expand All @@ -97,5 +123,31 @@ func main() {
}

// STAGE 4: Determine the output format and output the data structure
cli.Display(tree)
// If no output format is specified, exit
if !*visualPtr && !*jsonPtr {
fmt.Println("No output format specified, exiting...")
return
}
// If the visual output format is specified, display the data structure on
// the CLI
if *visualPtr {
cli.Display(tree)
}

// If the JSON output format is specified, output the data structure to a
// JSON file with the given name
if *jsonPtr {
jsonTree, err := json.Create(tree)
if err != nil {
fmt.Println("Error generating JSON")
logs.Println(err)
return
}
err = json.OutputToFile(jsonTree, *outputPtr)
if err != nil {
fmt.Println("Error outputting JSON to file")
logs.Println(err)
return
}
}
}
79 changes: 79 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -1 +1,80 @@
package main

import (
"bytes"
"io"
"os"
"testing"

"github.com/stretchr/testify/require"
)

// TestSetupLoggingTrue tests the setupLogging function when the logging
// parameter is true.
func TestSetupLoggingTrue(t *testing.T) {
setupLogging("true")
require.NotNil(t, logs, "Expected logs to be initialised")
require.Equal(t, true, logs.Enabled, "Expected logs to be enabled")
}

// TestSetupLoggingFalse tests the setupLogging function when the logging
// parameter is false.
func TestSetupLoggingFalse(t *testing.T) {
setupLogging("false")
require.NotNil(t, logs, "Expected logs to be initialised")
require.Equal(t, false, logs.Enabled, "Expected logs to be disabled")
}

// TestSetupLoggingInvalid tests the setupLogging function when the logging
// parameter is invalid.
func TestSetupLoggingInvalid(t *testing.T) {
setupLogging("invalid")
require.NotNil(t, logs, "Expected logs to be initialised")
require.Equal(t, false, logs.Enabled, "Expected logs to be disabled")
}

// TestSetupLoggingEmpty tests the setupLogging function when the logging
// parameter is empty.
func TestSetupLoggingEmpty(t *testing.T) {
setupLogging("")
require.NotNil(t, logs, "Expected logs to be initialised")
require.Equal(t, false, logs.Enabled, "Expected logs to be disabled")
}

// TestPrintlnLogging tests the Println function of the Logs struct.
func TestPrintlnLogging(t *testing.T) {
setupLogging("true")
output1 := captureOutput(func() {
logs.Println("Test")
})
require.Equal(t, "Test\n", output1, "Expected output to be 'Test\n'")

setupLogging("false")
output2 := captureOutput(func() {
logs.Println("Test")
})
require.Equal(t, "", output2, "Expected output to be ''")
}

// captureOutput is a helper function to capture the output of a function.
// This is used to test the output of the display functions.
func captureOutput(f func()) string {
// Store the old stdout and replace it with a pipe.
original := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
// Run the function.
f()
// Close the pipe and restore stdout.
w.Close()
os.Stdout = original

// Read the output from the pipe.
var buf bytes.Buffer
_, err := io.Copy(&buf, r)
if err != nil {
panic(err)
}
// Return the output.
return buf.String()
}

0 comments on commit 8ba1121

Please sign in to comment.