github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/backend/filestate/lock.go (about) 1 // Copyright 2016-2018, Pulumi Corporation. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package filestate 16 17 import ( 18 "context" 19 "encoding/json" 20 "errors" 21 "fmt" 22 "os" 23 "os/user" 24 "path" 25 "time" 26 27 "github.com/pulumi/pulumi/pkg/v3/backend" 28 "github.com/pulumi/pulumi/sdk/v3/go/common/diag" 29 "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" 30 "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" 31 "github.com/pulumi/pulumi/sdk/v3/go/common/util/fsutil" 32 "github.com/pulumi/pulumi/sdk/v3/go/common/workspace" 33 ) 34 35 type lockContent struct { 36 Pid int `json:"pid"` 37 Username string `json:"username"` 38 Hostname string `json:"hostname"` 39 Timestamp time.Time `json:"timestamp"` 40 } 41 42 func newLockContent() (*lockContent, error) { 43 u, err := user.Current() 44 if err != nil { 45 return nil, err 46 } 47 hostname, err := os.Hostname() 48 if err != nil { 49 return nil, err 50 } 51 return &lockContent{ 52 Pid: os.Getpid(), 53 Username: u.Username, 54 Hostname: hostname, 55 Timestamp: time.Now(), 56 }, nil 57 } 58 59 // checkForLock looks for any existing locks for this stack, and returns a helpful diagnostic if there is one. 60 func (b *localBackend) checkForLock(ctx context.Context, stackRef backend.StackReference) error { 61 allFiles, err := listBucket(b.bucket, stackLockDir(stackRef.Name())) 62 if err != nil { 63 return err 64 } 65 66 var lockKeys []string 67 for _, file := range allFiles { 68 if file.IsDir { 69 continue 70 } 71 if file.Key != b.lockPath(stackRef.Name()) { 72 lockKeys = append(lockKeys, file.Key) 73 } 74 } 75 76 if len(lockKeys) > 0 { 77 errorString := fmt.Sprintf("the stack is currently locked by %v lock(s). Either wait for the other "+ 78 "process(es) to end or delete the lock file with `pulumi cancel`.", len(lockKeys)) 79 80 for _, lock := range lockKeys { 81 content, err := b.bucket.ReadAll(ctx, lock) 82 if err != nil { 83 return err 84 } 85 l := &lockContent{} 86 err = json.Unmarshal(content, &l) 87 if err != nil { 88 return err 89 } 90 91 errorString += fmt.Sprintf("\n %v: created by %v@%v (pid %v) at %v", 92 b.url+"/"+lock, 93 l.Username, 94 l.Hostname, 95 l.Pid, 96 l.Timestamp.Format(time.RFC3339), 97 ) 98 } 99 100 return errors.New(errorString) 101 } 102 return nil 103 } 104 105 func (b *localBackend) Lock(ctx context.Context, stackRef backend.StackReference) error { 106 // 107 err := b.checkForLock(ctx, stackRef) 108 if err != nil { 109 return err 110 } 111 lockContent, err := newLockContent() 112 if err != nil { 113 return err 114 } 115 content, err := json.Marshal(lockContent) 116 if err != nil { 117 return err 118 } 119 err = b.bucket.WriteAll(ctx, b.lockPath(stackRef.Name()), content, nil) 120 if err != nil { 121 return err 122 } 123 err = b.checkForLock(ctx, stackRef) 124 if err != nil { 125 b.Unlock(ctx, stackRef) 126 return err 127 } 128 return nil 129 } 130 131 func (b *localBackend) Unlock(ctx context.Context, stackRef backend.StackReference) { 132 err := b.bucket.Delete(ctx, b.lockPath(stackRef.Name())) 133 if err != nil { 134 b.d.Errorf( 135 diag.Message("", "there was a problem deleting the lock at %v, manual clean up may be required: %v"), 136 path.Join(b.url, b.lockPath(stackRef.Name())), 137 err) 138 } 139 } 140 141 func lockDir() string { 142 return path.Join(workspace.BookkeepingDir, workspace.LockDir) 143 } 144 145 func stackLockDir(stack tokens.Name) string { 146 contract.Require(stack != "", "stack") 147 return path.Join(lockDir(), fsutil.NamePath(stack)) 148 } 149 150 func (b *localBackend) lockPath(stack tokens.Name) string { 151 contract.Require(stack != "", "stack") 152 return path.Join(stackLockDir(stack), b.lockID+".json") 153 }