// invokeEventScript will execute the given event script with the given // event. Depending on the event, the semantics of how data are passed // are a bit different. For all events, the SERF_EVENT environmental // variable is the type of the event. For user events, the SERF_USER_EVENT // environmental variable is also set, containing the name of the user // event that was fired. // // In all events, data is passed in via stdin to faciliate piping. See // the various stdin functions below for more information. func invokeEventScript(logger *log.Logger, script string, self serf.Member, event serf.Event) error { defer metrics.MeasureSince([]string{"agent", "invoke", script}, time.Now()) var output bytes.Buffer // Determine the shell invocation based on OS var shell, flag string if runtime.GOOS == windows { shell = "cmd" flag = "/C" } else { shell = "/bin/sh" flag = "-c" } cmd := exec.Command(shell, flag, script) cmd.Env = append(os.Environ(), "SERF_EVENT="+event.EventType().String(), "SERF_SELF_NAME="+self.Name, "SERF_SELF_ROLE="+self.Tags["role"], ) cmd.Stderr = &output cmd.Stdout = &output // Add all the tags for name, val := range self.Tags { tag_env := fmt.Sprintf("SERF_TAG_%s=%s", strings.ToUpper(name), val) cmd.Env = append(cmd.Env, tag_env) } stdin, err := cmd.StdinPipe() if err != nil { return err } switch e := event.(type) { case serf.MemberEvent: go memberEventStdin(logger, stdin, &e) case serf.UserEvent: cmd.Env = append(cmd.Env, "SERF_USER_EVENT="+e.Name) cmd.Env = append(cmd.Env, fmt.Sprintf("SERF_USER_LTIME=%d", e.LTime)) go streamPayload(logger, stdin, e.Payload) case *serf.Query: cmd.Env = append(cmd.Env, "SERF_QUERY_NAME="+e.Name) cmd.Env = append(cmd.Env, fmt.Sprintf("SERF_QUERY_LTIME=%d", e.LTime)) go streamPayload(logger, stdin, e.Payload) default: return fmt.Errorf("Unknown event type: %s", event.EventType().String()) } if err := cmd.Start(); err != nil { return err } err = cmd.Wait() logger.Printf("[DEBUG] agent: Event '%s' script output: %s", event.EventType().String(), output.String()) if err != nil { return err } // If this is a query and we have output, respond if query, ok := event.(*serf.Query); ok && len(output.Bytes()) > 0 { if err := query.Respond(output.Bytes()); err != nil { logger.Printf("[WARN] agent: Failed to respond to query '%s': %s", event.String(), err) } } return nil }
// invokeEventScript will execute the given event script with the given // event. Depending on the event, the semantics of how data are passed // are a bit different. For all events, the SERF_EVENT environmental // variable is the type of the event. For user events, the SERF_USER_EVENT // environmental variable is also set, containing the name of the user // event that was fired. // // In all events, data is passed in via stdin to faciliate piping. See // the various stdin functions below for more information. func invokeEventScript(logger *log.Logger, script string, self serf.Member, event serf.Event) error { defer metrics.MeasureSince([]string{"agent", "invoke", script}, time.Now()) output, _ := circbuf.NewBuffer(maxBufSize) // Determine the shell invocation based on OS var shell, flag string if runtime.GOOS == windows { shell = "cmd" flag = "/C" } else { shell = "/bin/sh" flag = "-c" } cmd := exec.Command(shell, flag, script) cmd.Env = append(os.Environ(), "SERF_EVENT="+event.EventType().String(), "SERF_SELF_NAME="+self.Name, "SERF_SELF_IP="+self.Addr.String(), "SERF_SELF_PORT="+fmt.Sprintf("%v", self.Port), "SERF_SELF_ROLE="+self.Tags["role"], ) cmd.Stderr = output cmd.Stdout = output // Add all the tags for name, val := range self.Tags { //http://stackoverflow.com/questions/2821043/allowed-characters-in-linux-environment-variable-names //(http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html for the long version) //says that env var names must be in [A-Z0-9_] and not start with [0-9]. //we only care about the first part, so convert all chars not in [A-Z0-9_] to _ sanitizedName := sanitizeTagRegexp.ReplaceAllString(strings.ToUpper(name), "_") tag_env := fmt.Sprintf("SERF_TAG_%s=%s", sanitizedName, val) cmd.Env = append(cmd.Env, tag_env) } stdin, err := cmd.StdinPipe() if err != nil { return err } switch e := event.(type) { case serf.MemberEvent: go memberEventStdin(logger, stdin, &e) case serf.UserEvent: cmd.Env = append(cmd.Env, "SERF_USER_EVENT="+e.Name) cmd.Env = append(cmd.Env, fmt.Sprintf("SERF_USER_LTIME=%d", e.LTime)) go streamPayload(logger, stdin, e.Payload) case *serf.Query: cmd.Env = append(cmd.Env, "SERF_QUERY_NAME="+e.Name) cmd.Env = append(cmd.Env, fmt.Sprintf("SERF_QUERY_LTIME=%d", e.LTime)) go streamPayload(logger, stdin, e.Payload) default: return fmt.Errorf("Unknown event type: %s", event.EventType().String()) } // Start a timer to warn about slow handlers slowTimer := time.AfterFunc(warnSlow, func() { logger.Printf("[WARN] agent: Script '%s' slow, execution exceeding %v", script, warnSlow) }) if err := cmd.Start(); err != nil { return err } // Warn if buffer is overritten if output.TotalWritten() > output.Size() { logger.Printf("[WARN] agent: Script '%s' generated %d bytes of output, truncated to %d", script, output.TotalWritten(), output.Size()) } err = cmd.Wait() slowTimer.Stop() logger.Printf("[DEBUG] agent: Event '%s' script output: %s", event.EventType().String(), output.String()) if err != nil { return err } // If this is a query and we have output, respond if query, ok := event.(*serf.Query); ok && output.TotalWritten() > 0 { if err := query.Respond(output.Bytes()); err != nil { logger.Printf("[WARN] agent: Failed to respond to query '%s': %s", event.String(), err) } } return nil }