go.etcd.io/etcd@v3.3.27+incompatible/etcdctl/ctlv3/command/lock_command.go (about) 1 // Copyright 2016 The etcd Authors 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 command 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "os" 22 "os/exec" 23 "os/signal" 24 "syscall" 25 26 "github.com/coreos/etcd/clientv3" 27 "github.com/coreos/etcd/clientv3/concurrency" 28 29 "github.com/spf13/cobra" 30 ) 31 32 var lockTTL = 10 33 34 // NewLockCommand returns the cobra command for "lock". 35 func NewLockCommand() *cobra.Command { 36 c := &cobra.Command{ 37 Use: "lock <lockname> [exec-command arg1 arg2 ...]", 38 Short: "Acquires a named lock", 39 Run: lockCommandFunc, 40 } 41 c.Flags().IntVarP(&lockTTL, "ttl", "", lockTTL, "timeout for session") 42 return c 43 } 44 45 func lockCommandFunc(cmd *cobra.Command, args []string) { 46 if len(args) == 0 { 47 ExitWithError(ExitBadArgs, errors.New("lock takes a lock name argument and an optional command to execute.")) 48 } 49 c := mustClientFromCmd(cmd) 50 if err := lockUntilSignal(c, args[0], args[1:]); err != nil { 51 ExitWithError(ExitError, err) 52 } 53 } 54 55 func lockUntilSignal(c *clientv3.Client, lockname string, cmdArgs []string) error { 56 s, err := concurrency.NewSession(c, concurrency.WithTTL(lockTTL)) 57 if err != nil { 58 return err 59 } 60 61 m := concurrency.NewMutex(s, lockname) 62 ctx, cancel := context.WithCancel(context.TODO()) 63 64 // unlock in case of ordinary shutdown 65 donec := make(chan struct{}) 66 sigc := make(chan os.Signal, 1) 67 signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) 68 go func() { 69 <-sigc 70 cancel() 71 close(donec) 72 }() 73 74 if err := m.Lock(ctx); err != nil { 75 return err 76 } 77 78 if len(cmdArgs) > 0 { 79 cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) 80 cmd.Env = append(environLockResponse(m), os.Environ()...) 81 cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr 82 err := cmd.Run() 83 unlockErr := m.Unlock(context.TODO()) 84 if err != nil { 85 return err 86 } 87 return unlockErr 88 } 89 90 k, kerr := c.Get(ctx, m.Key()) 91 if kerr != nil { 92 return kerr 93 } 94 if len(k.Kvs) == 0 { 95 return errors.New("lock lost on init") 96 } 97 display.Get(*k) 98 99 select { 100 case <-donec: 101 return m.Unlock(context.TODO()) 102 case <-s.Done(): 103 } 104 105 return errors.New("session expired") 106 } 107 108 func environLockResponse(m *concurrency.Mutex) []string { 109 return []string{ 110 "ETCD_LOCK_KEY=" + m.Key(), 111 fmt.Sprintf("ETCD_LOCK_REV=%d", m.Header().Revision), 112 } 113 }