Skip to content

Commit

Permalink
fix: nil pointer exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
Warnar Boekkooi committed Aug 22, 2024
1 parent bc14038 commit 5bb94cf
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 35 deletions.
3 changes: 3 additions & 0 deletions sanitize/sanitize_change.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (
// the particular locations marked by BeforeSensitive AfterSensitive
// with the value supplied as replaceWith.
func SanitizeChange(result *tfjson.Change, replaceWith interface{}) {
if result == nil {
return
}
result.Before = sanitizeChangeValue(result.Before, result.BeforeSensitive, replaceWith)
result.After = sanitizeChangeValue(result.After, result.AfterSensitive, replaceWith)
}
Expand Down
5 changes: 5 additions & 0 deletions sanitize/sanitize_change_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ type testChangeCase struct {

func changeCases() []testChangeCase {
return []testChangeCase{
{
name: "nil",
old: nil,
expected: nil,
},
{
name: "basic",
old: &tfjson.Change{
Expand Down
27 changes: 23 additions & 4 deletions sanitize/sanitize_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ func SanitizeProviderConfigs(result map[string]*tfjson.ProviderConfig, replaceWi

// SanitizeProviderConfig sanitises the constant_value from expressions of the provider_config to the value set in replaceWith parameter.
func SanitizeProviderConfig(result *tfjson.ProviderConfig, replaceWith interface{}) {
if result == nil {
return
}

for _, expression := range result.Expressions {
sanitizeExpression(expression, replaceWith)
}
Expand All @@ -19,7 +23,7 @@ func SanitizeProviderConfig(result *tfjson.ProviderConfig, replaceWith interface
// SanitizeConfigOutputs sanitises the constant_value from the expression of the outputs.
func SanitizeConfigOutputs(outputs map[string]*tfjson.ConfigOutput, replaceWith interface{}) {
for _, output := range outputs {
if output.Sensitive {
if output != nil && output.Sensitive {
sanitizeExpression(output.Expression, replaceWith)
}
}
Expand All @@ -28,38 +32,53 @@ func SanitizeConfigOutputs(outputs map[string]*tfjson.ConfigOutput, replaceWith
// SanitizeConfigVariables sanitizes the variables config.
func SanitizeConfigVariables(result map[string]*tfjson.ConfigVariable, replaceWith interface{}) {
for _, v := range result {
if v.Sensitive && v.Default != nil {
if v != nil && v.Sensitive && v.Default != nil {
v.Default = replaceWith
}
}
}

func sanitizeModuleConfig(module *tfjson.ConfigModule, replaceWith interface{}) {
if module == nil {
return
}

SanitizeConfigVariables(module.Variables, replaceWith)

for _, res := range module.Resources {
sanitizeResourceConfig(res, replaceWith)
}

for _, mod := range module.ModuleCalls {
if mod == nil || mod.Module == nil {
continue
}
for name, expr := range mod.Expressions {
if expr == nil {
continue
}
if mod.Module.Variables == nil {
// NOTE(i4k): this should never happen because a module always define all its input.
// but in case we are dealing with a pre-processed JSON, this ensures
// we don't leak variables missing definitions.
sanitizeExpression(expr, replaceWith)
}
if varConfig, ok := mod.Module.Variables[name]; ok && varConfig.Sensitive {
} else if varConfig, ok := mod.Module.Variables[name]; ok && varConfig.Sensitive {
sanitizeExpression(expr, replaceWith)
}
}

sanitizeModuleConfig(mod.Module, replaceWith)
}

// Sanitize outputs
SanitizeConfigOutputs(module.Outputs, replaceWith)
}

func sanitizeResourceConfig(r *tfjson.ConfigResource, replaceWith interface{}) {
for _, prov := range r.Provisioners {
if prov == nil {
continue
}
for _, expr := range prov.Expressions {
sanitizeExpression(expr, replaceWith)
}
Expand Down
43 changes: 23 additions & 20 deletions sanitize/sanitize_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,29 +54,28 @@ func SanitizePlanWithValue(result *tfjson.Plan, replaceWith interface{}) error {
}

// Sanitize ResourceChanges
for i := range result.ResourceChanges {
SanitizeChange(result.ResourceChanges[i].Change, replaceWith)
for _, v := range result.ResourceChanges {
SanitizeChange(v.Change, replaceWith)
}

// Sanitize ResourceDrifts
for i := range result.ResourceDrift {
SanitizeChange(result.ResourceDrift[i].Change, replaceWith)
for _, v := range result.ResourceDrift {
SanitizeChange(v.Change, replaceWith)
}

// Sanitize Variables
SanitizePlanVariables(result.Variables, result.Config.RootModule.Variables, replaceWith)

// Sanitize PlannedValues
SanitizeStateModule(
result.PlannedValues.RootModule,
result.ResourceChanges,
SanitizeStateModuleChangeModeAfter,
replaceWith)
if result.PlannedValues != nil {
SanitizeStateModule(
result.PlannedValues.RootModule,
result.ResourceChanges,
SanitizeStateModuleChangeModeAfter,
replaceWith)

SanitizeStateOutputs(result.PlannedValues.Outputs, replaceWith)
SanitizeStateOutputs(result.PlannedValues.Outputs, replaceWith)
}

// Sanitize PriorState
if result.PriorState != nil {
if result.PriorState != nil && result.PriorState.Values != nil {
SanitizeStateModule(
result.PriorState.Values.RootModule,
result.ResourceChanges,
Expand All @@ -91,13 +90,17 @@ func SanitizePlanWithValue(result *tfjson.Plan, replaceWith interface{}) error {
SanitizeChange(v, replaceWith)
}

// Sanitize ProviderConfigs
SanitizeProviderConfigs(result.Config.ProviderConfigs, replaceWith)
if result.Config != nil {
// Sanitize ProviderConfigs
SanitizeProviderConfigs(result.Config.ProviderConfigs, replaceWith)

// Sanitize RootModule recursively into module calls and child_modules
sanitizeModuleConfig(result.Config.RootModule, replaceWith)
if result.Config.RootModule != nil {
// Sanitize RootModule recursively into module calls and child_modules
sanitizeModuleConfig(result.Config.RootModule, replaceWith)

// Sanitize RootModule outputs
SanitizeConfigOutputs(result.Config.RootModule.Outputs, replaceWith)
// Sanitize Variables
SanitizePlanVariables(result.Variables, result.Config.RootModule.Variables, replaceWith)
}
}
return nil
}
18 changes: 18 additions & 0 deletions sanitize/sanitize_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package sanitize

import (
"encoding/json"
"errors"
"os"
"path/filepath"
"strings"
Expand All @@ -17,6 +18,23 @@ import (

const testDataDir = "testdata"

func TestSanitizePlanEmpty(t *testing.T) {
t.Run("nil", func(t *testing.T) {
err := SanitizePlan(nil)
if !errors.Is(err, NilPlanError) {
t.Error("expected NilPlanError")
}
})

t.Run("empty", func(t *testing.T) {
plan := tfjson.Plan{}
err := SanitizePlan(&plan)
if err != nil {
t.Error(err)
}
})
}

func TestSanitizePlanGolden(t *testing.T) {
cases, err := goldenCases()
if err != nil {
Expand Down
6 changes: 5 additions & 1 deletion sanitize/sanitize_plan_variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ func sanitizeVariable(
config *tfjson.ConfigVariable,
replaceWith interface{},
) {
if config != nil && config.Sensitive {
if result == nil || config == nil {
return
}

if config.Sensitive {
result.Value = replaceWith
}
}
35 changes: 27 additions & 8 deletions sanitize/sanitize_plan_variables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,38 +21,57 @@ type testVariablesCase struct {

func variablesCases() []testVariablesCase {
return []testVariablesCase{
{
name: "nil value",
old: map[string]*tfjson.PlanVariable{
"foo": nil,
},
configs: map[string]*tfjson.ConfigVariable{
"foo": {
Sensitive: true,
},
},
expected: map[string]*tfjson.PlanVariable{
"foo": nil,
},
expectedConfigs: map[string]*tfjson.ConfigVariable{
"foo": {
Sensitive: true,
},
},
},
{
name: "basic",
old: map[string]*tfjson.PlanVariable{
"foo": &tfjson.PlanVariable{
"foo": {
Value: "test-foo",
},
"bar": &tfjson.PlanVariable{
"bar": {
Value: "test-bar",
},
},
configs: map[string]*tfjson.ConfigVariable{
"foo": &tfjson.ConfigVariable{
"foo": {
Sensitive: false,
},
"bar": &tfjson.ConfigVariable{
"bar": {
Sensitive: true,
Default: DefaultSensitiveValue,
},
},
expected: map[string]*tfjson.PlanVariable{
"foo": &tfjson.PlanVariable{
"foo": {
Value: "test-foo",
},
"bar": &tfjson.PlanVariable{
"bar": {
Value: DefaultSensitiveValue,
},
},
expectedConfigs: map[string]*tfjson.ConfigVariable{
"foo": &tfjson.ConfigVariable{
"foo": {
Sensitive: false,
},
"bar": &tfjson.ConfigVariable{
"bar": {
Sensitive: true,
Default: DefaultSensitiveValue,
},
Expand Down
12 changes: 10 additions & 2 deletions sanitize/sanitize_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ func SanitizeStateModule(
mode SanitizeStateModuleChangeMode,
replaceWith interface{},
) {
if result == nil {
return
}

for _, v := range result.Resources {
sanitizeStateResource(
v,
Expand All @@ -59,6 +63,10 @@ func sanitizeStateResource(
mode SanitizeStateModuleChangeMode,
replaceWith interface{},
) {
if result == nil {
return
}

var sensitive interface{}
if rc == nil {
sensitive = result.SensitiveValues
Expand All @@ -80,7 +88,7 @@ func sanitizeStateResource(
func findResourceChange(resourceChanges []*tfjson.ResourceChange, addr string) *tfjson.ResourceChange {
// Linear search here, unfortunately :P
for _, rc := range resourceChanges {
if rc.Address == addr {
if rc != nil && rc.Address == addr {
return rc
}
}
Expand All @@ -93,7 +101,7 @@ func findResourceChange(resourceChanges []*tfjson.ResourceChange, addr string) *
// supplied in replaceWith.
func SanitizeStateOutputs(result map[string]*tfjson.StateOutput, replaceWith interface{}) {
for _, v := range result {
if v.Sensitive {
if v != nil && v.Sensitive {
v.Value = replaceWith
}
}
Expand Down
16 changes: 16 additions & 0 deletions sanitize/sanitize_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ type testStateCase struct {

func stateCases() []testStateCase {
return []testStateCase{
{
name: "nil",
old: nil,
resourceChanges: nil,
mode: "",
expected: nil,
},
{
name: "before",
old: &tfjson.StateModule{
Expand Down Expand Up @@ -211,6 +218,15 @@ type testOutputCase struct {

func outputCases() []testOutputCase {
return []testOutputCase{
{
name: "nil values",
old: map[string]*tfjson.StateOutput{
"foo": nil,
},
expected: map[string]*tfjson.StateOutput{
"foo": nil,
},
},
{
name: "basic",
old: map[string]*tfjson.StateOutput{
Expand Down

0 comments on commit 5bb94cf

Please sign in to comment.