github.com/paultyng/terraform@v0.6.11-0.20180227224804-66ff8f8bed40/backend/remote-state/manta/client.go (about) 1 package manta 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 "log" 10 "path" 11 12 uuid "github.com/hashicorp/go-uuid" 13 "github.com/hashicorp/terraform/state" 14 "github.com/hashicorp/terraform/state/remote" 15 tritonErrors "github.com/joyent/triton-go/errors" 16 "github.com/joyent/triton-go/storage" 17 ) 18 19 const ( 20 mantaDefaultRootStore = "/stor" 21 lockFileName = "tflock" 22 ) 23 24 type RemoteClient struct { 25 storageClient *storage.StorageClient 26 directoryName string 27 keyName string 28 statePath string 29 } 30 31 func (c *RemoteClient) Get() (*remote.Payload, error) { 32 output, err := c.storageClient.Objects().Get(context.Background(), &storage.GetObjectInput{ 33 ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, c.keyName), 34 }) 35 if err != nil { 36 if tritonErrors.IsResourceNotFound(err) { 37 return nil, nil 38 } 39 return nil, err 40 } 41 defer output.ObjectReader.Close() 42 43 buf := bytes.NewBuffer(nil) 44 if _, err := io.Copy(buf, output.ObjectReader); err != nil { 45 return nil, fmt.Errorf("Failed to read remote state: %s", err) 46 } 47 48 payload := &remote.Payload{ 49 Data: buf.Bytes(), 50 } 51 52 // If there was no data, then return nil 53 if len(payload.Data) == 0 { 54 return nil, nil 55 } 56 57 return payload, nil 58 59 } 60 61 func (c *RemoteClient) Put(data []byte) error { 62 contentType := "application/json" 63 contentLength := int64(len(data)) 64 65 params := &storage.PutObjectInput{ 66 ContentType: contentType, 67 ContentLength: uint64(contentLength), 68 ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, c.keyName), 69 ObjectReader: bytes.NewReader(data), 70 } 71 72 log.Printf("[DEBUG] Uploading remote state to Manta: %#v", params) 73 err := c.storageClient.Objects().Put(context.Background(), params) 74 if err != nil { 75 return err 76 } 77 78 return nil 79 } 80 81 func (c *RemoteClient) Delete() error { 82 err := c.storageClient.Objects().Delete(context.Background(), &storage.DeleteObjectInput{ 83 ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, c.keyName), 84 }) 85 86 return err 87 } 88 89 func (c *RemoteClient) Lock(info *state.LockInfo) (string, error) { 90 //At Joyent, we want to make sure that the State directory exists before we interact with it 91 //We don't expect users to have to create it in advance 92 //The order of operations of Backend State as follows: 93 // * Get - if this doesn't exist then we continue as though it's new 94 // * Lock - we make sure that the state directory exists as it's the entrance to writing to Manta 95 // * Put - put the state up there 96 // * Unlock - unlock the directory 97 //We can always guarantee that the user can put their state in the specified location because of this 98 err := c.storageClient.Dir().Put(context.Background(), &storage.PutDirectoryInput{ 99 DirectoryName: path.Join(mantaDefaultRootStore, c.directoryName), 100 }) 101 if err != nil { 102 return "", err 103 } 104 105 //firstly we want to check that a lock doesn't already exist 106 lockErr := &state.LockError{} 107 lockInfo, err := c.getLockInfo() 108 if err != nil { 109 if tritonErrors.IsResourceNotFound(err) { 110 lockErr.Err = fmt.Errorf("failed to retrieve lock info: %s", err) 111 return "", lockErr 112 } 113 } 114 115 if lockInfo != nil { 116 lockErr := &state.LockError{ 117 Err: fmt.Errorf("A lock is already acquired"), 118 Info: lockInfo, 119 } 120 return "", lockErr 121 } 122 123 info.Path = path.Join(c.directoryName, lockFileName) 124 125 if info.ID == "" { 126 lockID, err := uuid.GenerateUUID() 127 if err != nil { 128 return "", err 129 } 130 131 info.ID = lockID 132 } 133 134 data := info.Marshal() 135 136 contentType := "application/json" 137 contentLength := int64(len(data)) 138 139 params := &storage.PutObjectInput{ 140 ContentType: contentType, 141 ContentLength: uint64(contentLength), 142 ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, lockFileName), 143 ObjectReader: bytes.NewReader(data), 144 } 145 146 log.Printf("[DEBUG] Creating manta state lock: %#v", params) 147 err = c.storageClient.Objects().Put(context.Background(), params) 148 if err != nil { 149 return "", err 150 } 151 152 return info.ID, nil 153 } 154 155 func (c *RemoteClient) Unlock(id string) error { 156 lockErr := &state.LockError{} 157 158 lockInfo, err := c.getLockInfo() 159 if err != nil { 160 lockErr.Err = fmt.Errorf("failed to retrieve lock info: %s", err) 161 return lockErr 162 } 163 lockErr.Info = lockInfo 164 165 if lockInfo.ID != id { 166 lockErr.Err = fmt.Errorf("lock id %q does not match existing lock", id) 167 return lockErr 168 } 169 170 err = c.storageClient.Objects().Delete(context.Background(), &storage.DeleteObjectInput{ 171 ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, lockFileName), 172 }) 173 174 return err 175 } 176 177 func (c *RemoteClient) getLockInfo() (*state.LockInfo, error) { 178 output, err := c.storageClient.Objects().Get(context.Background(), &storage.GetObjectInput{ 179 ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, lockFileName), 180 }) 181 if err != nil { 182 return nil, err 183 } 184 185 defer output.ObjectReader.Close() 186 187 buf := bytes.NewBuffer(nil) 188 if _, err := io.Copy(buf, output.ObjectReader); err != nil { 189 return nil, fmt.Errorf("Failed to read lock info: %s", err) 190 } 191 192 lockInfo := &state.LockInfo{} 193 err = json.Unmarshal(buf.Bytes(), lockInfo) 194 if err != nil { 195 return nil, err 196 } 197 198 return lockInfo, nil 199 }