driftctl/pkg/cmd/scan/output/plan.go

129 lines
3.2 KiB
Go

package output
import (
"encoding/json"
"fmt"
"os"
"github.com/snyk/driftctl/pkg/analyser"
"github.com/snyk/driftctl/pkg/resource"
)
const FormatVersion = "0.1"
const PlanOutputType = "plan"
const PlanOutputExample = "plan://PATH/TO/FILE.json"
type plan struct {
FormatVersion string `json:"format_version,omitempty"`
PlannedValues plannedValues `json:"planned_values,omitempty"`
ResourceChanges []rscChange `json:"resource_changes,omitempty"`
}
type plannedValues struct {
RootModule module `json:"root_module,omitempty"`
}
type rscChange struct {
Address string `json:"address,omitempty"`
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Change change `json:"change,omitempty"`
}
type change struct {
Actions []string `json:"actions,omitempty"`
Before map[string]interface{} `json:"before,omitempty"`
After map[string]interface{} `json:"after,omitempty"`
}
type module struct {
Resources []rsc `json:"resources,omitempty"`
}
type rsc struct {
Address string `json:"address,omitempty"`
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
AttributeValues map[string]interface{} `json:"values,omitempty"`
}
type Plan struct {
path string
}
func NewPlan(path string) *Plan {
return &Plan{path}
}
func (c *Plan) Write(analysis *analyser.Analysis) error {
file := os.Stdout
if !isStdOut(c.path) {
f, err := os.OpenFile(c.path, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer f.Close()
file = f
}
output := plan{FormatVersion: FormatVersion}
output.PlannedValues.RootModule = addPlannedValues(analysis)
output.ResourceChanges = addResourceChanges(analysis)
jsonPlan, err := json.MarshalIndent(output, "", "\t")
if err != nil {
return err
}
if _, err := file.Write(jsonPlan); err != nil {
return err
}
return nil
}
func addPlannedValues(analysis *analyser.Analysis) module {
managedRsc := listRsc(analysis.Managed())
unmanagedRsc := listRsc(analysis.Unmanaged())
return module{
Resources: append(managedRsc, unmanagedRsc...),
}
}
func listRsc(resources []*resource.Resource) []rsc {
var ret []rsc
for _, res := range resources {
r := rsc{
Address: fmt.Sprintf("%s.%s", res.ResourceType(), res.ResourceId()),
Type: res.ResourceType(),
Name: res.ResourceId(),
AttributeValues: *res.Attributes(),
}
ret = append(ret, r)
}
return ret
}
func addResourceChanges(analysis *analyser.Analysis) []rscChange {
managedRsc := listRscChange(analysis.Managed(), "no-op")
unmanagedRsc := listRscChange(analysis.Unmanaged(), "create")
return append(managedRsc, unmanagedRsc...)
}
func listRscChange(resources []*resource.Resource, action string) []rscChange {
var ret []rscChange
for _, res := range resources {
r := rscChange{
Address: fmt.Sprintf("%s.%s", res.ResourceType(), res.ResourceId()),
Type: res.ResourceType(),
Name: res.ResourceId(),
Change: change{
Actions: []string{action},
After: *res.Attributes(),
},
}
if action == "no-op" {
r.Change.Before = *res.Attributes()
}
ret = append(ret, r)
}
return ret
}