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 }