helper/schema: support partial states
This commit is contained in:
parent
40e5608fa9
commit
87a488092c
|
@ -42,9 +42,11 @@ type ResourceData struct {
|
|||
diff *terraform.ResourceDiff
|
||||
diffing bool
|
||||
|
||||
setMap map[string]string
|
||||
newState *terraform.ResourceState
|
||||
once sync.Once
|
||||
setMap map[string]string
|
||||
newState *terraform.ResourceState
|
||||
partial bool
|
||||
partialMap map[string]struct{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// Get returns the data for the given key, or nil if the key doesn't exist
|
||||
|
@ -72,17 +74,17 @@ func (d *ResourceData) GetChange(key string) (interface{}, interface{}) {
|
|||
// existed or not in the configuration. The second boolean result will also
|
||||
// be false if a key is given that isn't in the schema at all.
|
||||
func (d *ResourceData) GetOk(key string) (interface{}, bool) {
|
||||
r := d.getRaw(key)
|
||||
r := d.getRaw(key, getSourceSet)
|
||||
return r.Value, r.Exists
|
||||
}
|
||||
|
||||
func (d *ResourceData) getRaw(key string) getResult {
|
||||
func (d *ResourceData) getRaw(key string, level getSource) getResult {
|
||||
var parts []string
|
||||
if key != "" {
|
||||
parts = strings.Split(key, ".")
|
||||
}
|
||||
|
||||
return d.getObject("", parts, d.schema, getSourceSet)
|
||||
return d.getObject("", parts, d.schema, level)
|
||||
}
|
||||
|
||||
// HasChange returns whether or not the given key has been changed.
|
||||
|
@ -91,6 +93,20 @@ func (d *ResourceData) HasChange(key string) bool {
|
|||
return !reflect.DeepEqual(o, n)
|
||||
}
|
||||
|
||||
// Partial turns partial state mode on/off.
|
||||
//
|
||||
// When partial state mode is enabled, then only key prefixes specified
|
||||
// by SetPartial will be in the final state. This allows providers to return
|
||||
// partial states for partially applied resources (when errors occur).
|
||||
func (d *ResourceData) Partial(on bool) {
|
||||
d.partial = on
|
||||
if on {
|
||||
d.partialMap = make(map[string]struct{})
|
||||
} else {
|
||||
d.partialMap = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets the value for the given key.
|
||||
//
|
||||
// If the key is invalid or the value is not a correct type, an error
|
||||
|
@ -104,6 +120,17 @@ func (d *ResourceData) Set(key string, value interface{}) error {
|
|||
return d.setObject("", parts, d.schema, value)
|
||||
}
|
||||
|
||||
// SetPartial adds the key prefix to the final state output while
|
||||
// in partial state mode.
|
||||
//
|
||||
// If partial state mode is disabled, then this has no effect. Additionally,
|
||||
// whenever partial state mode is toggled, the partial data is cleared.
|
||||
func (d *ResourceData) SetPartial(k string) {
|
||||
if d.partial {
|
||||
d.partialMap[k] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Id returns the ID of the resource.
|
||||
func (d *ResourceData) Id() string {
|
||||
var result string
|
||||
|
@ -799,7 +826,7 @@ func (d *ResourceData) setSet(
|
|||
func (d *ResourceData) stateList(
|
||||
prefix string,
|
||||
schema *Schema) map[string]string {
|
||||
countRaw := d.get(prefix, []string{"#"}, schema, getSourceSet)
|
||||
countRaw := d.get(prefix, []string{"#"}, schema, d.stateSource(prefix))
|
||||
if !countRaw.Exists {
|
||||
return nil
|
||||
}
|
||||
|
@ -831,7 +858,7 @@ func (d *ResourceData) stateList(
|
|||
func (d *ResourceData) stateMap(
|
||||
prefix string,
|
||||
schema *Schema) map[string]string {
|
||||
v := d.getMap(prefix, nil, schema, getSourceSet)
|
||||
v := d.getMap(prefix, nil, schema, d.stateSource(prefix))
|
||||
if !v.Exists {
|
||||
return nil
|
||||
}
|
||||
|
@ -869,7 +896,7 @@ func (d *ResourceData) stateObject(
|
|||
func (d *ResourceData) statePrimitive(
|
||||
prefix string,
|
||||
schema *Schema) map[string]string {
|
||||
raw := d.getRaw(prefix)
|
||||
raw := d.getRaw(prefix, d.stateSource(prefix))
|
||||
if !raw.Exists {
|
||||
return nil
|
||||
}
|
||||
|
@ -899,7 +926,7 @@ func (d *ResourceData) statePrimitive(
|
|||
func (d *ResourceData) stateSet(
|
||||
prefix string,
|
||||
schema *Schema) map[string]string {
|
||||
raw := d.get(prefix, nil, schema, getSourceSet)
|
||||
raw := d.get(prefix, nil, schema, d.stateSource(prefix))
|
||||
if !raw.Exists {
|
||||
return nil
|
||||
}
|
||||
|
@ -947,3 +974,20 @@ func (d *ResourceData) stateSingle(
|
|||
panic(fmt.Sprintf("%s: unknown type %#v", prefix, schema.Type))
|
||||
}
|
||||
}
|
||||
|
||||
func (d *ResourceData) stateSource(prefix string) getSource {
|
||||
// If we're not doing a partial apply, then get the set level
|
||||
if !d.partial {
|
||||
return getSourceSet
|
||||
}
|
||||
|
||||
// Otherwise, only return getSourceSet if its in the partial map.
|
||||
// Otherwise we use state level only.
|
||||
for k, _ := range d.partialMap {
|
||||
if strings.HasPrefix(prefix, k) {
|
||||
return getSourceSet
|
||||
}
|
||||
}
|
||||
|
||||
return getSourceState
|
||||
}
|
||||
|
|
|
@ -1363,11 +1363,12 @@ func TestResourceDataSet(t *testing.T) {
|
|||
|
||||
func TestResourceDataState(t *testing.T) {
|
||||
cases := []struct {
|
||||
Schema map[string]*Schema
|
||||
State *terraform.ResourceState
|
||||
Diff *terraform.ResourceDiff
|
||||
Set map[string]interface{}
|
||||
Result *terraform.ResourceState
|
||||
Schema map[string]*Schema
|
||||
State *terraform.ResourceState
|
||||
Diff *terraform.ResourceDiff
|
||||
Set map[string]interface{}
|
||||
Result *terraform.ResourceState
|
||||
Partial []string
|
||||
}{
|
||||
// Basic primitive in diff
|
||||
{
|
||||
|
@ -1728,6 +1729,221 @@ func TestResourceDataState(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
/*
|
||||
* PARTIAL STATES
|
||||
*/
|
||||
|
||||
// Basic primitive
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
},
|
||||
|
||||
State: nil,
|
||||
|
||||
Diff: &terraform.ResourceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"availability_zone": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "foo",
|
||||
RequiresNew: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Partial: []string{},
|
||||
|
||||
Result: &terraform.ResourceState{
|
||||
Attributes: map[string]string{},
|
||||
},
|
||||
},
|
||||
|
||||
// List
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeList,
|
||||
Required: true,
|
||||
Elem: &Schema{Type: TypeInt},
|
||||
},
|
||||
},
|
||||
|
||||
State: &terraform.ResourceState{
|
||||
Attributes: map[string]string{
|
||||
"ports.#": "1",
|
||||
"ports.0": "80",
|
||||
},
|
||||
},
|
||||
|
||||
Diff: &terraform.ResourceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"ports.#": &terraform.ResourceAttrDiff{
|
||||
Old: "1",
|
||||
New: "2",
|
||||
},
|
||||
"ports.1": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "100",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Partial: []string{},
|
||||
|
||||
Result: &terraform.ResourceState{
|
||||
Attributes: map[string]string{
|
||||
"ports.#": "1",
|
||||
"ports.0": "80",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// List of resources
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"ingress": &Schema{
|
||||
Type: TypeList,
|
||||
Required: true,
|
||||
Elem: &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"from": &Schema{
|
||||
Type: TypeInt,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
State: &terraform.ResourceState{
|
||||
Attributes: map[string]string{
|
||||
"ingress.#": "1",
|
||||
"ingress.0.from": "80",
|
||||
},
|
||||
},
|
||||
|
||||
Diff: &terraform.ResourceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"ingress.#": &terraform.ResourceAttrDiff{
|
||||
Old: "1",
|
||||
New: "2",
|
||||
},
|
||||
"ingress.0.from": &terraform.ResourceAttrDiff{
|
||||
Old: "80",
|
||||
New: "150",
|
||||
},
|
||||
"ingress.1.from": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "100",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Partial: []string{},
|
||||
|
||||
Result: &terraform.ResourceState{
|
||||
Attributes: map[string]string{
|
||||
"ingress.#": "1",
|
||||
"ingress.0.from": "80",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// List of maps
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"config_vars": &Schema{
|
||||
Type: TypeList,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &Schema{
|
||||
Type: TypeMap,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
State: &terraform.ResourceState{
|
||||
Attributes: map[string]string{
|
||||
"config_vars.#": "2",
|
||||
"config_vars.0.foo": "bar",
|
||||
"config_vars.0.bar": "bar",
|
||||
"config_vars.1.bar": "baz",
|
||||
},
|
||||
},
|
||||
|
||||
Diff: &terraform.ResourceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"config_vars.0.bar": &terraform.ResourceAttrDiff{
|
||||
NewRemoved: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Set: map[string]interface{}{
|
||||
"config_vars.1": map[string]interface{}{
|
||||
"baz": "bang",
|
||||
},
|
||||
},
|
||||
|
||||
Partial: []string{},
|
||||
|
||||
Result: &terraform.ResourceState{
|
||||
Attributes: map[string]string{
|
||||
"config_vars.#": "2",
|
||||
"config_vars.0.foo": "bar",
|
||||
"config_vars.0.bar": "bar",
|
||||
"config_vars.1.bar": "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Sets
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeSet,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &Schema{Type: TypeInt},
|
||||
Set: func(a interface{}) int {
|
||||
return a.(int)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
State: &terraform.ResourceState{
|
||||
Attributes: map[string]string{
|
||||
"ports.#": "3",
|
||||
"ports.0": "100",
|
||||
"ports.1": "80",
|
||||
"ports.2": "80",
|
||||
},
|
||||
},
|
||||
|
||||
Diff: &terraform.ResourceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"ports.1": &terraform.ResourceAttrDiff{
|
||||
New: "120",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Partial: []string{},
|
||||
|
||||
Result: &terraform.ResourceState{
|
||||
Attributes: map[string]string{
|
||||
"ports.#": "2",
|
||||
"ports.0": "80",
|
||||
"ports.1": "100",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
|
@ -1749,6 +1965,14 @@ func TestResourceDataState(t *testing.T) {
|
|||
d.SetId("foo")
|
||||
}
|
||||
|
||||
// If we have partial, then enable partial state mode.
|
||||
if tc.Partial != nil {
|
||||
d.Partial(true)
|
||||
for _, k := range tc.Partial {
|
||||
d.SetPartial(k)
|
||||
}
|
||||
}
|
||||
|
||||
actual := d.State()
|
||||
|
||||
// If we set an ID, then undo what we did so the comparison works
|
||||
|
@ -1758,7 +1982,7 @@ func TestResourceDataState(t *testing.T) {
|
|||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, tc.Result) {
|
||||
t.Fatalf("Bad: %d\n\n%#v", i, actual)
|
||||
t.Fatalf("Bad: %d\n\n%#v\n\nExpected:\n\n%#v", i, actual, tc.Result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue