helper/schema: support partial states
This commit is contained in:
parent
40e5608fa9
commit
87a488092c
|
@ -42,9 +42,11 @@ type ResourceData struct {
|
||||||
diff *terraform.ResourceDiff
|
diff *terraform.ResourceDiff
|
||||||
diffing bool
|
diffing bool
|
||||||
|
|
||||||
setMap map[string]string
|
setMap map[string]string
|
||||||
newState *terraform.ResourceState
|
newState *terraform.ResourceState
|
||||||
once sync.Once
|
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
|
// 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
|
// 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.
|
// be false if a key is given that isn't in the schema at all.
|
||||||
func (d *ResourceData) GetOk(key string) (interface{}, bool) {
|
func (d *ResourceData) GetOk(key string) (interface{}, bool) {
|
||||||
r := d.getRaw(key)
|
r := d.getRaw(key, getSourceSet)
|
||||||
return r.Value, r.Exists
|
return r.Value, r.Exists
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ResourceData) getRaw(key string) getResult {
|
func (d *ResourceData) getRaw(key string, level getSource) getResult {
|
||||||
var parts []string
|
var parts []string
|
||||||
if key != "" {
|
if key != "" {
|
||||||
parts = strings.Split(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.
|
// 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)
|
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.
|
// Set sets the value for the given key.
|
||||||
//
|
//
|
||||||
// If the key is invalid or the value is not a correct type, an error
|
// 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)
|
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.
|
// Id returns the ID of the resource.
|
||||||
func (d *ResourceData) Id() string {
|
func (d *ResourceData) Id() string {
|
||||||
var result string
|
var result string
|
||||||
|
@ -799,7 +826,7 @@ func (d *ResourceData) setSet(
|
||||||
func (d *ResourceData) stateList(
|
func (d *ResourceData) stateList(
|
||||||
prefix string,
|
prefix string,
|
||||||
schema *Schema) map[string]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 {
|
if !countRaw.Exists {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -831,7 +858,7 @@ func (d *ResourceData) stateList(
|
||||||
func (d *ResourceData) stateMap(
|
func (d *ResourceData) stateMap(
|
||||||
prefix string,
|
prefix string,
|
||||||
schema *Schema) map[string]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 {
|
if !v.Exists {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -869,7 +896,7 @@ func (d *ResourceData) stateObject(
|
||||||
func (d *ResourceData) statePrimitive(
|
func (d *ResourceData) statePrimitive(
|
||||||
prefix string,
|
prefix string,
|
||||||
schema *Schema) map[string]string {
|
schema *Schema) map[string]string {
|
||||||
raw := d.getRaw(prefix)
|
raw := d.getRaw(prefix, d.stateSource(prefix))
|
||||||
if !raw.Exists {
|
if !raw.Exists {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -899,7 +926,7 @@ func (d *ResourceData) statePrimitive(
|
||||||
func (d *ResourceData) stateSet(
|
func (d *ResourceData) stateSet(
|
||||||
prefix string,
|
prefix string,
|
||||||
schema *Schema) map[string]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 {
|
if !raw.Exists {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -947,3 +974,20 @@ func (d *ResourceData) stateSingle(
|
||||||
panic(fmt.Sprintf("%s: unknown type %#v", prefix, schema.Type))
|
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) {
|
func TestResourceDataState(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Schema map[string]*Schema
|
Schema map[string]*Schema
|
||||||
State *terraform.ResourceState
|
State *terraform.ResourceState
|
||||||
Diff *terraform.ResourceDiff
|
Diff *terraform.ResourceDiff
|
||||||
Set map[string]interface{}
|
Set map[string]interface{}
|
||||||
Result *terraform.ResourceState
|
Result *terraform.ResourceState
|
||||||
|
Partial []string
|
||||||
}{
|
}{
|
||||||
// Basic primitive in diff
|
// 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 {
|
for i, tc := range cases {
|
||||||
|
@ -1749,6 +1965,14 @@ func TestResourceDataState(t *testing.T) {
|
||||||
d.SetId("foo")
|
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()
|
actual := d.State()
|
||||||
|
|
||||||
// If we set an ID, then undo what we did so the comparison works
|
// 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) {
|
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