core: Pass components through to the destroy transformers
These transformers both construct temporary graphs using many of the same transformers used in the apply graph, and properly doing this now requires access to the providers and provisioners in order to obtain their schemas. Along with this, we also update the tests here to use the simpleMockComponentFactory helper to get a mock provider with a schema already configured, which means we also need to update the test fixtures and assertions to use the resource type and attributes defined in that mock factory.
This commit is contained in:
parent
1a9ee72ecb
commit
bec0f56808
|
@ -291,6 +291,7 @@ func (c *Context) Graph(typ GraphType, opts *ContextGraphOpts) (*Graph, tfdiags.
|
||||||
return (&DestroyPlanGraphBuilder{
|
return (&DestroyPlanGraphBuilder{
|
||||||
Config: c.config,
|
Config: c.config,
|
||||||
State: c.state,
|
State: c.state,
|
||||||
|
Components: c.components,
|
||||||
Targets: c.targets,
|
Targets: c.targets,
|
||||||
Validate: opts.Validate,
|
Validate: opts.Validate,
|
||||||
}).Build(addrs.RootModuleInstance)
|
}).Build(addrs.RootModuleInstance)
|
||||||
|
|
|
@ -88,10 +88,18 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
|
||||||
TransformProviders(b.Components.ResourceProviders(), concreteProvider, b.Config),
|
TransformProviders(b.Components.ResourceProviders(), concreteProvider, b.Config),
|
||||||
|
|
||||||
// Destruction ordering
|
// Destruction ordering
|
||||||
&DestroyEdgeTransformer{Config: b.Config, State: b.State},
|
&DestroyEdgeTransformer{
|
||||||
|
Config: b.Config,
|
||||||
|
State: b.State,
|
||||||
|
Components: b.Components,
|
||||||
|
},
|
||||||
GraphTransformIf(
|
GraphTransformIf(
|
||||||
func() bool { return !b.Destroy },
|
func() bool { return !b.Destroy },
|
||||||
&CBDEdgeTransformer{Config: b.Config, State: b.State},
|
&CBDEdgeTransformer{
|
||||||
|
Config: b.Config,
|
||||||
|
State: b.State,
|
||||||
|
Components: b.Components,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
// Provisioner-related transformations
|
// Provisioner-related transformations
|
||||||
|
|
|
@ -22,6 +22,10 @@ type DestroyPlanGraphBuilder struct {
|
||||||
// Targets are resources to target
|
// Targets are resources to target
|
||||||
Targets []addrs.Targetable
|
Targets []addrs.Targetable
|
||||||
|
|
||||||
|
// Components is a factory for the plug-in components (providers and
|
||||||
|
// provisioners) available for use.
|
||||||
|
Components contextComponentFactory
|
||||||
|
|
||||||
// Validate will do structural validation of the graph.
|
// Validate will do structural validation of the graph.
|
||||||
Validate bool
|
Validate bool
|
||||||
}
|
}
|
||||||
|
@ -55,7 +59,11 @@ func (b *DestroyPlanGraphBuilder) Steps() []GraphTransformer {
|
||||||
|
|
||||||
// Destruction ordering. We require this only so that
|
// Destruction ordering. We require this only so that
|
||||||
// targeting below will prune the correct things.
|
// targeting below will prune the correct things.
|
||||||
&DestroyEdgeTransformer{Config: b.Config, State: b.State},
|
&DestroyEdgeTransformer{
|
||||||
|
Config: b.Config,
|
||||||
|
State: b.State,
|
||||||
|
Components: b.Components,
|
||||||
|
},
|
||||||
|
|
||||||
// Target. Note we don't set "Destroy: true" here since we already
|
// Target. Note we don't set "Destroy: true" here since we already
|
||||||
// created proper destroy ordering.
|
// created proper destroy ordering.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
resource "test" "A" {}
|
resource "test_object" "A" {}
|
||||||
|
|
||||||
resource "test" "B" {
|
resource "test_object" "B" {
|
||||||
value = "${test.A.value}"
|
test_string = "${test_object.A.test_string}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
resource "aws_instance" "a" {}
|
resource "test_object" "a" {}
|
||||||
resource "aws_instance" "b" {
|
|
||||||
value = "${aws_instance.a.id}"
|
resource "test_object" "b" {
|
||||||
|
test_string = "${test_object.a.test_string}"
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_instance" "c" {
|
resource "test_object" "c" {
|
||||||
value = "${aws_instance.b.id}"
|
test_string = "${test_object.b.test_string}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
resource "aws_instance" "b" {
|
resource "test_object" "b" {
|
||||||
value = "foo"
|
test_string = "foo"
|
||||||
}
|
}
|
||||||
|
|
||||||
output "output" {
|
output "output" {
|
||||||
value = "${aws_instance.b.value}"
|
value = "${test_object.b.test_string}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
resource "aws_instance" "a" {
|
resource "test_object" "a" {
|
||||||
value = "${module.child.output}"
|
test_string = "${module.child.output}"
|
||||||
}
|
}
|
||||||
|
|
||||||
module "child" {
|
module "child" {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
resource "test" "A" {}
|
resource "test_object" "A" {}
|
||||||
|
|
||||||
resource "test" "B" {
|
resource "test_object" "B" {
|
||||||
value = "${test.A.value}"
|
test_string = "${test_object.A.test_string}"
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "test" "C" {
|
resource "test_object" "C" {
|
||||||
value = "${test.B.value}"
|
test_string = "${test_object.B.test_string}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
resource "test" "A" {}
|
resource "test_object" "A" {}
|
||||||
resource "test" "B" {
|
|
||||||
|
resource "test_object" "B" {
|
||||||
count = 2
|
count = 2
|
||||||
value = "${test.A.*.value}"
|
test_string = "${test_object.A.*.test_string}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,11 +40,14 @@ type CBDEdgeTransformer struct {
|
||||||
// any way possible. Either can be nil if not availabile.
|
// any way possible. Either can be nil if not availabile.
|
||||||
Config *configs.Config
|
Config *configs.Config
|
||||||
State *State
|
State *State
|
||||||
|
|
||||||
|
// If configuration is present then Components is required in order to
|
||||||
|
// obtain schema information from providers and provisioners in order
|
||||||
|
// to properly resolve implicit dependencies.
|
||||||
|
Components contextComponentFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *CBDEdgeTransformer) Transform(g *Graph) error {
|
func (t *CBDEdgeTransformer) Transform(g *Graph) error {
|
||||||
log.Printf("[TRACE] CBDEdgeTransformer: Beginning CBD transformation...")
|
|
||||||
|
|
||||||
// Go through and reverse any destroy edges
|
// Go through and reverse any destroy edges
|
||||||
destroyMap := make(map[string][]dag.Vertex)
|
destroyMap := make(map[string][]dag.Vertex)
|
||||||
for _, v := range g.Vertices() {
|
for _, v := range g.Vertices() {
|
||||||
|
@ -64,6 +67,7 @@ func (t *CBDEdgeTransformer) Transform(g *Graph) error {
|
||||||
// and we need to auto-upgrade this node to CBD. We do this because
|
// and we need to auto-upgrade this node to CBD. We do this because
|
||||||
// a CBD node depending on non-CBD will result in cycles. To avoid this,
|
// a CBD node depending on non-CBD will result in cycles. To avoid this,
|
||||||
// we always attempt to upgrade it.
|
// we always attempt to upgrade it.
|
||||||
|
log.Printf("[TRACE] CBDEdgeTransformer: forcing create_before_destroy on for %q (%T)", dag.VertexName(v), v)
|
||||||
if err := dn.ModifyCreateBeforeDestroy(true); err != nil {
|
if err := dn.ModifyCreateBeforeDestroy(true); err != nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"%s: must have create before destroy enabled because "+
|
"%s: must have create before destroy enabled because "+
|
||||||
|
@ -174,6 +178,7 @@ func (t *CBDEdgeTransformer) depMap(destroyMap map[string][]dag.Vertex) (map[str
|
||||||
&FlatConfigTransformer{Config: t.Config},
|
&FlatConfigTransformer{Config: t.Config},
|
||||||
&AttachResourceConfigTransformer{Config: t.Config},
|
&AttachResourceConfigTransformer{Config: t.Config},
|
||||||
&AttachStateTransformer{State: t.State},
|
&AttachStateTransformer{State: t.State},
|
||||||
|
&AttachSchemaTransformer{Components: t.Components},
|
||||||
&ReferenceTransformer{},
|
&ReferenceTransformer{},
|
||||||
},
|
},
|
||||||
Name: "CBDEdgeTransformer",
|
Name: "CBDEdgeTransformer",
|
||||||
|
|
|
@ -9,15 +9,16 @@ import (
|
||||||
|
|
||||||
func TestCBDEdgeTransformer(t *testing.T) {
|
func TestCBDEdgeTransformer(t *testing.T) {
|
||||||
g := Graph{Path: addrs.RootModuleInstance}
|
g := Graph{Path: addrs.RootModuleInstance}
|
||||||
g.Add(&graphNodeCreatorTest{AddrString: "test.A"})
|
g.Add(&graphNodeCreatorTest{AddrString: "test_object.A"})
|
||||||
g.Add(&graphNodeCreatorTest{AddrString: "test.B"})
|
g.Add(&graphNodeCreatorTest{AddrString: "test_object.B"})
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "test.A", CBD: true})
|
g.Add(&graphNodeDestroyerTest{AddrString: "test_object.A", CBD: true})
|
||||||
|
|
||||||
module := testModule(t, "transform-destroy-edge-basic")
|
module := testModule(t, "transform-destroy-edge-basic")
|
||||||
|
|
||||||
{
|
{
|
||||||
tf := &DestroyEdgeTransformer{
|
tf := &DestroyEdgeTransformer{
|
||||||
Config: module,
|
Config: module,
|
||||||
|
Components: simpleMockComponentFactory(),
|
||||||
}
|
}
|
||||||
if err := tf.Transform(&g); err != nil {
|
if err := tf.Transform(&g); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
@ -25,7 +26,10 @@ func TestCBDEdgeTransformer(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
tf := &CBDEdgeTransformer{Config: module}
|
tf := &CBDEdgeTransformer{
|
||||||
|
Config: module,
|
||||||
|
Components: simpleMockComponentFactory(),
|
||||||
|
}
|
||||||
if err := tf.Transform(&g); err != nil {
|
if err := tf.Transform(&g); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -34,22 +38,23 @@ func TestCBDEdgeTransformer(t *testing.T) {
|
||||||
actual := strings.TrimSpace(g.String())
|
actual := strings.TrimSpace(g.String())
|
||||||
expected := strings.TrimSpace(testTransformCBDEdgeBasicStr)
|
expected := strings.TrimSpace(testTransformCBDEdgeBasicStr)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Fatalf("bad:\n\n%s", actual)
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCBDEdgeTransformer_depNonCBD(t *testing.T) {
|
func TestCBDEdgeTransformer_depNonCBD(t *testing.T) {
|
||||||
g := Graph{Path: addrs.RootModuleInstance}
|
g := Graph{Path: addrs.RootModuleInstance}
|
||||||
g.Add(&graphNodeCreatorTest{AddrString: "test.A"})
|
g.Add(&graphNodeCreatorTest{AddrString: "test_object.A"})
|
||||||
g.Add(&graphNodeCreatorTest{AddrString: "test.B"})
|
g.Add(&graphNodeCreatorTest{AddrString: "test_object.B"})
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
|
g.Add(&graphNodeDestroyerTest{AddrString: "test_object.A"})
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "test.B", CBD: true})
|
g.Add(&graphNodeDestroyerTest{AddrString: "test_object.B", CBD: true})
|
||||||
|
|
||||||
module := testModule(t, "transform-destroy-edge-basic")
|
module := testModule(t, "transform-destroy-edge-basic")
|
||||||
|
|
||||||
{
|
{
|
||||||
tf := &DestroyEdgeTransformer{
|
tf := &DestroyEdgeTransformer{
|
||||||
Config: module,
|
Config: module,
|
||||||
|
Components: simpleMockComponentFactory(),
|
||||||
}
|
}
|
||||||
if err := tf.Transform(&g); err != nil {
|
if err := tf.Transform(&g); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
@ -57,7 +62,10 @@ func TestCBDEdgeTransformer_depNonCBD(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
tf := &CBDEdgeTransformer{Config: module}
|
tf := &CBDEdgeTransformer{
|
||||||
|
Config: module,
|
||||||
|
Components: simpleMockComponentFactory(),
|
||||||
|
}
|
||||||
if err := tf.Transform(&g); err != nil {
|
if err := tf.Transform(&g); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -66,22 +74,23 @@ func TestCBDEdgeTransformer_depNonCBD(t *testing.T) {
|
||||||
actual := strings.TrimSpace(g.String())
|
actual := strings.TrimSpace(g.String())
|
||||||
expected := strings.TrimSpace(testTransformCBDEdgeDepNonCBDStr)
|
expected := strings.TrimSpace(testTransformCBDEdgeDepNonCBDStr)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Fatalf("bad:\n\n%s", actual)
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCBDEdgeTransformer_depNonCBDCount(t *testing.T) {
|
func TestCBDEdgeTransformer_depNonCBDCount(t *testing.T) {
|
||||||
g := Graph{Path: addrs.RootModuleInstance}
|
g := Graph{Path: addrs.RootModuleInstance}
|
||||||
g.Add(&graphNodeCreatorTest{AddrString: "test.A"})
|
g.Add(&graphNodeCreatorTest{AddrString: "test_object.A"})
|
||||||
g.Add(&graphNodeCreatorTest{AddrString: "test.B[0]"})
|
g.Add(&graphNodeCreatorTest{AddrString: "test_object.B[0]"})
|
||||||
g.Add(&graphNodeCreatorTest{AddrString: "test.B[1]"})
|
g.Add(&graphNodeCreatorTest{AddrString: "test_object.B[1]"})
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "test.A", CBD: true})
|
g.Add(&graphNodeDestroyerTest{AddrString: "test_object.A", CBD: true})
|
||||||
|
|
||||||
module := testModule(t, "transform-destroy-edge-splat")
|
module := testModule(t, "transform-destroy-edge-splat")
|
||||||
|
|
||||||
{
|
{
|
||||||
tf := &DestroyEdgeTransformer{
|
tf := &DestroyEdgeTransformer{
|
||||||
Config: module,
|
Config: module,
|
||||||
|
Components: simpleMockComponentFactory(),
|
||||||
}
|
}
|
||||||
if err := tf.Transform(&g); err != nil {
|
if err := tf.Transform(&g); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
@ -89,7 +98,10 @@ func TestCBDEdgeTransformer_depNonCBDCount(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
tf := &CBDEdgeTransformer{Config: module}
|
tf := &CBDEdgeTransformer{
|
||||||
|
Config: module,
|
||||||
|
Components: simpleMockComponentFactory(),
|
||||||
|
}
|
||||||
if err := tf.Transform(&g); err != nil {
|
if err := tf.Transform(&g); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -97,33 +109,34 @@ func TestCBDEdgeTransformer_depNonCBDCount(t *testing.T) {
|
||||||
|
|
||||||
actual := strings.TrimSpace(g.String())
|
actual := strings.TrimSpace(g.String())
|
||||||
expected := strings.TrimSpace(`
|
expected := strings.TrimSpace(`
|
||||||
test.A
|
test_object.A
|
||||||
test.A (destroy)
|
test_object.A (destroy)
|
||||||
test.A
|
test_object.A
|
||||||
test.B[0]
|
test_object.B[0]
|
||||||
test.B[1]
|
test_object.B[1]
|
||||||
test.B[0]
|
test_object.B[0]
|
||||||
test.B[1]
|
test_object.B[1]
|
||||||
`)
|
`)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Fatalf("bad:\n\n%s", actual)
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCBDEdgeTransformer_depNonCBDCountBoth(t *testing.T) {
|
func TestCBDEdgeTransformer_depNonCBDCountBoth(t *testing.T) {
|
||||||
g := Graph{Path: addrs.RootModuleInstance}
|
g := Graph{Path: addrs.RootModuleInstance}
|
||||||
g.Add(&graphNodeCreatorTest{AddrString: "test.A[0]"})
|
g.Add(&graphNodeCreatorTest{AddrString: "test_object.A[0]"})
|
||||||
g.Add(&graphNodeCreatorTest{AddrString: "test.A[1]"})
|
g.Add(&graphNodeCreatorTest{AddrString: "test_object.A[1]"})
|
||||||
g.Add(&graphNodeCreatorTest{AddrString: "test.B[0]"})
|
g.Add(&graphNodeCreatorTest{AddrString: "test_object.B[0]"})
|
||||||
g.Add(&graphNodeCreatorTest{AddrString: "test.B[1]"})
|
g.Add(&graphNodeCreatorTest{AddrString: "test_object.B[1]"})
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "test.A[0]", CBD: true})
|
g.Add(&graphNodeDestroyerTest{AddrString: "test_object.A[0]", CBD: true})
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "test.A[1]", CBD: true})
|
g.Add(&graphNodeDestroyerTest{AddrString: "test_object.A[1]", CBD: true})
|
||||||
|
|
||||||
module := testModule(t, "transform-destroy-edge-splat")
|
module := testModule(t, "transform-destroy-edge-splat")
|
||||||
|
|
||||||
{
|
{
|
||||||
tf := &DestroyEdgeTransformer{
|
tf := &DestroyEdgeTransformer{
|
||||||
Config: module,
|
Config: module,
|
||||||
|
Components: simpleMockComponentFactory(),
|
||||||
}
|
}
|
||||||
if err := tf.Transform(&g); err != nil {
|
if err := tf.Transform(&g); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
@ -131,7 +144,10 @@ func TestCBDEdgeTransformer_depNonCBDCountBoth(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
tf := &CBDEdgeTransformer{Config: module}
|
tf := &CBDEdgeTransformer{
|
||||||
|
Config: module,
|
||||||
|
Components: simpleMockComponentFactory(),
|
||||||
|
}
|
||||||
if err := tf.Transform(&g); err != nil {
|
if err := tf.Transform(&g); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -139,39 +155,39 @@ func TestCBDEdgeTransformer_depNonCBDCountBoth(t *testing.T) {
|
||||||
|
|
||||||
actual := strings.TrimSpace(g.String())
|
actual := strings.TrimSpace(g.String())
|
||||||
expected := strings.TrimSpace(`
|
expected := strings.TrimSpace(`
|
||||||
test.A[0]
|
test_object.A[0]
|
||||||
test.A[0] (destroy)
|
test_object.A[0] (destroy)
|
||||||
test.A[0]
|
test_object.A[0]
|
||||||
test.B[0]
|
test_object.B[0]
|
||||||
test.B[1]
|
test_object.B[1]
|
||||||
test.A[1]
|
test_object.A[1]
|
||||||
test.A[1] (destroy)
|
test_object.A[1] (destroy)
|
||||||
test.A[1]
|
test_object.A[1]
|
||||||
test.B[0]
|
test_object.B[0]
|
||||||
test.B[1]
|
test_object.B[1]
|
||||||
test.B[0]
|
test_object.B[0]
|
||||||
test.B[1]
|
test_object.B[1]
|
||||||
`)
|
`)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Fatalf("bad:\n\n%s", actual)
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const testTransformCBDEdgeBasicStr = `
|
const testTransformCBDEdgeBasicStr = `
|
||||||
test.A
|
test_object.A
|
||||||
test.A (destroy)
|
test_object.A (destroy)
|
||||||
test.A
|
test_object.A
|
||||||
test.B
|
test_object.B
|
||||||
test.B
|
test_object.B
|
||||||
`
|
`
|
||||||
|
|
||||||
const testTransformCBDEdgeDepNonCBDStr = `
|
const testTransformCBDEdgeDepNonCBDStr = `
|
||||||
test.A
|
test_object.A
|
||||||
test.A (destroy) (modified)
|
test_object.A (destroy) (modified)
|
||||||
test.A
|
test_object.A
|
||||||
test.B
|
test_object.B
|
||||||
test.B (destroy)
|
test_object.B (destroy)
|
||||||
test.B
|
test_object.B
|
||||||
test.B (destroy)
|
test_object.B (destroy)
|
||||||
test.B
|
test_object.B
|
||||||
`
|
`
|
||||||
|
|
|
@ -44,11 +44,14 @@ type DestroyEdgeTransformer struct {
|
||||||
// to determine what a destroy node depends on. Any of these can be nil.
|
// to determine what a destroy node depends on. Any of these can be nil.
|
||||||
Config *configs.Config
|
Config *configs.Config
|
||||||
State *State
|
State *State
|
||||||
|
|
||||||
|
// If configuration is present then Components is required in order to
|
||||||
|
// obtain schema information from providers and provisioners in order
|
||||||
|
// to properly resolve implicit dependencies.
|
||||||
|
Components contextComponentFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
|
func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
|
||||||
log.Printf("[TRACE] DestroyEdgeTransformer: Beginning destroy edge transformation...")
|
|
||||||
|
|
||||||
// Build a map of what is being destroyed (by address string) to
|
// Build a map of what is being destroyed (by address string) to
|
||||||
// the list of destroyers. In general there will only be one destroyer
|
// the list of destroyers. In general there will only be one destroyer
|
||||||
// but to make it more robust we support multiple.
|
// but to make it more robust we support multiple.
|
||||||
|
@ -67,7 +70,7 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
|
||||||
addr := *addrP
|
addr := *addrP
|
||||||
|
|
||||||
key := addr.String()
|
key := addr.String()
|
||||||
log.Printf("[TRACE] DestroyEdgeTransformer: %s will destroy %s", dag.VertexName(dn), key)
|
log.Printf("[TRACE] DestroyEdgeTransformer: %q (%T) destroys %s", dag.VertexName(dn), v, key)
|
||||||
destroyers[key] = append(destroyers[key], dn)
|
destroyers[key] = append(destroyers[key], dn)
|
||||||
destroyerAddrs[key] = addr
|
destroyerAddrs[key] = addr
|
||||||
}
|
}
|
||||||
|
@ -103,7 +106,7 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
|
||||||
a := v
|
a := v
|
||||||
|
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"[TRACE] DestroyEdgeTransformer: connecting creator/destroyer: %s, %s",
|
"[TRACE] DestroyEdgeTransformer: connecting creator %q with destroyer %q",
|
||||||
dag.VertexName(a), dag.VertexName(a_d))
|
dag.VertexName(a), dag.VertexName(a_d))
|
||||||
|
|
||||||
g.Connect(&DestroyEdge{S: a, T: a_d})
|
g.Connect(&DestroyEdge{S: a, T: a_d})
|
||||||
|
@ -138,6 +141,10 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
|
||||||
&RootVariableTransformer{Config: t.Config},
|
&RootVariableTransformer{Config: t.Config},
|
||||||
&ModuleVariableTransformer{Config: t.Config},
|
&ModuleVariableTransformer{Config: t.Config},
|
||||||
|
|
||||||
|
// Must be before ReferenceTransformer, since schema is required to
|
||||||
|
// extract references from config.
|
||||||
|
&AttachSchemaTransformer{Components: t.Components},
|
||||||
|
|
||||||
&ReferenceTransformer{},
|
&ReferenceTransformer{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,12 +164,7 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
|
||||||
// This part is a little bit weird but is the best way to
|
// This part is a little bit weird but is the best way to
|
||||||
// find the dependencies we need to: build a graph and use the
|
// find the dependencies we need to: build a graph and use the
|
||||||
// attach config and state transformers then ask for references.
|
// attach config and state transformers then ask for references.
|
||||||
abstract := &NodeAbstractResourceInstance{
|
abstract := NewNodeAbstractResourceInstance(addr)
|
||||||
NodeAbstractResource: NodeAbstractResource{
|
|
||||||
Addr: addr.ContainingResource(),
|
|
||||||
},
|
|
||||||
InstanceKey: addr.Resource.Key,
|
|
||||||
}
|
|
||||||
tempG.Add(abstract)
|
tempG.Add(abstract)
|
||||||
tempDestroyed = append(tempDestroyed, abstract)
|
tempDestroyed = append(tempDestroyed, abstract)
|
||||||
|
|
||||||
|
@ -175,13 +177,15 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
|
||||||
|
|
||||||
// Run the graph transforms so we have the information we need to
|
// Run the graph transforms so we have the information we need to
|
||||||
// build references.
|
// build references.
|
||||||
|
log.Println("[TRACE] DestroyEdgeTransformer: constructing temporary graph for analysis of references")
|
||||||
for _, s := range steps {
|
for _, s := range steps {
|
||||||
|
log.Printf("[TRACE] DestroyEdgeTransformer: running %T on temporary graph", s)
|
||||||
if err := s.Transform(&tempG); err != nil {
|
if err := s.Transform(&tempG); err != nil {
|
||||||
|
log.Printf("[TRACE] DestroyEdgeTransformer: %T failed: %s", s, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.Printf("[TRACE] DestroyEdgeTransformer: temporary reference graph: %s", tempG.String())
|
||||||
log.Printf("[TRACE] DestroyEdgeTransformer: reference graph: %s", tempG.String())
|
|
||||||
|
|
||||||
// Go through all the nodes in the graph and determine what they
|
// Go through all the nodes in the graph and determine what they
|
||||||
// depend on.
|
// depend on.
|
||||||
|
|
|
@ -9,10 +9,11 @@ import (
|
||||||
|
|
||||||
func TestDestroyEdgeTransformer_basic(t *testing.T) {
|
func TestDestroyEdgeTransformer_basic(t *testing.T) {
|
||||||
g := Graph{Path: addrs.RootModuleInstance}
|
g := Graph{Path: addrs.RootModuleInstance}
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
|
g.Add(&graphNodeDestroyerTest{AddrString: "test_object.A"})
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "test.B"})
|
g.Add(&graphNodeDestroyerTest{AddrString: "test_object.B"})
|
||||||
tf := &DestroyEdgeTransformer{
|
tf := &DestroyEdgeTransformer{
|
||||||
Config: testModule(t, "transform-destroy-edge-basic"),
|
Config: testModule(t, "transform-destroy-edge-basic"),
|
||||||
|
Components: simpleMockComponentFactory(),
|
||||||
}
|
}
|
||||||
if err := tf.Transform(&g); err != nil {
|
if err := tf.Transform(&g); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
@ -21,17 +22,18 @@ func TestDestroyEdgeTransformer_basic(t *testing.T) {
|
||||||
actual := strings.TrimSpace(g.String())
|
actual := strings.TrimSpace(g.String())
|
||||||
expected := strings.TrimSpace(testTransformDestroyEdgeBasicStr)
|
expected := strings.TrimSpace(testTransformDestroyEdgeBasicStr)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Fatalf("bad:\n\n%s", actual)
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDestroyEdgeTransformer_create(t *testing.T) {
|
func TestDestroyEdgeTransformer_create(t *testing.T) {
|
||||||
g := Graph{Path: addrs.RootModuleInstance}
|
g := Graph{Path: addrs.RootModuleInstance}
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
|
g.Add(&graphNodeDestroyerTest{AddrString: "test_object.A"})
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "test.B"})
|
g.Add(&graphNodeDestroyerTest{AddrString: "test_object.B"})
|
||||||
g.Add(&graphNodeCreatorTest{AddrString: "test.A"})
|
g.Add(&graphNodeCreatorTest{AddrString: "test_object.A"})
|
||||||
tf := &DestroyEdgeTransformer{
|
tf := &DestroyEdgeTransformer{
|
||||||
Config: testModule(t, "transform-destroy-edge-basic"),
|
Config: testModule(t, "transform-destroy-edge-basic"),
|
||||||
|
Components: simpleMockComponentFactory(),
|
||||||
}
|
}
|
||||||
if err := tf.Transform(&g); err != nil {
|
if err := tf.Transform(&g); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
@ -40,17 +42,18 @@ func TestDestroyEdgeTransformer_create(t *testing.T) {
|
||||||
actual := strings.TrimSpace(g.String())
|
actual := strings.TrimSpace(g.String())
|
||||||
expected := strings.TrimSpace(testTransformDestroyEdgeCreatorStr)
|
expected := strings.TrimSpace(testTransformDestroyEdgeCreatorStr)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Fatalf("bad:\n\n%s", actual)
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDestroyEdgeTransformer_multi(t *testing.T) {
|
func TestDestroyEdgeTransformer_multi(t *testing.T) {
|
||||||
g := Graph{Path: addrs.RootModuleInstance}
|
g := Graph{Path: addrs.RootModuleInstance}
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
|
g.Add(&graphNodeDestroyerTest{AddrString: "test_object.A"})
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "test.B"})
|
g.Add(&graphNodeDestroyerTest{AddrString: "test_object.B"})
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "test.C"})
|
g.Add(&graphNodeDestroyerTest{AddrString: "test_object.C"})
|
||||||
tf := &DestroyEdgeTransformer{
|
tf := &DestroyEdgeTransformer{
|
||||||
Config: testModule(t, "transform-destroy-edge-multi"),
|
Config: testModule(t, "transform-destroy-edge-multi"),
|
||||||
|
Components: simpleMockComponentFactory(),
|
||||||
}
|
}
|
||||||
if err := tf.Transform(&g); err != nil {
|
if err := tf.Transform(&g); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
@ -59,15 +62,16 @@ func TestDestroyEdgeTransformer_multi(t *testing.T) {
|
||||||
actual := strings.TrimSpace(g.String())
|
actual := strings.TrimSpace(g.String())
|
||||||
expected := strings.TrimSpace(testTransformDestroyEdgeMultiStr)
|
expected := strings.TrimSpace(testTransformDestroyEdgeMultiStr)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Fatalf("bad:\n\n%s", actual)
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDestroyEdgeTransformer_selfRef(t *testing.T) {
|
func TestDestroyEdgeTransformer_selfRef(t *testing.T) {
|
||||||
g := Graph{Path: addrs.RootModuleInstance}
|
g := Graph{Path: addrs.RootModuleInstance}
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
|
g.Add(&graphNodeDestroyerTest{AddrString: "test_object.A"})
|
||||||
tf := &DestroyEdgeTransformer{
|
tf := &DestroyEdgeTransformer{
|
||||||
Config: testModule(t, "transform-destroy-edge-self-ref"),
|
Config: testModule(t, "transform-destroy-edge-self-ref"),
|
||||||
|
Components: simpleMockComponentFactory(),
|
||||||
}
|
}
|
||||||
if err := tf.Transform(&g); err != nil {
|
if err := tf.Transform(&g); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
@ -76,16 +80,17 @@ func TestDestroyEdgeTransformer_selfRef(t *testing.T) {
|
||||||
actual := strings.TrimSpace(g.String())
|
actual := strings.TrimSpace(g.String())
|
||||||
expected := strings.TrimSpace(testTransformDestroyEdgeSelfRefStr)
|
expected := strings.TrimSpace(testTransformDestroyEdgeSelfRefStr)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Fatalf("bad:\n\n%s", actual)
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDestroyEdgeTransformer_module(t *testing.T) {
|
func TestDestroyEdgeTransformer_module(t *testing.T) {
|
||||||
g := Graph{Path: addrs.RootModuleInstance}
|
g := Graph{Path: addrs.RootModuleInstance}
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "module.child.aws_instance.b"})
|
g.Add(&graphNodeDestroyerTest{AddrString: "module.child.test_object.b"})
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "aws_instance.a"})
|
g.Add(&graphNodeDestroyerTest{AddrString: "test_object.a"})
|
||||||
tf := &DestroyEdgeTransformer{
|
tf := &DestroyEdgeTransformer{
|
||||||
Config: testModule(t, "transform-destroy-edge-module"),
|
Config: testModule(t, "transform-destroy-edge-module"),
|
||||||
|
Components: simpleMockComponentFactory(),
|
||||||
}
|
}
|
||||||
if err := tf.Transform(&g); err != nil {
|
if err := tf.Transform(&g); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
@ -94,17 +99,18 @@ func TestDestroyEdgeTransformer_module(t *testing.T) {
|
||||||
actual := strings.TrimSpace(g.String())
|
actual := strings.TrimSpace(g.String())
|
||||||
expected := strings.TrimSpace(testTransformDestroyEdgeModuleStr)
|
expected := strings.TrimSpace(testTransformDestroyEdgeModuleStr)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Fatalf("bad:\n\n%s", actual)
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDestroyEdgeTransformer_moduleOnly(t *testing.T) {
|
func TestDestroyEdgeTransformer_moduleOnly(t *testing.T) {
|
||||||
g := Graph{Path: addrs.RootModuleInstance}
|
g := Graph{Path: addrs.RootModuleInstance}
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "module.child.aws_instance.a"})
|
g.Add(&graphNodeDestroyerTest{AddrString: "module.child.test_object.a"})
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "module.child.aws_instance.b"})
|
g.Add(&graphNodeDestroyerTest{AddrString: "module.child.test_object.b"})
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "module.child.aws_instance.c"})
|
g.Add(&graphNodeDestroyerTest{AddrString: "module.child.test_object.c"})
|
||||||
tf := &DestroyEdgeTransformer{
|
tf := &DestroyEdgeTransformer{
|
||||||
Config: testModule(t, "transform-destroy-edge-module-only"),
|
Config: testModule(t, "transform-destroy-edge-module-only"),
|
||||||
|
Components: simpleMockComponentFactory(),
|
||||||
}
|
}
|
||||||
if err := tf.Transform(&g); err != nil {
|
if err := tf.Transform(&g); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
@ -112,15 +118,15 @@ func TestDestroyEdgeTransformer_moduleOnly(t *testing.T) {
|
||||||
|
|
||||||
actual := strings.TrimSpace(g.String())
|
actual := strings.TrimSpace(g.String())
|
||||||
expected := strings.TrimSpace(`
|
expected := strings.TrimSpace(`
|
||||||
module.child.aws_instance.a (destroy)
|
module.child.test_object.a (destroy)
|
||||||
module.child.aws_instance.b (destroy)
|
module.child.test_object.b (destroy)
|
||||||
module.child.aws_instance.c (destroy)
|
module.child.test_object.c (destroy)
|
||||||
module.child.aws_instance.b (destroy)
|
module.child.test_object.b (destroy)
|
||||||
module.child.aws_instance.c (destroy)
|
module.child.test_object.c (destroy)
|
||||||
module.child.aws_instance.c (destroy)
|
module.child.test_object.c (destroy)
|
||||||
`)
|
`)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Fatalf("bad:\n\n%s", actual)
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,17 +135,43 @@ type graphNodeCreatorTest struct {
|
||||||
Refs []string
|
Refs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *graphNodeCreatorTest) Name() string { return n.CreateAddr().String() }
|
var (
|
||||||
func (n *graphNodeCreatorTest) CreateAddr() *ResourceAddress {
|
_ GraphNodeCreator = (*graphNodeCreatorTest)(nil)
|
||||||
addr, err := ParseResourceAddress(n.AddrString)
|
_ GraphNodeReferencer = (*graphNodeCreatorTest)(nil)
|
||||||
if err != nil {
|
)
|
||||||
panic(err)
|
|
||||||
|
func (n *graphNodeCreatorTest) Name() string {
|
||||||
|
return n.CreateAddr().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeCreatorTest) mustAddr() addrs.AbsResourceInstance {
|
||||||
|
addr, diags := addrs.ParseAbsResourceInstanceStr(n.AddrString)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
panic(diags.Err())
|
||||||
|
}
|
||||||
return addr
|
return addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *graphNodeCreatorTest) References() []string { return n.Refs }
|
func (n *graphNodeCreatorTest) Path() addrs.ModuleInstance {
|
||||||
|
return n.mustAddr().Module
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeCreatorTest) CreateAddr() *addrs.AbsResourceInstance {
|
||||||
|
addr := n.mustAddr()
|
||||||
|
return &addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeCreatorTest) References() []*addrs.Reference {
|
||||||
|
ret := make([]*addrs.Reference, len(n.Refs))
|
||||||
|
for i, str := range n.Refs {
|
||||||
|
ref, diags := addrs.ParseRefStr(str)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
panic(diags.Err())
|
||||||
|
}
|
||||||
|
ret[i] = ref
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
type graphNodeDestroyerTest struct {
|
type graphNodeDestroyerTest struct {
|
||||||
AddrString string
|
AddrString string
|
||||||
|
@ -147,6 +179,8 @@ type graphNodeDestroyerTest struct {
|
||||||
Modified bool
|
Modified bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ GraphNodeDestroyer = (*graphNodeDestroyerTest)(nil)
|
||||||
|
|
||||||
func (n *graphNodeDestroyerTest) Name() string {
|
func (n *graphNodeDestroyerTest) Name() string {
|
||||||
result := n.DestroyAddr().String() + " (destroy)"
|
result := n.DestroyAddr().String() + " (destroy)"
|
||||||
if n.Modified {
|
if n.Modified {
|
||||||
|
@ -156,51 +190,57 @@ func (n *graphNodeDestroyerTest) Name() string {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *graphNodeDestroyerTest) CreateBeforeDestroy() bool { return n.CBD }
|
func (n *graphNodeDestroyerTest) mustAddr() addrs.AbsResourceInstance {
|
||||||
|
addr, diags := addrs.ParseAbsResourceInstanceStr(n.AddrString)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
panic(diags.Err())
|
||||||
|
}
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeDestroyerTest) CreateBeforeDestroy() bool {
|
||||||
|
return n.CBD
|
||||||
|
}
|
||||||
|
|
||||||
func (n *graphNodeDestroyerTest) ModifyCreateBeforeDestroy(v bool) error {
|
func (n *graphNodeDestroyerTest) ModifyCreateBeforeDestroy(v bool) error {
|
||||||
n.Modified = true
|
n.Modified = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *graphNodeDestroyerTest) DestroyAddr() *ResourceAddress {
|
func (n *graphNodeDestroyerTest) DestroyAddr() *addrs.AbsResourceInstance {
|
||||||
addr, err := ParseResourceAddress(n.AddrString)
|
addr := n.mustAddr()
|
||||||
if err != nil {
|
return &addr
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return addr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const testTransformDestroyEdgeBasicStr = `
|
const testTransformDestroyEdgeBasicStr = `
|
||||||
test.A (destroy)
|
test_object.A (destroy)
|
||||||
test.B (destroy)
|
test_object.B (destroy)
|
||||||
test.B (destroy)
|
test_object.B (destroy)
|
||||||
`
|
`
|
||||||
|
|
||||||
const testTransformDestroyEdgeCreatorStr = `
|
const testTransformDestroyEdgeCreatorStr = `
|
||||||
test.A
|
test_object.A
|
||||||
test.A (destroy)
|
test_object.A (destroy)
|
||||||
test.A (destroy)
|
test_object.A (destroy)
|
||||||
test.B (destroy)
|
test_object.B (destroy)
|
||||||
test.B (destroy)
|
test_object.B (destroy)
|
||||||
`
|
`
|
||||||
|
|
||||||
const testTransformDestroyEdgeMultiStr = `
|
const testTransformDestroyEdgeMultiStr = `
|
||||||
test.A (destroy)
|
test_object.A (destroy)
|
||||||
test.B (destroy)
|
test_object.B (destroy)
|
||||||
test.C (destroy)
|
test_object.C (destroy)
|
||||||
test.B (destroy)
|
test_object.B (destroy)
|
||||||
test.C (destroy)
|
test_object.C (destroy)
|
||||||
test.C (destroy)
|
test_object.C (destroy)
|
||||||
`
|
`
|
||||||
|
|
||||||
const testTransformDestroyEdgeSelfRefStr = `
|
const testTransformDestroyEdgeSelfRefStr = `
|
||||||
test.A (destroy)
|
test_object.A (destroy)
|
||||||
`
|
`
|
||||||
|
|
||||||
const testTransformDestroyEdgeModuleStr = `
|
const testTransformDestroyEdgeModuleStr = `
|
||||||
aws_instance.a (destroy)
|
module.child.test_object.b (destroy)
|
||||||
module.child.aws_instance.b (destroy)
|
test_object.a (destroy)
|
||||||
aws_instance.a (destroy)
|
test_object.a (destroy)
|
||||||
`
|
`
|
||||||
|
|
Loading…
Reference in New Issue