github.com/skyscape-cloud-services/terraform@v0.9.2-0.20170609144644-7ece028a1747/terraform/transform_import_state.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 ) 6 7 // ImportStateTransformer is a GraphTransformer that adds nodes to the 8 // graph to represent the imports we want to do for resources. 9 type ImportStateTransformer struct { 10 Targets []*ImportTarget 11 } 12 13 func (t *ImportStateTransformer) Transform(g *Graph) error { 14 nodes := make([]*graphNodeImportState, 0, len(t.Targets)) 15 for _, target := range t.Targets { 16 addr, err := ParseResourceAddress(target.Addr) 17 if err != nil { 18 return fmt.Errorf( 19 "failed to parse resource address '%s': %s", 20 target.Addr, err) 21 } 22 23 nodes = append(nodes, &graphNodeImportState{ 24 Addr: addr, 25 ID: target.ID, 26 Provider: target.Provider, 27 }) 28 } 29 30 // Build the graph vertices 31 for _, n := range nodes { 32 g.Add(n) 33 } 34 35 return nil 36 } 37 38 type graphNodeImportState struct { 39 Addr *ResourceAddress // Addr is the resource address to import to 40 ID string // ID is the ID to import as 41 Provider string // Provider string 42 43 states []*InstanceState 44 } 45 46 func (n *graphNodeImportState) Name() string { 47 return fmt.Sprintf("%s (import id: %s)", n.Addr, n.ID) 48 } 49 50 func (n *graphNodeImportState) ProvidedBy() []string { 51 return []string{resourceProvider(n.Addr.Type, n.Provider)} 52 } 53 54 // GraphNodeSubPath 55 func (n *graphNodeImportState) Path() []string { 56 return normalizeModulePath(n.Addr.Path) 57 } 58 59 // GraphNodeEvalable impl. 60 func (n *graphNodeImportState) EvalTree() EvalNode { 61 var provider ResourceProvider 62 info := &InstanceInfo{ 63 Id: fmt.Sprintf("%s.%s", n.Addr.Type, n.Addr.Name), 64 ModulePath: n.Path(), 65 Type: n.Addr.Type, 66 } 67 68 // Reset our states 69 n.states = nil 70 71 // Return our sequence 72 return &EvalSequence{ 73 Nodes: []EvalNode{ 74 &EvalGetProvider{ 75 Name: n.ProvidedBy()[0], 76 Output: &provider, 77 }, 78 &EvalImportState{ 79 Provider: &provider, 80 Info: info, 81 Id: n.ID, 82 Output: &n.states, 83 }, 84 }, 85 } 86 } 87 88 // GraphNodeDynamicExpandable impl. 89 // 90 // We use DynamicExpand as a way to generate the subgraph of refreshes 91 // and state inserts we need to do for our import state. Since they're new 92 // resources they don't depend on anything else and refreshes are isolated 93 // so this is nearly a perfect use case for dynamic expand. 94 func (n *graphNodeImportState) DynamicExpand(ctx EvalContext) (*Graph, error) { 95 g := &Graph{Path: ctx.Path()} 96 97 // nameCounter is used to de-dup names in the state. 98 nameCounter := make(map[string]int) 99 100 // Compile the list of addresses that we'll be inserting into the state. 101 // We do this ahead of time so we can verify that we aren't importing 102 // something that already exists. 103 addrs := make([]*ResourceAddress, len(n.states)) 104 for i, state := range n.states { 105 addr := *n.Addr 106 if t := state.Ephemeral.Type; t != "" { 107 addr.Type = t 108 } 109 110 // Determine if we need to suffix the name to de-dup 111 key := addr.String() 112 count, ok := nameCounter[key] 113 if ok { 114 count++ 115 addr.Name += fmt.Sprintf("-%d", count) 116 } 117 nameCounter[key] = count 118 119 // Add it to our list 120 addrs[i] = &addr 121 } 122 123 // Verify that all the addresses are clear 124 state, lock := ctx.State() 125 lock.RLock() 126 defer lock.RUnlock() 127 filter := &StateFilter{State: state} 128 for _, addr := range addrs { 129 result, err := filter.Filter(addr.String()) 130 if err != nil { 131 return nil, fmt.Errorf("Error verifying address %s: %s", addr, err) 132 } 133 134 // Go through the filter results and it is an error if we find 135 // a matching InstanceState, meaning that we would have a collision. 136 for _, r := range result { 137 if _, ok := r.Value.(*InstanceState); ok { 138 return nil, fmt.Errorf( 139 "Can't import %s, would collide with an existing resource.\n\n"+ 140 "Please remove or rename this resource before continuing.", 141 addr) 142 } 143 } 144 } 145 146 // For each of the states, we add a node to handle the refresh/add to state. 147 // "n.states" is populated by our own EvalTree with the result of 148 // ImportState. Since DynamicExpand is always called after EvalTree, this 149 // is safe. 150 for i, state := range n.states { 151 g.Add(&graphNodeImportStateSub{ 152 Target: addrs[i], 153 Path_: n.Path(), 154 State: state, 155 Provider: n.Provider, 156 }) 157 } 158 159 // Root transform for a single root 160 t := &RootTransformer{} 161 if err := t.Transform(g); err != nil { 162 return nil, err 163 } 164 165 // Done! 166 return g, nil 167 } 168 169 // graphNodeImportStateSub is the sub-node of graphNodeImportState 170 // and is part of the subgraph. This node is responsible for refreshing 171 // and adding a resource to the state once it is imported. 172 type graphNodeImportStateSub struct { 173 Target *ResourceAddress 174 State *InstanceState 175 Path_ []string 176 Provider string 177 } 178 179 func (n *graphNodeImportStateSub) Name() string { 180 return fmt.Sprintf("import %s result: %s", n.Target, n.State.ID) 181 } 182 183 func (n *graphNodeImportStateSub) Path() []string { 184 return n.Path_ 185 } 186 187 // GraphNodeEvalable impl. 188 func (n *graphNodeImportStateSub) EvalTree() EvalNode { 189 // If the Ephemeral type isn't set, then it is an error 190 if n.State.Ephemeral.Type == "" { 191 err := fmt.Errorf( 192 "import of %s didn't set type for %s", 193 n.Target.String(), n.State.ID) 194 return &EvalReturnError{Error: &err} 195 } 196 197 // DeepCopy so we're only modifying our local copy 198 state := n.State.DeepCopy() 199 200 // Build the resource info 201 info := &InstanceInfo{ 202 Id: fmt.Sprintf("%s.%s", n.Target.Type, n.Target.Name), 203 ModulePath: n.Path_, 204 Type: n.State.Ephemeral.Type, 205 } 206 207 // Key is the resource key 208 key := &ResourceStateKey{ 209 Name: n.Target.Name, 210 Type: info.Type, 211 Index: n.Target.Index, 212 } 213 214 // The eval sequence 215 var provider ResourceProvider 216 return &EvalSequence{ 217 Nodes: []EvalNode{ 218 &EvalGetProvider{ 219 Name: resourceProvider(info.Type, n.Provider), 220 Output: &provider, 221 }, 222 &EvalRefresh{ 223 Provider: &provider, 224 State: &state, 225 Info: info, 226 Output: &state, 227 }, 228 &EvalImportStateVerify{ 229 Info: info, 230 Id: n.State.ID, 231 State: &state, 232 }, 233 &EvalWriteState{ 234 Name: key.String(), 235 ResourceType: info.Type, 236 Provider: resourceProvider(info.Type, n.Provider), 237 State: &state, 238 }, 239 }, 240 } 241 }