github.com/hugorut/terraform@v1.1.3/src/backend/remote-state/azure/backend_state.go (about) 1 package azure 2 3 import ( 4 "context" 5 "fmt" 6 "sort" 7 "strings" 8 9 "github.com/hugorut/terraform/src/backend" 10 "github.com/hugorut/terraform/src/states" 11 "github.com/hugorut/terraform/src/states/remote" 12 "github.com/hugorut/terraform/src/states/statemgr" 13 "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/blobs" 14 "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers" 15 ) 16 17 const ( 18 // This will be used as directory name, the odd looking colon is simply to 19 // reduce the chance of name conflicts with existing objects. 20 keyEnvPrefix = "env:" 21 ) 22 23 func (b *Backend) Workspaces() ([]string, error) { 24 prefix := b.keyName + keyEnvPrefix 25 params := containers.ListBlobsInput{ 26 Prefix: &prefix, 27 } 28 29 ctx := context.TODO() 30 client, err := b.armClient.getContainersClient(ctx) 31 if err != nil { 32 return nil, err 33 } 34 resp, err := client.ListBlobs(ctx, b.armClient.storageAccountName, b.containerName, params) 35 if err != nil { 36 return nil, err 37 } 38 39 envs := map[string]struct{}{} 40 for _, obj := range resp.Blobs.Blobs { 41 key := obj.Name 42 if strings.HasPrefix(key, prefix) { 43 name := strings.TrimPrefix(key, prefix) 44 // we store the state in a key, not a directory 45 if strings.Contains(name, "/") { 46 continue 47 } 48 49 envs[name] = struct{}{} 50 } 51 } 52 53 result := []string{backend.DefaultStateName} 54 for name := range envs { 55 result = append(result, name) 56 } 57 sort.Strings(result[1:]) 58 return result, nil 59 } 60 61 func (b *Backend) DeleteWorkspace(name string) error { 62 if name == backend.DefaultStateName || name == "" { 63 return fmt.Errorf("can't delete default state") 64 } 65 66 ctx := context.TODO() 67 client, err := b.armClient.getBlobClient(ctx) 68 if err != nil { 69 return err 70 } 71 72 if resp, err := client.Delete(ctx, b.armClient.storageAccountName, b.containerName, b.path(name), blobs.DeleteInput{}); err != nil { 73 if resp.Response.StatusCode != 404 { 74 return err 75 } 76 } 77 78 return nil 79 } 80 81 func (b *Backend) StateMgr(name string) (statemgr.Full, error) { 82 ctx := context.TODO() 83 blobClient, err := b.armClient.getBlobClient(ctx) 84 if err != nil { 85 return nil, err 86 } 87 88 client := &RemoteClient{ 89 giovanniBlobClient: *blobClient, 90 containerName: b.containerName, 91 keyName: b.path(name), 92 accountName: b.accountName, 93 snapshot: b.snapshot, 94 } 95 96 stateMgr := &remote.State{Client: client} 97 98 // Grab the value 99 if err := stateMgr.RefreshState(); err != nil { 100 return nil, err 101 } 102 //if this isn't the default state name, we need to create the object so 103 //it's listed by States. 104 if v := stateMgr.State(); v == nil { 105 // take a lock on this state while we write it 106 lockInfo := statemgr.NewLockInfo() 107 lockInfo.Operation = "init" 108 lockId, err := client.Lock(lockInfo) 109 if err != nil { 110 return nil, fmt.Errorf("failed to lock azure state: %s", err) 111 } 112 113 // Local helper function so we can call it multiple places 114 lockUnlock := func(parent error) error { 115 if err := stateMgr.Unlock(lockId); err != nil { 116 return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err) 117 } 118 return parent 119 } 120 121 // Grab the value 122 if err := stateMgr.RefreshState(); err != nil { 123 err = lockUnlock(err) 124 return nil, err 125 } 126 //if this isn't the default state name, we need to create the object so 127 //it's listed by States. 128 if v := stateMgr.State(); v == nil { 129 // If we have no state, we have to create an empty state 130 if err := stateMgr.WriteState(states.NewState()); err != nil { 131 err = lockUnlock(err) 132 return nil, err 133 } 134 if err := stateMgr.PersistState(); err != nil { 135 err = lockUnlock(err) 136 return nil, err 137 } 138 139 // Unlock, the state should now be initialized 140 if err := lockUnlock(nil); err != nil { 141 return nil, err 142 } 143 } 144 } 145 146 return stateMgr, nil 147 } 148 149 func (b *Backend) client() *RemoteClient { 150 return &RemoteClient{} 151 } 152 153 func (b *Backend) path(name string) string { 154 if name == backend.DefaultStateName { 155 return b.keyName 156 } 157 158 return b.keyName + keyEnvPrefix + name 159 } 160 161 const errStateUnlock = ` 162 Error unlocking Azure state. Lock ID: %s 163 164 Error: %s 165 166 You may have to force-unlock this state in order to use it again. 167 `