blob: 892dcba32530e588e4612843311ef1d98c7069ef [file] [log] [blame]
package jsonchecks
import (
"encoding/json"
"fmt"
"sort"
"github.com/hashicorp/terraform/internal/states"
)
// MarshalCheckStates is the main entry-point for this package, which takes
// the top-level model object for checks in state and plan, and returns a
// JSON representation of it suitable for use in public integration points.
func MarshalCheckStates(results *states.CheckResults) []byte {
jsonResults := make([]checkResultStatic, 0, results.ConfigResults.Len())
for _, elem := range results.ConfigResults.Elems {
staticAddr := elem.Key
aggrResult := elem.Value
objects := make([]checkResultDynamic, 0, aggrResult.ObjectResults.Len())
for _, elem := range aggrResult.ObjectResults.Elems {
dynamicAddr := elem.Key
result := elem.Value
problems := make([]checkProblem, 0, len(result.FailureMessages))
for _, msg := range result.FailureMessages {
problems = append(problems, checkProblem{
Message: msg,
})
}
sort.Slice(problems, func(i, j int) bool {
return problems[i].Message < problems[j].Message
})
objects = append(objects, checkResultDynamic{
Address: makeDynamicObjectAddr(dynamicAddr),
Status: checkStatusForJSON(result.Status),
Problems: problems,
})
}
sort.Slice(objects, func(i, j int) bool {
return objects[i].Address["to_display"].(string) < objects[j].Address["to_display"].(string)
})
jsonResults = append(jsonResults, checkResultStatic{
Address: makeStaticObjectAddr(staticAddr),
Status: checkStatusForJSON(aggrResult.Status),
Instances: objects,
})
}
sort.Slice(jsonResults, func(i, j int) bool {
return jsonResults[i].Address["to_display"].(string) < jsonResults[j].Address["to_display"].(string)
})
ret, err := json.Marshal(jsonResults)
if err != nil {
// We totally control the input to json.Marshal, so any error here
// is a bug in the code above.
panic(fmt.Sprintf("invalid input to json.Marshal: %s", err))
}
return ret
}
// checkResultStatic is the container for the static, configuration-driven
// idea of "checkable object" -- a resource block with conditions, for example --
// which ensures that we can always say _something_ about each checkable
// object in the configuration even if Terraform Core encountered an error
// before being able to determine the dynamic instances of the checkable object.
type checkResultStatic struct {
ExperimentalNote experimentalNote `json:"//"`
// Address is the address of the checkable object this result relates to.
Address staticObjectAddr `json:"address"`
// Status is the aggregate status for all of the dynamic objects belonging
// to this static object.
Status checkStatus `json:"status"`
// Instances contains the results for each individual dynamic object that
// belongs to this static object.
Instances []checkResultDynamic `json:"instances,omitempty"`
}
// checkResultDynamic describes the check result for a dynamic object, which
// results from Terraform Core evaluating the "expansion" (e.g. count or for_each)
// of the containing object or its own containing module(s).
type checkResultDynamic struct {
// Address augments the Address of the containing checkResultStatic with
// instance-specific extra properties or overridden properties.
Address dynamicObjectAddr `json:"address"`
// Status is the status for this specific dynamic object.
Status checkStatus `json:"status"`
// Problems describes some optional details associated with a failure
// status, describing what fails.
//
// This does not include the errors for status "error", because Terraform
// Core emits those separately as normal diagnostics. However, if a
// particular object has a mixture of conditions that failed and conditions
// that were invalid then status can be "error" while simultaneously
// returning problems in this property.
Problems []checkProblem `json:"problems,omitempty"`
}
// checkProblem describes one of potentially several problems that led to
// a check being classified as status "fail".
type checkProblem struct {
// Message is the condition error message provided by the author.
Message string `json:"message"`
// We don't currently have any other problem-related data, but this is
// intentionally an object to allow us to add other data over time, such
// as the source location where the failing condition was defined.
}
type experimentalNote struct{}
func (n experimentalNote) MarshalJSON() ([]byte, error) {
return []byte(`"EXPERIMENTAL: see docs for details"`), nil
}