github.com/masahide/goansible@v0.0.0-20160116054156-01eac649e9f2/builtin.go (about)

     1  package goansible
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"crypto/md5"
     7  	"encoding/hex"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"os/exec"
    12  	"os/user"
    13  	"path/filepath"
    14  	"strconv"
    15  	"strings"
    16  	"sync"
    17  	"syscall"
    18  	"time"
    19  
    20  	"github.com/flynn/go-shlex"
    21  )
    22  
    23  func captureCmd(c *exec.Cmd, show bool) ([]byte, []byte, error) {
    24  	stdout, err := c.StdoutPipe()
    25  
    26  	if err != nil {
    27  		return nil, nil, err
    28  	}
    29  
    30  	defer stdout.Close()
    31  
    32  	var wg sync.WaitGroup
    33  
    34  	var bout bytes.Buffer
    35  	var berr bytes.Buffer
    36  
    37  	prefix := []byte(`| `)
    38  
    39  	wg.Add(1)
    40  	go func() {
    41  		defer wg.Done()
    42  		buf := bufio.NewReader(stdout)
    43  
    44  		for {
    45  			line, err := buf.ReadSlice('\n')
    46  
    47  			if err != nil {
    48  				break
    49  			}
    50  
    51  			bout.Write(line)
    52  
    53  			if show {
    54  				os.Stdout.Write(prefix)
    55  				os.Stdout.Write(line)
    56  			}
    57  		}
    58  	}()
    59  
    60  	stderr, err := c.StderrPipe()
    61  
    62  	if err != nil {
    63  		stdout.Close()
    64  		return nil, nil, err
    65  	}
    66  
    67  	defer stderr.Close()
    68  
    69  	wg.Add(1)
    70  	go func() {
    71  		defer wg.Done()
    72  		buf := bufio.NewReader(stderr)
    73  
    74  		for {
    75  			line, err := buf.ReadSlice('\n')
    76  
    77  			if err != nil {
    78  				break
    79  			}
    80  
    81  			berr.Write(line)
    82  
    83  			if show {
    84  				os.Stdout.Write(prefix)
    85  				os.Stdout.Write(line)
    86  			}
    87  		}
    88  	}()
    89  
    90  	c.Start()
    91  
    92  	wg.Wait()
    93  
    94  	err = c.Wait()
    95  
    96  	return bout.Bytes(), berr.Bytes(), err
    97  }
    98  
    99  type CommandResult struct {
   100  	ReturnCode int
   101  	Stdout     []byte
   102  	Stderr     []byte
   103  }
   104  
   105  func RunCommand(env *CommandEnv, parts ...string) (*CommandResult, error) {
   106  	c := exec.Command(parts[0], parts[1:]...)
   107  
   108  	if env.Env.config.ShowCommandOutput {
   109  		fmt.Printf("RUN: %s\n", strings.Join(parts, " "))
   110  	}
   111  
   112  	rc := 0
   113  
   114  	stdout, stderr, err := captureCmd(c, env.Env.config.ShowCommandOutput)
   115  	if err != nil {
   116  		if e2, ok := err.(*exec.ExitError); ok {
   117  			if s, ok := e2.Sys().(syscall.WaitStatus); ok {
   118  				rc = s.ExitStatus()
   119  			} else {
   120  				return nil, fmt.Errorf("Unimplemented for system where exec.ExitError.Sys() is not syscall.WaitStatus.,err:%s", err)
   121  			}
   122  		} else {
   123  			return nil, err
   124  		}
   125  	}
   126  
   127  	return &CommandResult{rc, stdout, stderr}, nil
   128  }
   129  
   130  func RunCommandInEnv(env *CommandEnv, unixEnv []string, parts ...string) (*CommandResult, error) {
   131  	c := exec.Command(parts[0], parts[1:]...)
   132  	c.Env = unixEnv
   133  
   134  	if env.Env.config.ShowCommandOutput {
   135  		fmt.Printf("RUN: %s\n", strings.Join(parts, " "))
   136  	}
   137  
   138  	rc := 0
   139  
   140  	stdout, stderr, err := captureCmd(c, env.Env.config.ShowCommandOutput)
   141  	if err != nil {
   142  		if e2, ok := err.(*exec.ExitError); ok {
   143  			if s, ok := e2.Sys().(syscall.WaitStatus); ok {
   144  				rc = s.ExitStatus()
   145  			} else {
   146  				return nil, fmt.Errorf("Unimplemented for system where exec.ExitError.Sys() is not syscall.WaitStatus.,err:%s", err)
   147  			}
   148  		} else {
   149  			return nil, err
   150  		}
   151  	}
   152  
   153  	return &CommandResult{rc, stdout, stderr}, nil
   154  }
   155  
   156  type runCmdParam struct {
   157  	IgnoreFail    bool
   158  	IgnoreChanged bool
   159  	ManualStatus  bool
   160  	ChangedRc     int
   161  	OkRc          int
   162  	ChangedCreate string
   163  }
   164  
   165  func runCmd(env *CommandEnv, cmd runCmdParam, parts ...string) (*Result, error) {
   166  	res, err := RunCommand(env, parts...)
   167  	if res == nil && err != nil {
   168  		return FailureResult(err), err
   169  	}
   170  	r := NewResult(!cmd.IgnoreChanged)
   171  	r.Add("rc", res.ReturnCode)
   172  	r.Add("stdout", strings.TrimSpace(string(res.Stdout)))
   173  	r.Add("stderr", strings.TrimSpace(string(res.Stderr)))
   174  	if cmd.ManualStatus {
   175  		switch {
   176  		case cmd.ChangedRc == res.ReturnCode:
   177  			r.Changed = true
   178  		case cmd.OkRc == res.ReturnCode:
   179  			r.Changed = false
   180  		default:
   181  			return FailureResult(err), err
   182  		}
   183  		return r, nil
   184  	}
   185  	if !cmd.IgnoreFail && err != nil {
   186  		return nil, err
   187  	}
   188  	r.Changed = !cmd.IgnoreChanged
   189  	if res.ReturnCode != 0 {
   190  		r.Failed = true
   191  		if !cmd.IgnoreFail {
   192  			return r, fmt.Errorf("Return code:%d, stderr:%s", res.ReturnCode, res.Stderr)
   193  		}
   194  	} else if r.Changed && cmd.ChangedCreate != "" {
   195  		cf, err := os.Create(cmd.ChangedCreate)
   196  		if err != nil {
   197  			return FailureResult(err), err
   198  		}
   199  		defer cf.Close()
   200  		if _, err = fmt.Fprintf(cf, "%s result:%# v", time.Now(), r); err != nil {
   201  			return FailureResult(err), err
   202  		}
   203  	}
   204  	return r, nil
   205  }
   206  
   207  type CommandCmd struct {
   208  	Command       string `goansible:"command,required"`
   209  	Creates       string `goansible:"creates"`
   210  	IgnoreFail    bool   `goansible:"ignore_failure"`
   211  	IgnoreChanged bool   `goansible:"ignore_changed"`
   212  	ManualStatus  bool   `goansible:"manual_status"`
   213  	OkRc          int    `goansible:"ok_rc"`
   214  	ChangedRc     int    `goansible:"changed_rc"`
   215  	ChangedCreate string `goansible:"changed_create"`
   216  }
   217  
   218  func (cmd *CommandCmd) Run(env *CommandEnv) (*Result, error) {
   219  	if cmd.Creates != "" {
   220  		if _, err := os.Stat(cmd.Creates); err == nil {
   221  			r := NewResult(false)
   222  			r.Add("rc", 0)
   223  			r.Add("exists", cmd.Creates)
   224  
   225  			return r, nil
   226  		}
   227  	}
   228  
   229  	parts, err := shlex.Split(cmd.Command)
   230  
   231  	if err != nil {
   232  		return FailureResult(err), err
   233  	}
   234  
   235  	param := runCmdParam{
   236  		IgnoreFail:    cmd.IgnoreFail,
   237  		IgnoreChanged: cmd.IgnoreChanged,
   238  		ManualStatus:  cmd.ManualStatus,
   239  		ChangedRc:     cmd.ChangedRc,
   240  		OkRc:          cmd.OkRc,
   241  		ChangedCreate: cmd.ChangedCreate,
   242  	}
   243  	return runCmd(env, param, parts...)
   244  }
   245  
   246  func (cmd *CommandCmd) ParseArgs(s Scope, args string) (Vars, error) {
   247  	if args == "" {
   248  		return Vars{}, nil
   249  	}
   250  
   251  	return Vars{"command": Any(args)}, nil
   252  }
   253  
   254  type ShellCmd struct {
   255  	Command       string `goansible:"command,required"`
   256  	Creates       string `goansible:"creates"`
   257  	IgnoreFail    bool   `goansible:"ignore_failure"`
   258  	IgnoreChanged bool   `goansible:"ignore_changed"`
   259  	ManualStatus  bool   `goansible:"manual_status"`
   260  	OkRc          int    `goansible:"ok_rc"`
   261  	ChangedRc     int    `goansible:"changed_rc"`
   262  	ChangedCreate string `goansible:"changed_create"`
   263  }
   264  
   265  func (cmd *ShellCmd) Run(env *CommandEnv) (*Result, error) {
   266  	if cmd.Creates != "" {
   267  		if _, err := os.Stat(cmd.Creates); err == nil {
   268  			r := NewResult(false)
   269  			r.Add("rc", 0)
   270  			r.Add("exists", cmd.Creates)
   271  
   272  			return r, nil
   273  		}
   274  	}
   275  
   276  	param := runCmdParam{
   277  		IgnoreFail:    cmd.IgnoreFail,
   278  		IgnoreChanged: cmd.IgnoreChanged,
   279  		ManualStatus:  cmd.ManualStatus,
   280  		ChangedRc:     cmd.ChangedRc,
   281  		OkRc:          cmd.OkRc,
   282  		ChangedCreate: cmd.ChangedCreate,
   283  	}
   284  	return runCmd(env, param, "sh", "-c", cmd.Command)
   285  }
   286  
   287  func (cmd *ShellCmd) ParseArgs(s Scope, args string) (Vars, error) {
   288  	if args == "" {
   289  		return Vars{}, nil
   290  	}
   291  
   292  	return Vars{"command": Any(args)}, nil
   293  }
   294  
   295  func md5string(s string) []byte {
   296  	h := md5.New()
   297  	io.WriteString(h, s)
   298  	return h.Sum(nil)
   299  }
   300  
   301  func md5file(path string) ([]byte, error) {
   302  	h := md5.New()
   303  
   304  	i, err := os.Open(path)
   305  	if err != nil {
   306  		return nil, err
   307  	}
   308  	defer i.Close()
   309  
   310  	if _, err := io.Copy(h, i); err != nil {
   311  		return nil, err
   312  	}
   313  
   314  	return h.Sum(nil), nil
   315  }
   316  
   317  type MkFileCmd struct {
   318  	Src   string `goansible:"src,required"`
   319  	Dest  string `goansible:"dest,required"`
   320  	Owner string `goansible:"owner"`
   321  	Uid   int    `goansible:"uid"`
   322  	Gid   int    `goansible:"gid"`
   323  	Mode  int    `goansible:"mode"`
   324  }
   325  
   326  func (cmd *MkFileCmd) Run(env *CommandEnv) (*Result, error) {
   327  	srcDigest := md5string(cmd.Src)
   328  	var dstDigest []byte
   329  
   330  	dest := cmd.Dest
   331  
   332  	link := false
   333  
   334  	destStat, err := os.Lstat(dest)
   335  	if err == nil {
   336  		dstDigest, _ = md5file(dest)
   337  		link = destStat.Mode()&os.ModeSymlink != 0
   338  	}
   339  
   340  	rd := ResultData{
   341  		"md5sum": Any(hex.EncodeToString(srcDigest)),
   342  		"src":    Any(cmd.Src),
   343  		"dest":   Any(dest),
   344  	}
   345  
   346  	if dstDigest != nil && bytes.Equal(srcDigest, dstDigest) {
   347  		changed := false
   348  
   349  		if cmd.Mode != 0 && destStat.Mode() != os.FileMode(cmd.Mode) {
   350  			changed = true
   351  			if err := os.Chmod(dest, os.FileMode(cmd.Mode)); err != nil {
   352  				return FailureResult(err), err
   353  			}
   354  		}
   355  		if cmd.Uid, cmd.Gid, err = ChangePerm(cmd.Owner, cmd.Uid, cmd.Gid); err != nil {
   356  			return FailureResult(err), err
   357  		}
   358  		if estat, ok := destStat.Sys().(*syscall.Stat_t); ok {
   359  			if cmd.Uid != int(estat.Uid) || cmd.Gid != int(estat.Gid) {
   360  				changed = true
   361  				os.Chown(dest, cmd.Uid, cmd.Gid)
   362  			}
   363  		}
   364  
   365  		return WrapResult(changed, rd), nil
   366  	}
   367  
   368  	tmp := fmt.Sprintf("%s.tmp.%d", cmd.Dest, os.Getpid())
   369  
   370  	output, err := os.OpenFile(tmp, os.O_CREATE|os.O_WRONLY, 0644)
   371  
   372  	if err != nil {
   373  		return FailureResult(err), err
   374  	}
   375  
   376  	defer output.Close()
   377  
   378  	if _, err = output.Write([]byte(cmd.Src)); err != nil {
   379  		os.Remove(tmp)
   380  		return FailureResult(err), err
   381  	}
   382  
   383  	if link {
   384  		os.Remove(dest)
   385  	}
   386  
   387  	if cmd.Mode != 0 {
   388  		if err := os.Chmod(tmp, os.FileMode(cmd.Mode)); err != nil {
   389  			os.Remove(tmp)
   390  			return FailureResult(err), err
   391  		}
   392  	}
   393  
   394  	if cmd.Mode != 0 {
   395  		if cmd.Uid, cmd.Gid, err = ChangePerm(cmd.Owner, cmd.Uid, cmd.Gid); err != nil {
   396  			return FailureResult(err), err
   397  		}
   398  	}
   399  	os.Chown(tmp, cmd.Uid, cmd.Gid)
   400  
   401  	err = os.Rename(tmp, dest)
   402  	if err != nil {
   403  		os.Remove(tmp)
   404  		return FailureResult(err), err
   405  	}
   406  
   407  	return WrapResult(true, rd), nil
   408  }
   409  
   410  type CopyCmd struct {
   411  	Src   string `goansible:"src,required"`
   412  	Dest  string `goansible:"dest,required"`
   413  	Owner string `goansible:"owner"`
   414  	Mkdir bool   `goansible:"mkdir"`
   415  	Uid   int    `goansible:"uid"`
   416  	Gid   int    `goansible:"gid"`
   417  	Mode  int    `goansible:"mode"`
   418  }
   419  
   420  func (cmd *CopyCmd) Run(env *CommandEnv) (*Result, error) {
   421  	var src string
   422  
   423  	if cmd.Src[0] == '/' {
   424  		src = cmd.Src
   425  	} else {
   426  		src = env.Paths.File(cmd.Src)
   427  	}
   428  	if cmd.Mode == 0 {
   429  		fi, err := os.Stat(src)
   430  		if err != nil {
   431  			return FailureResult(err), err
   432  		}
   433  		cmd.Mode = int(fi.Mode())
   434  
   435  	}
   436  
   437  	input, err := os.Open(src)
   438  
   439  	if err != nil {
   440  		return FailureResult(err), err
   441  	}
   442  	defer input.Close()
   443  
   444  	srcDigest, err := md5file(src)
   445  	if err != nil {
   446  		return FailureResult(err), err
   447  	}
   448  
   449  	var dstDigest []byte
   450  
   451  	dest := cmd.Dest
   452  
   453  	link := false
   454  
   455  	destStat, err := os.Lstat(dest)
   456  	if err == nil {
   457  		if destStat.IsDir() {
   458  			dest = filepath.Join(dest, filepath.Base(src))
   459  		} else {
   460  			dstDigest, _ = md5file(dest)
   461  		}
   462  
   463  		link = destStat.Mode()&os.ModeSymlink != 0
   464  	}
   465  
   466  	rd := ResultData{
   467  		"md5sum": Any(hex.EncodeToString(srcDigest)),
   468  		"src":    Any(src),
   469  		"dest":   Any(dest),
   470  	}
   471  
   472  	if dstDigest != nil && bytes.Equal(srcDigest, dstDigest) {
   473  		changed := false
   474  
   475  		if cmd.Mode != 0 && destStat.Mode() != os.FileMode(cmd.Mode) {
   476  			changed = true
   477  			if err := os.Chmod(dest, os.FileMode(cmd.Mode)); err != nil {
   478  				return FailureResult(err), err
   479  			}
   480  		}
   481  		if cmd.Uid, cmd.Gid, err = ChangePerm(cmd.Owner, cmd.Uid, cmd.Gid); err != nil {
   482  			return FailureResult(err), err
   483  		}
   484  		if estat, ok := destStat.Sys().(*syscall.Stat_t); ok {
   485  			if cmd.Uid != int(estat.Uid) || cmd.Gid != int(estat.Gid) {
   486  				changed = true
   487  				os.Chown(dest, cmd.Uid, cmd.Gid)
   488  			}
   489  		}
   490  
   491  		return WrapResult(changed, rd), nil
   492  	}
   493  
   494  	tmp := fmt.Sprintf("%s.tmp.%d", cmd.Dest, os.Getpid())
   495  
   496  	output, err := os.OpenFile(tmp, os.O_CREATE|os.O_WRONLY, 0644)
   497  
   498  	if err != nil {
   499  		return FailureResult(err), err
   500  	}
   501  
   502  	defer output.Close()
   503  
   504  	if _, err = io.Copy(output, input); err != nil {
   505  		os.Remove(tmp)
   506  		return FailureResult(err), err
   507  	}
   508  
   509  	if link {
   510  		os.Remove(dest)
   511  	}
   512  
   513  	if err := os.Chmod(tmp, os.FileMode(cmd.Mode)); err != nil {
   514  		os.Remove(tmp)
   515  		return FailureResult(err), err
   516  	}
   517  
   518  	if cmd.Uid, cmd.Gid, err = ChangePerm(cmd.Owner, cmd.Uid, cmd.Gid); err != nil {
   519  		return FailureResult(err), err
   520  	}
   521  	os.Chown(tmp, cmd.Uid, cmd.Gid)
   522  
   523  	err = os.Rename(tmp, dest)
   524  	if err != nil {
   525  		os.Remove(tmp)
   526  		return FailureResult(err), err
   527  	}
   528  
   529  	return WrapResult(true, rd), nil
   530  }
   531  
   532  type ScriptCmd struct {
   533  	Script        string `goansible:"command,required"`
   534  	Creates       string `goansible:"creates"`
   535  	IgnoreFail    bool   `goansible:"ignore_failure"`
   536  	IgnoreChanged bool   `goansible:"ignore_changed"`
   537  	ManualStatus  bool   `goansible:"manual_status"`
   538  	OkRc          int    `goansible:"ok_rc"`
   539  	ChangedRc     int    `goansible:"changed_rc"`
   540  	ChangedCreate string `goansible:"changed_create"`
   541  }
   542  
   543  func (cmd *ScriptCmd) ParseArgs(s Scope, args string) (Vars, error) {
   544  	if args == "" {
   545  		return Vars{}, nil
   546  	}
   547  
   548  	return Vars{"command": Any(args)}, nil
   549  }
   550  
   551  func (cmd *ScriptCmd) Run(env *CommandEnv) (*Result, error) {
   552  	if cmd.Creates != "" {
   553  		if _, err := os.Stat(cmd.Creates); err == nil {
   554  			r := NewResult(false)
   555  			r.Add("rc", 0)
   556  			r.Add("exists", cmd.Creates)
   557  
   558  			return r, nil
   559  		}
   560  	}
   561  
   562  	script := cmd.Script
   563  
   564  	parts, err := shlex.Split(cmd.Script)
   565  	if err == nil {
   566  		script = parts[0]
   567  	}
   568  
   569  	path := env.Paths.File(script)
   570  
   571  	_, err = os.Stat(path)
   572  	if err != nil {
   573  		return FailureResult(err), err
   574  	}
   575  
   576  	runArgs := append([]string{"sh", path}, parts[1:]...)
   577  
   578  	param := runCmdParam{
   579  		IgnoreFail:    cmd.IgnoreFail,
   580  		IgnoreChanged: cmd.IgnoreChanged,
   581  		ManualStatus:  cmd.ManualStatus,
   582  		ChangedRc:     cmd.ChangedRc,
   583  		OkRc:          cmd.OkRc,
   584  		ChangedCreate: cmd.ChangedCreate,
   585  	}
   586  	return runCmd(env, param, runArgs...)
   587  }
   588  
   589  func init() {
   590  	RegisterCommand("command", &CommandCmd{})
   591  	RegisterCommand("shell", &ShellCmd{})
   592  	RegisterCommand("copy", &CopyCmd{})
   593  	RegisterCommand("mkfile", &MkFileCmd{})
   594  	RegisterCommand("script", &ScriptCmd{})
   595  }
   596  
   597  func ChangePerm(owner string, suid, sgid int) (uid, gid int, err error) {
   598  	var u *user.User
   599  	switch {
   600  	case owner != "" && suid != 0:
   601  		err = fmt.Errorf("both uid and owner is specified. owner:%s,uid:%d", owner, suid)
   602  		return
   603  	case owner == "" && suid == 0 && sgid == 0:
   604  		if u, err = user.Current(); err != nil {
   605  			return
   606  		}
   607  		uid, _ = strconv.Atoi(u.Uid)
   608  		gid, _ = strconv.Atoi(u.Gid)
   609  	case owner != "":
   610  		if u, err = user.Lookup(owner); err != nil {
   611  			return
   612  		}
   613  		uid, _ = strconv.Atoi(u.Uid)
   614  		if sgid != 0 {
   615  			gid = sgid
   616  		} else {
   617  			gid, _ = strconv.Atoi(u.Gid)
   618  		}
   619  	default:
   620  		uid = suid
   621  		gid = sgid
   622  	}
   623  	return
   624  }