github.com/hugorut/terraform@v1.1.3/src/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/hugorut/terraform/src/states/remote" 14 "github.com/hugorut/terraform/src/states/statemgr" 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 *statemgr.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 := &statemgr.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 := &statemgr.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 ForceInsert: true, 145 } 146 147 log.Printf("[DEBUG] Creating manta state lock: %#v", params) 148 err = c.storageClient.Objects().Put(context.Background(), params) 149 if err != nil { 150 return "", err 151 } 152 153 return info.ID, nil 154 } 155 156 func (c *RemoteClient) Unlock(id string) error { 157 lockErr := &statemgr.LockError{} 158 159 lockInfo, err := c.getLockInfo() 160 if err != nil { 161 lockErr.Err = fmt.Errorf("failed to retrieve lock info: %s", err) 162 return lockErr 163 } 164 lockErr.Info = lockInfo 165 166 if lockInfo.ID != id { 167 lockErr.Err = fmt.Errorf("lock id %q does not match existing lock", id) 168 return lockErr 169 } 170 171 err = c.storageClient.Objects().Delete(context.Background(), &storage.DeleteObjectInput{ 172 ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, lockFileName), 173 }) 174 175 return err 176 } 177 178 func (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) { 179 output, err := c.storageClient.Objects().Get(context.Background(), &storage.GetObjectInput{ 180 ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, lockFileName), 181 }) 182 if err != nil { 183 return nil, err 184 } 185 186 defer output.ObjectReader.Close() 187 188 buf := bytes.NewBuffer(nil) 189 if _, err := io.Copy(buf, output.ObjectReader); err != nil { 190 return nil, fmt.Errorf("Failed to read lock info: %s", err) 191 } 192 193 lockInfo := &statemgr.LockInfo{} 194 err = json.Unmarshal(buf.Bytes(), lockInfo) 195 if err != nil { 196 return nil, err 197 } 198 199 return lockInfo, nil 200 }