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