Merge pull request #1517 from bradrydzewski/master

remove vexp from vendoring process [CI SKIP]
This commit is contained in:
Brad Rydzewski 2016-03-07 13:41:11 -08:00
commit 671461dc00
648 changed files with 76035 additions and 167838 deletions

View file

@ -38,14 +38,14 @@ func Run(client dockerclient.Client, conf *dockerclient.ContainerConfig, name st
func RunDaemon(client dockerclient.Client, conf *dockerclient.ContainerConfig, name string) (*dockerclient.ContainerInfo, error) {
// attempts to create the contianer
id, err := client.CreateContainer(conf, name)
id, err := client.CreateContainer(conf, name, nil)
if err != nil {
// and pull the image and re-create if that fails
err = client.PullImage(conf.Image, nil)
if err != nil {
return nil, err
}
id, err = client.CreateContainer(conf, name)
id, err = client.CreateContainer(conf, name, nil)
if err != nil {
client.RemoveContainer(id, true, true)
return nil, err

View file

@ -1,5 +1,4 @@
Copyright (c) 2013 Apollic Software, LLC. All rights reserved.
Copyright (c) 2015 Functional Software, Inc. All rights reserved.
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
@ -11,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Apollic Software, LLC nor the names of its
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

22
vendor/code.google.com/p/go.crypto/PATENTS generated vendored Normal file
View file

@ -0,0 +1,22 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.

View file

@ -1,87 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssh
import (
"io"
"testing"
)
var BYTES = []byte("abcdefghijklmnopqrstuvwxyz")
func TestBufferReadwrite(t *testing.T) {
b := newBuffer()
b.write(BYTES[:10])
r, _ := b.Read(make([]byte, 10))
if r != 10 {
t.Fatalf("Expected written == read == 10, written: 10, read %d", r)
}
b = newBuffer()
b.write(BYTES[:5])
r, _ = b.Read(make([]byte, 10))
if r != 5 {
t.Fatalf("Expected written == read == 5, written: 5, read %d", r)
}
b = newBuffer()
b.write(BYTES[:10])
r, _ = b.Read(make([]byte, 5))
if r != 5 {
t.Fatalf("Expected written == 10, read == 5, written: 10, read %d", r)
}
b = newBuffer()
b.write(BYTES[:5])
b.write(BYTES[5:15])
r, _ = b.Read(make([]byte, 10))
r2, _ := b.Read(make([]byte, 10))
if r != 10 || r2 != 5 || 15 != r+r2 {
t.Fatal("Expected written == read == 15")
}
}
func TestBufferClose(t *testing.T) {
b := newBuffer()
b.write(BYTES[:10])
b.eof()
_, err := b.Read(make([]byte, 5))
if err != nil {
t.Fatal("expected read of 5 to not return EOF")
}
b = newBuffer()
b.write(BYTES[:10])
b.eof()
r, err := b.Read(make([]byte, 5))
r2, err2 := b.Read(make([]byte, 10))
if r != 5 || r2 != 5 || err != nil || err2 != nil {
t.Fatal("expected reads of 5 and 5")
}
b = newBuffer()
b.write(BYTES[:10])
b.eof()
r, err = b.Read(make([]byte, 5))
r2, err2 = b.Read(make([]byte, 10))
r3, err3 := b.Read(make([]byte, 10))
if r != 5 || r2 != 5 || r3 != 0 || err != nil || err2 != nil || err3 != io.EOF {
t.Fatal("expected reads of 5 and 5 and 0, with EOF")
}
b = newBuffer()
b.write(make([]byte, 5))
b.write(make([]byte, 10))
b.eof()
r, err = b.Read(make([]byte, 9))
r2, err2 = b.Read(make([]byte, 3))
r3, err3 = b.Read(make([]byte, 3))
r4, err4 := b.Read(make([]byte, 10))
if err != nil || err2 != nil || err3 != nil || err4 != io.EOF {
t.Fatalf("Expected EOF on forth read only, err=%v, err2=%v, err3=%v, err4=%v", err, err2, err3, err4)
}
if r != 9 || r2 != 3 || r3 != 3 || r4 != 0 {
t.Fatal("Expected written == read == 15", r, r2, r3, r4)
}
}

View file

@ -1,55 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssh
import (
"bytes"
"testing"
)
// Cert generated by ssh-keygen 6.0p1 Debian-4.
// % ssh-keygen -s ca-key -I test user-key
var exampleSSHCert = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgb1srW/W3ZDjYAO45xLYAwzHBDLsJ4Ux6ICFIkTjb1LEAAAADAQABAAAAYQCkoR51poH0wE8w72cqSB8Sszx+vAhzcMdCO0wqHTj7UNENHWEXGrU0E0UQekD7U+yhkhtoyjbPOVIP7hNa6aRk/ezdh/iUnCIt4Jt1v3Z1h1P+hA4QuYFMHNB+rmjPwAcAAAAAAAAAAAAAAAEAAAAEdGVzdAAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAHcAAAAHc3NoLXJzYQAAAAMBAAEAAABhANFS2kaktpSGc+CcmEKPyw9mJC4nZKxHKTgLVZeaGbFZOvJTNzBspQHdy7Q1uKSfktxpgjZnksiu/tFF9ngyY2KFoc+U88ya95IZUycBGCUbBQ8+bhDtw/icdDGQD5WnUwAAAG8AAAAHc3NoLXJzYQAAAGC8Y9Z2LQKhIhxf52773XaWrXdxP0t3GBVo4A10vUWiYoAGepr6rQIoGGXFxT4B9Gp+nEBJjOwKDXPrAevow0T9ca8gZN+0ykbhSrXLE5Ao48rqr3zP4O1/9P7e6gp0gw8=`
func TestParseCert(t *testing.T) {
authKeyBytes := []byte(exampleSSHCert)
key, _, _, rest, ok := ParseAuthorizedKey(authKeyBytes)
if !ok {
t.Fatalf("could not parse certificate")
}
if len(rest) > 0 {
t.Errorf("rest: got %q, want empty", rest)
}
if _, ok = key.(*OpenSSHCertV01); !ok {
t.Fatalf("got %#v, want *OpenSSHCertV01", key)
}
marshaled := MarshalAuthorizedKey(key)
// Before comparison, remove the trailing newline that
// MarshalAuthorizedKey adds.
marshaled = marshaled[:len(marshaled)-1]
if !bytes.Equal(authKeyBytes, marshaled) {
t.Errorf("marshaled certificate does not match original: got %q, want %q", marshaled, authKeyBytes)
}
}
func TestVerifyCert(t *testing.T) {
key, _, _, _, _ := ParseAuthorizedKey([]byte(exampleSSHCert))
validCert := key.(*OpenSSHCertV01)
if ok := validateOpenSSHCertV01Signature(validCert); !ok {
t.Error("Unable to validate certificate!")
}
invalidCert := &OpenSSHCertV01{
Key: rsaKey.PublicKey(),
SignatureKey: ecdsaKey.PublicKey(),
Signature: &signature{},
}
if ok := validateOpenSSHCertV01Signature(invalidCert); ok {
t.Error("Invalid cert signature passed validation!")
}
}

View file

@ -1,62 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssh
import (
"bytes"
"testing"
)
// TestCipherReversal tests that each cipher factory produces ciphers that can
// encrypt and decrypt some data successfully.
func TestCipherReversal(t *testing.T) {
testData := []byte("abcdefghijklmnopqrstuvwxyz012345")
testKey := []byte("AbCdEfGhIjKlMnOpQrStUvWxYz012345")
testIv := []byte("sdflkjhsadflkjhasdflkjhsadfklhsa")
cryptBuffer := make([]byte, 32)
for name, cipherMode := range cipherModes {
encrypter, err := cipherMode.createCipher(testKey, testIv)
if err != nil {
t.Errorf("failed to create encrypter for %q: %s", name, err)
continue
}
decrypter, err := cipherMode.createCipher(testKey, testIv)
if err != nil {
t.Errorf("failed to create decrypter for %q: %s", name, err)
continue
}
copy(cryptBuffer, testData)
encrypter.XORKeyStream(cryptBuffer, cryptBuffer)
if name == "none" {
if !bytes.Equal(cryptBuffer, testData) {
t.Errorf("encryption made change with 'none' cipher")
continue
}
} else {
if bytes.Equal(cryptBuffer, testData) {
t.Errorf("encryption made no change with %q", name)
continue
}
}
decrypter.XORKeyStream(cryptBuffer, cryptBuffer)
if !bytes.Equal(cryptBuffer, testData) {
t.Errorf("decrypted bytes not equal to input with %q", name)
continue
}
}
}
func TestDefaultCiphersExist(t *testing.T) {
for _, cipherAlgo := range DefaultCipherOrder {
if _, ok := cipherModes[cipherAlgo]; !ok {
t.Errorf("default cipher %q is unknown", cipherAlgo)
}
}
}

View file

@ -1,368 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssh
import (
"bytes"
"crypto/dsa"
"io"
"io/ioutil"
"math/big"
"strings"
"testing"
_ "crypto/sha1"
)
// private key for mock server
const testServerPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA19lGVsTqIT5iiNYRgnoY1CwkbETW5cq+Rzk5v/kTlf31XpSU
70HVWkbTERECjaYdXM2gGcbb+sxpq6GtXf1M3kVomycqhxwhPv4Cr6Xp4WT/jkFx
9z+FFzpeodGJWjOH6L2H5uX1Cvr9EDdQp9t9/J32/qBFntY8GwoUI/y/1MSTmMiF
tupdMODN064vd3gyMKTwrlQ8tZM6aYuyOPsutLlUY7M5x5FwMDYvnPDSeyT/Iw0z
s3B+NCyqeeMd2T7YzQFnRATj0M7rM5LoSs7DVqVriOEABssFyLj31PboaoLhOKgc
qoM9khkNzr7FHVvi+DhYM2jD0DwvqZLN6NmnLwIDAQABAoIBAQCGVj+kuSFOV1lT
+IclQYA6bM6uY5mroqcSBNegVxCNhWU03BxlW//BE9tA/+kq53vWylMeN9mpGZea
riEMIh25KFGWXqXlOOioH8bkMsqA8S7sBmc7jljyv+0toQ9vCCtJ+sueNPhxQQxH
D2YvUjfzBQ04I9+wn30BByDJ1QA/FoPsunxIOUCcRBE/7jxuLYcpR+JvEF68yYIh
atXRld4W4in7T65YDR8jK1Uj9XAcNeDYNpT/M6oFLx1aPIlkG86aCWRO19S1jLPT
b1ZAKHHxPMCVkSYW0RqvIgLXQOR62D0Zne6/2wtzJkk5UCjkSQ2z7ZzJpMkWgDgN
ifCULFPBAoGBAPoMZ5q1w+zB+knXUD33n1J+niN6TZHJulpf2w5zsW+m2K6Zn62M
MXndXlVAHtk6p02q9kxHdgov34Uo8VpuNjbS1+abGFTI8NZgFo+bsDxJdItemwC4
KJ7L1iz39hRN/ZylMRLz5uTYRGddCkeIHhiG2h7zohH/MaYzUacXEEy3AoGBANz8
e/msleB+iXC0cXKwds26N4hyMdAFE5qAqJXvV3S2W8JZnmU+sS7vPAWMYPlERPk1
D8Q2eXqdPIkAWBhrx4RxD7rNc5qFNcQWEhCIxC9fccluH1y5g2M+4jpMX2CT8Uv+
3z+NoJ5uDTXZTnLCfoZzgZ4nCZVZ+6iU5U1+YXFJAoGBANLPpIV920n/nJmmquMj
orI1R/QXR9Cy56cMC65agezlGOfTYxk5Cfl5Ve+/2IJCfgzwJyjWUsFx7RviEeGw
64o7JoUom1HX+5xxdHPsyZ96OoTJ5RqtKKoApnhRMamau0fWydH1yeOEJd+TRHhc
XStGfhz8QNa1dVFvENczja1vAoGABGWhsd4VPVpHMc7lUvrf4kgKQtTC2PjA4xoc
QJ96hf/642sVE76jl+N6tkGMzGjnVm4P2j+bOy1VvwQavKGoXqJBRd5Apppv727g
/SM7hBXKFc/zH80xKBBgP/i1DR7kdjakCoeu4ngeGywvu2jTS6mQsqzkK+yWbUxJ
I7mYBsECgYB/KNXlTEpXtz/kwWCHFSYA8U74l7zZbVD8ul0e56JDK+lLcJ0tJffk
gqnBycHj6AhEycjda75cs+0zybZvN4x65KZHOGW/O/7OAWEcZP5TPb3zf9ned3Hl
NsZoFj52ponUM6+99A2CmezFCN16c4mbA//luWF+k3VVqR6BpkrhKw==
-----END RSA PRIVATE KEY-----`
const testClientPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBALdGZxkXDAjsYk10ihwU6Id2KeILz1TAJuoq4tOgDWxEEGeTrcld
r/ZwVaFzjWzxaf6zQIJbfaSEAhqD5yo72+sCAwEAAQJBAK8PEVU23Wj8mV0QjwcJ
tZ4GcTUYQL7cF4+ezTCE9a1NrGnCP2RuQkHEKxuTVrxXt+6OF15/1/fuXnxKjmJC
nxkCIQDaXvPPBi0c7vAxGwNY9726x01/dNbHCE0CBtcotobxpwIhANbbQbh3JHVW
2haQh4fAG5mhesZKAGcxTyv4mQ7uMSQdAiAj+4dzMpJWdSzQ+qGHlHMIBvVHLkqB
y2VdEyF7DPCZewIhAI7GOI/6LDIFOvtPo6Bj2nNmyQ1HU6k/LRtNIXi4c9NJAiAr
rrxx26itVhJmcvoUhOjwuzSlP2bE5VHAvkGB352YBg==
-----END RSA PRIVATE KEY-----`
// keychain implements the ClientKeyring interface
type keychain struct {
keys []Signer
}
func (k *keychain) Key(i int) (PublicKey, error) {
if i < 0 || i >= len(k.keys) {
return nil, nil
}
return k.keys[i].PublicKey(), nil
}
func (k *keychain) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) {
return k.keys[i].Sign(rand, data)
}
func (k *keychain) add(key Signer) {
k.keys = append(k.keys, key)
}
func (k *keychain) loadPEM(file string) error {
buf, err := ioutil.ReadFile(file)
if err != nil {
return err
}
key, err := ParsePrivateKey(buf)
if err != nil {
return err
}
k.add(key)
return nil
}
// password implements the ClientPassword interface
type password string
func (p password) Password(user string) (string, error) {
return string(p), nil
}
type keyboardInteractive map[string]string
func (cr *keyboardInteractive) Challenge(user string, instruction string, questions []string, echos []bool) ([]string, error) {
var answers []string
for _, q := range questions {
answers = append(answers, (*cr)[q])
}
return answers, nil
}
// reused internally by tests
var (
rsaKey Signer
dsaKey Signer
clientKeychain = new(keychain)
clientPassword = password("tiger")
serverConfig = &ServerConfig{
PasswordCallback: func(conn *ServerConn, user, pass string) bool {
return user == "testuser" && pass == string(clientPassword)
},
PublicKeyCallback: func(conn *ServerConn, user, algo string, pubkey []byte) bool {
key, _ := clientKeychain.Key(0)
expected := MarshalPublicKey(key)
algoname := key.PublicKeyAlgo()
return user == "testuser" && algo == algoname && bytes.Equal(pubkey, expected)
},
KeyboardInteractiveCallback: func(conn *ServerConn, user string, client ClientKeyboardInteractive) bool {
ans, err := client.Challenge("user",
"instruction",
[]string{"question1", "question2"},
[]bool{true, true})
if err != nil {
return false
}
ok := user == "testuser" && ans[0] == "answer1" && ans[1] == "answer2"
client.Challenge("user", "motd", nil, nil)
return ok
},
}
)
func init() {
var err error
rsaKey, err = ParsePrivateKey([]byte(testServerPrivateKey))
if err != nil {
panic("unable to set private key: " + err.Error())
}
rawDSAKey := new(dsa.PrivateKey)
// taken from crypto/dsa/dsa_test.go
rawDSAKey.P, _ = new(big.Int).SetString("A9B5B793FB4785793D246BAE77E8FF63CA52F442DA763C440259919FE1BC1D6065A9350637A04F75A2F039401D49F08E066C4D275A5A65DA5684BC563C14289D7AB8A67163BFBF79D85972619AD2CFF55AB0EE77A9002B0EF96293BDD0F42685EBB2C66C327079F6C98000FBCB79AACDE1BC6F9D5C7B1A97E3D9D54ED7951FEF", 16)
rawDSAKey.Q, _ = new(big.Int).SetString("E1D3391245933D68A0714ED34BBCB7A1F422B9C1", 16)
rawDSAKey.G, _ = new(big.Int).SetString("634364FC25248933D01D1993ECABD0657CC0CB2CEED7ED2E3E8AECDFCDC4A25C3B15E9E3B163ACA2984B5539181F3EFF1A5E8903D71D5B95DA4F27202B77D2C44B430BB53741A8D59A8F86887525C9F2A6A5980A195EAA7F2FF910064301DEF89D3AA213E1FAC7768D89365318E370AF54A112EFBA9246D9158386BA1B4EEFDA", 16)
rawDSAKey.Y, _ = new(big.Int).SetString("32969E5780CFE1C849A1C276D7AEB4F38A23B591739AA2FE197349AEEBD31366AEE5EB7E6C6DDB7C57D02432B30DB5AA66D9884299FAA72568944E4EEDC92EA3FBC6F39F53412FBCC563208F7C15B737AC8910DBC2D9C9B8C001E72FDC40EB694AB1F06A5A2DBD18D9E36C66F31F566742F11EC0A52E9F7B89355C02FB5D32D2", 16)
rawDSAKey.X, _ = new(big.Int).SetString("5078D4D29795CBE76D3AACFE48C9AF0BCDBEE91A", 16)
dsaKey, err = NewSignerFromKey(rawDSAKey)
if err != nil {
panic("NewSignerFromKey: " + err.Error())
}
clientKeychain.add(rsaKey)
serverConfig.AddHostKey(rsaKey)
}
// newMockAuthServer creates a new Server bound to
// the loopback interface. The server exits after
// processing one handshake.
func newMockAuthServer(t *testing.T) string {
l, err := Listen("tcp", "127.0.0.1:0", serverConfig)
if err != nil {
t.Fatalf("unable to newMockAuthServer: %s", err)
}
go func() {
defer l.Close()
c, err := l.Accept()
if err != nil {
t.Errorf("Unable to accept incoming connection: %v", err)
return
}
if err := c.Handshake(); err != nil {
// not Errorf because this is expected to
// fail for some tests.
t.Logf("Handshaking error: %v", err)
return
}
defer c.Close()
}()
return l.Addr().String()
}
func TestClientAuthPublicKey(t *testing.T) {
config := &ClientConfig{
User: "testuser",
Auth: []ClientAuth{
ClientAuthKeyring(clientKeychain),
},
}
c, err := Dial("tcp", newMockAuthServer(t), config)
if err != nil {
t.Fatalf("unable to dial remote side: %s", err)
}
c.Close()
}
func TestClientAuthPassword(t *testing.T) {
config := &ClientConfig{
User: "testuser",
Auth: []ClientAuth{
ClientAuthPassword(clientPassword),
},
}
c, err := Dial("tcp", newMockAuthServer(t), config)
if err != nil {
t.Fatalf("unable to dial remote side: %s", err)
}
c.Close()
}
func TestClientAuthWrongPassword(t *testing.T) {
wrongPw := password("wrong")
config := &ClientConfig{
User: "testuser",
Auth: []ClientAuth{
ClientAuthPassword(wrongPw),
ClientAuthKeyring(clientKeychain),
},
}
c, err := Dial("tcp", newMockAuthServer(t), config)
if err != nil {
t.Fatalf("unable to dial remote side: %s", err)
}
c.Close()
}
func TestClientAuthKeyboardInteractive(t *testing.T) {
answers := keyboardInteractive(map[string]string{
"question1": "answer1",
"question2": "answer2",
})
config := &ClientConfig{
User: "testuser",
Auth: []ClientAuth{
ClientAuthKeyboardInteractive(&answers),
},
}
c, err := Dial("tcp", newMockAuthServer(t), config)
if err != nil {
t.Fatalf("unable to dial remote side: %s", err)
}
c.Close()
}
func TestClientAuthWrongKeyboardInteractive(t *testing.T) {
answers := keyboardInteractive(map[string]string{
"question1": "answer1",
"question2": "WRONG",
})
config := &ClientConfig{
User: "testuser",
Auth: []ClientAuth{
ClientAuthKeyboardInteractive(&answers),
},
}
c, err := Dial("tcp", newMockAuthServer(t), config)
if err == nil {
c.Close()
t.Fatalf("wrong answers should not have authenticated with KeyboardInteractive")
}
}
// the mock server will only authenticate ssh-rsa keys
func TestClientAuthInvalidPublicKey(t *testing.T) {
kc := new(keychain)
kc.add(dsaKey)
config := &ClientConfig{
User: "testuser",
Auth: []ClientAuth{
ClientAuthKeyring(kc),
},
}
c, err := Dial("tcp", newMockAuthServer(t), config)
if err == nil {
c.Close()
t.Fatalf("dsa private key should not have authenticated with rsa public key")
}
}
// the client should authenticate with the second key
func TestClientAuthRSAandDSA(t *testing.T) {
kc := new(keychain)
kc.add(dsaKey)
kc.add(rsaKey)
config := &ClientConfig{
User: "testuser",
Auth: []ClientAuth{
ClientAuthKeyring(kc),
},
}
c, err := Dial("tcp", newMockAuthServer(t), config)
if err != nil {
t.Fatalf("client could not authenticate with rsa key: %v", err)
}
c.Close()
}
func TestClientHMAC(t *testing.T) {
kc := new(keychain)
kc.add(rsaKey)
for _, mac := range DefaultMACOrder {
config := &ClientConfig{
User: "testuser",
Auth: []ClientAuth{
ClientAuthKeyring(kc),
},
Crypto: CryptoConfig{
MACs: []string{mac},
},
}
c, err := Dial("tcp", newMockAuthServer(t), config)
if err != nil {
t.Fatalf("client could not authenticate with mac algo %s: %v", mac, err)
}
c.Close()
}
}
// issue 4285.
func TestClientUnsupportedCipher(t *testing.T) {
kc := new(keychain)
config := &ClientConfig{
User: "testuser",
Auth: []ClientAuth{
ClientAuthKeyring(kc),
},
Crypto: CryptoConfig{
Ciphers: []string{"aes128-cbc"}, // not currently supported
},
}
c, err := Dial("tcp", newMockAuthServer(t), config)
if err == nil {
t.Errorf("expected no ciphers in common")
c.Close()
}
}
func TestClientUnsupportedKex(t *testing.T) {
kc := new(keychain)
config := &ClientConfig{
User: "testuser",
Auth: []ClientAuth{
ClientAuthKeyring(kc),
},
Crypto: CryptoConfig{
KeyExchanges: []string{"diffie-hellman-group-exchange-sha256"}, // not currently supported
},
}
c, err := Dial("tcp", newMockAuthServer(t), config)
if err == nil || !strings.Contains(err.Error(), "no common algorithms") {
t.Errorf("got %v, expected 'no common algorithms'", err)
}
if c != nil {
c.Close()
}
}

View file

@ -1,34 +0,0 @@
package ssh
import (
"net"
"testing"
)
func testClientVersion(t *testing.T, config *ClientConfig, expected string) {
clientConn, serverConn := net.Pipe()
receivedVersion := make(chan string, 1)
go func() {
version, err := readVersion(serverConn)
if err != nil {
receivedVersion <- ""
} else {
receivedVersion <- string(version)
}
serverConn.Close()
}()
Client(clientConn, config)
actual := <-receivedVersion
if actual != expected {
t.Fatalf("got %s; want %s", actual, expected)
}
}
func TestCustomClientVersion(t *testing.T) {
version := "Test-Client-Version-0.0"
testClientVersion(t, &ClientConfig{ClientVersion: version}, version)
}
func TestDefaultClientVersion(t *testing.T) {
testClientVersion(t, &ClientConfig{}, packageVersion)
}

View file

@ -1,57 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssh
import (
"io"
"net"
"testing"
)
func TestSafeString(t *testing.T) {
strings := map[string]string{
"\x20\x0d\x0a": "\x20\x0d\x0a",
"flibble": "flibble",
"new\x20line": "new\x20line",
"123456\x07789": "123456 789",
"\t\t\x10\r\n": "\t\t \r\n",
}
for s, expected := range strings {
actual := safeString(s)
if expected != actual {
t.Errorf("expected: %v, actual: %v", []byte(expected), []byte(actual))
}
}
}
// Make sure Read/Write are not exposed.
func TestConnHideRWMethods(t *testing.T) {
for _, c := range []interface{}{new(ServerConn), new(ClientConn)} {
if _, ok := c.(io.Reader); ok {
t.Errorf("%T implements io.Reader", c)
}
if _, ok := c.(io.Writer); ok {
t.Errorf("%T implements io.Writer", c)
}
}
}
func TestConnSupportsLocalRemoteMethods(t *testing.T) {
type LocalAddr interface {
LocalAddr() net.Addr
}
type RemoteAddr interface {
RemoteAddr() net.Addr
}
for _, c := range []interface{}{new(ServerConn), new(ClientConn)} {
if _, ok := c.(LocalAddr); !ok {
t.Errorf("%T does not implement LocalAddr", c)
}
if _, ok := c.(RemoteAddr); !ok {
t.Errorf("%T does not implement RemoteAddr", c)
}
}
}

View file

@ -1,191 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssh
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"net/http"
"code.google.com/p/go.crypto/ssh/terminal"
)
func ExampleListen() {
// An SSH server is represented by a ServerConfig, which holds
// certificate details and handles authentication of ServerConns.
config := &ServerConfig{
PasswordCallback: func(conn *ServerConn, user, pass string) bool {
return user == "testuser" && pass == "tiger"
},
}
privateBytes, err := ioutil.ReadFile("id_rsa")
if err != nil {
panic("Failed to load private key")
}
private, err := ParsePrivateKey(privateBytes)
if err != nil {
panic("Failed to parse private key")
}
config.AddHostKey(private)
// Once a ServerConfig has been configured, connections can be
// accepted.
listener, err := Listen("tcp", "0.0.0.0:2022", config)
if err != nil {
panic("failed to listen for connection")
}
sConn, err := listener.Accept()
if err != nil {
panic("failed to accept incoming connection")
}
if err := sConn.Handshake(); err != nil {
panic("failed to handshake")
}
// A ServerConn multiplexes several channels, which must
// themselves be Accepted.
for {
// Accept reads from the connection, demultiplexes packets
// to their corresponding channels and returns when a new
// channel request is seen. Some goroutine must always be
// calling Accept; otherwise no messages will be forwarded
// to the channels.
channel, err := sConn.Accept()
if err != nil {
panic("error from Accept")
}
// Channels have a type, depending on the application level
// protocol intended. In the case of a shell, the type is
// "session" and ServerShell may be used to present a simple
// terminal interface.
if channel.ChannelType() != "session" {
channel.Reject(UnknownChannelType, "unknown channel type")
continue
}
channel.Accept()
term := terminal.NewTerminal(channel, "> ")
serverTerm := &ServerTerminal{
Term: term,
Channel: channel,
}
go func() {
defer channel.Close()
for {
line, err := serverTerm.ReadLine()
if err != nil {
break
}
fmt.Println(line)
}
}()
}
}
func ExampleDial() {
// An SSH client is represented with a ClientConn. Currently only
// the "password" authentication method is supported.
//
// To authenticate with the remote server you must pass at least one
// implementation of ClientAuth via the Auth field in ClientConfig.
config := &ClientConfig{
User: "username",
Auth: []ClientAuth{
// ClientAuthPassword wraps a ClientPassword implementation
// in a type that implements ClientAuth.
ClientAuthPassword(password("yourpassword")),
},
}
client, err := Dial("tcp", "yourserver.com:22", config)
if err != nil {
panic("Failed to dial: " + err.Error())
}
// Each ClientConn can support multiple interactive sessions,
// represented by a Session.
session, err := client.NewSession()
if err != nil {
panic("Failed to create session: " + err.Error())
}
defer session.Close()
// Once a Session is created, you can execute a single command on
// the remote side using the Run method.
var b bytes.Buffer
session.Stdout = &b
if err := session.Run("/usr/bin/whoami"); err != nil {
panic("Failed to run: " + err.Error())
}
fmt.Println(b.String())
}
func ExampleClientConn_Listen() {
config := &ClientConfig{
User: "username",
Auth: []ClientAuth{
ClientAuthPassword(password("password")),
},
}
// Dial your ssh server.
conn, err := Dial("tcp", "localhost:22", config)
if err != nil {
log.Fatalf("unable to connect: %s", err)
}
defer conn.Close()
// Request the remote side to open port 8080 on all interfaces.
l, err := conn.Listen("tcp", "0.0.0.0:8080")
if err != nil {
log.Fatalf("unable to register tcp forward: %v", err)
}
defer l.Close()
// Serve HTTP with your SSH server acting as a reverse proxy.
http.Serve(l, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
fmt.Fprintf(resp, "Hello world!\n")
}))
}
func ExampleSession_RequestPty() {
// Create client config
config := &ClientConfig{
User: "username",
Auth: []ClientAuth{
ClientAuthPassword(password("password")),
},
}
// Connect to ssh server
conn, err := Dial("tcp", "localhost:22", config)
if err != nil {
log.Fatalf("unable to connect: %s", err)
}
defer conn.Close()
// Create a session
session, err := conn.NewSession()
if err != nil {
log.Fatalf("unable to create session: %s", err)
}
defer session.Close()
// Set up terminal modes
modes := TerminalModes{
ECHO: 0, // disable echoing
TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
// Request pseudo terminal
if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
log.Fatalf("request for pseudo terminal failed: %s", err)
}
// Start remote shell
if err := session.Shell(); err != nil {
log.Fatalf("failed to start shell: %s", err)
}
}

View file

@ -1,48 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssh
// Key exchange tests.
import (
"crypto/rand"
"reflect"
"testing"
)
func TestKexes(t *testing.T) {
type kexResultErr struct {
result *kexResult
err error
}
for name, kex := range kexAlgoMap {
a, b := memPipe()
s := make(chan kexResultErr, 1)
c := make(chan kexResultErr, 1)
var magics handshakeMagics
go func() {
r, e := kex.Client(a, rand.Reader, &magics)
c <- kexResultErr{r, e}
}()
go func() {
r, e := kex.Server(b, rand.Reader, &magics, ecdsaKey)
s <- kexResultErr{r, e}
}()
clientRes := <-c
serverRes := <-s
if clientRes.err != nil {
t.Errorf("client: %v", clientRes.err)
}
if serverRes.err != nil {
t.Errorf("server: %v", serverRes.err)
}
if !reflect.DeepEqual(clientRes.result, serverRes.result) {
t.Errorf("kex %q: mismatch %#v, %#v", name, clientRes.result, serverRes.result)
}
}
}

View file

@ -1,214 +0,0 @@
package ssh
import (
"crypto/dsa"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"reflect"
"strings"
"testing"
)
var (
ecdsaKey Signer
ecdsa384Key Signer
ecdsa521Key Signer
testCertKey Signer
)
type testSigner struct {
Signer
pub PublicKey
}
func (ts *testSigner) PublicKey() PublicKey {
if ts.pub != nil {
return ts.pub
}
return ts.Signer.PublicKey()
}
func init() {
raw256, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
ecdsaKey, _ = NewSignerFromKey(raw256)
raw384, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
ecdsa384Key, _ = NewSignerFromKey(raw384)
raw521, _ := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
ecdsa521Key, _ = NewSignerFromKey(raw521)
// Create a cert and sign it for use in tests.
testCert := &OpenSSHCertV01{
Nonce: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
Key: ecdsaKey.PublicKey(),
ValidPrincipals: []string{"gopher1", "gopher2"}, // increases test coverage
ValidAfter: 0, // unix epoch
ValidBefore: maxUint64, // The end of currently representable time.
Reserved: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
SignatureKey: rsaKey.PublicKey(),
}
sigBytes, _ := rsaKey.Sign(rand.Reader, testCert.BytesForSigning())
testCert.Signature = &signature{
Format: testCert.SignatureKey.PublicKeyAlgo(),
Blob: sigBytes,
}
testCertKey = &testSigner{
Signer: ecdsaKey,
pub: testCert,
}
}
func rawKey(pub PublicKey) interface{} {
switch k := pub.(type) {
case *rsaPublicKey:
return (*rsa.PublicKey)(k)
case *dsaPublicKey:
return (*dsa.PublicKey)(k)
case *ecdsaPublicKey:
return (*ecdsa.PublicKey)(k)
case *OpenSSHCertV01:
return k
}
panic("unknown key type")
}
func TestKeyMarshalParse(t *testing.T) {
keys := []Signer{rsaKey, dsaKey, ecdsaKey, ecdsa384Key, ecdsa521Key, testCertKey}
for _, priv := range keys {
pub := priv.PublicKey()
roundtrip, rest, ok := ParsePublicKey(MarshalPublicKey(pub))
if !ok {
t.Errorf("ParsePublicKey(%T) failed", pub)
}
if len(rest) > 0 {
t.Errorf("ParsePublicKey(%T): trailing junk", pub)
}
k1 := rawKey(pub)
k2 := rawKey(roundtrip)
if !reflect.DeepEqual(k1, k2) {
t.Errorf("got %#v in roundtrip, want %#v", k2, k1)
}
}
}
func TestUnsupportedCurves(t *testing.T) {
raw, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
if err != nil {
t.Fatalf("GenerateKey: %v", err)
}
if _, err = NewSignerFromKey(raw); err == nil || !strings.Contains(err.Error(), "only P256") {
t.Fatalf("NewPrivateKey should not succeed with P224, got: %v", err)
}
if _, err = NewPublicKey(&raw.PublicKey); err == nil || !strings.Contains(err.Error(), "only P256") {
t.Fatalf("NewPublicKey should not succeed with P224, got: %v", err)
}
}
func TestNewPublicKey(t *testing.T) {
keys := []Signer{rsaKey, dsaKey, ecdsaKey}
for _, k := range keys {
raw := rawKey(k.PublicKey())
pub, err := NewPublicKey(raw)
if err != nil {
t.Errorf("NewPublicKey(%#v): %v", raw, err)
}
if !reflect.DeepEqual(k.PublicKey(), pub) {
t.Errorf("NewPublicKey(%#v) = %#v, want %#v", raw, pub, k.PublicKey())
}
}
}
func TestKeySignVerify(t *testing.T) {
keys := []Signer{rsaKey, dsaKey, ecdsaKey, testCertKey}
for _, priv := range keys {
pub := priv.PublicKey()
data := []byte("sign me")
sig, err := priv.Sign(rand.Reader, data)
if err != nil {
t.Fatalf("Sign(%T): %v", priv, err)
}
if !pub.Verify(data, sig) {
t.Errorf("publicKey.Verify(%T) failed", priv)
}
}
}
func TestParseRSAPrivateKey(t *testing.T) {
key, err := ParsePrivateKey([]byte(testServerPrivateKey))
if err != nil {
t.Fatalf("ParsePrivateKey: %v", err)
}
rsa, ok := key.(*rsaPrivateKey)
if !ok {
t.Fatalf("got %T, want *rsa.PrivateKey", rsa)
}
if err := rsa.Validate(); err != nil {
t.Errorf("Validate: %v", err)
}
}
func TestParseECPrivateKey(t *testing.T) {
// Taken from the data in test/ .
pem := []byte(`-----BEGIN EC PRIVATE KEY-----
MHcCAQEEINGWx0zo6fhJ/0EAfrPzVFyFC9s18lBt3cRoEDhS3ARooAoGCCqGSM49
AwEHoUQDQgAEi9Hdw6KvZcWxfg2IDhA7UkpDtzzt6ZqJXSsFdLd+Kx4S3Sx4cVO+
6/ZOXRnPmNAlLUqjShUsUBBngG0u2fqEqA==
-----END EC PRIVATE KEY-----`)
key, err := ParsePrivateKey(pem)
if err != nil {
t.Fatalf("ParsePrivateKey: %v", err)
}
ecKey, ok := key.(*ecdsaPrivateKey)
if !ok {
t.Fatalf("got %T, want *ecdsaPrivateKey", ecKey)
}
if !validateECPublicKey(ecKey.Curve, ecKey.X, ecKey.Y) {
t.Fatalf("public key does not validate.")
}
}
// ssh-keygen -t dsa -f /tmp/idsa.pem
var dsaPEM = `-----BEGIN DSA PRIVATE KEY-----
MIIBuwIBAAKBgQD6PDSEyXiI9jfNs97WuM46MSDCYlOqWw80ajN16AohtBncs1YB
lHk//dQOvCYOsYaE+gNix2jtoRjwXhDsc25/IqQbU1ahb7mB8/rsaILRGIbA5WH3
EgFtJmXFovDz3if6F6TzvhFpHgJRmLYVR8cqsezL3hEZOvvs2iH7MorkxwIVAJHD
nD82+lxh2fb4PMsIiaXudAsBAoGAQRf7Q/iaPRn43ZquUhd6WwvirqUj+tkIu6eV
2nZWYmXLlqFQKEy4Tejl7Wkyzr2OSYvbXLzo7TNxLKoWor6ips0phYPPMyXld14r
juhT24CrhOzuLMhDduMDi032wDIZG4Y+K7ElU8Oufn8Sj5Wge8r6ANmmVgmFfynr
FhdYCngCgYEA3ucGJ93/Mx4q4eKRDxcWD3QzWyqpbRVRRV1Vmih9Ha/qC994nJFz
DQIdjxDIT2Rk2AGzMqFEB68Zc3O+Wcsmz5eWWzEwFxaTwOGWTyDqsDRLm3fD+QYj
nOwuxb0Kce+gWI8voWcqC9cyRm09jGzu2Ab3Bhtpg8JJ8L7gS3MRZK4CFEx4UAfY
Fmsr0W6fHB9nhS4/UXM8
-----END DSA PRIVATE KEY-----`
func TestParseDSA(t *testing.T) {
s, err := ParsePrivateKey([]byte(dsaPEM))
if err != nil {
t.Fatalf("ParsePrivateKey returned error: %s", err)
}
data := []byte("sign me")
sig, err := s.Sign(rand.Reader, data)
if err != nil {
t.Fatalf("dsa.Sign: %v", err)
}
if !s.PublicKey().Verify(data, sig) {
t.Error("Verify failed.")
}
}

View file

@ -1,102 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssh
import (
"io"
"sync"
"testing"
)
// An in-memory packetConn. It is safe to call Close and writePacket
// from different goroutines.
type memTransport struct {
eof bool
pending [][]byte
write *memTransport
sync.Mutex
*sync.Cond
}
func (t *memTransport) readPacket() ([]byte, error) {
t.Lock()
defer t.Unlock()
for {
if len(t.pending) > 0 {
r := t.pending[0]
t.pending = t.pending[1:]
return r, nil
}
if t.eof {
return nil, io.EOF
}
t.Cond.Wait()
}
}
func (t *memTransport) Close() error {
t.write.Lock()
defer t.write.Unlock()
if t.write.eof {
return io.EOF
}
t.write.eof = true
t.write.Cond.Broadcast()
return nil
}
func (t *memTransport) writePacket(p []byte) error {
t.write.Lock()
defer t.write.Unlock()
if t.write.eof {
return io.EOF
}
t.write.pending = append(t.write.pending, p)
t.write.Cond.Signal()
return nil
}
func memPipe() (a, b packetConn) {
t1 := memTransport{}
t2 := memTransport{}
t1.write = &t2
t2.write = &t1
t1.Cond = sync.NewCond(&t1.Mutex)
t2.Cond = sync.NewCond(&t2.Mutex)
return &t1, &t2
}
func TestmemPipe(t *testing.T) {
a, b := memPipe()
if err := a.writePacket([]byte{42}); err != nil {
t.Fatalf("writePacket: %v", err)
}
if err := a.Close(); err != nil {
t.Fatal("Close: ", err)
}
p, err := b.readPacket()
if err != nil {
t.Fatal("readPacket: ", err)
}
if len(p) != 1 || p[0] != 42 {
t.Fatalf("got %v, want {42}", p)
}
p, err = b.readPacket()
if err != io.EOF {
t.Fatalf("got %v, %v, want EOF", p, err)
}
}
func TestDoubleClose(t *testing.T) {
a, _ := memPipe()
err := a.Close()
if err != nil {
t.Errorf("Close: %v", err)
}
err = a.Close()
if err != io.EOF {
t.Errorf("expect EOF on double close.")
}
}

View file

@ -1,232 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssh
import (
"math/big"
"math/rand"
"reflect"
"testing"
"testing/quick"
)
var intLengthTests = []struct {
val, length int
}{
{0, 4 + 0},
{1, 4 + 1},
{127, 4 + 1},
{128, 4 + 2},
{-1, 4 + 1},
}
func TestIntLength(t *testing.T) {
for _, test := range intLengthTests {
v := new(big.Int).SetInt64(int64(test.val))
length := intLength(v)
if length != test.length {
t.Errorf("For %d, got length %d but expected %d", test.val, length, test.length)
}
}
}
var messageTypes = []interface{}{
&kexInitMsg{},
&kexDHInitMsg{},
&serviceRequestMsg{},
&serviceAcceptMsg{},
&userAuthRequestMsg{},
&channelOpenMsg{},
&channelOpenConfirmMsg{},
&channelOpenFailureMsg{},
&channelRequestMsg{},
&channelRequestSuccessMsg{},
}
func TestMarshalUnmarshal(t *testing.T) {
rand := rand.New(rand.NewSource(0))
for i, iface := range messageTypes {
ty := reflect.ValueOf(iface).Type()
n := 100
if testing.Short() {
n = 5
}
for j := 0; j < n; j++ {
v, ok := quick.Value(ty, rand)
if !ok {
t.Errorf("#%d: failed to create value", i)
break
}
m1 := v.Elem().Interface()
m2 := iface
marshaled := marshal(msgIgnore, m1)
if err := unmarshal(m2, marshaled, msgIgnore); err != nil {
t.Errorf("#%d failed to unmarshal %#v: %s", i, m1, err)
break
}
if !reflect.DeepEqual(v.Interface(), m2) {
t.Errorf("#%d\ngot: %#v\nwant:%#v\n%x", i, m2, m1, marshaled)
break
}
}
}
}
func TestUnmarshalEmptyPacket(t *testing.T) {
var b []byte
var m channelRequestSuccessMsg
err := unmarshal(&m, b, msgChannelRequest)
want := ParseError{msgChannelRequest}
if _, ok := err.(ParseError); !ok {
t.Fatalf("got %T, want %T", err, want)
}
if got := err.(ParseError); want != got {
t.Fatal("got %#v, want %#v", got, want)
}
}
func TestUnmarshalUnexpectedPacket(t *testing.T) {
type S struct {
I uint32
S string
B bool
}
s := S{42, "hello", true}
packet := marshal(42, s)
roundtrip := S{}
err := unmarshal(&roundtrip, packet, 43)
if err == nil {
t.Fatal("expected error, not nil")
}
want := UnexpectedMessageError{43, 42}
if got, ok := err.(UnexpectedMessageError); !ok || want != got {
t.Fatal("expected %q, got %q", want, got)
}
}
func TestBareMarshalUnmarshal(t *testing.T) {
type S struct {
I uint32
S string
B bool
}
s := S{42, "hello", true}
packet := marshal(0, s)
roundtrip := S{}
unmarshal(&roundtrip, packet, 0)
if !reflect.DeepEqual(s, roundtrip) {
t.Errorf("got %#v, want %#v", roundtrip, s)
}
}
func TestBareMarshal(t *testing.T) {
type S2 struct {
I uint32
}
s := S2{42}
packet := marshal(0, s)
i, rest, ok := parseUint32(packet)
if len(rest) > 0 || !ok {
t.Errorf("parseInt(%q): parse error", packet)
}
if i != s.I {
t.Errorf("got %d, want %d", i, s.I)
}
}
func randomBytes(out []byte, rand *rand.Rand) {
for i := 0; i < len(out); i++ {
out[i] = byte(rand.Int31())
}
}
func randomNameList(rand *rand.Rand) []string {
ret := make([]string, rand.Int31()&15)
for i := range ret {
s := make([]byte, 1+(rand.Int31()&15))
for j := range s {
s[j] = 'a' + uint8(rand.Int31()&15)
}
ret[i] = string(s)
}
return ret
}
func randomInt(rand *rand.Rand) *big.Int {
return new(big.Int).SetInt64(int64(int32(rand.Uint32())))
}
func (*kexInitMsg) Generate(rand *rand.Rand, size int) reflect.Value {
ki := &kexInitMsg{}
randomBytes(ki.Cookie[:], rand)
ki.KexAlgos = randomNameList(rand)
ki.ServerHostKeyAlgos = randomNameList(rand)
ki.CiphersClientServer = randomNameList(rand)
ki.CiphersServerClient = randomNameList(rand)
ki.MACsClientServer = randomNameList(rand)
ki.MACsServerClient = randomNameList(rand)
ki.CompressionClientServer = randomNameList(rand)
ki.CompressionServerClient = randomNameList(rand)
ki.LanguagesClientServer = randomNameList(rand)
ki.LanguagesServerClient = randomNameList(rand)
if rand.Int31()&1 == 1 {
ki.FirstKexFollows = true
}
return reflect.ValueOf(ki)
}
func (*kexDHInitMsg) Generate(rand *rand.Rand, size int) reflect.Value {
dhi := &kexDHInitMsg{}
dhi.X = randomInt(rand)
return reflect.ValueOf(dhi)
}
// TODO(dfc) maybe this can be removed in the future if testing/quick can handle
// derived basic types.
func (RejectionReason) Generate(rand *rand.Rand, size int) reflect.Value {
m := RejectionReason(Prohibited)
return reflect.ValueOf(m)
}
var (
_kexInitMsg = new(kexInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface()
_kexDHInitMsg = new(kexDHInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface()
_kexInit = marshal(msgKexInit, _kexInitMsg)
_kexDHInit = marshal(msgKexDHInit, _kexDHInitMsg)
)
func BenchmarkMarshalKexInitMsg(b *testing.B) {
for i := 0; i < b.N; i++ {
marshal(msgKexInit, _kexInitMsg)
}
}
func BenchmarkUnmarshalKexInitMsg(b *testing.B) {
m := new(kexInitMsg)
for i := 0; i < b.N; i++ {
unmarshal(m, _kexInit, msgKexInit)
}
}
func BenchmarkMarshalKexDHInitMsg(b *testing.B) {
for i := 0; i < b.N; i++ {
marshal(msgKexDHInit, _kexDHInitMsg)
}
}
func BenchmarkUnmarshalKexDHInitMsg(b *testing.B) {
m := new(kexDHInitMsg)
for i := 0; i < b.N; i++ {
unmarshal(m, _kexDHInit, msgKexDHInit)
}
}

View file

@ -1,789 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssh
// Session tests.
import (
"bytes"
crypto_rand "crypto/rand"
"io"
"io/ioutil"
"math/rand"
"net"
"testing"
"code.google.com/p/go.crypto/ssh/terminal"
)
type serverType func(*serverChan, *testing.T)
// dial constructs a new test server and returns a *ClientConn.
func dial(handler serverType, t *testing.T) *ClientConn {
l, err := Listen("tcp", "127.0.0.1:0", serverConfig)
if err != nil {
t.Fatalf("unable to listen: %v", err)
}
go func() {
defer l.Close()
conn, err := l.Accept()
if err != nil {
t.Errorf("Unable to accept: %v", err)
return
}
defer conn.Close()
if err := conn.Handshake(); err != nil {
t.Errorf("Unable to handshake: %v", err)
return
}
done := make(chan struct{})
for {
ch, err := conn.Accept()
if err == io.EOF || err == io.ErrUnexpectedEOF {
return
}
// We sometimes get ECONNRESET rather than EOF.
if _, ok := err.(*net.OpError); ok {
return
}
if err != nil {
t.Errorf("Unable to accept incoming channel request: %v", err)
return
}
if ch.ChannelType() != "session" {
ch.Reject(UnknownChannelType, "unknown channel type")
continue
}
ch.Accept()
go func() {
defer close(done)
handler(ch.(*serverChan), t)
}()
}
<-done
}()
config := &ClientConfig{
User: "testuser",
Auth: []ClientAuth{
ClientAuthPassword(clientPassword),
},
}
c, err := Dial("tcp", l.Addr().String(), config)
if err != nil {
t.Fatalf("unable to dial remote side: %v", err)
}
return c
}
// Test a simple string is returned to session.Stdout.
func TestSessionShell(t *testing.T) {
conn := dial(shellHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
stdout := new(bytes.Buffer)
session.Stdout = stdout
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %s", err)
}
if err := session.Wait(); err != nil {
t.Fatalf("Remote command did not exit cleanly: %v", err)
}
actual := stdout.String()
if actual != "golang" {
t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
}
}
// TODO(dfc) add support for Std{in,err}Pipe when the Server supports it.
// Test a simple string is returned via StdoutPipe.
func TestSessionStdoutPipe(t *testing.T) {
conn := dial(shellHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
stdout, err := session.StdoutPipe()
if err != nil {
t.Fatalf("Unable to request StdoutPipe(): %v", err)
}
var buf bytes.Buffer
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
done := make(chan bool, 1)
go func() {
if _, err := io.Copy(&buf, stdout); err != nil {
t.Errorf("Copy of stdout failed: %v", err)
}
done <- true
}()
if err := session.Wait(); err != nil {
t.Fatalf("Remote command did not exit cleanly: %v", err)
}
<-done
actual := buf.String()
if actual != "golang" {
t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
}
}
// Test that a simple string is returned via the Output helper,
// and that stderr is discarded.
func TestSessionOutput(t *testing.T) {
conn := dial(fixedOutputHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
buf, err := session.Output("") // cmd is ignored by fixedOutputHandler
if err != nil {
t.Error("Remote command did not exit cleanly:", err)
}
w := "this-is-stdout."
g := string(buf)
if g != w {
t.Error("Remote command did not return expected string:")
t.Logf("want %q", w)
t.Logf("got %q", g)
}
}
// Test that both stdout and stderr are returned
// via the CombinedOutput helper.
func TestSessionCombinedOutput(t *testing.T) {
conn := dial(fixedOutputHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
buf, err := session.CombinedOutput("") // cmd is ignored by fixedOutputHandler
if err != nil {
t.Error("Remote command did not exit cleanly:", err)
}
const stdout = "this-is-stdout."
const stderr = "this-is-stderr."
g := string(buf)
if g != stdout+stderr && g != stderr+stdout {
t.Error("Remote command did not return expected string:")
t.Logf("want %q, or %q", stdout+stderr, stderr+stdout)
t.Logf("got %q", g)
}
}
// Test non-0 exit status is returned correctly.
func TestExitStatusNonZero(t *testing.T) {
conn := dial(exitStatusNonZeroHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
err = session.Wait()
if err == nil {
t.Fatalf("expected command to fail but it didn't")
}
e, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected *ExitError but got %T", err)
}
if e.ExitStatus() != 15 {
t.Fatalf("expected command to exit with 15 but got %v", e.ExitStatus())
}
}
// Test 0 exit status is returned correctly.
func TestExitStatusZero(t *testing.T) {
conn := dial(exitStatusZeroHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
err = session.Wait()
if err != nil {
t.Fatalf("expected nil but got %v", err)
}
}
// Test exit signal and status are both returned correctly.
func TestExitSignalAndStatus(t *testing.T) {
conn := dial(exitSignalAndStatusHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
err = session.Wait()
if err == nil {
t.Fatalf("expected command to fail but it didn't")
}
e, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected *ExitError but got %T", err)
}
if e.Signal() != "TERM" || e.ExitStatus() != 15 {
t.Fatalf("expected command to exit with signal TERM and status 15 but got signal %s and status %v", e.Signal(), e.ExitStatus())
}
}
// Test exit signal and status are both returned correctly.
func TestKnownExitSignalOnly(t *testing.T) {
conn := dial(exitSignalHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
err = session.Wait()
if err == nil {
t.Fatalf("expected command to fail but it didn't")
}
e, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected *ExitError but got %T", err)
}
if e.Signal() != "TERM" || e.ExitStatus() != 143 {
t.Fatalf("expected command to exit with signal TERM and status 143 but got signal %s and status %v", e.Signal(), e.ExitStatus())
}
}
// Test exit signal and status are both returned correctly.
func TestUnknownExitSignal(t *testing.T) {
conn := dial(exitSignalUnknownHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
err = session.Wait()
if err == nil {
t.Fatalf("expected command to fail but it didn't")
}
e, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected *ExitError but got %T", err)
}
if e.Signal() != "SYS" || e.ExitStatus() != 128 {
t.Fatalf("expected command to exit with signal SYS and status 128 but got signal %s and status %v", e.Signal(), e.ExitStatus())
}
}
// Test WaitMsg is not returned if the channel closes abruptly.
func TestExitWithoutStatusOrSignal(t *testing.T) {
conn := dial(exitWithoutSignalOrStatus, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
err = session.Wait()
if err == nil {
t.Fatalf("expected command to fail but it didn't")
}
_, ok := err.(*ExitError)
if ok {
// you can't actually test for errors.errorString
// because it's not exported.
t.Fatalf("expected *errorString but got %T", err)
}
}
func TestInvalidServerMessage(t *testing.T) {
conn := dial(sendInvalidRecord, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
// Make sure that we closed all the clientChans when the connection
// failed.
session.wait()
defer session.Close()
}
// In the wild some clients (and servers) send zero sized window updates.
// Test that the client can continue after receiving a zero sized update.
func TestClientZeroWindowAdjust(t *testing.T) {
conn := dial(sendZeroWindowAdjust, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
err = session.Wait()
if err != nil {
t.Fatalf("expected nil but got %v", err)
}
}
// In the wild some clients (and servers) send zero sized window updates.
// Test that the server can continue after receiving a zero size update.
func TestServerZeroWindowAdjust(t *testing.T) {
conn := dial(exitStatusZeroHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
// send a bogus zero sized window update
session.clientChan.sendWindowAdj(0)
err = session.Wait()
if err != nil {
t.Fatalf("expected nil but got %v", err)
}
}
// Verify that the client never sends a packet larger than maxpacket.
func TestClientStdinRespectsMaxPacketSize(t *testing.T) {
conn := dial(discardHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("failed to request new session: %v", err)
}
defer session.Close()
stdin, err := session.StdinPipe()
if err != nil {
t.Fatalf("failed to obtain stdinpipe: %v", err)
}
const size = 100 * 1000
for i := 0; i < 10; i++ {
n, err := stdin.Write(make([]byte, size))
if n != size || err != nil {
t.Fatalf("failed to write: %d, %v", n, err)
}
}
}
// Verify that the client never accepts a packet larger than maxpacket.
func TestServerStdoutRespectsMaxPacketSize(t *testing.T) {
conn := dial(largeSendHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
out, err := session.StdoutPipe()
if err != nil {
t.Fatalf("Unable to connect to Stdout: %v", err)
}
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
if _, err := ioutil.ReadAll(out); err != nil {
t.Fatalf("failed to read: %v", err)
}
}
func TestClientCannotSendAfterEOF(t *testing.T) {
conn := dial(exitWithoutSignalOrStatus, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
in, err := session.StdinPipe()
if err != nil {
t.Fatalf("Unable to connect channel stdin: %v", err)
}
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
if err := in.Close(); err != nil {
t.Fatalf("Unable to close stdin: %v", err)
}
if _, err := in.Write([]byte("foo")); err == nil {
t.Fatalf("Session write should fail")
}
}
func TestClientCannotSendAfterClose(t *testing.T) {
conn := dial(exitWithoutSignalOrStatus, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
in, err := session.StdinPipe()
if err != nil {
t.Fatalf("Unable to connect channel stdin: %v", err)
}
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
// close underlying channel
if err := session.channel.Close(); err != nil {
t.Fatalf("Unable to close session: %v", err)
}
if _, err := in.Write([]byte("foo")); err == nil {
t.Fatalf("Session write should fail")
}
}
func TestClientCannotSendHugePacket(t *testing.T) {
// client and server use the same transport write code so this
// test suffices for both.
conn := dial(shellHandler, t)
defer conn.Close()
if err := conn.transport.writePacket(make([]byte, maxPacket*2)); err == nil {
t.Fatalf("huge packet write should fail")
}
}
// windowTestBytes is the number of bytes that we'll send to the SSH server.
const windowTestBytes = 16000 * 200
// TestServerWindow writes random data to the server. The server is expected to echo
// the same data back, which is compared against the original.
func TestServerWindow(t *testing.T) {
origBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes))
io.CopyN(origBuf, crypto_rand.Reader, windowTestBytes)
origBytes := origBuf.Bytes()
conn := dial(echoHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatal(err)
}
defer session.Close()
result := make(chan []byte)
go func() {
defer close(result)
echoedBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes))
serverStdout, err := session.StdoutPipe()
if err != nil {
t.Errorf("StdoutPipe failed: %v", err)
return
}
n, err := copyNRandomly("stdout", echoedBuf, serverStdout, windowTestBytes)
if err != nil && err != io.EOF {
t.Errorf("Read only %d bytes from server, expected %d: %v", n, windowTestBytes, err)
}
result <- echoedBuf.Bytes()
}()
serverStdin, err := session.StdinPipe()
if err != nil {
t.Fatalf("StdinPipe failed: %v", err)
}
written, err := copyNRandomly("stdin", serverStdin, origBuf, windowTestBytes)
if err != nil {
t.Fatalf("failed to copy origBuf to serverStdin: %v", err)
}
if written != windowTestBytes {
t.Fatalf("Wrote only %d of %d bytes to server", written, windowTestBytes)
}
echoedBytes := <-result
if !bytes.Equal(origBytes, echoedBytes) {
t.Fatalf("Echoed buffer differed from original, orig %d, echoed %d", len(origBytes), len(echoedBytes))
}
}
// Verify the client can handle a keepalive packet from the server.
func TestClientHandlesKeepalives(t *testing.T) {
conn := dial(channelKeepaliveSender, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatal(err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
err = session.Wait()
if err != nil {
t.Fatalf("expected nil but got: %v", err)
}
}
type exitStatusMsg struct {
PeersId uint32
Request string
WantReply bool
Status uint32
}
type exitSignalMsg struct {
PeersId uint32
Request string
WantReply bool
Signal string
CoreDumped bool
Errmsg string
Lang string
}
func newServerShell(ch *serverChan, prompt string) *ServerTerminal {
term := terminal.NewTerminal(ch, prompt)
return &ServerTerminal{
Term: term,
Channel: ch,
}
}
func exitStatusZeroHandler(ch *serverChan, t *testing.T) {
defer ch.Close()
// this string is returned to stdout
shell := newServerShell(ch, "> ")
readLine(shell, t)
sendStatus(0, ch, t)
}
func exitStatusNonZeroHandler(ch *serverChan, t *testing.T) {
defer ch.Close()
shell := newServerShell(ch, "> ")
readLine(shell, t)
sendStatus(15, ch, t)
}
func exitSignalAndStatusHandler(ch *serverChan, t *testing.T) {
defer ch.Close()
shell := newServerShell(ch, "> ")
readLine(shell, t)
sendStatus(15, ch, t)
sendSignal("TERM", ch, t)
}
func exitSignalHandler(ch *serverChan, t *testing.T) {
defer ch.Close()
shell := newServerShell(ch, "> ")
readLine(shell, t)
sendSignal("TERM", ch, t)
}
func exitSignalUnknownHandler(ch *serverChan, t *testing.T) {
defer ch.Close()
shell := newServerShell(ch, "> ")
readLine(shell, t)
sendSignal("SYS", ch, t)
}
func exitWithoutSignalOrStatus(ch *serverChan, t *testing.T) {
defer ch.Close()
shell := newServerShell(ch, "> ")
readLine(shell, t)
}
func shellHandler(ch *serverChan, t *testing.T) {
defer ch.Close()
// this string is returned to stdout
shell := newServerShell(ch, "golang")
readLine(shell, t)
sendStatus(0, ch, t)
}
// Ignores the command, writes fixed strings to stderr and stdout.
// Strings are "this-is-stdout." and "this-is-stderr.".
func fixedOutputHandler(ch *serverChan, t *testing.T) {
defer ch.Close()
_, err := ch.Read(make([]byte, 0))
if _, ok := err.(ChannelRequest); !ok {
t.Fatalf("error: expected channel request, got: %#v", err)
return
}
// ignore request, always send some text
ch.AckRequest(true)
_, err = io.WriteString(ch, "this-is-stdout.")
if err != nil {
t.Fatalf("error writing on server: %v", err)
}
_, err = io.WriteString(ch.Stderr(), "this-is-stderr.")
if err != nil {
t.Fatalf("error writing on server: %v", err)
}
sendStatus(0, ch, t)
}
func readLine(shell *ServerTerminal, t *testing.T) {
if _, err := shell.ReadLine(); err != nil && err != io.EOF {
t.Errorf("unable to read line: %v", err)
}
}
func sendStatus(status uint32, ch *serverChan, t *testing.T) {
msg := exitStatusMsg{
PeersId: ch.remoteId,
Request: "exit-status",
WantReply: false,
Status: status,
}
if err := ch.writePacket(marshal(msgChannelRequest, msg)); err != nil {
t.Errorf("unable to send status: %v", err)
}
}
func sendSignal(signal string, ch *serverChan, t *testing.T) {
sig := exitSignalMsg{
PeersId: ch.remoteId,
Request: "exit-signal",
WantReply: false,
Signal: signal,
CoreDumped: false,
Errmsg: "Process terminated",
Lang: "en-GB-oed",
}
if err := ch.writePacket(marshal(msgChannelRequest, sig)); err != nil {
t.Errorf("unable to send signal: %v", err)
}
}
func sendInvalidRecord(ch *serverChan, t *testing.T) {
defer ch.Close()
packet := make([]byte, 1+4+4+1)
packet[0] = msgChannelData
marshalUint32(packet[1:], 29348723 /* invalid channel id */)
marshalUint32(packet[5:], 1)
packet[9] = 42
if err := ch.writePacket(packet); err != nil {
t.Errorf("unable send invalid record: %v", err)
}
}
func sendZeroWindowAdjust(ch *serverChan, t *testing.T) {
defer ch.Close()
// send a bogus zero sized window update
ch.sendWindowAdj(0)
shell := newServerShell(ch, "> ")
readLine(shell, t)
sendStatus(0, ch, t)
}
func discardHandler(ch *serverChan, t *testing.T) {
defer ch.Close()
// grow the window to avoid being fooled by
// the initial 1 << 14 window.
ch.sendWindowAdj(1024 * 1024)
io.Copy(ioutil.Discard, ch)
}
func largeSendHandler(ch *serverChan, t *testing.T) {
defer ch.Close()
// grow the window to avoid being fooled by
// the initial 1 << 14 window.
ch.sendWindowAdj(1024 * 1024)
shell := newServerShell(ch, "> ")
readLine(shell, t)
// try to send more than the 32k window
// will allow
if err := ch.writePacket(make([]byte, 128*1024)); err == nil {
t.Errorf("wrote packet larger than 32k")
}
}
func echoHandler(ch *serverChan, t *testing.T) {
defer ch.Close()
if n, err := copyNRandomly("echohandler", ch, ch, windowTestBytes); err != nil {
t.Errorf("short write, wrote %d, expected %d: %v ", n, windowTestBytes, err)
}
}
// copyNRandomly copies n bytes from src to dst. It uses a variable, and random,
// buffer size to exercise more code paths.
func copyNRandomly(title string, dst io.Writer, src io.Reader, n int) (int, error) {
var (
buf = make([]byte, 32*1024)
written int
remaining = n
)
for remaining > 0 {
l := rand.Intn(1 << 15)
if remaining < l {
l = remaining
}
nr, er := src.Read(buf[:l])
nw, ew := dst.Write(buf[:nr])
remaining -= nw
written += nw
if ew != nil {
return written, ew
}
if nr != nw {
return written, io.ErrShortWrite
}
if er != nil && er != io.EOF {
return written, er
}
}
return written, nil
}
func channelKeepaliveSender(ch *serverChan, t *testing.T) {
defer ch.Close()
shell := newServerShell(ch, "> ")
readLine(shell, t)
msg := channelRequestMsg{
PeersId: ch.remoteId,
Request: "keepalive@openssh.com",
WantReply: true,
}
if err := ch.writePacket(marshal(msgChannelRequest, msg)); err != nil {
t.Errorf("unable to send channel keepalive request: %v", err)
}
sendStatus(0, ch, t)
}

View file

@ -1,16 +0,0 @@
package ssh
import (
"testing"
)
func TestAutoPortListenBroken(t *testing.T) {
broken := "SSH-2.0-OpenSSH_5.9hh11"
works := "SSH-2.0-OpenSSH_6.1"
if !isBrokenOpenSSHVersion(broken) {
t.Errorf("version %q not marked as broken", broken)
}
if isBrokenOpenSSHVersion(works) {
t.Errorf("version %q marked as broken", works)
}
}

View file

@ -1,699 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package terminal
import (
"io"
"sync"
"unicode/utf8"
)
// EscapeCodes contains escape sequences that can be written to the terminal in
// order to achieve different styles of text.
type EscapeCodes struct {
// Foreground colors
Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte
// Reset all attributes
Reset []byte
}
var vt100EscapeCodes = EscapeCodes{
Black: []byte{keyEscape, '[', '3', '0', 'm'},
Red: []byte{keyEscape, '[', '3', '1', 'm'},
Green: []byte{keyEscape, '[', '3', '2', 'm'},
Yellow: []byte{keyEscape, '[', '3', '3', 'm'},
Blue: []byte{keyEscape, '[', '3', '4', 'm'},
Magenta: []byte{keyEscape, '[', '3', '5', 'm'},
Cyan: []byte{keyEscape, '[', '3', '6', 'm'},
White: []byte{keyEscape, '[', '3', '7', 'm'},
Reset: []byte{keyEscape, '[', '0', 'm'},
}
// Terminal contains the state for running a VT100 terminal that is capable of
// reading lines of input.
type Terminal struct {
// AutoCompleteCallback, if non-null, is called for each keypress with
// the full input line and the current position of the cursor (in
// bytes, as an index into |line|). If it returns ok=false, the key
// press is processed normally. Otherwise it returns a replacement line
// and the new cursor position.
AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool)
// Escape contains a pointer to the escape codes for this terminal.
// It's always a valid pointer, although the escape codes themselves
// may be empty if the terminal doesn't support them.
Escape *EscapeCodes
// lock protects the terminal and the state in this object from
// concurrent processing of a key press and a Write() call.
lock sync.Mutex
c io.ReadWriter
prompt string
// line is the current line being entered.
line []rune
// pos is the logical position of the cursor in line
pos int
// echo is true if local echo is enabled
echo bool
// cursorX contains the current X value of the cursor where the left
// edge is 0. cursorY contains the row number where the first row of
// the current line is 0.
cursorX, cursorY int
// maxLine is the greatest value of cursorY so far.
maxLine int
termWidth, termHeight int
// outBuf contains the terminal data to be sent.
outBuf []byte
// remainder contains the remainder of any partial key sequences after
// a read. It aliases into inBuf.
remainder []byte
inBuf [256]byte
// history contains previously entered commands so that they can be
// accessed with the up and down keys.
history stRingBuffer
// historyIndex stores the currently accessed history entry, where zero
// means the immediately previous entry.
historyIndex int
// When navigating up and down the history it's possible to return to
// the incomplete, initial line. That value is stored in
// historyPending.
historyPending string
}
// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
// a local terminal, that terminal must first have been put into raw mode.
// prompt is a string that is written at the start of each input line (i.e.
// "> ").
func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
return &Terminal{
Escape: &vt100EscapeCodes,
c: c,
prompt: prompt,
termWidth: 80,
termHeight: 24,
echo: true,
historyIndex: -1,
}
}
const (
keyCtrlD = 4
keyEnter = '\r'
keyEscape = 27
keyBackspace = 127
keyUnknown = 0xd800 /* UTF-16 surrogate area */ + iota
keyUp
keyDown
keyLeft
keyRight
keyAltLeft
keyAltRight
keyHome
keyEnd
keyDeleteWord
keyDeleteLine
)
// bytesToKey tries to parse a key sequence from b. If successful, it returns
// the key and the remainder of the input. Otherwise it returns utf8.RuneError.
func bytesToKey(b []byte) (rune, []byte) {
if len(b) == 0 {
return utf8.RuneError, nil
}
switch b[0] {
case 1: // ^A
return keyHome, b[1:]
case 5: // ^E
return keyEnd, b[1:]
case 8: // ^H
return keyBackspace, b[1:]
case 11: // ^K
return keyDeleteLine, b[1:]
case 23: // ^W
return keyDeleteWord, b[1:]
}
if b[0] != keyEscape {
if !utf8.FullRune(b) {
return utf8.RuneError, b
}
r, l := utf8.DecodeRune(b)
return r, b[l:]
}
if len(b) >= 3 && b[0] == keyEscape && b[1] == '[' {
switch b[2] {
case 'A':
return keyUp, b[3:]
case 'B':
return keyDown, b[3:]
case 'C':
return keyRight, b[3:]
case 'D':
return keyLeft, b[3:]
}
}
if len(b) >= 3 && b[0] == keyEscape && b[1] == 'O' {
switch b[2] {
case 'H':
return keyHome, b[3:]
case 'F':
return keyEnd, b[3:]
}
}
if len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
switch b[5] {
case 'C':
return keyAltRight, b[6:]
case 'D':
return keyAltLeft, b[6:]
}
}
// If we get here then we have a key that we don't recognise, or a
// partial sequence. It's not clear how one should find the end of a
// sequence without knowing them all, but it seems that [a-zA-Z] only
// appears at the end of a sequence.
for i, c := range b[0:] {
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' {
return keyUnknown, b[i+1:]
}
}
return utf8.RuneError, b
}
// queue appends data to the end of t.outBuf
func (t *Terminal) queue(data []rune) {
t.outBuf = append(t.outBuf, []byte(string(data))...)
}
var eraseUnderCursor = []rune{' ', keyEscape, '[', 'D'}
var space = []rune{' '}
func isPrintable(key rune) bool {
isInSurrogateArea := key >= 0xd800 && key <= 0xdbff
return key >= 32 && !isInSurrogateArea
}
// moveCursorToPos appends data to t.outBuf which will move the cursor to the
// given, logical position in the text.
func (t *Terminal) moveCursorToPos(pos int) {
if !t.echo {
return
}
x := len(t.prompt) + pos
y := x / t.termWidth
x = x % t.termWidth
up := 0
if y < t.cursorY {
up = t.cursorY - y
}
down := 0
if y > t.cursorY {
down = y - t.cursorY
}
left := 0
if x < t.cursorX {
left = t.cursorX - x
}
right := 0
if x > t.cursorX {
right = x - t.cursorX
}
t.cursorX = x
t.cursorY = y
t.move(up, down, left, right)
}
func (t *Terminal) move(up, down, left, right int) {
movement := make([]rune, 3*(up+down+left+right))
m := movement
for i := 0; i < up; i++ {
m[0] = keyEscape
m[1] = '['
m[2] = 'A'
m = m[3:]
}
for i := 0; i < down; i++ {
m[0] = keyEscape
m[1] = '['
m[2] = 'B'
m = m[3:]
}
for i := 0; i < left; i++ {
m[0] = keyEscape
m[1] = '['
m[2] = 'D'
m = m[3:]
}
for i := 0; i < right; i++ {
m[0] = keyEscape
m[1] = '['
m[2] = 'C'
m = m[3:]
}
t.queue(movement)
}
func (t *Terminal) clearLineToRight() {
op := []rune{keyEscape, '[', 'K'}
t.queue(op)
}
const maxLineLength = 4096
func (t *Terminal) setLine(newLine []rune, newPos int) {
if t.echo {
t.moveCursorToPos(0)
t.writeLine(newLine)
for i := len(newLine); i < len(t.line); i++ {
t.writeLine(space)
}
t.moveCursorToPos(newPos)
}
t.line = newLine
t.pos = newPos
}
func (t *Terminal) eraseNPreviousChars(n int) {
if n == 0 {
return
}
if t.pos < n {
n = t.pos
}
t.pos -= n
t.moveCursorToPos(t.pos)
copy(t.line[t.pos:], t.line[n+t.pos:])
t.line = t.line[:len(t.line)-n]
if t.echo {
t.writeLine(t.line[t.pos:])
for i := 0; i < n; i++ {
t.queue(space)
}
t.cursorX += n
t.moveCursorToPos(t.pos)
}
}
// countToLeftWord returns then number of characters from the cursor to the
// start of the previous word.
func (t *Terminal) countToLeftWord() int {
if t.pos == 0 {
return 0
}
pos := t.pos - 1
for pos > 0 {
if t.line[pos] != ' ' {
break
}
pos--
}
for pos > 0 {
if t.line[pos] == ' ' {
pos++
break
}
pos--
}
return t.pos - pos
}
// countToRightWord returns then number of characters from the cursor to the
// start of the next word.
func (t *Terminal) countToRightWord() int {
pos := t.pos
for pos < len(t.line) {
if t.line[pos] == ' ' {
break
}
pos++
}
for pos < len(t.line) {
if t.line[pos] != ' ' {
break
}
pos++
}
return pos - t.pos
}
// handleKey processes the given key and, optionally, returns a line of text
// that the user has entered.
func (t *Terminal) handleKey(key rune) (line string, ok bool) {
switch key {
case keyBackspace:
if t.pos == 0 {
return
}
t.eraseNPreviousChars(1)
case keyAltLeft:
// move left by a word.
t.pos -= t.countToLeftWord()
t.moveCursorToPos(t.pos)
case keyAltRight:
// move right by a word.
t.pos += t.countToRightWord()
t.moveCursorToPos(t.pos)
case keyLeft:
if t.pos == 0 {
return
}
t.pos--
t.moveCursorToPos(t.pos)
case keyRight:
if t.pos == len(t.line) {
return
}
t.pos++
t.moveCursorToPos(t.pos)
case keyHome:
if t.pos == 0 {
return
}
t.pos = 0
t.moveCursorToPos(t.pos)
case keyEnd:
if t.pos == len(t.line) {
return
}
t.pos = len(t.line)
t.moveCursorToPos(t.pos)
case keyUp:
entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1)
if !ok {
return "", false
}
if t.historyIndex == -1 {
t.historyPending = string(t.line)
}
t.historyIndex++
runes := []rune(entry)
t.setLine(runes, len(runes))
case keyDown:
switch t.historyIndex {
case -1:
return
case 0:
runes := []rune(t.historyPending)
t.setLine(runes, len(runes))
t.historyIndex--
default:
entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1)
if ok {
t.historyIndex--
runes := []rune(entry)
t.setLine(runes, len(runes))
}
}
case keyEnter:
t.moveCursorToPos(len(t.line))
t.queue([]rune("\r\n"))
line = string(t.line)
ok = true
t.line = t.line[:0]
t.pos = 0
t.cursorX = 0
t.cursorY = 0
t.maxLine = 0
case keyDeleteWord:
// Delete zero or more spaces and then one or more characters.
t.eraseNPreviousChars(t.countToLeftWord())
case keyDeleteLine:
// Delete everything from the current cursor position to the
// end of line.
for i := t.pos; i < len(t.line); i++ {
t.queue(space)
t.cursorX++
}
t.line = t.line[:t.pos]
t.moveCursorToPos(t.pos)
default:
if t.AutoCompleteCallback != nil {
prefix := string(t.line[:t.pos])
suffix := string(t.line[t.pos:])
t.lock.Unlock()
newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key)
t.lock.Lock()
if completeOk {
t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos]))
return
}
}
if !isPrintable(key) {
return
}
if len(t.line) == maxLineLength {
return
}
if len(t.line) == cap(t.line) {
newLine := make([]rune, len(t.line), 2*(1+len(t.line)))
copy(newLine, t.line)
t.line = newLine
}
t.line = t.line[:len(t.line)+1]
copy(t.line[t.pos+1:], t.line[t.pos:])
t.line[t.pos] = key
if t.echo {
t.writeLine(t.line[t.pos:])
}
t.pos++
t.moveCursorToPos(t.pos)
}
return
}
func (t *Terminal) writeLine(line []rune) {
for len(line) != 0 {
remainingOnLine := t.termWidth - t.cursorX
todo := len(line)
if todo > remainingOnLine {
todo = remainingOnLine
}
t.queue(line[:todo])
t.cursorX += todo
line = line[todo:]
if t.cursorX == t.termWidth {
t.cursorX = 0
t.cursorY++
if t.cursorY > t.maxLine {
t.maxLine = t.cursorY
}
}
}
}
func (t *Terminal) Write(buf []byte) (n int, err error) {
t.lock.Lock()
defer t.lock.Unlock()
if t.cursorX == 0 && t.cursorY == 0 {
// This is the easy case: there's nothing on the screen that we
// have to move out of the way.
return t.c.Write(buf)
}
// We have a prompt and possibly user input on the screen. We
// have to clear it first.
t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */)
t.cursorX = 0
t.clearLineToRight()
for t.cursorY > 0 {
t.move(1 /* up */, 0, 0, 0)
t.cursorY--
t.clearLineToRight()
}
if _, err = t.c.Write(t.outBuf); err != nil {
return
}
t.outBuf = t.outBuf[:0]
if n, err = t.c.Write(buf); err != nil {
return
}
t.queue([]rune(t.prompt))
chars := len(t.prompt)
if t.echo {
t.queue(t.line)
chars += len(t.line)
}
t.cursorX = chars % t.termWidth
t.cursorY = chars / t.termWidth
t.moveCursorToPos(t.pos)
if _, err = t.c.Write(t.outBuf); err != nil {
return
}
t.outBuf = t.outBuf[:0]
return
}
// ReadPassword temporarily changes the prompt and reads a password, without
// echo, from the terminal.
func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
t.lock.Lock()
defer t.lock.Unlock()
oldPrompt := t.prompt
t.prompt = prompt
t.echo = false
line, err = t.readLine()
t.prompt = oldPrompt
t.echo = true
return
}
// ReadLine returns a line of input from the terminal.
func (t *Terminal) ReadLine() (line string, err error) {
t.lock.Lock()
defer t.lock.Unlock()
return t.readLine()
}
func (t *Terminal) readLine() (line string, err error) {
// t.lock must be held at this point
if t.cursorX == 0 && t.cursorY == 0 {
t.writeLine([]rune(t.prompt))
t.c.Write(t.outBuf)
t.outBuf = t.outBuf[:0]
}
for {
rest := t.remainder
lineOk := false
for !lineOk {
var key rune
key, rest = bytesToKey(rest)
if key == utf8.RuneError {
break
}
if key == keyCtrlD {
return "", io.EOF
}
line, lineOk = t.handleKey(key)
}
if len(rest) > 0 {
n := copy(t.inBuf[:], rest)
t.remainder = t.inBuf[:n]
} else {
t.remainder = nil
}
t.c.Write(t.outBuf)
t.outBuf = t.outBuf[:0]
if lineOk {
if t.echo {
t.historyIndex = -1
t.history.Add(line)
}
return
}
// t.remainder is a slice at the beginning of t.inBuf
// containing a partial key sequence
readBuf := t.inBuf[len(t.remainder):]
var n int
t.lock.Unlock()
n, err = t.c.Read(readBuf)
t.lock.Lock()
if err != nil {
return
}
t.remainder = t.inBuf[:n+len(t.remainder)]
}
panic("unreachable") // for Go 1.0.
}
// SetPrompt sets the prompt to be used when reading subsequent lines.
func (t *Terminal) SetPrompt(prompt string) {
t.lock.Lock()
defer t.lock.Unlock()
t.prompt = prompt
}
func (t *Terminal) SetSize(width, height int) {
t.lock.Lock()
defer t.lock.Unlock()
t.termWidth, t.termHeight = width, height
}
// stRingBuffer is a ring buffer of strings.
type stRingBuffer struct {
// entries contains max elements.
entries []string
max int
// head contains the index of the element most recently added to the ring.
head int
// size contains the number of elements in the ring.
size int
}
func (s *stRingBuffer) Add(a string) {
if s.entries == nil {
const defaultNumEntries = 100
s.entries = make([]string, defaultNumEntries)
s.max = defaultNumEntries
}
s.head = (s.head + 1) % s.max
s.entries[s.head] = a
if s.size < s.max {
s.size++
}
}
// NthPreviousEntry returns the value passed to the nth previous call to Add.
// If n is zero then the immediately prior value is returned, if one, then the
// next most recent, and so on. If such an element doesn't exist then ok is
// false.
func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) {
if n >= s.size {
return "", false
}
index := s.head - n
if index < 0 {
index += s.max
}
return s.entries[index], true
}

View file

@ -1,209 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package terminal
import (
"io"
"testing"
)
type MockTerminal struct {
toSend []byte
bytesPerRead int
received []byte
}
func (c *MockTerminal) Read(data []byte) (n int, err error) {
n = len(data)
if n == 0 {
return
}
if n > len(c.toSend) {
n = len(c.toSend)
}
if n == 0 {
return 0, io.EOF
}
if c.bytesPerRead > 0 && n > c.bytesPerRead {
n = c.bytesPerRead
}
copy(data, c.toSend[:n])
c.toSend = c.toSend[n:]
return
}
func (c *MockTerminal) Write(data []byte) (n int, err error) {
c.received = append(c.received, data...)
return len(data), nil
}
func TestClose(t *testing.T) {
c := &MockTerminal{}
ss := NewTerminal(c, "> ")
line, err := ss.ReadLine()
if line != "" {
t.Errorf("Expected empty line but got: %s", line)
}
if err != io.EOF {
t.Errorf("Error should have been EOF but got: %s", err)
}
}
var keyPressTests = []struct {
in string
line string
err error
throwAwayLines int
}{
{
err: io.EOF,
},
{
in: "\r",
line: "",
},
{
in: "foo\r",
line: "foo",
},
{
in: "a\x1b[Cb\r", // right
line: "ab",
},
{
in: "a\x1b[Db\r", // left
line: "ba",
},
{
in: "a\177b\r", // backspace
line: "b",
},
{
in: "\x1b[A\r", // up
},
{
in: "\x1b[B\r", // down
},
{
in: "line\x1b[A\x1b[B\r", // up then down
line: "line",
},
{
in: "line1\rline2\x1b[A\r", // recall previous line.
line: "line1",
throwAwayLines: 1,
},
{
// recall two previous lines and append.
in: "line1\rline2\rline3\x1b[A\x1b[Axxx\r",
line: "line1xxx",
throwAwayLines: 2,
},
{
// Ctrl-A to move to beginning of line followed by ^K to kill
// line.
in: "a b \001\013\r",
line: "",
},
{
// Ctrl-A to move to beginning of line, Ctrl-E to move to end,
// finally ^K to kill nothing.
in: "a b \001\005\013\r",
line: "a b ",
},
{
in: "\027\r",
line: "",
},
{
in: "a\027\r",
line: "",
},
{
in: "a \027\r",
line: "",
},
{
in: "a b\027\r",
line: "a ",
},
{
in: "a b \027\r",
line: "a ",
},
{
in: "one two thr\x1b[D\027\r",
line: "one two r",
},
{
in: "\013\r",
line: "",
},
{
in: "a\013\r",
line: "a",
},
{
in: "ab\x1b[D\013\r",
line: "a",
},
{
in: "Ξεσκεπάζω\r",
line: "Ξεσκεπάζω",
},
{
in: "£\r\x1b[A\177\r", // non-ASCII char, enter, up, backspace.
line: "",
throwAwayLines: 1,
},
{
in: "£\r££\x1b[A\x1b[B\177\r", // non-ASCII char, enter, 2x non-ASCII, up, down, backspace, enter.
line: "£",
throwAwayLines: 1,
},
}
func TestKeyPresses(t *testing.T) {
for i, test := range keyPressTests {
for j := 1; j < len(test.in); j++ {
c := &MockTerminal{
toSend: []byte(test.in),
bytesPerRead: j,
}
ss := NewTerminal(c, "> ")
for k := 0; k < test.throwAwayLines; k++ {
_, err := ss.ReadLine()
if err != nil {
t.Errorf("Throwaway line %d from test %d resulted in error: %s", k, i, err)
}
}
line, err := ss.ReadLine()
if line != test.line {
t.Errorf("Line resulting from test %d (%d bytes per read) was '%s', expected '%s'", i, j, line, test.line)
break
}
if err != test.err {
t.Errorf("Error resulting from test %d (%d bytes per read) was '%v', expected '%v'", i, j, err, test.err)
break
}
}
}
}
func TestPasswordNotSaved(t *testing.T) {
c := &MockTerminal{
toSend: []byte("password\r\x1b[A\r"),
bytesPerRead: 1,
}
ss := NewTerminal(c, "> ")
pw, _ := ss.ReadPassword("> ")
if pw != "password" {
t.Fatalf("failed to read password, got %s", pw)
}
line, _ := ss.ReadLine()
if len(line) > 0 {
t.Fatalf("password was saved in history")
}
}

View file

@ -1,128 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux,!appengine darwin
// Package terminal provides support functions for dealing with terminals, as
// commonly found on UNIX systems.
//
// Putting a terminal into raw mode is the most common requirement:
//
// oldState, err := terminal.MakeRaw(0)
// if err != nil {
// panic(err)
// }
// defer terminal.Restore(0, oldState)
package terminal
import (
"io"
"syscall"
"unsafe"
)
// State contains the state of a terminal.
type State struct {
termios syscall.Termios
}
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd int) bool {
var termios syscall.Termios
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0
}
// MakeRaw put the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
func MakeRaw(fd int) (*State, error) {
var oldState State
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
return nil, err
}
newState := oldState.termios
newState.Iflag &^= syscall.ISTRIP | syscall.INLCR | syscall.ICRNL | syscall.IGNCR | syscall.IXON | syscall.IXOFF
newState.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.ISIG
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
return nil, err
}
return &oldState, nil
}
// GetState returns the current state of a terminal which may be useful to
// restore the terminal after a signal.
func GetState(fd int) (*State, error) {
var oldState State
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
return nil, err
}
return &oldState, nil
}
// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func Restore(fd int, state *State) error {
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0)
return err
}
// GetSize returns the dimensions of the given terminal.
func GetSize(fd int) (width, height int, err error) {
var dimensions [4]uint16
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0); err != 0 {
return -1, -1, err
}
return int(dimensions[1]), int(dimensions[0]), nil
}
// ReadPassword reads a line of input from a terminal without local echo. This
// is commonly used for inputting passwords and other sensitive data. The slice
// returned does not include the \n.
func ReadPassword(fd int) ([]byte, error) {
var oldState syscall.Termios
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); err != 0 {
return nil, err
}
newState := oldState
newState.Lflag &^= syscall.ECHO
newState.Lflag |= syscall.ICANON | syscall.ISIG
newState.Iflag |= syscall.ICRNL
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
return nil, err
}
defer func() {
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0)
}()
var buf [16]byte
var ret []byte
for {
n, err := syscall.Read(fd, buf[:])
if err != nil {
return nil, err
}
if n == 0 {
if len(ret) == 0 {
return nil, io.EOF
}
break
}
if buf[n-1] == '\n' {
n--
}
ret = append(ret, buf[:n]...)
if n < len(buf) {
break
}
}
return ret, nil
}

View file

@ -1,12 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin
package terminal
import "syscall"
const ioctlReadTermios = syscall.TIOCGETA
const ioctlWriteTermios = syscall.TIOCSETA

View file

@ -1,12 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package terminal
import "syscall"
const ioctlReadTermios = syscall.TCGETS
const ioctlWriteTermios = syscall.TCSETS

View file

@ -1,7 +0,0 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This package contains integration tests for the
// code.google.com/p/go.crypto/ssh package.
package test

View file

@ -1,160 +0,0 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin freebsd linux netbsd openbsd plan9
package test
import (
"bytes"
"io"
"io/ioutil"
"math/rand"
"net"
"testing"
"time"
)
func TestPortForward(t *testing.T) {
server := newServer(t)
defer server.Shutdown()
conn := server.Dial(clientConfig())
defer conn.Close()
sshListener, err := conn.Listen("tcp", "localhost:0")
if err != nil {
t.Fatal(err)
}
go func() {
sshConn, err := sshListener.Accept()
if err != nil {
t.Fatalf("listen.Accept failed: %v", err)
}
_, err = io.Copy(sshConn, sshConn)
if err != nil && err != io.EOF {
t.Fatalf("ssh client copy: %v", err)
}
sshConn.Close()
}()
forwardedAddr := sshListener.Addr().String()
tcpConn, err := net.Dial("tcp", forwardedAddr)
if err != nil {
t.Fatalf("TCP dial failed: %v", err)
}
readChan := make(chan []byte)
go func() {
data, _ := ioutil.ReadAll(tcpConn)
readChan <- data
}()
// Invent some data.
data := make([]byte, 100*1000)
for i := range data {
data[i] = byte(i % 255)
}
var sent []byte
for len(sent) < 1000*1000 {
// Send random sized chunks
m := rand.Intn(len(data))
n, err := tcpConn.Write(data[:m])
if err != nil {
break
}
sent = append(sent, data[:n]...)
}
if err := tcpConn.(*net.TCPConn).CloseWrite(); err != nil {
t.Errorf("tcpConn.CloseWrite: %v", err)
}
read := <-readChan
if len(sent) != len(read) {
t.Fatalf("got %d bytes, want %d", len(read), len(sent))
}
if bytes.Compare(sent, read) != 0 {
t.Fatalf("read back data does not match")
}
if err := sshListener.Close(); err != nil {
t.Fatalf("sshListener.Close: %v", err)
}
// Check that the forward disappeared.
tcpConn, err = net.Dial("tcp", forwardedAddr)
if err == nil {
tcpConn.Close()
t.Errorf("still listening to %s after closing", forwardedAddr)
}
}
func TestAcceptClose(t *testing.T) {
server := newServer(t)
defer server.Shutdown()
conn := server.Dial(clientConfig())
sshListener, err := conn.Listen("tcp", "localhost:0")
if err != nil {
t.Fatal(err)
}
quit := make(chan error, 1)
go func() {
for {
c, err := sshListener.Accept()
if err != nil {
quit <- err
break
}
c.Close()
}
}()
sshListener.Close()
select {
case <-time.After(1 * time.Second):
t.Errorf("timeout: listener did not close.")
case err := <-quit:
t.Logf("quit as expected (error %v)", err)
}
}
// Check that listeners exit if the underlying client transport dies.
func TestPortForwardConnectionClose(t *testing.T) {
server := newServer(t)
defer server.Shutdown()
conn := server.Dial(clientConfig())
sshListener, err := conn.Listen("tcp", "localhost:0")
if err != nil {
t.Fatal(err)
}
quit := make(chan error, 1)
go func() {
for {
c, err := sshListener.Accept()
if err != nil {
quit <- err
break
}
c.Close()
}
}()
// It would be even nicer if we closed the server side, but it
// is more involved as the fd for that side is dup()ed.
server.clientConn.Close()
select {
case <-time.After(1 * time.Second):
t.Errorf("timeout: listener did not close.")
case err := <-quit:
t.Logf("quit as expected (error %v)", err)
}
}

View file

@ -1,246 +0,0 @@
package test
import (
"reflect"
"strings"
"testing"
"code.google.com/p/go.crypto/ssh"
)
var (
validKey = `AAAAB3NzaC1yc2EAAAADAQABAAABAQDEX/dPu4PmtvgK3La9zioCEDrJ` +
`yUr6xEIK7Pr+rLgydcqWTU/kt7w7gKjOw4vvzgHfjKl09CWyvgb+y5dCiTk` +
`9MxI+erGNhs3pwaoS+EavAbawB7iEqYyTep3YaJK+4RJ4OX7ZlXMAIMrTL+` +
`UVrK89t56hCkFYaAgo3VY+z6rb/b3bDBYtE1Y2tS7C3au73aDgeb9psIrSV` +
`86ucKBTl5X62FnYiyGd++xCnLB6uLximM5OKXfLzJQNS/QyZyk12g3D8y69` +
`Xw1GzCSKX1u1+MQboyf0HJcG2ryUCLHdcDVppApyHx2OLq53hlkQ/yxdflD` +
`qCqAE4j+doagSsIfC1T2T`
authWithOptions = []string{
`# comments to ignore before any keys...`,
``,
`env="HOME=/home/root",no-port-forwarding ssh-rsa ` + validKey + ` user@host`,
`# comments to ignore, along with a blank line`,
``,
`env="HOME=/home/root2" ssh-rsa ` + validKey + ` user2@host2`,
``,
`# more comments, plus a invalid entry`,
`ssh-rsa data-that-will-not-parse user@host3`,
}
authOptions = strings.Join(authWithOptions, "\n")
authWithCRLF = strings.Join(authWithOptions, "\r\n")
authInvalid = []byte(`ssh-rsa`)
authWithQuotedCommaInEnv = []byte(`env="HOME=/home/root,dir",no-port-forwarding ssh-rsa ` + validKey + ` user@host`)
authWithQuotedSpaceInEnv = []byte(`env="HOME=/home/root dir",no-port-forwarding ssh-rsa ` + validKey + ` user@host`)
authWithQuotedQuoteInEnv = []byte(`env="HOME=/home/\"root dir",no-port-forwarding` + "\t" + `ssh-rsa` + "\t" + validKey + ` user@host`)
authWithDoubleQuotedQuote = []byte(`no-port-forwarding,env="HOME=/home/ \"root dir\"" ssh-rsa ` + validKey + "\t" + `user@host`)
authWithInvalidSpace = []byte(`env="HOME=/home/root dir", no-port-forwarding ssh-rsa ` + validKey + ` user@host
#more to follow but still no valid keys`)
authWithMissingQuote = []byte(`env="HOME=/home/root,no-port-forwarding ssh-rsa ` + validKey + ` user@host
env="HOME=/home/root",shared-control ssh-rsa ` + validKey + ` user@host`)
testClientPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAxF/3T7uD5rb4Cty2vc4qAhA6yclK+sRCCuz6/qy4MnXKlk1P
5Le8O4CozsOL784B34ypdPQlsr4G/suXQok5PTMSPnqxjYbN6cGqEvhGrwG2sAe4
hKmMk3qd2GiSvuESeDl+2ZVzACDK0y/lFayvPbeeoQpBWGgIKN1WPs+q2/292wwW
LRNWNrUuwt2ru92g4Hm/abCK0lfOrnCgU5eV+thZ2IshnfvsQpyweri8YpjOTil3
y8yUDUv0MmcpNdoNw/MuvV8NRswkil9btfjEG6Mn9ByXBtq8lAix3XA1aaQKch8d
ji6ud4ZZEP8sXX5Q6gqgBOI/naGoErCHwtU9kwIDAQABAoIBAFJRKAp0QEZmTHPB
MZk+4r0asIoFpziXLFgIHu7C2DPOzK1Umzj1DCKlPB3wOqi7Ym2jOSWdcnAK2EPW
dAGgJC5TSkKGjAcXixmB5RkumfKidUI0+lQh/puTurcMnvcEwglDkLkEvMBA/sSo
Pw9m486rOgOnmNzGPyViItURmD2+0yDdLl/vOsO/L1p76GCd0q0J3LqnmsQmawi7
Zwj2Stm6BIrggG5GsF204Iet5219TYLo4g1Qb2AlJ9C8P1FtAWhMwJalDxH9Os2/
KCDjnaq5n3bXbIU+3QjskjeVXL/Fnbhjnh4zs1EA7eHzl9dCGbcZ2LOimo2PRo8q
wVQmz4ECgYEA9dhiu74TxRVoaO5N2X+FsMzRO8gZdP3Z9IrV4jVN8WT4Vdp0snoF
gkVkqqbQUNKUb5K6B3Js/qNKfcjLbCNq9fewTcT6WsHQdtPbX/QA6Pa2Z29wrlA2
wrIYaAkmVaHny7wsOmgX01aOnuf2MlUnksK43sjZHdIo/m+sDKwwY1cCgYEAzHx4
mwUDMdRF4qpDKJhthraBNejRextNQQYsHVnNaMwZ4aeQcH5l85Cgjm7VpGlbVyBQ
h4zwFvllImp3D2U3mjVkV8Tm9ID98eWvw2YDzBnS3P3SysajD23Z+BXSG9GNv/8k
oAm+bVlvnJy4haK2AcIMk1YFuDuAOmy73abk7iUCgYEAj4qVM1sq/eKfAM1LJRfg
/jbIX+hYfMePD8pUUWygIra6jJ4tjtvSBZrwyPb3IImjY3W/KoP0AcVjxAeORohz
dkP1a6L8LiuFxSuzpdW5BkyuebxGhXCOWKVVvMDC4jLTPVCUXlHSv3GFemCjjgXM
QlNxT5rjsha4Gr8nLIsJAacCgYA4VA1Q/pd7sXKy1p37X8nD8yAyvnh+Be5I/C9I
woUP2jFC9MqYAmmJJ4ziz2swiAkuPeuQ+2Tjnz2ZtmQnrIUdiJmkh8vrDGFnshKx
q7deELsCPzVCwGcIiAUkDra7DQWUHu9y2lxHePyC0rUNst2aLF8UcvzOXC2danhx
vViQtQKBgCmZ7YavE/GNWww8N3xHBJ6UPmUuhQlnAbgNCcdyz30MevBg/JbyUTs2
slftTH15QusJ1UoITnnZuFJ40LqDvh8UhiK09ffM/IbUx839/m2vUOdFZB/WNn9g
Cy0LzddU4KE8JZ/tlk68+hM5fjLLA0aqSunaql5CKfplwLu8x1hL
-----END RSA PRIVATE KEY-----
`
keys = map[string]string{
"ssh_host_dsa_key": `-----BEGIN DSA PRIVATE KEY-----
MIIBugIBAAKBgQDe2SIKvZdBp+InawtSXH0NotiMPhm3udyu4hh/E+icMz264kDX
v+sV7ddnSQGQWZ/eVU7Jtx29dCMD1VlFpEd7yGKzmdwJIeA+YquNWoqBRQEJsWWS
7Fsfvv83dA/DTNIQfOY3+TIs6Mb9vagbgQMU3JUWEhbLE9LCEU6UwwRlpQIVAL4p
JF83SwpE8Jx6KnDpR89npkl/AoGAAy00TdDnAXvStwrZiAFbjZi8xDmPa9WwpfhJ
Rkno45TthDLrS+WmqY8/LTwlqZdOBtoBAynMJfKkUiZM21lWWpL1hRKYdwBlIBy5
XdR2/6wcPSuZ0tCQhDBTstX0Q3P1j198KGKvzy7q9vILKQwtSRqLS1y4JJERafdO
E+9CnGwCgYBz0WwBe2EZtGhGhBdnelTIBeo7PIsr0PzqxQj+dc8PBl8K9FfhRyOp
U39stUvoUxE9vaIFrY1P5xENjLFnPf+hlcuf40GUWEssW9YWPOaBp8afa9hY5Sxs
pvNR6eZFEFOJnx/ZgcA4g+vbrgGi5cM0W470mbGw2CkfJQUafdoIgAIUF+2I9kZe
2FTBuC9uacqczDlc+0k=
-----END DSA PRIVATE KEY-----`,
"ssh_host_rsa_key": `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAuf76Ue2Wtae9oDtaS6rIJgO7iCFTsZUTW9LBsvx/2nli6jKU
d9tUbBRzgdbnRLJ32UljXhERuB/axlrX8/lBzUZ+oYiM0KkEEOXY1z/bcMxdRxGF
XHuf4uXvyC2XyA4+ZvBeS4j1QFyIHZ62o7gAlKMTjiek3B4AQEJAlCLmhH3jB8wc
K/IYXAOlNGM5G44/ZLQpTi8diOV6DLs7tJ7rtEQedOEJfZng5rwp0USFkqcbfDbe
9/hk0J32jZvOtZNBokYtBb4YEdIiWBzzNtHzU3Dzw61+TKVXaH5HaIvzL9iMrw9f
kJbJyogfZk9BJfemEN+xqP72jlhE8LXNhpTxFQIDAQABAoIBAHbdf+Y5+5XuNF6h
b8xpwW2h9whBnDYiOnP1VfroKWFbMB7R4lZS4joMO+FfkP8zOyqvHwTvza4pFWys
g9SUmDvy8FyVYsC7MzEFYzX0xm3o/Te898ip7P1Zy4rXsGeWysSImwqU5X+TYx3i
33/zyNM1APtZVJ+jwK9QZ+sD/uPuZK2yS03HGSMZq6ebdoOSaYhluKrxXllSLO1J
KJxDiDdy2lEFw0W8HcI3ly1lg6OI+TRqqaCcLVNF4fNJmYIFM+2VEI9BdgynIh0Q
pMZlJKgaEBcSqCymnTK81ohYD1cV4st2B0km3Sw35Rl04Ij5ITeiya3hp8VfE6UY
PljkA6UCgYEA4811FTFj+kzNZ86C4OW1T5sM4NZt8gcz6CSvVnl+bDzbEOMMyzP7
2I9zKsR5ApdodH2m8d+RUw1Oe0bNGW5xig/DH/hn9lLQaO52JAi0we8A94dUUMSq
fUk9jKZEXpP/MlfTdJaPos9mxT7z8jREQxIiqH9AV0rLVDOCfDbSWj8CgYEA0QTE
IAUuki3UUqYKzLQrh/QmhY5KTx5amNW9XZ2VGtJvDPJrtBSBZlPEuXZAc4eBWEc7
U3Y9QwsalzupU6Yi6+gmofaXs8xJnj+jKth1DnJvrbLLGlSmf2Ijnwt22TyFUOtt
UAknpjHutDjQPf7pUGWaCPgwwKFsdB8EBjpJF6sCgYAfXesBQAvEK08dPBJJZVfR
3kenrd71tIgxLtv1zETcIoUHjjv0vvOunhH9kZAYC0EWyTZzl5UrGmn0D4uuNMbt
e74iaNHn2P9Zc3xQ+eHp0j8P1lKFzI6tMaiH9Vz0qOw6wl0bcJ/WizhbcI+migvc
MGMVUHBLlMDqly0gbWwJgQKBgQCgtb9ut01FjANSwORQ3L8Tu3/a9Lrh9n7GQKFn
V4CLrP1BwStavOF5ojMCPo/zxF6JV8ufsqwL3n/FhFP/QyBarpb1tTqTPiHkkR2O
Ffx67TY9IdnUFv4lt3mYEiKBiW0f+MSF42Qe/wmAfKZw5IzUCirTdrFVi0huSGK5
vxrwHQKBgHZ7RoC3I2f6F5fflA2ZAe9oJYC7XT624rY7VeOBwK0W0F47iV3euPi/
pKvLIBLcWL1Lboo+girnmSZtIYg2iLS3b4T9VFcKWg0y4AVwmhMWe9jWIltfWAAX
9l0lNikMRGAx3eXudKXEtbGt3/cUzPVaQUHy5LiBxkxnFxgaJPXs
-----END RSA PRIVATE KEY-----`,
"ssh_host_ecdsa_key": `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEINGWx0zo6fhJ/0EAfrPzVFyFC9s18lBt3cRoEDhS3ARooAoGCCqGSM49
AwEHoUQDQgAEi9Hdw6KvZcWxfg2IDhA7UkpDtzzt6ZqJXSsFdLd+Kx4S3Sx4cVO+
6/ZOXRnPmNAlLUqjShUsUBBngG0u2fqEqA==
-----END EC PRIVATE KEY-----`,
"authorized_keys": `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDEX/dPu4PmtvgK3La9zioCEDrJyUr6xEIK7Pr+rLgydcqWTU/kt7w7gKjOw4vvzgHfjKl09CWyvgb+y5dCiTk9MxI+erGNhs3pwaoS+EavAbawB7iEqYyTep3YaJK+4RJ4OX7ZlXMAIMrTL+UVrK89t56hCkFYaAgo3VY+z6rb/b3bDBYtE1Y2tS7C3au73aDgeb9psIrSV86ucKBTl5X62FnYiyGd++xCnLB6uLximM5OKXfLzJQNS/QyZyk12g3D8y69Xw1GzCSKX1u1+MQboyf0HJcG2ryUCLHdcDVppApyHx2OLq53hlkQ/yxdflDqCqAE4j+doagSsIfC1T2T user@host`,
}
)
func TestMarshalParsePublicKey(t *testing.T) {
pub := getTestPublicKey(t)
authKeys := ssh.MarshalAuthorizedKey(pub)
actualFields := strings.Fields(string(authKeys))
if len(actualFields) == 0 {
t.Fatalf("failed authKeys: %v", authKeys)
}
// drop the comment
expectedFields := strings.Fields(keys["authorized_keys"])[0:2]
if !reflect.DeepEqual(actualFields, expectedFields) {
t.Errorf("got %v, expected %v", actualFields, expectedFields)
}
actPub, _, _, _, ok := ssh.ParseAuthorizedKey([]byte(keys["authorized_keys"]))
if !ok {
t.Fatalf("cannot parse %v", keys["authorized_keys"])
}
if !reflect.DeepEqual(actPub, pub) {
t.Errorf("got %v, expected %v", actPub, pub)
}
}
type authResult struct {
pubKey interface{} //*rsa.PublicKey
options []string
comments string
rest string
ok bool
}
func testAuthorizedKeys(t *testing.T, authKeys []byte, expected []authResult) {
rest := authKeys
var values []authResult
for len(rest) > 0 {
var r authResult
r.pubKey, r.comments, r.options, rest, r.ok = ssh.ParseAuthorizedKey(rest)
r.rest = string(rest)
values = append(values, r)
}
if !reflect.DeepEqual(values, expected) {
t.Errorf("got %q, expected %q", values, expected)
}
}
func getTestPublicKey(t *testing.T) ssh.PublicKey {
priv, err := ssh.ParsePrivateKey([]byte(testClientPrivateKey))
if err != nil {
t.Fatalf("ParsePrivateKey: %v", err)
}
return priv.PublicKey()
}
func TestAuth(t *testing.T) {
pub := getTestPublicKey(t)
rest2 := strings.Join(authWithOptions[3:], "\n")
rest3 := strings.Join(authWithOptions[6:], "\n")
testAuthorizedKeys(t, []byte(authOptions), []authResult{
{pub, []string{`env="HOME=/home/root"`, "no-port-forwarding"}, "user@host", rest2, true},
{pub, []string{`env="HOME=/home/root2"`}, "user2@host2", rest3, true},
{nil, nil, "", "", false},
})
}
func TestAuthWithCRLF(t *testing.T) {
pub := getTestPublicKey(t)
rest2 := strings.Join(authWithOptions[3:], "\r\n")
rest3 := strings.Join(authWithOptions[6:], "\r\n")
testAuthorizedKeys(t, []byte(authWithCRLF), []authResult{
{pub, []string{`env="HOME=/home/root"`, "no-port-forwarding"}, "user@host", rest2, true},
{pub, []string{`env="HOME=/home/root2"`}, "user2@host2", rest3, true},
{nil, nil, "", "", false},
})
}
func TestAuthWithQuotedSpaceInEnv(t *testing.T) {
pub := getTestPublicKey(t)
testAuthorizedKeys(t, []byte(authWithQuotedSpaceInEnv), []authResult{
{pub, []string{`env="HOME=/home/root dir"`, "no-port-forwarding"}, "user@host", "", true},
})
}
func TestAuthWithQuotedCommaInEnv(t *testing.T) {
pub := getTestPublicKey(t)
testAuthorizedKeys(t, []byte(authWithQuotedCommaInEnv), []authResult{
{pub, []string{`env="HOME=/home/root,dir"`, "no-port-forwarding"}, "user@host", "", true},
})
}
func TestAuthWithQuotedQuoteInEnv(t *testing.T) {
pub := getTestPublicKey(t)
testAuthorizedKeys(t, []byte(authWithQuotedQuoteInEnv), []authResult{
{pub, []string{`env="HOME=/home/\"root dir"`, "no-port-forwarding"}, "user@host", "", true},
})
testAuthorizedKeys(t, []byte(authWithDoubleQuotedQuote), []authResult{
{pub, []string{"no-port-forwarding", `env="HOME=/home/ \"root dir\""`}, "user@host", "", true},
})
}
func TestAuthWithInvalidSpace(t *testing.T) {
testAuthorizedKeys(t, []byte(authWithInvalidSpace), []authResult{
{nil, nil, "", "", false},
})
}
func TestAuthWithMissingQuote(t *testing.T) {
pub := getTestPublicKey(t)
testAuthorizedKeys(t, []byte(authWithMissingQuote), []authResult{
{pub, []string{`env="HOME=/home/root"`, `shared-control`}, "user@host", "", true},
})
}
func TestInvalidEntry(t *testing.T) {
_, _, _, _, ok := ssh.ParseAuthorizedKey(authInvalid)
if ok {
t.Errorf("Expected invalid entry, returned valid entry")
}
}

View file

@ -1,185 +0,0 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !windows
package test
// Session functional tests.
import (
"bytes"
"code.google.com/p/go.crypto/ssh"
"io"
"strings"
"testing"
)
func TestRunCommandSuccess(t *testing.T) {
server := newServer(t)
defer server.Shutdown()
conn := server.Dial(clientConfig())
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("session failed: %v", err)
}
defer session.Close()
err = session.Run("true")
if err != nil {
t.Fatalf("session failed: %v", err)
}
}
func TestHostKeyCheck(t *testing.T) {
server := newServer(t)
defer server.Shutdown()
conf := clientConfig()
k := conf.HostKeyChecker.(*storedHostKey)
// change the keys.
k.keys[ssh.KeyAlgoRSA][25]++
k.keys[ssh.KeyAlgoDSA][25]++
k.keys[ssh.KeyAlgoECDSA256][25]++
conn, err := server.TryDial(conf)
if err == nil {
conn.Close()
t.Fatalf("dial should have failed.")
} else if !strings.Contains(err.Error(), "host key mismatch") {
t.Fatalf("'host key mismatch' not found in %v", err)
}
}
func TestRunCommandFailed(t *testing.T) {
server := newServer(t)
defer server.Shutdown()
conn := server.Dial(clientConfig())
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("session failed: %v", err)
}
defer session.Close()
err = session.Run(`bash -c "kill -9 $$"`)
if err == nil {
t.Fatalf("session succeeded: %v", err)
}
}
func TestRunCommandWeClosed(t *testing.T) {
server := newServer(t)
defer server.Shutdown()
conn := server.Dial(clientConfig())
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("session failed: %v", err)
}
err = session.Shell()
if err != nil {
t.Fatalf("shell failed: %v", err)
}
err = session.Close()
if err != nil {
t.Fatalf("shell failed: %v", err)
}
}
func TestFuncLargeRead(t *testing.T) {
server := newServer(t)
defer server.Shutdown()
conn := server.Dial(clientConfig())
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("unable to create new session: %s", err)
}
stdout, err := session.StdoutPipe()
if err != nil {
t.Fatalf("unable to acquire stdout pipe: %s", err)
}
err = session.Start("dd if=/dev/urandom bs=2048 count=1")
if err != nil {
t.Fatalf("unable to execute remote command: %s", err)
}
buf := new(bytes.Buffer)
n, err := io.Copy(buf, stdout)
if err != nil {
t.Fatalf("error reading from remote stdout: %s", err)
}
if n != 2048 {
t.Fatalf("Expected %d bytes but read only %d from remote command", 2048, n)
}
}
func TestInvalidTerminalMode(t *testing.T) {
server := newServer(t)
defer server.Shutdown()
conn := server.Dial(clientConfig())
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("session failed: %v", err)
}
defer session.Close()
if err = session.RequestPty("vt100", 80, 40, ssh.TerminalModes{255: 1984}); err == nil {
t.Fatalf("req-pty failed: successful request with invalid mode")
}
}
func TestValidTerminalMode(t *testing.T) {
server := newServer(t)
defer server.Shutdown()
conn := server.Dial(clientConfig())
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("session failed: %v", err)
}
defer session.Close()
stdout, err := session.StdoutPipe()
if err != nil {
t.Fatalf("unable to acquire stdout pipe: %s", err)
}
stdin, err := session.StdinPipe()
if err != nil {
t.Fatalf("unable to acquire stdin pipe: %s", err)
}
tm := ssh.TerminalModes{ssh.ECHO: 0}
if err = session.RequestPty("xterm", 80, 40, tm); err != nil {
t.Fatalf("req-pty failed: %s", err)
}
err = session.Shell()
if err != nil {
t.Fatalf("session failed: %s", err)
}
stdin.Write([]byte("stty -a && exit\n"))
var buf bytes.Buffer
if _, err := io.Copy(&buf, stdout); err != nil {
t.Fatalf("reading failed: %s", err)
}
if sttyOutput := buf.String(); !strings.Contains(sttyOutput, "-echo ") {
t.Fatalf("terminal mode failure: expected -echo in stty output, got %s", sttyOutput)
}
}

View file

@ -1,47 +0,0 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !windows
package test
// direct-tcpip functional tests
import (
"net"
"net/http"
"testing"
)
func TestTCPIPHTTP(t *testing.T) {
// google.com will generate at least one redirect, possibly three
// depending on your location.
doTest(t, "http://google.com")
}
func TestTCPIPHTTPS(t *testing.T) {
doTest(t, "https://encrypted.google.com/")
}
func doTest(t *testing.T, url string) {
server := newServer(t)
defer server.Shutdown()
conn := server.Dial(clientConfig())
defer conn.Close()
tr := &http.Transport{
Dial: func(n, addr string) (net.Conn, error) {
return conn.Dial(n, addr)
},
}
client := &http.Client{
Transport: tr,
}
resp, err := client.Get(url)
if err != nil {
t.Fatalf("unable to proxy: %s", err)
}
// got a body without error
t.Log(resp)
}

View file

@ -1,296 +0,0 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin freebsd linux netbsd openbsd plan9
package test
// functional test harness for unix.
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"os"
"os/exec"
"os/user"
"path/filepath"
"testing"
"text/template"
"code.google.com/p/go.crypto/ssh"
)
const sshd_config = `
Protocol 2
HostKey {{.Dir}}/ssh_host_rsa_key
HostKey {{.Dir}}/ssh_host_dsa_key
HostKey {{.Dir}}/ssh_host_ecdsa_key
Pidfile {{.Dir}}/sshd.pid
#UsePrivilegeSeparation no
KeyRegenerationInterval 3600
ServerKeyBits 768
SyslogFacility AUTH
LogLevel DEBUG2
LoginGraceTime 120
PermitRootLogin no
StrictModes no
RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile {{.Dir}}/authorized_keys
IgnoreRhosts yes
RhostsRSAAuthentication no
HostbasedAuthentication no
`
var (
configTmpl template.Template
privateKey ssh.Signer
hostKeyRSA ssh.Signer
hostKeyECDSA ssh.Signer
hostKeyDSA ssh.Signer
)
func init() {
template.Must(configTmpl.Parse(sshd_config))
for n, k := range map[string]*ssh.Signer{
"ssh_host_ecdsa_key": &hostKeyECDSA,
"ssh_host_rsa_key": &hostKeyRSA,
"ssh_host_dsa_key": &hostKeyDSA,
} {
var err error
*k, err = ssh.ParsePrivateKey([]byte(keys[n]))
if err != nil {
panic(fmt.Sprintf("ParsePrivateKey(%q): %v", n, err))
}
}
var err error
privateKey, err = ssh.ParsePrivateKey([]byte(testClientPrivateKey))
if err != nil {
panic(fmt.Sprintf("ParsePrivateKey: %v", err))
}
}
type server struct {
t *testing.T
cleanup func() // executed during Shutdown
configfile string
cmd *exec.Cmd
output bytes.Buffer // holds stderr from sshd process
// Client half of the network connection.
clientConn net.Conn
}
func username() string {
var username string
if user, err := user.Current(); err == nil {
username = user.Username
} else {
// user.Current() currently requires cgo. If an error is
// returned attempt to get the username from the environment.
log.Printf("user.Current: %v; falling back on $USER", err)
username = os.Getenv("USER")
}
if username == "" {
panic("Unable to get username")
}
return username
}
type storedHostKey struct {
// keys map from an algorithm string to binary key data.
keys map[string][]byte
}
func (k *storedHostKey) Add(key ssh.PublicKey) {
if k.keys == nil {
k.keys = map[string][]byte{}
}
k.keys[key.PublicKeyAlgo()] = ssh.MarshalPublicKey(key)
}
func (k *storedHostKey) Check(addr string, remote net.Addr, algo string, key []byte) error {
if k.keys == nil || bytes.Compare(key, k.keys[algo]) != 0 {
return fmt.Errorf("host key mismatch. Got %q, want %q", key, k.keys[algo])
}
return nil
}
func clientConfig() *ssh.ClientConfig {
keyChecker := storedHostKey{}
keyChecker.Add(hostKeyECDSA.PublicKey())
keyChecker.Add(hostKeyRSA.PublicKey())
keyChecker.Add(hostKeyDSA.PublicKey())
kc := new(keychain)
kc.keys = append(kc.keys, privateKey)
config := &ssh.ClientConfig{
User: username(),
Auth: []ssh.ClientAuth{
ssh.ClientAuthKeyring(kc),
},
HostKeyChecker: &keyChecker,
}
return config
}
// unixConnection creates two halves of a connected net.UnixConn. It
// is used for connecting the Go SSH client with sshd without opening
// ports.
func unixConnection() (*net.UnixConn, *net.UnixConn, error) {
dir, err := ioutil.TempDir("", "unixConnection")
if err != nil {
return nil, nil, err
}
defer os.Remove(dir)
addr := filepath.Join(dir, "ssh")
listener, err := net.Listen("unix", addr)
if err != nil {
return nil, nil, err
}
defer listener.Close()
c1, err := net.Dial("unix", addr)
if err != nil {
return nil, nil, err
}
c2, err := listener.Accept()
if err != nil {
c1.Close()
return nil, nil, err
}
return c1.(*net.UnixConn), c2.(*net.UnixConn), nil
}
func (s *server) TryDial(config *ssh.ClientConfig) (*ssh.ClientConn, error) {
sshd, err := exec.LookPath("sshd")
if err != nil {
s.t.Skipf("skipping test: %v", err)
}
c1, c2, err := unixConnection()
if err != nil {
s.t.Fatalf("unixConnection: %v", err)
}
s.cmd = exec.Command(sshd, "-f", s.configfile, "-i", "-e")
f, err := c2.File()
if err != nil {
s.t.Fatalf("UnixConn.File: %v", err)
}
defer f.Close()
s.cmd.Stdin = f
s.cmd.Stdout = f
s.cmd.Stderr = &s.output
if err := s.cmd.Start(); err != nil {
s.t.Fail()
s.Shutdown()
s.t.Fatalf("s.cmd.Start: %v", err)
}
s.clientConn = c1
return ssh.Client(c1, config)
}
func (s *server) Dial(config *ssh.ClientConfig) *ssh.ClientConn {
conn, err := s.TryDial(config)
if err != nil {
s.t.Fail()
s.Shutdown()
s.t.Fatalf("ssh.Client: %v", err)
}
return conn
}
func (s *server) Shutdown() {
if s.cmd != nil && s.cmd.Process != nil {
// Don't check for errors; if it fails it's most
// likely "os: process already finished", and we don't
// care about that. Use os.Interrupt, so child
// processes are killed too.
s.cmd.Process.Signal(os.Interrupt)
s.cmd.Wait()
}
if s.t.Failed() {
// log any output from sshd process
s.t.Logf("sshd: %s", s.output.String())
}
s.cleanup()
}
// newServer returns a new mock ssh server.
func newServer(t *testing.T) *server {
dir, err := ioutil.TempDir("", "sshtest")
if err != nil {
t.Fatal(err)
}
f, err := os.Create(filepath.Join(dir, "sshd_config"))
if err != nil {
t.Fatal(err)
}
err = configTmpl.Execute(f, map[string]string{
"Dir": dir,
})
if err != nil {
t.Fatal(err)
}
f.Close()
for k, v := range keys {
f, err := os.OpenFile(filepath.Join(dir, k), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
if err != nil {
t.Fatal(err)
}
if _, err := f.Write([]byte(v)); err != nil {
t.Fatal(err)
}
f.Close()
}
return &server{
t: t,
configfile: f.Name(),
cleanup: func() {
if err := os.RemoveAll(dir); err != nil {
t.Error(err)
}
},
}
}
// keychain implements the ClientKeyring interface.
type keychain struct {
keys []ssh.Signer
}
func (k *keychain) Key(i int) (ssh.PublicKey, error) {
if i < 0 || i >= len(k.keys) {
return nil, nil
}
return k.keys[i].PublicKey(), nil
}
func (k *keychain) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) {
return k.keys[i].Sign(rand, data)
}
func (k *keychain) loadPEM(file string) error {
buf, err := ioutil.ReadFile(file)
if err != nil {
return err
}
key, err := ssh.ParsePrivateKey(buf)
if err != nil {
return err
}
k.keys = append(k.keys, key)
return nil
}

View file

@ -1,69 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssh
import (
"bytes"
"strings"
"testing"
)
func TestReadVersion(t *testing.T) {
longversion := strings.Repeat("SSH-2.0-bla", 50)[:253]
cases := map[string]string{
"SSH-2.0-bla\r\n": "SSH-2.0-bla",
"SSH-2.0-bla\n": "SSH-2.0-bla",
longversion + "\r\n": longversion,
}
for in, want := range cases {
result, err := readVersion(bytes.NewBufferString(in))
if err != nil {
t.Errorf("readVersion(%q): %s", in, err)
}
got := string(result)
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
}
func TestReadVersionError(t *testing.T) {
longversion := strings.Repeat("SSH-2.0-bla", 50)[:253]
cases := []string{
longversion + "too-long\r\n",
}
for _, in := range cases {
if _, err := readVersion(bytes.NewBufferString(in)); err == nil {
t.Errorf("readVersion(%q) should have failed", in)
}
}
}
func TestExchangeVersionsBasic(t *testing.T) {
v := "SSH-2.0-bla"
buf := bytes.NewBufferString(v + "\r\n")
them, err := exchangeVersions(buf, []byte("xyz"))
if err != nil {
t.Errorf("exchangeVersions: %v", err)
}
if want := "SSH-2.0-bla"; string(them) != want {
t.Errorf("got %q want %q for our version", them, want)
}
}
func TestExchangeVersions(t *testing.T) {
cases := []string{
"not\x000allowed",
"not allowed\n",
}
for _, c := range cases {
buf := bytes.NewBufferString("SSH-2.0-bla\r\n")
if _, err := exchangeVersions(buf, []byte(c)); err == nil {
t.Errorf("exchangeVersions(%q): should have failed", c)
}
}
}

View file

@ -1,8 +0,0 @@
all:deps test
deps:
go get github.com/stretchr/testify
go get ./...
test:
go test -cover -short ./...

View file

@ -1,81 +0,0 @@
go-gitlab-client
================
This is a fork of project https://github.com/plouc/go-gitlab-client
go-gitlab-client is a simple client written in golang to consume gitlab API.
[![Build Status](https://travis-ci.org/Bugagazavr/go-gitlab-client.svg?branch=master)](https://travis-ci.org/Bugagazavr/go-gitlab-client)
##features
*
###Session [gitlab api doc](http://doc.gitlab.com/ce/api/session.html)
* get session
*
###Projects [gitlab api doc](http://doc.gitlab.com/ce/api/projects.html)
* list projects
* get single project
* list project merge requests
* list notes on merge requests
* add comments to merge requests
*
###Repositories [gitlab api doc](http://doc.gitlab.com/ce/api/repositories.html)
* list repository branches
* get single repository branch
* list project repository tags
* list repository commits
* list project hooks
* add/get/edit/rm project hook
*
###Users [gitlab api doc](http://doc.gitlab.com/ce/api/users.html)
* get single user
* manage user keys
*
###Deploy Keys [gitlab api doc](http://doc.gitlab.com/ce/api/deploy_keys.html)
* list project deploy keys
* add/get/rm project deploy key
##Installation
To install go-gitlab-client, use `go get`:
go get github.com/bugagazavr/go-gitlab-client
Import the `go-gitlab-client` package into your code:
```go
package whatever
import (
"github.com/bugagazavr/go-gitlab-client"
)
```
##Update
To update `go-gitlab-client`, use `go get -u`:
go get -u github.com/bugagazavr/go-gitlab-client
##Documentation
Visit the docs at http://godoc.org/github.com/Bugagazavr/go-gitlab-client
## Examples
You can play with the examples located in the `examples` directory
* [projects](https://github.com/Bugagazavr/go-gitlab-client/tree/master/examples/projects)
* [repositories](https://github.com/Bugagazavr/go-gitlab-client/tree/master/examples/repositories)

View file

@ -1,118 +0,0 @@
package gogitlab
import (
"encoding/json"
"net/url"
)
const (
// ID
project_url_deploy_keys = "/projects/:id/keys" // Get list of project deploy keys
// PROJECT ID AND KEY ID
project_url_deploy_key = "/projects/:id/keys/:key_id" // Get single project deploy key
)
/*
Get list of project deploy keys.
GET /projects/:id/keys
Parameters:
id The ID of a project
*/
func (g *Gitlab) ProjectDeployKeys(id string) ([]*PublicKey, error) {
url, opaque := g.ResourceUrlRaw(project_url_deploy_keys, map[string]string{":id": id})
var deployKeys []*PublicKey
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &deployKeys)
}
return deployKeys, err
}
/*
Get single project deploy key.
GET /projects/:id/keys/:key_id
Parameters:
id The ID of a project
key_id The ID of a key
*/
func (g *Gitlab) ProjectDeployKey(id, key_id string) (*PublicKey, error) {
url, opaque := g.ResourceUrlRaw(project_url_deploy_key, map[string]string{
":id": id,
":key_id": key_id,
})
var deployKey *PublicKey
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &deployKey)
}
return deployKey, err
}
/*
Add deploy key to project.
POST /projects/:id/keys
Parameters:
id The ID of a project
title The key title
key The key value
*/
func (g *Gitlab) AddProjectDeployKey(id, title, key string) error {
var err error
path, opaque := g.ResourceUrlRaw(project_url_deploy_keys, map[string]string{":id": id})
v := url.Values{}
v.Set("title", title)
v.Set("key", key)
body := v.Encode()
_, err = g.buildAndExecRequestRaw("POST", path, opaque, []byte(body))
return err
}
/*
Remove deploy key from project
DELETE /projects/:id/keys/:key_id
Parameters:
id The ID of a project
key_id The ID of a key
*/
func (g *Gitlab) RemoveProjectDeployKey(id, key_id string) error {
url, opaque := g.ResourceUrlRaw(project_url_deploy_key, map[string]string{
":id": id,
":key_id": key_id,
})
var err error
_, err = g.buildAndExecRequestRaw("DELETE", url, opaque, nil)
return err
}

View file

@ -1,72 +0,0 @@
package gogitlab
import (
"encoding/xml"
"fmt"
"time"
)
type Person struct {
Name string `xml:"name"json:"name"`
Email string `xml:"email"json:"email"`
}
type Link struct {
Rel string `xml:"rel,attr,omitempty"json:"rel"`
Href string `xml:"href,attr"json:"href"`
}
type ActivityFeed struct {
Title string `xml:"title"json:"title"`
Id string `xml:"id"json:"id"`
Link []Link `xml:"link"json:"link"`
Updated time.Time `xml:"updated,attr"json:"updated"`
Entries []*FeedCommit `xml:"entry"json:"entries"`
}
type FeedCommit struct {
Id string `xml:"id"json:"id"`
Title string `xml:"title"json:"title"`
Link []Link `xml:"link"json:"link"`
Updated time.Time `xml:"updated"json:"updated"`
Author Person `xml:"author"json:"author"`
Summary string `xml:"summary"json:"summary"`
//<media:thumbnail width="40" height="40" url="https://secure.gravatar.com/avatar/7070eab7c6206530d3b7820362227fec?s=40&amp;d=mm"/>
}
func (g *Gitlab) Activity() (ActivityFeed, error) {
url := g.BaseUrl + dasboard_feed_path + "?private_token=" + g.Token
fmt.Println(url)
contents, err := g.buildAndExecRequest("GET", url, nil)
if err != nil {
fmt.Println("%s", err)
}
var activity ActivityFeed
err = xml.Unmarshal(contents, &activity)
if err != nil {
fmt.Println("%s", err)
}
return activity, err
}
func (g *Gitlab) RepoActivityFeed(feedPath string) ActivityFeed {
url := g.BaseUrl + g.RepoFeedPath + "?private_token=" + g.Token
contents, err := g.buildAndExecRequest("GET", url, nil)
if err != nil {
fmt.Println("%s", err)
}
var activity ActivityFeed
err = xml.Unmarshal(contents, &activity)
if err != nil {
fmt.Println("%s", err)
}
return activity
}

View file

@ -1,5 +0,0 @@
{
"host": "https://gitlab.domain.com",
"api_path": "/api/v3",
"token": "TOKEN"
}

View file

@ -1,246 +0,0 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"github.com/bugagazavr/go-gitlab-client"
"io/ioutil"
"os"
"strconv"
"time"
)
type Config struct {
Host string `json:"host"`
ApiPath string `json:"api_path"`
Token string `json:"token"`
}
func main() {
help := flag.Bool("help", false, "Show usage")
file, e := ioutil.ReadFile("../config.json")
if e != nil {
fmt.Printf("Config file error: %v\n", e)
os.Exit(1)
}
var config Config
json.Unmarshal(file, &config)
fmt.Printf("Results: %+v\n", config)
var gitlab *gogitlab.Gitlab
gitlab = gogitlab.NewGitlab(config.Host, config.ApiPath, config.Token)
var method string
flag.StringVar(&method, "m", "", "Specify method to retrieve projects infos, available methods:\n"+
" > -m projects\n"+
" > -m project -id PROJECT_ID\n"+
" > -m hooks -id PROJECT_ID\n"+
" > -m branches -id PROJECT_ID\n"+
" > -m merge_requests -id PROJECT_ID\n"+
" > -m merge_request_notes -id PROJECT_ID -merge_id MERGE_REQUEST_ID\n"+
" > -m merge_request_comment -id PROJECT_ID -merge_id MERGE_REQUEST_ID -comment COMMENT_BODY\n"+
" > -m team -id PROJECT_ID")
var id string
flag.StringVar(&id, "id", "", "Specify repository id")
var merge_id string
flag.StringVar(&merge_id, "merge_id", "", "Specify merge request id")
var comment string
flag.StringVar(&comment, "comment", "", "The body of the new comment")
flag.Usage = func() {
fmt.Printf("Usage:\n")
flag.PrintDefaults()
}
flag.Parse()
if *help == true || method == "" {
flag.Usage()
return
}
startedAt := time.Now()
defer func() {
fmt.Printf("processed in %v\n", time.Now().Sub(startedAt))
}()
switch method {
case "projects":
fmt.Println("Fetching projects…")
projects, err := gitlab.Projects(1, 100)
if err != nil {
fmt.Println(err.Error())
return
}
for _, project := range projects {
fmt.Printf("> %6d | %s\n", project.Id, project.Name)
}
case "project":
fmt.Println("Fetching project…")
if id == "" {
flag.Usage()
return
}
project, err := gitlab.Project(id)
if err != nil {
fmt.Println(err.Error())
return
}
format := "> %-23s: %s\n"
fmt.Printf("%s\n", project.Name)
fmt.Printf(format, "id", strconv.Itoa(project.Id))
fmt.Printf(format, "name", project.Name)
fmt.Printf(format, "description", project.Description)
fmt.Printf(format, "default branch", project.DefaultBranch)
if project.Owner != nil {
fmt.Printf(format, "owner.name", project.Owner.Username)
}
fmt.Printf(format, "public", strconv.FormatBool(project.Public))
fmt.Printf(format, "path", project.Path)
fmt.Printf(format, "path with namespace", project.PathWithNamespace)
fmt.Printf(format, "issues enabled", strconv.FormatBool(project.IssuesEnabled))
fmt.Printf(format, "merge requests enabled", strconv.FormatBool(project.MergeRequestsEnabled))
fmt.Printf(format, "wall enabled", strconv.FormatBool(project.WallEnabled))
fmt.Printf(format, "wiki enabled", strconv.FormatBool(project.WikiEnabled))
fmt.Printf(format, "created at", project.CreatedAtRaw)
//fmt.Printf(format, "namespace", project.Namespace)
case "branches":
fmt.Println("Fetching project branches…")
if id == "" {
flag.Usage()
return
}
branches, err := gitlab.ProjectBranches(id)
if err != nil {
fmt.Println(err.Error())
return
}
for _, branch := range branches {
fmt.Printf("> %s\n", branch.Name)
}
case "merge_requests":
fmt.Println("Fetching project merge_requests…")
if id == "" {
flag.Usage()
return
}
mrs, err := gitlab.ProjectMergeRequests(id, 0, 30, "opened")
if err != nil {
fmt.Println(err.Error())
return
}
for _, mr := range mrs {
author := ""
if mr.Author != nil {
author = mr.Author.Username
}
assignee := ""
if mr.Assignee != nil {
assignee = mr.Assignee.Username
}
fmt.Printf(" (#%d) %s -> %s [%s] author[%s] assignee[%s]\n",
mr.Id, mr.SourceBranch, mr.TargetBranch, mr.State,
author, assignee)
}
case "merge_request_notes":
fmt.Println("Fetching merge_request notes…")
if id == "" {
flag.Usage()
return
}
notes, err := gitlab.MergeRequestNotes(id, merge_id, 0, 30)
if err != nil {
fmt.Println(err.Error())
return
}
for _, note := range notes {
author := ""
if note.Author != nil {
author = note.Author.Username
}
fmt.Printf(" [%d] author: %s <%s> %s\n",
note.Id, author, note.CreatedAt, note.Body)
}
case "merge_request_comment":
fmt.Println("Sending new merge_request comment…")
if id == "" {
flag.Usage()
return
}
note, err := gitlab.SendMergeRequestComment(id, merge_id, comment)
if err != nil {
fmt.Println(err.Error())
return
}
author := ""
if note.Author != nil {
author = note.Author.Username
}
fmt.Printf(" [%d] author: %s <%s> %s\n",
note.Id, author, note.CreatedAt, note.Body)
case "hooks":
fmt.Println("Fetching project hooks…")
if id == "" {
flag.Usage()
return
}
hooks, err := gitlab.ProjectHooks(id)
if err != nil {
fmt.Println(err.Error())
return
}
for _, hook := range hooks {
fmt.Printf("> [%d] %s, created on %s\n", hook.Id, hook.Url, hook.CreatedAtRaw)
}
case "team":
fmt.Println("Fetching project team members…")
if id == "" {
flag.Usage()
return
}
members, err := gitlab.ProjectMembers(id)
if err != nil {
fmt.Println(err.Error())
return
}
for _, member := range members {
fmt.Printf("> [%d] %s (%s) since %s\n", member.Id, member.Username, member.Name, member.CreatedAt)
}
}
}

View file

@ -1,124 +0,0 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"github.com/bugagazavr/go-gitlab-client"
"io/ioutil"
"os"
"time"
)
type Config struct {
Host string `json:"host"`
ApiPath string `json:"api_path"`
Token string `json:"token"`
}
func main() {
help := flag.Bool("help", false, "Show usage")
file, e := ioutil.ReadFile("../config.json")
if e != nil {
fmt.Printf("Config file error: %v\n", e)
os.Exit(1)
}
var config Config
json.Unmarshal(file, &config)
fmt.Printf("Results: %+v\n", config)
gitlab := gogitlab.NewGitlab(config.Host, config.ApiPath, config.Token)
var method string
flag.StringVar(&method, "m", "", "Specify method to retrieve repositories, available methods:\n"+
" > branches\n"+
" > branch\n"+
" > tags\n"+
" > commits\n"+
" > commit_comments -sha COMMIT_SHA\n"+
" > comment_a_commit -sha COMMIT_SHA -comment COMMENT_BODY")
var id string
flag.StringVar(&id, "id", "", "Specify repository id")
var sha string
flag.StringVar(&sha, "sha", "", "Specify commit sha")
var comment string
flag.StringVar(&comment, "comment", "", "The body of the new comment")
flag.Usage = func() {
fmt.Printf("Usage:\n")
flag.PrintDefaults()
}
flag.Parse()
if *help == true || method == "" || id == "" {
flag.Usage()
return
}
startedAt := time.Now()
defer func() {
fmt.Printf("processed in %v\n", time.Now().Sub(startedAt))
}()
switch method {
case "branches":
fmt.Println("Fetching repository branches…")
branches, err := gitlab.RepoBranches(id)
if err != nil {
fmt.Println(err.Error())
}
for _, branch := range branches {
fmt.Printf("> %s\n", branch.Name)
}
case "branch":
case "tags":
fmt.Println("Fetching repository tags…")
tags, err := gitlab.RepoTags(id)
if err != nil {
fmt.Println(err.Error())
}
for _, tag := range tags {
fmt.Printf("> %s\n", tag.Name)
}
case "commits":
fmt.Println("Fetching repository commits…")
commits, err := gitlab.RepoCommits(id)
if err != nil {
fmt.Println(err.Error())
}
for _, commit := range commits {
fmt.Printf("(%s) %s > [%s] %s\n", commit.Id, commit.CreatedAt.Format("Mon 02 Jan 15:04"), commit.Author_Name, commit.Title)
}
case "commit_comments":
fmt.Println("Fetching comments on a repository commit…")
comments, err := gitlab.RepoCommitComments(id, sha)
if err != nil {
fmt.Println(err.Error())
}
for _, c := range comments {
fmt.Printf("[%s] %s\n", c.Author.Username, c.Note)
}
case "comment_a_commit":
fmt.Println("Sending a new comment on a repository commit…")
c, err := gitlab.SendRepoCommitComment(id, sha, comment)
if err != nil {
fmt.Println(err.Error())
}
fmt.Printf("[%s] %s\n", c.Author.Username, c.Note)
}
}

View file

@ -1,220 +0,0 @@
// Package github implements a simple client to consume gitlab API.
package gogitlab
import (
"bytes"
"crypto/tls"
"flag"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
const (
dasboard_feed_path = "/dashboard.atom"
)
type Gitlab struct {
BaseUrl string
ApiPath string
RepoFeedPath string
Token string
Bearer bool
Client *http.Client
}
const (
dateLayout = "2006-01-02T15:04:05-07:00"
)
var (
skipCertVerify = flag.Bool("gitlab.skip-cert-check", false,
`If set to true, gitlab client will skip certificate checking for https, possibly exposing your system to MITM attack.`)
)
func NewGitlab(baseUrl, apiPath, token string) *Gitlab {
return NewGitlabCert(baseUrl, apiPath, token, *skipCertVerify)
}
func NewGitlabCert(baseUrl, apiPath, token string, skipVerify bool) *Gitlab {
config := &tls.Config{InsecureSkipVerify: skipVerify}
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: config,
}
client := &http.Client{Transport: tr}
return &Gitlab{
BaseUrl: baseUrl,
ApiPath: apiPath,
Token: token,
Client: client,
}
}
func (g *Gitlab) ResourceUrl(url string, params map[string]string) string {
if params != nil {
for key, val := range params {
url = strings.Replace(url, key, encodeParameter(val), -1)
}
}
url = g.BaseUrl + g.ApiPath + url
if !g.Bearer {
url = url + "?private_token=" + g.Token
}
return url
}
func (g *Gitlab) buildAndExecRequest(method, url string, body []byte) ([]byte, error) {
var req *http.Request
var err error
if body != nil {
reader := bytes.NewReader(body)
req, err = http.NewRequest(method, url, reader)
} else {
req, err = http.NewRequest(method, url, nil)
}
if err != nil {
panic("Error while building gitlab request")
}
if g.Bearer {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", g.Token))
}
resp, err := g.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("Client.Do error: %q", err)
}
defer resp.Body.Close()
contents, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("%s", err)
}
if resp.StatusCode >= 400 {
err = fmt.Errorf("*Gitlab.buildAndExecRequest failed: <%d> %s", resp.StatusCode, req.URL)
}
return contents, err
}
func (g *Gitlab) ResourceUrlQuery(u string, params, query map[string]string) string {
if params != nil {
for key, val := range params {
u = strings.Replace(u, key, encodeParameter(val), -1)
}
}
query_params := url.Values{}
if !g.Bearer {
query_params.Add("private_token", g.Token)
}
if query != nil {
for key, val := range query {
query_params.Set(key, val)
}
}
u = g.BaseUrl + g.ApiPath + u + "?" + query_params.Encode()
return u
}
func (g *Gitlab) ResourceUrlQueryRaw(u string, params, query map[string]string) (string, string) {
if params != nil {
for key, val := range params {
u = strings.Replace(u, key, encodeParameter(val), -1)
}
}
query_params := url.Values{}
if !g.Bearer {
query_params.Add("private_token", g.Token)
}
if query != nil {
for key, val := range query {
query_params.Set(key, val)
}
}
u = g.BaseUrl + g.ApiPath + u + "?" + query_params.Encode()
p, err := url.Parse(u)
if err != nil {
return u, ""
}
opaque := "//" + p.Host + p.Path
return u, opaque
}
func (g *Gitlab) ResourceUrlRaw(u string, params map[string]string) (string, string) {
if params != nil {
for key, val := range params {
u = strings.Replace(u, key, encodeParameter(val), -1)
}
}
path := u
u = g.BaseUrl + g.ApiPath + path
if !g.Bearer {
u = u + "?private_token=" + g.Token
}
p, err := url.Parse(u)
if err != nil {
return u, ""
}
opaque := "//" + p.Host + p.Path
return u, opaque
}
func (g *Gitlab) buildAndExecRequestRaw(method, url, opaque string, body []byte) ([]byte, error) {
var req *http.Request
var err error
if body != nil {
reader := bytes.NewReader(body)
req, err = http.NewRequest(method, url, reader)
} else {
req, err = http.NewRequest(method, url, nil)
}
if err != nil {
panic("Error while building gitlab request")
}
if g.Bearer {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", g.Token))
}
if len(opaque) > 0 {
req.URL.Opaque = opaque
}
resp, err := g.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("Client.Do error: %q", err)
}
defer resp.Body.Close()
contents, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("%s", err)
}
if resp.StatusCode >= 400 {
err = fmt.Errorf("*Gitlab.buildAndExecRequestRaw failed: <%d> %s", resp.StatusCode, req.URL)
}
return contents, err
}

View file

@ -1,13 +0,0 @@
package gogitlab
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestResourceUrl(t *testing.T) {
gitlab := NewGitlab("http://base_url/", "api_path", "token")
assert.Equal(t, gitlab.ResourceUrl(projects_url, nil), "http://base_url/api_path/projects?private_token=token")
assert.Equal(t, gitlab.ResourceUrl(project_url, map[string]string{":id": "123"}), "http://base_url/api_path/projects/123?private_token=token")
}

View file

@ -1,16 +0,0 @@
package gogitlab
import (
"io/ioutil"
"net/http"
"net/http/httptest"
)
func Stub(filename string) (*httptest.Server, *Gitlab) {
stub, _ := ioutil.ReadFile(filename)
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(stub))
}))
gitlab := NewGitlab(ts.URL, "", "")
return ts, gitlab
}

View file

@ -1,156 +0,0 @@
package gogitlab
import (
"encoding/json"
"fmt"
"strings"
)
type HookObjAttr struct {
Id int `json:"id,omitempty"`
Title string `json:"title,omitempty"`
AssigneeId int `json:"assignee_id,omitempty"`
AuthorId int `json:"author_id,omitempty"`
ProjectId int `json:"project_id,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
Position int `json:"position,omitempty"`
BranchName string `json:"branch_name,omitempty"`
Description string `json:"description,omitempty"`
MilestoneId int `json:"milestone_id,omitempty"`
State string `json:"state,omitempty"`
IId int `json:"iid,omitempty"`
TargetBranch string `json:"target_branch,omitempty"`
SourceBranch string `json:"source_branch,omitempty"`
SourceProjectId int `json:"source_project_id,omitempty"`
StCommits string `json:"st_commits,omitempty"`
StDiffs string `json:"st_diffs,omitempty"`
MergeStatus string `json:"merge_status,omitempty"`
TargetProjectId int `json:"target_project_id,omitempty"`
Url string `json:"url,omiyempty"`
Source *hProject `json:"source,omitempty"`
Target *hProject `json:"target,omitempty"`
LastCommit *hCommit `json:"last_commit,omitempty"`
}
type hProject struct {
Name string `json:"name"`
SshUrl string `json:"ssh_url"`
HttpUrl string `json:"http_url"`
VisibilityLevel int `json:"visibility_level"`
WebUrl string `json:"web_url"`
Namespace string `json:"namespace"`
}
type hRepository struct {
Name string `json:"name,omitempty"`
URL string `json:"url,omitempty"`
Description string `json:"description,omitempty"`
Homepage string `json:"homepage,omitempty"`
GitHttpUrl string `json:"git_http_url,omitempty"`
GitSshUrl string `json:"git_ssh_url,omitempty"`
VisibilityLevel int `json:"visibility_level,omitempty"`
}
type hCommit struct {
Id string `json:"id,omitempty"`
Message string `json:"message,omitempty"`
Timestamp string `json:"timestamp,omitempty"`
URL string `json:"url,omitempty"`
Author *Person `json:"author,omitempty"`
}
type HookPayload struct {
Before string `json:"before,omitempty"`
After string `json:"after,omitempty"`
Ref string `json:"ref,omitempty"`
UserId int `json:"user_id,omitempty"`
UserName string `json:"user_name,omitempty"`
ProjectId int `json:"project_id,omitempty"`
Repository *hRepository `json:"repository,omitempty"`
Commits []hCommit `json:"commits,omitempty"`
TotalCommitsCount int `json:"total_commits_count,omitempty"`
ObjectKind string `json:"object_kind,omitempty"`
ObjectAttributes *HookObjAttr `json:"object_attributes,omitempty"`
}
// ParseHook parses hook payload from GitLab
func ParseHook(payload []byte) (*HookPayload, error) {
hp := HookPayload{}
if err := json.Unmarshal(payload, &hp); err != nil {
return nil, err
}
// Basic sanity check
switch {
case len(hp.ObjectKind) == 0:
// Assume this is a post-receive within repository
if len(hp.After) == 0 {
return nil, fmt.Errorf("Invalid hook received, commit hash not found.")
}
case hp.ObjectKind == "push":
if hp.Repository == nil {
return nil, fmt.Errorf("Invalid push hook received, attributes not found")
}
case hp.ObjectKind == "tag_push":
if hp.Repository == nil {
return nil, fmt.Errorf("Invalid tag push hook received, attributes not found")
}
case hp.ObjectKind == "issue":
fallthrough
case hp.ObjectKind == "merge_request":
if hp.ObjectAttributes == nil {
return nil, fmt.Errorf("Invalid hook received, attributes not found.")
}
default:
return nil, fmt.Errorf("Invalid hook received, payload format not recognized.")
}
return &hp, nil
}
// Type return current event type
// This function returns "unknown" type if event not supported
func (h *HookPayload) Type() string {
switch {
case strings.HasPrefix(h.Ref, "refs/heads/") && len(h.After) == 0:
return "branch_deleted"
case strings.HasPrefix(h.Ref, "refs/heads/") && len(h.Before) == 0:
return "branch"
case strings.HasPrefix(h.Ref, "refs/heads/"):
return "commit"
case strings.HasPrefix(h.Ref, "refs/tags/") && len(h.After) == 0:
return "tag_deleted"
case strings.HasPrefix(h.Ref, "refs/tags/"):
return "tag"
case h.ObjectKind == "issue":
return "issue"
case h.ObjectKind == "merge_request":
return "merge_request"
default:
return "unknown"
}
}
// Tag returns current tag for push event hook payload
// This function returns empty string for any other events
func (h *HookPayload) Tag() string {
return strings.TrimPrefix(h.Ref, "refs/tags/")
}
// Branch returns current branch for push event hook payload
// This function returns empty string for any other events
func (h *HookPayload) Branch() string {
return strings.TrimPrefix(h.Ref, "refs/heads/")
}
// Head returns the latest changeset for push event hook payload
func (h *HookPayload) Head() hCommit {
c := hCommit{}
for _, cm := range h.Commits {
if h.After == cm.Id {
return cm
}
}
return c
}

View file

@ -1,45 +0,0 @@
package gogitlab
import (
"github.com/stretchr/testify/assert"
"io/ioutil"
"testing"
)
func TestParsePushHook(t *testing.T) {
stub, _ := ioutil.ReadFile("stubs/hook_payloads/push.json")
p, err := ParseHook([]byte(stub))
assert.Equal(t, err, nil)
assert.Equal(t, p.ObjectKind, "push")
assert.IsType(t, new(HookPayload), p)
assert.Equal(t, p.After, "da1560886d4f094c3e6c9ef40349f7d38b5d27d7")
assert.Equal(t, p.Repository.URL, "git@example.com:mike/diasporadiaspora.git")
assert.Equal(t, p.Repository.GitHttpUrl, "http://example.com/mike/diaspora.git")
assert.Equal(t, p.Repository.GitSshUrl, "git@example.com:mike/diaspora.git")
assert.Equal(t, p.Repository.VisibilityLevel, 0)
assert.Equal(t, len(p.Commits), 2)
assert.Equal(t, p.Commits[0].Author.Email, "jordi@softcatala.org")
assert.Equal(t, p.Commits[1].Id, "da1560886d4f094c3e6c9ef40349f7d38b5d27d7")
assert.Equal(t, p.Branch(), "master")
assert.Equal(t, p.Head().Message, "fixed readme")
}
func TestParseIssueHook(t *testing.T) {
stub, _ := ioutil.ReadFile("stubs/hook_payloads/issue.json")
p, err := ParseHook([]byte(stub))
assert.Equal(t, err, nil)
assert.Equal(t, p.ObjectKind, "issue")
assert.Equal(t, p.ObjectAttributes.Id, 301)
}
func TestParseMergeRequestHook(t *testing.T) {
stub, _ := ioutil.ReadFile("stubs/hook_payloads/merge_request.json")
p, err := ParseHook([]byte(stub))
assert.Equal(t, err, nil)
assert.Equal(t, p.ObjectKind, "merge_request")
assert.Equal(t, p.ObjectAttributes.TargetBranch, "master")
assert.Equal(t, p.ObjectAttributes.SourceProjectId, p.ObjectAttributes.TargetProjectId)
}

View file

@ -1,187 +0,0 @@
package gogitlab
import (
"encoding/json"
"net/url"
)
const (
project_url_hooks = "/projects/:id/hooks" // Get list of project hooks
project_url_hook = "/projects/:id/hooks/:hook_id" // Get single project hook
)
type Hook struct {
Id int `json:"id,omitempty"`
Url string `json:"url,omitempty"`
CreatedAtRaw string `json:"created_at,omitempty"`
}
/*
Get list of project hooks.
GET /projects/:id/hooks
Parameters:
id The ID of a project
*/
func (g *Gitlab) ProjectHooks(id string) ([]*Hook, error) {
url, opaque := g.ResourceUrlRaw(project_url_hooks, map[string]string{":id": id})
var err error
var hooks []*Hook
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err != nil {
return hooks, err
}
err = json.Unmarshal(contents, &hooks)
return hooks, err
}
/*
Get single project hook.
GET /projects/:id/hooks/:hook_id
Parameters:
id The ID of a project
hook_id The ID of a hook
*/
func (g *Gitlab) ProjectHook(id, hook_id string) (*Hook, error) {
url, opaque := g.ResourceUrlRaw(project_url_hook, map[string]string{
":id": id,
":hook_id": hook_id,
})
var err error
hook := new(Hook)
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err != nil {
return hook, err
}
err = json.Unmarshal(contents, &hook)
return hook, err
}
/*
Add new project hook.
POST /projects/:id/hooks
Parameters:
id The ID or NAMESPACE/PROJECT_NAME of a project
hook_url The hook URL
push_events Trigger hook on push events
issues_events Trigger hook on issues events
merge_requests_events Trigger hook on merge_requests events
*/
func (g *Gitlab) AddProjectHook(id, hook_url string, push_events, issues_events, merge_requests_events, tag_events bool) error {
url, opaque := g.ResourceUrlRaw(project_url_hooks, map[string]string{":id": id})
var err error
body := buildHookQuery(hook_url, push_events, issues_events, merge_requests_events, tag_events)
_, err = g.buildAndExecRequestRaw("POST", url, opaque, []byte(body))
return err
}
/*
Edit existing project hook.
PUT /projects/:id/hooks/:hook_id
Parameters:
id The ID or NAMESPACE/PROJECT_NAME of a project
hook_id The ID of a project hook
hook_url The hook URL
push_events Trigger hook on push events
issues_events Trigger hook on issues events
merge_requests_events Trigger hook on merge_requests events
*/
func (g *Gitlab) EditProjectHook(id, hook_id, hook_url string, push_events, issues_events, merge_requests_events, tag_events bool) error {
url, opaque := g.ResourceUrlRaw(project_url_hook, map[string]string{
":id": id,
":hook_id": hook_id,
})
var err error
body := buildHookQuery(hook_url, push_events, issues_events, merge_requests_events, tag_events)
_, err = g.buildAndExecRequestRaw("PUT", url, opaque, []byte(body))
return err
}
/*
Remove hook from project.
DELETE /projects/:id/hooks/:hook_id
Parameters:
id The ID or NAMESPACE/PROJECT_NAME of a project
hook_id The ID of hook to delete
*/
func (g *Gitlab) RemoveProjectHook(id, hook_id string) error {
url, opaque := g.ResourceUrlRaw(project_url_hook, map[string]string{
":id": id,
":hook_id": hook_id,
})
var err error
_, err = g.buildAndExecRequestRaw("DELETE", url, opaque, nil)
return err
}
/*
Build HTTP query to add or edit hook
*/
func buildHookQuery(hook_url string, push_events, issues_events, merge_requests_events, tag_events bool) string {
v := url.Values{}
v.Set("url", hook_url)
if push_events {
v.Set("push_events", "true")
} else {
v.Set("push_events", "false")
}
if issues_events {
v.Set("issues_events", "true")
} else {
v.Set("issues_events", "false")
}
if merge_requests_events {
v.Set("merge_requests_events", "true")
} else {
v.Set("merge_requests_events", "false")
}
if tag_events {
v.Set("tag_push_events", "true")
} else {
v.Set("tag_push_events", "false")
}
return v.Encode()
}

View file

@ -1,16 +0,0 @@
package gogitlab
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestHook(t *testing.T) {
ts, gitlab := Stub("stubs/hooks/show.json")
hook, err := gitlab.ProjectHook("1", "2")
assert.Equal(t, err, nil)
assert.IsType(t, new(Hook), hook)
assert.Equal(t, hook.Url, "http://example.com/hook")
defer ts.Close()
}

View file

@ -1,292 +0,0 @@
package gogitlab
import (
"encoding/json"
"fmt"
"strconv"
"strings"
)
const (
projects_url = "/projects" // Get a list of projects owned by the authenticated user
projects_search_url = "/projects/search/:query" // Search for projects by name
project_url = "/projects/:id" // Get a specific project, identified by project ID or NAME
project_url_events = "/projects/:id/events" // Get project events
project_url_branches = "/projects/:id/repository/branches" // Lists all branches of a project
project_url_members = "/projects/:id/members" // List project team members
project_url_member = "/projects/:id/members/:user_id" // Get project team member
project_url_merge_requests = "/projects/:id/merge_requests" // List all merge requests of a project
merge_request_url_notes = "/projects/:id/merge_requests/:merge_request_id/notes" // Manage comments for a given merge request
)
type Member struct {
Id int
Username string
Email string
Name string
State string
CreatedAt string `json:"created_at,omitempty"`
// AccessLevel int
}
type Namespace struct {
Id int
Name string
Path string
Description string
Owner_Id int
Created_At string
Updated_At string
}
type ProjectAccess struct {
AccessLevel int `json:"access_level,omitempty"`
NotificationLevel int `json:"notification_level,omitempty"`
}
type GroupAccess struct {
AccessLevel int `json:"access_level,omitempty"`
NotificationLevel int `json:"notification_level,omitempty"`
}
type Permissions struct {
ProjectAccess *ProjectAccess `json:"project_access,omitempty"`
GroupAccess *GroupAccess `json:"group_access,omitempty"`
}
// A gitlab project
type Project struct {
Id int `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
DefaultBranch string `json:"default_branch,omitempty"`
Owner *Member `json:"owner,omitempty"`
Public bool `json:"public,omitempty"`
Path string `json:"path,omitempty"`
PathWithNamespace string `json:"path_with_namespace,omitempty"`
IssuesEnabled bool `json:"issues_enabled,omitempty"`
MergeRequestsEnabled bool `json:"merge_requests_enabled,omitempty"`
WallEnabled bool `json:"wall_enabled,omitempty"`
WikiEnabled bool `json:"wiki_enabled,omitempty"`
CreatedAtRaw string `json:"created_at,omitempty"`
Namespace *Namespace `json:"namespace,omitempty"`
SshRepoUrl string `json:"ssh_url_to_repo"`
HttpRepoUrl string `json:"http_url_to_repo"`
Url string `json:"web_url"`
Permissions *Permissions `json:"permissions,omitempty"`
}
type MergeRequest struct {
Id int `json:"id,omitempty"`
// IId
TargetBranch string `json:"target_branch,omitempty"`
SourceBranch string `json:"source_branch,omitempty"`
ProjectId int `json:"project_id,omitempty"`
Title string `json:"title,omitempty"`
State string `json:"state,omitempty"`
Upvotes int `json:"upvotes,omitempty"`
Downvotes int `json:"downvotes,omitempty"`
Author *Member `json:"author,omitempty"`
Assignee *Member `json:"assignee,omitempty"`
Description string `json:"description,omitempty"`
}
type MergeRequestNote struct {
Attachment interface{} `json:"attachment"`
Body string `json:"body"`
CreatedAt string `json:"created_at"`
Id int `json:"id"`
Author *Member `json:"author"`
}
/*
Get a list of all projects owned by the authenticated user.
*/
func (g *Gitlab) AllProjects() ([]*Project, error) {
var per_page = 100
var projects []*Project
for i := 1; true; i++ {
contents, err := g.Projects(i, per_page)
if err != nil {
return projects, err
}
for _, value := range contents {
projects = append(projects, value)
}
if len(projects) == 0 {
break
}
if len(projects)/i < per_page {
break
}
}
return projects, nil
}
/*
Get a list of projects owned by the authenticated user.
*/
func (g *Gitlab) Projects(page int, per_page int) ([]*Project, error) {
url := g.ResourceUrlQuery(projects_url, nil, map[string]string{"page": strconv.Itoa(page), "per_page": strconv.Itoa(per_page)})
var projects []*Project
contents, err := g.buildAndExecRequest("GET", url, nil)
if err == nil {
err = json.Unmarshal(contents, &projects)
}
return projects, err
}
/*
Get a specific project, identified by project ID or NAME,
which is owned by the authentication user.
Namespaced project may be retrieved by specifying the namespace
and its project name like this:
`namespace%2Fproject-name`
*/
func (g *Gitlab) Project(id string) (*Project, error) {
url, opaque := g.ResourceUrlRaw(project_url, map[string]string{":id": id})
var project *Project
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &project)
}
return project, err
}
/*
Lists all branches of a project.
*/
func (g *Gitlab) ProjectBranches(id string) ([]*Branch, error) {
url, opaque := g.ResourceUrlRaw(project_url_branches, map[string]string{":id": id})
var branches []*Branch
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &branches)
}
return branches, err
}
func (g *Gitlab) ProjectMembers(id string) ([]*Member, error) {
url, opaque := g.ResourceUrlRaw(project_url_members, map[string]string{":id": id})
var members []*Member
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &members)
}
return members, err
}
/*
Lists all merge requests of a project.
*/
func (g *Gitlab) ProjectMergeRequests(id string, page int, per_page int, state string) ([]*MergeRequest, error) {
par := map[string]string{":id": id}
qry := map[string]string{
"state": state,
"page": strconv.Itoa(page),
"per_page": strconv.Itoa(per_page)}
url := g.ResourceUrlQuery(project_url_merge_requests, par, qry)
var mr []*MergeRequest
contents, err := g.buildAndExecRequest("GET", url, nil)
if err == nil {
err = json.Unmarshal(contents, &mr)
}
return mr, err
}
/*
Lists all comments on merge request.
*/
func (g *Gitlab) MergeRequestNotes(id string, merge_request_id string, page int, per_page int) ([]*MergeRequestNote, error) {
par := map[string]string{":id": id, ":merge_request_id": merge_request_id}
qry := map[string]string{
"page": strconv.Itoa(page),
"per_page": strconv.Itoa(per_page)}
url := g.ResourceUrlQuery(merge_request_url_notes, par, qry)
var mr []*MergeRequestNote
contents, err := g.buildAndExecRequest("GET", url, nil)
if err == nil {
err = json.Unmarshal(contents, &mr)
}
return mr, err
}
/*
Creates a new comment on a merge request.
*/
func (g *Gitlab) SendMergeRequestComment(id string, merge_request_id string, comment string) (*MergeRequestNote, error) {
par := map[string]string{":id": id, ":merge_request_id": merge_request_id}
url := g.ResourceUrlQuery(merge_request_url_notes, par, map[string]string{})
var mr *MergeRequestNote
contents, err := g.buildAndExecRequest("POST", url, []byte(fmt.Sprintf("body=%s", comment)))
if err == nil {
err = json.Unmarshal(contents, &mr)
}
return mr, err
}
/*
Get single project id.
GET /projects/search/:query
Parameters:
namespace The namespace of a project
name The id of a project
*/
func (g *Gitlab) SearchProjectId(namespace string, name string) (id int, err error) {
url, opaque := g.ResourceUrlRaw(projects_search_url, map[string]string{
":query": strings.ToLower(name),
})
var projects []*Project
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &projects)
} else {
return id, err
}
for _, project := range projects {
if project.Namespace.Name == namespace && strings.ToLower(project.Name) == strings.ToLower(name) {
id = project.Id
}
}
return id, err
}

View file

@ -1,85 +0,0 @@
package gogitlab
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestALlProjects(t *testing.T) {
ts, gitlab := Stub("stubs/projects/index.json")
projects, err := gitlab.AllProjects()
assert.Equal(t, err, nil)
assert.Equal(t, len(projects), 2)
defer ts.Close()
}
func TestProjects(t *testing.T) {
ts, gitlab := Stub("stubs/projects/index.json")
projects, err := gitlab.Projects(1, 100)
assert.Equal(t, err, nil)
assert.Equal(t, len(projects), 2)
defer ts.Close()
}
func TestProject(t *testing.T) {
ts, gitlab := Stub("stubs/projects/show.json")
project, err := gitlab.Project("1")
assert.Equal(t, err, nil)
assert.IsType(t, new(Project), project)
assert.Equal(t, project.SshRepoUrl, "git@example.com:diaspora/diaspora-project-site.git")
assert.Equal(t, project.HttpRepoUrl, "http://example.com/diaspora/diaspora-project-site.git")
defer ts.Close()
}
func TestProjectBranches(t *testing.T) {
ts, gitlab := Stub("stubs/projects/branches/index.json")
branches, err := gitlab.ProjectBranches("1")
assert.Equal(t, err, nil)
assert.Equal(t, len(branches), 2)
defer ts.Close()
}
func TestProjectMergeRequests(t *testing.T) {
ts, gitlab := Stub("stubs/projects/merge_requests/index.json")
defer ts.Close()
mr, err := gitlab.ProjectMergeRequests("1", 0, 30, "all")
assert.Equal(t, err, nil)
assert.Equal(t, len(mr), 1)
if len(mr) > 0 {
assert.Equal(t, mr[0].TargetBranch, "master")
assert.Equal(t, mr[0].SourceBranch, "test1")
}
}
func TestMergeRequestNotes(t *testing.T) {
ts, gitlab := Stub("stubs/projects/merge_requests/notes/index.json")
defer ts.Close()
notes, err := gitlab.MergeRequestNotes("1", "1", 0, 30)
assert.Equal(t, err, nil)
assert.Equal(t, len(notes), 1)
if len(notes) > 0 {
assert.Equal(t, notes[0].Id, 301)
assert.Equal(t, notes[0].Body, "Comment for MR")
assert.Equal(t, notes[0].Author.Username, "pipin")
}
}
func TestSearchProjectId(t *testing.T) {
ts, gitlab := Stub("stubs/projects/index.json")
namespace := "Brightbox"
name := "Puppet"
id, err := gitlab.SearchProjectId(namespace, name)
assert.Equal(t, err, nil)
assert.Equal(t, id, 6)
defer ts.Close()
}

View file

@ -1,69 +0,0 @@
package gogitlab
import (
"encoding/json"
"net/url"
)
const (
// ID
user_keys = "/user/keys" // Get current user keys
user_key = "/user/keys/:id" // Get user key by id
custom_user_keys = "/user/:id/keys" // Create key for user with :id
)
type PublicKey struct {
Id int `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Key string `json:"key,omitempty"`
CreatedAtRaw string `json:"created_at,omitempty"`
}
func (g *Gitlab) UserKeys() ([]*PublicKey, error) {
url := g.ResourceUrl(user_keys, nil)
var keys []*PublicKey
contents, err := g.buildAndExecRequest("GET", url, nil)
if err == nil {
err = json.Unmarshal(contents, &keys)
}
return keys, err
}
func (g *Gitlab) UserKey(id string) (*PublicKey, error) {
url := g.ResourceUrl(user_key, map[string]string{":id": id})
var key *PublicKey
contents, err := g.buildAndExecRequest("GET", url, nil)
if err == nil {
err = json.Unmarshal(contents, &key)
}
return key, err
}
func (g *Gitlab) AddKey(title, key string) error {
path := g.ResourceUrl(user_keys, nil)
var err error
v := url.Values{}
v.Set("title", title)
v.Set("key", key)
body := v.Encode()
_, err = g.buildAndExecRequest("POST", path, []byte(body))
return err
}
func (g *Gitlab) AddUserKey(id, title, key string) error {
path := g.ResourceUrl(user_keys, map[string]string{":id": id})
var err error
v := url.Values{}
v.Set("title", title)
v.Set("key", key)
body := v.Encode()
_, err = g.buildAndExecRequest("POST", path, []byte(body))
return err
}
func (g *Gitlab) DeleteKey(id string) error {
url := g.ResourceUrl(user_key, map[string]string{":id": id})
var err error
_, err = g.buildAndExecRequest("DELETE", url, nil)
return err
}

View file

@ -1,50 +0,0 @@
package gogitlab
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestGetUserKeys(t *testing.T) {
ts, gitlab := Stub("stubs/public_keys/index.json")
keys, err := gitlab.UserKeys()
assert.Equal(t, err, nil)
assert.Equal(t, len(keys), 2)
defer ts.Close()
}
func TestGetUserKey(t *testing.T) {
ts, gitlab := Stub("stubs/public_keys/show.json")
key, err := gitlab.UserKey("1")
assert.Equal(t, err, nil)
assert.IsType(t, new(PublicKey), key)
assert.Equal(t, key.Title, "Public key")
assert.Equal(t, key.Key, "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=")
defer ts.Close()
}
func TestAddKey(t *testing.T) {
ts, gitlab := Stub("")
err := gitlab.AddKey("Public key", "stubbed key")
assert.Equal(t, err, nil)
defer ts.Close()
}
func TestAddUserKey(t *testing.T) {
ts, gitlab := Stub("")
err := gitlab.AddUserKey("1", "Public key", "stubbed key")
assert.Equal(t, err, nil)
defer ts.Close()
}
func TestDeleteKey(t *testing.T) {
ts, gitlab := Stub("")
err := gitlab.DeleteKey("1")
assert.Equal(t, err, nil)
defer ts.Close()
}

View file

@ -1,323 +0,0 @@
package gogitlab
import (
"encoding/json"
"fmt"
"net/url"
"time"
)
const (
repo_url_branches = "/projects/:id/repository/branches" // List repository branches
repo_url_branch = "/projects/:id/repository/branches/:branch" // Get a specific branch of a project.
repo_url_tags = "/projects/:id/repository/tags" // List project repository tags
repo_url_commits = "/projects/:id/repository/commits" // List repository commits
repo_url_commit_comments = "/projects/:id/repository/commits/:sha/comments" // New comment or list of commit comments
repo_url_tree = "/projects/:id/repository/tree" // List repository tree
repo_url_raw_file = "/projects/:id/repository/blobs/:sha" // Get raw file content for specific commit/branch
)
type BranchCommit struct {
Id string `json:"id,omitempty"`
Tree string `json:"tree,omitempty"`
AuthoredDateRaw string `json:"authored_date,omitempty"`
CommittedDateRaw string `json:"committed_date,omitempty"`
Message string `json:"message,omitempty"`
Author *Person `json:"author,omitempty"`
Committer *Person `json:"committer,omitempty"`
/*
"parents": [
{"id": "9b0c4b08e7890337fc8111e66f809c8bbec467a9"},
{"id": "3ac634dca850cab70ab14b43ad6073d1e0a7827f"}
]
*/
}
type Branch struct {
Name string `json:"name,omitempty"`
Protected bool `json:"protected,omitempty"`
Commit *BranchCommit `json:"commit,omitempty"`
}
type Tag struct {
Name string `json:"name,omitempty"`
Protected bool `json:"protected,omitempty"`
Commit *BranchCommit `json:"commit,omitempty"`
}
type Commit struct {
Id string
Short_Id string
Title string
Author_Name string
Author_Email string
Created_At string
CreatedAt time.Time
}
type File struct {
Id string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Mode string `json:"mode,omitempty"`
Children []*File
}
type CommitComment struct {
Author *Member `json:"author,omitempty"`
Line int `json:"line,omitempty"`
LineType string `json:"line_type,omitempty"`
Note string `json:"note,omitempty"`
Path string `json:"path,omitempty"`
}
/*
Get a list of repository branches from a project, sorted by name alphabetically.
GET /projects/:id/repository/branches
Parameters:
id The ID of a project
Usage:
branches, err := gitlab.RepoBranches("your_projet_id")
if err != nil {
fmt.Println(err.Error())
}
for _, branch := range branches {
fmt.Printf("%+v\n", branch)
}
*/
func (g *Gitlab) RepoBranches(id string) ([]*Branch, error) {
url, opaque := g.ResourceUrlRaw(repo_url_branches, map[string]string{":id": id})
var branches []*Branch
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &branches)
}
return branches, err
}
/*
Get a single project repository branch.
GET /projects/:id/repository/branches/:branch
Parameters:
id The ID of a project
branch The name of the branch
*/
func (g *Gitlab) RepoBranch(id, refName string) (*Branch, error) {
url, opaque := g.ResourceUrlRaw(repo_url_branch, map[string]string{
":id": id,
":branch": refName,
})
branch := new(Branch)
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &branch)
}
return branch, err
}
/*
Get a list of repository tags from a project, sorted by name in reverse alphabetical order.
GET /projects/:id/repository/tags
Parameters:
id The ID of a project
Usage:
tags, err := gitlab.RepoTags("your_projet_id")
if err != nil {
fmt.Println(err.Error())
}
for _, tag := range tags {
fmt.Printf("%+v\n", tag)
}
*/
func (g *Gitlab) RepoTags(id string) ([]*Tag, error) {
url, opaque := g.ResourceUrlRaw(repo_url_tags, map[string]string{":id": id})
var tags []*Tag
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &tags)
}
return tags, err
}
/*
Get a list of repository commits in a project.
GET /projects/:id/repository/commits
Parameters:
id The ID of a project
refName The name of a repository branch or tag or if not given the default branch
Usage:
commits, err := gitlab.RepoCommits("your_projet_id")
if err != nil {
fmt.Println(err.Error())
}
for _, commit := range commits {
fmt.Printf("%+v\n", commit)
}
*/
func (g *Gitlab) RepoCommits(id string) ([]*Commit, error) {
url, opaque := g.ResourceUrlRaw(repo_url_commits, map[string]string{":id": id})
var commits []*Commit
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &commits)
if err == nil {
for _, commit := range commits {
t, _ := time.Parse(dateLayout, commit.Created_At)
commit.CreatedAt = t
}
}
}
return commits, err
}
/*
Get a list of comments in a repository commit.
GET /projects/:id/repository/commits/:sha/comments
Parameters:
id The ID of a project
sha The sha of the commit
Usage:
comments, err := gitlab.RepoCommitComments("your_projet_id", "commit_sha")
if err != nil {
fmt.Println(err.Error())
}
for _, comment := range comments {
fmt.Printf("%+v\n", comment)
}
*/
func (g *Gitlab) RepoCommitComments(id string, sha string) ([]*CommitComment, error) {
url, opaque := g.ResourceUrlRaw(repo_url_commit_comments, map[string]string{":id": id, ":sha": sha})
var comments []*CommitComment
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &comments)
}
return comments, err
}
/*
Create a comment in a repository commit.
POST /projects/:id/repository/commits/:sha/comments
Parameters:
id The ID of a project
sha The sha of the commit
body The body of the comment
Usage:
comment, err := gitlab.SendRepoCommitComment("your_projet_id", "commit_sha", "your comment goes here")
if err != nil {
fmt.Println(err.Error())
}
fmt.Printf("%+v\n", comment)
*/
func (g *Gitlab) SendRepoCommitComment(id string, sha string, body string) (*CommitComment, error) {
url, opaque := g.ResourceUrlRaw(repo_url_commit_comments, map[string]string{":id": id, ":sha": sha})
var comment *CommitComment
contents, err := g.buildAndExecRequestRaw("POST", url, opaque, []byte(fmt.Sprintf("note=%s", body)))
if err == nil {
err = json.Unmarshal(contents, &comment)
}
return comment, err
}
/*
Get Raw file content
*/
func (g *Gitlab) RepoRawFile(id, sha, filepath string) ([]byte, error) {
url_ := g.ResourceUrlQuery(repo_url_raw_file, map[string]string{
":id": id,
":sha": sha,
}, map[string]string{
"filepath": filepath,
})
p, err := url.Parse(url_)
if err != nil {
return nil, err
}
opaque := "//" + p.Host + p.Path
contents, err := g.buildAndExecRequestRaw("GET", url_, opaque, nil)
return contents, err
}
/*
Get Raw file content
*/
func (g *Gitlab) RepoTree(id, ref, path string) ([]*File, error) {
url := g.ResourceUrlQuery(repo_url_tree, map[string]string{
":id": id,
}, map[string]string{
"ref": ref,
"path": path,
})
var files []*File
contents, err := g.buildAndExecRequest("GET", url, nil)
if err == nil {
err = json.Unmarshal(contents, &files)
}
for _, f := range files {
if f.Type == "tree" {
f.Children, err = g.RepoTree(id, ref, path+"/"+f.Name)
}
}
return files, err
}

View file

@ -1,52 +0,0 @@
package gogitlab
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestRepoBranches(t *testing.T) {
ts, gitlab := Stub("stubs/branches/index.json")
branches, err := gitlab.RepoBranches("1")
assert.Equal(t, err, nil)
assert.Equal(t, len(branches), 1)
defer ts.Close()
}
func TestRepoBranch(t *testing.T) {
ts, gitlab := Stub("stubs/branches/show.json")
branch, err := gitlab.RepoBranch("1", "master")
assert.Equal(t, err, nil)
assert.IsType(t, new(Branch), branch)
assert.Equal(t, branch.Name, "master")
defer ts.Close()
}
func TestRepoTags(t *testing.T) {
ts, gitlab := Stub("stubs/tags/index.json")
tags, err := gitlab.RepoTags("1")
assert.Equal(t, err, nil)
assert.Equal(t, len(tags), 1)
defer ts.Close()
}
func TestRepoCommits(t *testing.T) {
ts, gitlab := Stub("stubs/commits/index.json")
commits, err := gitlab.RepoCommits("1")
assert.Equal(t, err, nil)
assert.Equal(t, len(commits), 2)
defer ts.Close()
}
func TestRepoCommitComments(t *testing.T) {
ts, gitlab := Stub("stubs/commits/comments/index.json")
comments, err := gitlab.RepoCommitComments("1", "a9e6a5io4e695923c995ed2e836789b50oi77e0b")
assert.Equal(t, err, nil)
assert.Equal(t, len(comments), 1)
defer ts.Close()
}

View file

@ -1,19 +0,0 @@
package gogitlab
const (
drone_service_url = "/projects/:id/services/drone-ci"
)
func (g *Gitlab) AddDroneService(id string, params map[string]string) error {
url, opaque := g.ResourceUrlQueryRaw(drone_service_url, map[string]string{":id": id}, params)
_, err := g.buildAndExecRequestRaw("PUT", url, opaque, nil)
return err
}
func (g *Gitlab) DeleteDroneService(id string) error {
url, opaque := g.ResourceUrlQueryRaw(drone_service_url, map[string]string{":id": id}, nil)
_, err := g.buildAndExecRequestRaw("DELETE", url, opaque, nil)
return err
}

View file

@ -1,54 +0,0 @@
package gogitlab
import (
"encoding/json"
"net/url"
)
const (
session_path = "/session"
)
type Session struct {
Id int `json:"id"`
UserName string `json:"username"`
Name string `json:"name"`
Blocked bool `json:"blocked"`
State string `json:"state"`
AvatarURL string `json:"avatar_url",omitempty`
IsAdmin bool `json:"is_admin"`
Bio string `json:"bio",omitempty`
Email string `json:"email"`
ThemeId int `json:"theme_id",omitempty`
ColorSchemeId int `json:"color_scheme_id",omitempty`
ExternUid string `json:"extern_uid",omitempty`
Provider string `json:"provider",omitempty`
CanCreateGroup bool `json:"can_create_group"`
CanCreateProject bool `json:"can_create_project"`
Skype string `json:"skype",omitempty`
Twitter string `json:"twitter",omitempty`
LinkedIn string `json:"linkedin",omitempty`
WebsiteURL string `json:"website_url",omitempty`
PrivateToken string `json:"private_token"`
}
func (g *Gitlab) GetSession(email string, password string) (*Session, error) {
session_url := g.ResourceUrl(session_path, map[string]string{})
var session *Session
v := url.Values{}
v.Set("email", email)
v.Set("password", password)
body := v.Encode()
contents, err := g.buildAndExecRequest("POST", session_url, []byte(body))
if err != nil {
return session, err
}
err = json.Unmarshal(contents, &session)
return session, err
}

View file

@ -1,33 +0,0 @@
package gogitlab
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestGetSesison(t *testing.T) {
ts, gitlab := Stub("stubs/session/index.json")
session, err := gitlab.GetSession("john@example.com", "samplepassword")
assert.Equal(t, err, nil)
assert.Equal(t, session.Id, 1)
assert.Equal(t, session.UserName, "john_smith")
assert.Equal(t, session.Name, "John Smith")
assert.Equal(t, session.State, "active")
assert.Equal(t, session.AvatarURL, "http://someurl.com/avatar.png")
assert.Equal(t, session.IsAdmin, false)
assert.Equal(t, session.Bio, "somebio")
assert.Equal(t, session.Skype, "someskype")
assert.Equal(t, session.LinkedIn, "somelinkedin")
assert.Equal(t, session.Twitter, "sometwitter")
assert.Equal(t, session.WebsiteURL, "http://example.com")
assert.Equal(t, session.Email, "john@example.com")
assert.Equal(t, session.ThemeId, 1)
assert.Equal(t, session.ColorSchemeId, 1)
assert.Equal(t, session.ExternUid, "someuid")
assert.Equal(t, session.Provider, "github.com")
assert.Equal(t, session.CanCreateGroup, true)
assert.Equal(t, session.CanCreateProject, true)
assert.Equal(t, session.PrivateToken, "dd34asd13as")
defer ts.Close()
}

View file

@ -1,26 +0,0 @@
[
{
"name": "master",
"commit": {
"id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
"parents": [
{
"id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
}
],
"tree": "46e82de44b1061621357f24c05515327f2795a95",
"message": "add projects API",
"author": {
"name": "John Smith",
"email": "john@example.com"
},
"committer": {
"name": "John Smith",
"email": "john@example.com"
},
"authored_date": "2012-06-27T05:51:39-07:00",
"committed_date": "2012-06-28T03:44:20-07:00"
},
"protected": true
}
]

View file

@ -1,24 +0,0 @@
{
"name": "master",
"commit": {
"id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
"parents": [
{
"id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
}
],
"tree": "46e82de44b1061621357f24c05515327f2795a95",
"message": "add projects API",
"author": {
"name": "John Smith",
"email": "john@example.com"
},
"committer": {
"name": "John Smith",
"email": "john@example.com"
},
"authored_date": "2012-06-27T05:51:39-07:00",
"committed_date": "2012-06-28T03:44:20-07:00"
},
"protected": true
}

View file

@ -1,16 +0,0 @@
[
{
"author": {
"id": 1,
"username": "admin",
"email": "admin@local.host",
"name": "Administrator",
"blocked": false,
"created_at": "2012-04-29T08:46:00Z"
},
"note": "text1",
"path": "example.rb",
"line": 5,
"line_type": "new"
}
]

View file

@ -1,18 +0,0 @@
[
{
"id": "ed899a2f4b50b4370feeea94676502b42383c746",
"short_id": "ed899a2f4b5",
"title": "Replace sanitize with escape once",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dzaporozhets@sphereconsultinginc.com",
"created_at": "2012-09-20T11:50:22+03:00"
},
{
"id": "6104942438c14ec7bd21c6cd5bd995272b3faff6",
"short_id": "6104942438c",
"title": "Sanitize for network graph",
"author_name": "randx",
"author_email": "dmitriy.zaporozhets@gmail.com",
"created_at": "2012-09-20T09:06:12+03:00"
}
]

View file

@ -1,18 +0,0 @@
{
"object_kind": "issue",
"object_attributes": {
"id": 301,
"title": "New API: create/update/delete file",
"assignee_id": 51,
"author_id": 51,
"project_id": 14,
"created_at": "2013-12-03T17:15:43Z",
"updated_at": "2013-12-03T17:15:43Z",
"position": 0,
"branch_name": null,
"description": "Create new API for manipulations with repository",
"milestone_id": null,
"state": "opened",
"iid": 23
}
}

View file

@ -1,22 +0,0 @@
{
"object_kind": "merge_request",
"object_attributes": {
"id": 99,
"target_branch": "master",
"source_branch": "ms-viewport",
"source_project_id": 14,
"author_id": 51,
"assignee_id": 6,
"title": "MS-Viewport",
"created_at": "2013-12-03T17:23:34Z",
"updated_at": "2013-12-03T17:23:34Z",
"st_commits": null,
"st_diffs": null,
"milestone_id": null,
"state": "opened",
"merge_status": "unchecked",
"target_project_id": 14,
"iid": 1,
"description": ""
}
}

View file

@ -1,42 +0,0 @@
{
"object_kind": "push",
"before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
"after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"ref": "refs/heads/master",
"user_id": 4,
"user_name": "John Smith",
"user_email": "john@example.com",
"project_id": 15,
"repository": {
"name": "Diaspora",
"url": "git@example.com:mike/diasporadiaspora.git",
"description": "",
"homepage": "http://example.com/mike/diaspora",
"git_http_url":"http://example.com/mike/diaspora.git",
"git_ssh_url":"git@example.com:mike/diaspora.git",
"visibility_level":0
},
"commits": [
{
"id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
"message": "Update Catalan translation to e38cb41.",
"timestamp": "2011-12-12T14:27:31+02:00",
"url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
"author": {
"name": "Jordi Mallach",
"email": "jordi@softcatala.org"
}
},
{
"id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"message": "fixed readme",
"timestamp": "2012-01-03T23:36:29+02:00",
"url": "http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"author": {
"name": "GitLab dev user",
"email": "gitlabdev@dv6700.(none)"
}
}
],
"total_commits_count": 4
}

View file

@ -1,9 +0,0 @@
{
"id": 1,
"url": "http://example.com/hook",
"project_id": 3,
"push_events": "true",
"issues_events": "true",
"merge_requests_events": "true",
"created_at": "2012-10-12T17:04:47Z"
}

View file

@ -1,50 +0,0 @@
[
{
"name": "async",
"commit": {
"id": "a2b702edecdf41f07b42653eb1abe30ce98b9fca",
"parents": [
{
"id": "3f94fc7c85061973edc9906ae170cc269b07ca55"
}
],
"tree": "c68537c6534a02cc2b176ca1549f4ffa190b58ee",
"message": "give caolan credit where it's due (up top)",
"author": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
"committer": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
"authored_date": "2010-12-08T21:28:50+00:00",
"committed_date": "2010-12-08T21:28:50+00:00"
},
"protected": false
},
{
"name": "gh-pages",
"commit": {
"id": "101c10a60019fe870d21868835f65c25d64968fc",
"parents": [
{
"id": "9c15d2e26945a665131af5d7b6d30a06ba338aaa"
}
],
"tree": "fb5cc9d45da3014b17a876ad539976a0fb9b352a",
"message": "Underscore.js 1.5.2",
"author": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
"committer": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
"authored_date": "2013-09-07T12: 58: 21+00: 00",
"committed_date": "2013-09-07T12: 58: 21+00: 00"
},
"protected": false
}
]

View file

@ -1,72 +0,0 @@
[
{
"id": 4,
"description": null,
"default_branch": "master",
"public": false,
"visibility_level": 0,
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
"http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
"web_url": "http://example.com/diaspora/diaspora-client",
"owner": {
"id": 3,
"name": "Diaspora",
"created_at": "2013-09-30T13: 46: 02Z"
},
"name": "Diaspora Client",
"name_with_namespace": "Diaspora / Diaspora Client",
"path": "diaspora-client",
"path_with_namespace": "diaspora/diaspora-client",
"issues_enabled": true,
"merge_requests_enabled": true,
"wall_enabled": false,
"wiki_enabled": true,
"snippets_enabled": false,
"created_at": "2013-09-30T13: 46: 02Z",
"last_activity_at": "2013-09-30T13: 46: 02Z",
"namespace": {
"created_at": "2013-09-30T13: 46: 02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
"updated_at": "2013-09-30T13: 46: 02Z"
}
},
{
"id": 6,
"description": null,
"default_branch": "master",
"public": false,
"visibility_level": 0,
"ssh_url_to_repo": "git@example.com:brightbox/puppet.git",
"http_url_to_repo": "http://example.com/brightbox/puppet.git",
"web_url": "http://example.com/brightbox/puppet",
"owner": {
"id": 4,
"name": "Brightbox",
"created_at": "2013-09-30T13:46:02Z"
},
"name": "Puppet",
"name_with_namespace": "Brightbox / Puppet",
"path": "puppet",
"path_with_namespace": "brightbox/puppet",
"issues_enabled": true,
"merge_requests_enabled": true,
"wall_enabled": false,
"wiki_enabled": true,
"snippets_enabled": false,
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
"namespace": {
"created_at": "2013-09-30T13:46:02Z",
"description": "",
"id": 4,
"name": "Brightbox",
"owner_id": 1,
"path": "brightbox",
"updated_at": "2013-09-30T13:46:02Z"
}
}
]

View file

@ -1,30 +0,0 @@
[
{
"id": 1,
"iid": 1,
"target_branch": "master",
"source_branch": "test1",
"project_id": 3,
"title": "test1",
"state": "opened",
"upvotes": 0,
"downvotes": 0,
"author": {
"id": 1,
"username": "admin",
"email": "admin@example.com",
"name": "Administrator",
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
},
"assignee": {
"id": 1,
"username": "admin",
"email": "admin@example.com",
"name": "Administrator",
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
},
"description":"fixed login page css paddings"
}
]

View file

@ -1,16 +0,0 @@
[
{
"id": 301,
"body": "Comment for MR",
"attachment": null,
"author": {
"id": 1,
"username": "pipin",
"email": "admin@example.com",
"name": "Pip",
"state": "active",
"created_at": "2013-09-30T13:46:01Z"
},
"created_at": "2013-10-02T08:57:14Z"
}
]

View file

@ -1,45 +0,0 @@
{
"id": 3,
"description": null,
"default_branch": "master",
"public": false,
"visibility_level": 0,
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git",
"http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
"web_url": "http://example.com/diaspora/diaspora-project-site",
"owner": {
"id": 3,
"name": "Diaspora",
"created_at": "2013-09-30T13: 46: 02Z"
},
"name": "Diaspora Project Site",
"name_with_namespace": "Diaspora / Diaspora Project Site",
"path": "diaspora-project-site",
"path_with_namespace": "diaspora/diaspora-project-site",
"issues_enabled": true,
"merge_requests_enabled": true,
"wall_enabled": false,
"wiki_enabled": true,
"snippets_enabled": false,
"created_at": "2013-09-30T13: 46: 02Z",
"last_activity_at": "2013-09-30T13: 46: 02Z",
"namespace": {
"created_at": "2013-09-30T13: 46: 02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
"updated_at": "2013-09-30T13: 46: 02Z"
},
"permissions": {
"project_access": {
"access_level": 10,
"notification_level": 3
},
"group_access": {
"access_level": 50,
"notification_level": 3
}
}
}

View file

@ -1,12 +0,0 @@
[
{
"id": 1,
"title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
},
{
"id": 3,
"title": "Another Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
}
]

View file

@ -1,5 +0,0 @@
{
"id": 1,
"title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
}

View file

@ -1,23 +0,0 @@
{
"id": 1,
"username": "john_smith",
"email": "john@example.com",
"name": "John Smith",
"avatar_url": "http://someurl.com/avatar.png",
"state": "active",
"created_at": "2012-05-23T08:00:58Z",
"bio": "somebio",
"skype": "someskype",
"linkedin": "somelinkedin",
"twitter": "sometwitter",
"website_url": "http://example.com",
"theme_id": 1,
"color_scheme_id": 1,
"extern_uid": "someuid",
"provider": "github.com",
"is_admin": false,
"can_create_group": true,
"can_create_team": true,
"can_create_project": true,
"private_token": "dd34asd13as"
}

View file

@ -1,22 +0,0 @@
[
{
"name": "v1.0.0",
"commit": {
"id": "2695effb5807a22ff3d138d593fd856244e155e7",
"parents": [],
"tree": "38017f2f189336fe4497e9d230c5bb1bf873f08d",
"message": "Initial commit",
"author": {
"name": "John Smith",
"email": "john@example.com"
},
"committer": {
"name": "Jack Smith",
"email": "jack@example.com"
},
"authored_date": "2012-05-28T04:42:42-07:00",
"committed_date": "2012-05-28T04:42:42-07:00"
},
"protected": null
}
]

View file

@ -1,19 +0,0 @@
{
"id": 1,
"username": "john_smith",
"email": "john@example.com",
"name": "John Smith",
"private_token": "dd34asd13as",
"state": "active",
"created_at": "2012-05-23T08:00:58Z",
"bio": null,
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
"theme_id": 1,
"color_scheme_id": 2,
"is_admin": false,
"can_create_group": true,
"can_create_project": true
}

View file

@ -1,41 +0,0 @@
[
{
"id": 1,
"username": "john_smith",
"email": "john@example.com",
"name": "John Smith",
"state": "active",
"created_at": "2012-05-23T08:00:58Z",
"bio": null,
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
"extern_uid": "john.smith",
"provider": "provider_name",
"theme_id": 1,
"color_scheme_id": 2,
"is_admin": false,
"can_create_group": true
},
{
"id": 2,
"username": "jack_smith",
"email": "jack@example.com",
"name": "Jack Smith",
"state": "blocked",
"created_at": "2012-05-23T08:01:01Z",
"bio": null,
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
"extern_uid": "jack.smith",
"provider": "provider_name",
"theme_id": 1,
"color_scheme_id": 3,
"is_admin": false,
"can_create_group": true,
"can_create_project": true
}
]

View file

@ -1,15 +0,0 @@
{
"id": 6,
"username": "plouc",
"email": "plouc@plouc.com",
"name": "Raphaël Benitte",
"bio": null,
"skype": "",
"linkedin": "",
"twitter": "",
"theme_id": 2,
"state": "active",
"created_at": "2001-01-01T00:00:00Z",
"extern_uid": "uid=plouc",
"provider": "ldap"
}

View file

@ -1,97 +0,0 @@
package gogitlab
import (
"encoding/json"
"strconv"
)
const (
users_url = "/users" // Get users list
user_url = "/users/:id" // Get a single user.
current_user_url = "/user" // Get current user
)
type User struct {
Id int `json:"id,omitempty"`
Username string `json:"username,omitempty"`
Email string `json:"email,omitempty"`
AvatarUrl string `json:"avatar_url,omitempty"`
Name string `json:"name,omitempty"`
State string `json:"state,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
Bio string `json:"bio,omitempty"`
Skype string `json:"skype,omitempty"`
LinkedIn string `json:"linkedin,omitempty"`
Twitter string `json:"twitter,omitempty"`
ExternUid string `json:"extern_uid,omitempty"`
Provider string `json:"provider,omitempty"`
ThemeId int `json:"theme_id,omitempty"`
ColorSchemeId int `json:"color_scheme_id,color_scheme_id"`
}
func (g *Gitlab) Users(page int, per_page int) ([]*User, error) {
qry := map[string]string{
"page": strconv.Itoa(page),
"per_page": strconv.Itoa(per_page)}
url := g.ResourceUrlQuery(users_url, nil, qry)
var users []*User
contents, err := g.buildAndExecRequest("GET", url, nil)
if err == nil {
err = json.Unmarshal(contents, &users)
}
return users, err
}
/*
Get a single user.
GET /users/:id
Parameters:
id The ID of a user
Usage:
user, err := gitlab.User("your_user_id")
if err != nil {
fmt.Println(err.Error())
}
fmt.Printf("%+v\n", user)
*/
func (g *Gitlab) User(id string) (*User, error) {
url := g.ResourceUrl(user_url, map[string]string{":id": id})
user := new(User)
contents, err := g.buildAndExecRequest("GET", url, nil)
if err == nil {
err = json.Unmarshal(contents, &user)
}
return user, err
}
func (g *Gitlab) DeleteUser(id string) error {
url := g.ResourceUrl(user_url, map[string]string{":id": id})
var err error
_, err = g.buildAndExecRequest("DELETE", url, nil)
return err
}
func (g *Gitlab) CurrentUser() (User, error) {
url := g.ResourceUrl(current_user_url, nil)
var user User
contents, err := g.buildAndExecRequest("GET", url, nil)
if err == nil {
err = json.Unmarshal(contents, &user)
}
return user, err
}

View file

@ -1,53 +0,0 @@
package gogitlab
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestUsers(t *testing.T) {
ts, gitlab := Stub("stubs/users/index.json")
users, err := gitlab.Users(0, 100)
assert.Equal(t, err, nil)
assert.Equal(t, len(users), 2)
defer ts.Close()
}
func TestUser(t *testing.T) {
ts, gitlab := Stub("stubs/users/show.json")
user, err := gitlab.User("plouc")
assert.Equal(t, err, nil)
assert.IsType(t, new(User), user)
assert.Equal(t, user.Id, 6)
assert.Equal(t, user.Username, "plouc")
assert.Equal(t, user.Name, "Raphaël Benitte")
assert.Equal(t, user.Bio, "")
assert.Equal(t, user.Skype, "")
assert.Equal(t, user.LinkedIn, "")
assert.Equal(t, user.Twitter, "")
assert.Equal(t, user.ThemeId, 2)
assert.Equal(t, user.State, "active")
assert.Equal(t, user.CreatedAt, "2001-01-01T00:00:00Z")
assert.Equal(t, user.ExternUid, "uid=plouc")
assert.Equal(t, user.Provider, "ldap")
defer ts.Close()
}
func TestDeleteUser(t *testing.T) {
ts, gitlab := Stub("")
err := gitlab.DeleteUser("1")
assert.Equal(t, err, nil)
defer ts.Close()
}
func TestCurrentUser(t *testing.T) {
ts, gitlab := Stub("stubs/users/current.json")
user, err := gitlab.CurrentUser()
assert.Equal(t, err, nil)
assert.Equal(t, user.Username, "john_smith")
defer ts.Close()
}

View file

@ -1,10 +0,0 @@
package gogitlab
import (
"net/url"
"strings"
)
func encodeParameter(value string) string {
return strings.Replace(url.QueryEscape(value), "/", "%2F", 0)
}

View file

@ -1,11 +0,0 @@
package gogitlab
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestParameterEncoding(t *testing.T) {
assert.Equal(t, encodeParameter("namespace/project"), "namespace%2Fproject")
assert.Equal(t, encodeParameter("14"), "14")
}

View file

@ -1,53 +0,0 @@
package logrus
import (
"bytes"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestEntryPanicln(t *testing.T) {
errBoom := fmt.Errorf("boom time")
defer func() {
p := recover()
assert.NotNil(t, p)
switch pVal := p.(type) {
case *Entry:
assert.Equal(t, "kaboom", pVal.Message)
assert.Equal(t, errBoom, pVal.Data["err"])
default:
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
}
}()
logger := New()
logger.Out = &bytes.Buffer{}
entry := NewEntry(logger)
entry.WithField("err", errBoom).Panicln("kaboom")
}
func TestEntryPanicf(t *testing.T) {
errBoom := fmt.Errorf("boom again")
defer func() {
p := recover()
assert.NotNil(t, p)
switch pVal := p.(type) {
case *Entry:
assert.Equal(t, "kaboom true", pVal.Message)
assert.Equal(t, errBoom, pVal.Data["err"])
default:
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
}
}()
logger := New()
logger.Out = &bytes.Buffer{}
entry := NewEntry(logger)
entry.WithField("err", errBoom).Panicf("kaboom %v", true)
}

View file

@ -1,40 +0,0 @@
package main
import (
"github.com/Sirupsen/logrus"
)
var log = logrus.New()
func init() {
log.Formatter = new(logrus.JSONFormatter)
log.Formatter = new(logrus.TextFormatter) // default
}
func main() {
defer func() {
err := recover()
if err != nil {
log.WithFields(logrus.Fields{
"omg": true,
"err": err,
"number": 100,
}).Fatal("The ice breaks!")
}
}()
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"animal": "orca",
"size": 9009,
}).Panic("It's over 9000!")
}

View file

@ -1,35 +0,0 @@
package main
import (
"github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/airbrake"
"github.com/tobi/airbrake-go"
)
var log = logrus.New()
func init() {
log.Formatter = new(logrus.TextFormatter) // default
log.Hooks.Add(new(logrus_airbrake.AirbrakeHook))
}
func main() {
airbrake.Endpoint = "https://exceptions.whatever.com/notifier_api/v2/notices.xml"
airbrake.ApiKey = "whatever"
airbrake.Environment = "production"
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"omg": true,
"number": 100,
}).Fatal("The ice breaks!")
}

View file

@ -1,88 +0,0 @@
package logrus
import (
"testing"
"time"
)
// smallFields is a small size data set for benchmarking
var smallFields = Fields{
"foo": "bar",
"baz": "qux",
"one": "two",
"three": "four",
}
// largeFields is a large size data set for benchmarking
var largeFields = Fields{
"foo": "bar",
"baz": "qux",
"one": "two",
"three": "four",
"five": "six",
"seven": "eight",
"nine": "ten",
"eleven": "twelve",
"thirteen": "fourteen",
"fifteen": "sixteen",
"seventeen": "eighteen",
"nineteen": "twenty",
"a": "b",
"c": "d",
"e": "f",
"g": "h",
"i": "j",
"k": "l",
"m": "n",
"o": "p",
"q": "r",
"s": "t",
"u": "v",
"w": "x",
"y": "z",
"this": "will",
"make": "thirty",
"entries": "yeah",
}
func BenchmarkSmallTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{DisableColors: true}, smallFields)
}
func BenchmarkLargeTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{DisableColors: true}, largeFields)
}
func BenchmarkSmallColoredTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{ForceColors: true}, smallFields)
}
func BenchmarkLargeColoredTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{ForceColors: true}, largeFields)
}
func BenchmarkSmallJSONFormatter(b *testing.B) {
doBenchmark(b, &JSONFormatter{}, smallFields)
}
func BenchmarkLargeJSONFormatter(b *testing.B) {
doBenchmark(b, &JSONFormatter{}, largeFields)
}
func doBenchmark(b *testing.B, formatter Formatter, fields Fields) {
entry := &Entry{
Time: time.Time{},
Level: InfoLevel,
Message: "message",
Data: fields,
}
var d []byte
var err error
for i := 0; i < b.N; i++ {
d, err = formatter.Format(entry)
if err != nil {
b.Fatal(err)
}
b.SetBytes(int64(len(d)))
}
}

View file

@ -1,122 +0,0 @@
package logrus
import (
"testing"
"github.com/stretchr/testify/assert"
)
type TestHook struct {
Fired bool
}
func (hook *TestHook) Fire(entry *Entry) error {
hook.Fired = true
return nil
}
func (hook *TestHook) Levels() []Level {
return []Level{
DebugLevel,
InfoLevel,
WarnLevel,
ErrorLevel,
FatalLevel,
PanicLevel,
}
}
func TestHookFires(t *testing.T) {
hook := new(TestHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
assert.Equal(t, hook.Fired, false)
log.Print("test")
}, func(fields Fields) {
assert.Equal(t, hook.Fired, true)
})
}
type ModifyHook struct {
}
func (hook *ModifyHook) Fire(entry *Entry) error {
entry.Data["wow"] = "whale"
return nil
}
func (hook *ModifyHook) Levels() []Level {
return []Level{
DebugLevel,
InfoLevel,
WarnLevel,
ErrorLevel,
FatalLevel,
PanicLevel,
}
}
func TestHookCanModifyEntry(t *testing.T) {
hook := new(ModifyHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
log.WithField("wow", "elephant").Print("test")
}, func(fields Fields) {
assert.Equal(t, fields["wow"], "whale")
})
}
func TestCanFireMultipleHooks(t *testing.T) {
hook1 := new(ModifyHook)
hook2 := new(TestHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook1)
log.Hooks.Add(hook2)
log.WithField("wow", "elephant").Print("test")
}, func(fields Fields) {
assert.Equal(t, fields["wow"], "whale")
assert.Equal(t, hook2.Fired, true)
})
}
type ErrorHook struct {
Fired bool
}
func (hook *ErrorHook) Fire(entry *Entry) error {
hook.Fired = true
return nil
}
func (hook *ErrorHook) Levels() []Level {
return []Level{
ErrorLevel,
}
}
func TestErrorHookShouldntFireOnInfo(t *testing.T) {
hook := new(ErrorHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
log.Info("test")
}, func(fields Fields) {
assert.Equal(t, hook.Fired, false)
})
}
func TestErrorHookShouldFireOnError(t *testing.T) {
hook := new(ErrorHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
log.Error("test")
}, func(fields Fields) {
assert.Equal(t, hook.Fired, true)
})
}

View file

@ -1,54 +0,0 @@
package logrus_airbrake
import (
"github.com/Sirupsen/logrus"
"github.com/tobi/airbrake-go"
)
// AirbrakeHook to send exceptions to an exception-tracking service compatible
// with the Airbrake API. You must set:
// * airbrake.Endpoint
// * airbrake.ApiKey
// * airbrake.Environment (only sends exceptions when set to "production")
//
// Before using this hook, to send an error. Entries that trigger an Error,
// Fatal or Panic should now include an "error" field to send to Airbrake.
type AirbrakeHook struct{}
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
if entry.Data["error"] == nil {
entry.Logger.WithFields(logrus.Fields{
"source": "airbrake",
"endpoint": airbrake.Endpoint,
}).Warn("Exceptions sent to Airbrake must have an 'error' key with the error")
return nil
}
err, ok := entry.Data["error"].(error)
if !ok {
entry.Logger.WithFields(logrus.Fields{
"source": "airbrake",
"endpoint": airbrake.Endpoint,
}).Warn("Exceptions sent to Airbrake must have an `error` key of type `error`")
return nil
}
airErr := airbrake.Notify(err)
if airErr != nil {
entry.Logger.WithFields(logrus.Fields{
"source": "airbrake",
"endpoint": airbrake.Endpoint,
"error": airErr,
}).Warn("Failed to send error to Airbrake")
}
return nil
}
func (hook *AirbrakeHook) Levels() []logrus.Level {
return []logrus.Level{
logrus.ErrorLevel,
logrus.FatalLevel,
logrus.PanicLevel,
}
}

View file

@ -1,28 +0,0 @@
# Papertrail Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
[Papertrail](https://papertrailapp.com) provides hosted log management. Once stored in Papertrail, you can [group](http://help.papertrailapp.com/kb/how-it-works/groups/) your logs on various dimensions, [search](http://help.papertrailapp.com/kb/how-it-works/search-syntax) them, and trigger [alerts](http://help.papertrailapp.com/kb/how-it-works/alerts).
In most deployments, you'll want to send logs to Papertrail via their [remote_syslog](http://help.papertrailapp.com/kb/configuration/configuring-centralized-logging-from-text-log-files-in-unix/) daemon, which requires no application-specific configuration. This hook is intended for relatively low-volume logging, likely in managed cloud hosting deployments where installing `remote_syslog` is not possible.
## Usage
You can find your Papertrail UDP port on your [Papertrail account page](https://papertrailapp.com/account/destinations). Substitute it below for `YOUR_PAPERTRAIL_UDP_PORT`.
For `YOUR_APP_NAME`, substitute a short string that will readily identify your application or service in the logs.
```go
import (
"log/syslog"
"github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/papertrail"
)
func main() {
log := logrus.New()
hook, err := logrus_papertrail.NewPapertrailHook("logs.papertrailapp.com", YOUR_PAPERTRAIL_UDP_PORT, YOUR_APP_NAME)
if err == nil {
log.Hooks.Add(hook)
}
}
```

View file

@ -1,55 +0,0 @@
package logrus_papertrail
import (
"fmt"
"net"
"os"
"time"
"github.com/Sirupsen/logrus"
)
const (
format = "Jan 2 15:04:05"
)
// PapertrailHook to send logs to a logging service compatible with the Papertrail API.
type PapertrailHook struct {
Host string
Port int
AppName string
UDPConn net.Conn
}
// NewPapertrailHook creates a hook to be added to an instance of logger.
func NewPapertrailHook(host string, port int, appName string) (*PapertrailHook, error) {
conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", host, port))
return &PapertrailHook{host, port, appName, conn}, err
}
// Fire is called when a log event is fired.
func (hook *PapertrailHook) Fire(entry *logrus.Entry) error {
date := time.Now().Format(format)
msg, _ := entry.String()
payload := fmt.Sprintf("<22> %s %s: %s", date, hook.AppName, msg)
bytesWritten, err := hook.UDPConn.Write([]byte(payload))
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to send log line to Papertrail via UDP. Wrote %d bytes before error: %v", bytesWritten, err)
return err
}
return nil
}
// Levels returns the available logging levels.
func (hook *PapertrailHook) Levels() []logrus.Level {
return []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
logrus.WarnLevel,
logrus.InfoLevel,
logrus.DebugLevel,
}
}

View file

@ -1,26 +0,0 @@
package logrus_papertrail
import (
"fmt"
"testing"
"github.com/Sirupsen/logrus"
"github.com/stvp/go-udp-testing"
)
func TestWritingToUDP(t *testing.T) {
port := 16661
udp.SetAddr(fmt.Sprintf(":%d", port))
hook, err := NewPapertrailHook("localhost", port, "test")
if err != nil {
t.Errorf("Unable to connect to local UDP server.")
}
log := logrus.New()
log.Hooks.Add(hook)
udp.ShouldReceive(t, "foo", func() {
log.Info("foo")
})
}

View file

@ -1,61 +0,0 @@
# Sentry Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
[Sentry](https://getsentry.com) provides both self-hosted and hosted
solutions for exception tracking.
Both client and server are
[open source](https://github.com/getsentry/sentry).
## Usage
Every sentry application defined on the server gets a different
[DSN](https://www.getsentry.com/docs/). In the example below replace
`YOUR_DSN` with the one created for your application.
```go
import (
"github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/sentry"
)
func main() {
log := logrus.New()
hook, err := logrus_sentry.NewSentryHook(YOUR_DSN, []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
})
if err == nil {
log.Hooks.Add(hook)
}
}
```
## Special fields
Some logrus fields have a special meaning in this hook,
these are server_name and logger.
When logs are sent to sentry these fields are treated differently.
- server_name (also known as hostname) is the name of the server which
is logging the event (hostname.example.com)
- logger is the part of the application which is logging the event.
In go this usually means setting it to the name of the package.
## Timeout
`Timeout` is the time the sentry hook will wait for a response
from the sentry server.
If this time elapses with no response from
the server an error will be returned.
If `Timeout` is set to 0 the SentryHook will not wait for a reply
and will assume a correct delivery.
The SentryHook has a default timeout of `100 milliseconds` when created
with a call to `NewSentryHook`. This can be changed by assigning a value to the `Timeout` field:
```go
hook, _ := logrus_sentry.NewSentryHook(...)
hook.Timeout = 20*time.Seconds
```

View file

@ -1,100 +0,0 @@
package logrus_sentry
import (
"fmt"
"time"
"github.com/Sirupsen/logrus"
"github.com/getsentry/raven-go"
)
var (
severityMap = map[logrus.Level]raven.Severity{
logrus.DebugLevel: raven.DEBUG,
logrus.InfoLevel: raven.INFO,
logrus.WarnLevel: raven.WARNING,
logrus.ErrorLevel: raven.ERROR,
logrus.FatalLevel: raven.FATAL,
logrus.PanicLevel: raven.FATAL,
}
)
func getAndDel(d logrus.Fields, key string) (string, bool) {
var (
ok bool
v interface{}
val string
)
if v, ok = d[key]; !ok {
return "", false
}
if val, ok = v.(string); !ok {
return "", false
}
delete(d, key)
return val, true
}
// SentryHook delivers logs to a sentry server.
type SentryHook struct {
// Timeout sets the time to wait for a delivery error from the sentry server.
// If this is set to zero the server will not wait for any response and will
// consider the message correctly sent
Timeout time.Duration
client *raven.Client
levels []logrus.Level
}
// NewSentryHook creates a hook to be added to an instance of logger
// and initializes the raven client.
// This method sets the timeout to 100 milliseconds.
func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
client, err := raven.NewClient(DSN, nil)
if err != nil {
return nil, err
}
return &SentryHook{100 * time.Millisecond, client, levels}, nil
}
// Called when an event should be sent to sentry
// Special fields that sentry uses to give more information to the server
// are extracted from entry.Data (if they are found)
// These fields are: logger and server_name
func (hook *SentryHook) Fire(entry *logrus.Entry) error {
packet := &raven.Packet{
Message: entry.Message,
Timestamp: raven.Timestamp(entry.Time),
Level: severityMap[entry.Level],
Platform: "go",
}
d := entry.Data
if logger, ok := getAndDel(d, "logger"); ok {
packet.Logger = logger
}
if serverName, ok := getAndDel(d, "server_name"); ok {
packet.ServerName = serverName
}
packet.Extra = map[string]interface{}(d)
_, errCh := hook.client.Capture(packet, nil)
timeout := hook.Timeout
if timeout != 0 {
timeoutCh := time.After(timeout)
select {
case err := <-errCh:
return err
case <-timeoutCh:
return fmt.Errorf("no response from sentry server in %s", timeout)
}
}
return nil
}
// Levels returns the available logging levels.
func (hook *SentryHook) Levels() []logrus.Level {
return hook.levels
}

View file

@ -1,97 +0,0 @@
package logrus_sentry
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/Sirupsen/logrus"
"github.com/getsentry/raven-go"
)
const (
message = "error message"
server_name = "testserver.internal"
logger_name = "test.logger"
)
func getTestLogger() *logrus.Logger {
l := logrus.New()
l.Out = ioutil.Discard
return l
}
func WithTestDSN(t *testing.T, tf func(string, <-chan *raven.Packet)) {
pch := make(chan *raven.Packet, 1)
s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
defer req.Body.Close()
d := json.NewDecoder(req.Body)
p := &raven.Packet{}
err := d.Decode(p)
if err != nil {
t.Fatal(err.Error())
}
pch <- p
}))
defer s.Close()
fragments := strings.SplitN(s.URL, "://", 2)
dsn := fmt.Sprintf(
"%s://public:secret@%s/sentry/project-id",
fragments[0],
fragments[1],
)
tf(dsn, pch)
}
func TestSpecialFields(t *testing.T) {
WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) {
logger := getTestLogger()
hook, err := NewSentryHook(dsn, []logrus.Level{
logrus.ErrorLevel,
})
if err != nil {
t.Fatal(err.Error())
}
logger.Hooks.Add(hook)
logger.WithFields(logrus.Fields{
"server_name": server_name,
"logger": logger_name,
}).Error(message)
packet := <-pch
if packet.Logger != logger_name {
t.Errorf("logger should have been %s, was %s", logger_name, packet.Logger)
}
if packet.ServerName != server_name {
t.Errorf("server_name should have been %s, was %s", server_name, packet.ServerName)
}
})
}
func TestSentryHandler(t *testing.T) {
WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) {
logger := getTestLogger()
hook, err := NewSentryHook(dsn, []logrus.Level{
logrus.ErrorLevel,
})
if err != nil {
t.Fatal(err.Error())
}
logger.Hooks.Add(hook)
logger.Error(message)
packet := <-pch
if packet.Message != message {
t.Errorf("message should have been %s, was %s", message, packet.Message)
}
})
}

View file

@ -1,20 +0,0 @@
# Syslog Hooks for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>
## Usage
```go
import (
"log/syslog"
"github.com/Sirupsen/logrus"
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
)
func main() {
log := logrus.New()
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err == nil {
log.Hooks.Add(hook)
}
}
```

View file

@ -1,59 +0,0 @@
package logrus_syslog
import (
"fmt"
"github.com/Sirupsen/logrus"
"log/syslog"
"os"
)
// SyslogHook to send logs via syslog.
type SyslogHook struct {
Writer *syslog.Writer
SyslogNetwork string
SyslogRaddr string
}
// Creates a hook to be added to an instance of logger. This is called with
// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")`
// `if err == nil { log.Hooks.Add(hook) }`
func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) {
w, err := syslog.Dial(network, raddr, priority, tag)
return &SyslogHook{w, network, raddr}, err
}
func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
line, err := entry.String()
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err)
return err
}
switch entry.Level {
case logrus.PanicLevel:
return hook.Writer.Crit(line)
case logrus.FatalLevel:
return hook.Writer.Crit(line)
case logrus.ErrorLevel:
return hook.Writer.Err(line)
case logrus.WarnLevel:
return hook.Writer.Warning(line)
case logrus.InfoLevel:
return hook.Writer.Info(line)
case logrus.DebugLevel:
return hook.Writer.Debug(line)
default:
return nil
}
}
func (hook *SyslogHook) Levels() []logrus.Level {
return []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
logrus.WarnLevel,
logrus.InfoLevel,
logrus.DebugLevel,
}
}

View file

@ -1,26 +0,0 @@
package logrus_syslog
import (
"github.com/Sirupsen/logrus"
"log/syslog"
"testing"
)
func TestLocalhostAddAndPrint(t *testing.T) {
log := logrus.New()
hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err != nil {
t.Errorf("Unable to connect to local syslog.")
}
log.Hooks.Add(hook)
for _, level := range hook.Levels() {
if len(log.Hooks[level]) != 1 {
t.Errorf("SyslogHook was not added. The length of log.Hooks[%v]: %v", level, len(log.Hooks[level]))
}
}
log.Info("Congratulations!")
}

View file

@ -1,283 +0,0 @@
package logrus
import (
"bytes"
"encoding/json"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) {
var buffer bytes.Buffer
var fields Fields
logger := New()
logger.Out = &buffer
logger.Formatter = new(JSONFormatter)
log(logger)
err := json.Unmarshal(buffer.Bytes(), &fields)
assert.Nil(t, err)
assertions(fields)
}
func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields map[string]string)) {
var buffer bytes.Buffer
logger := New()
logger.Out = &buffer
logger.Formatter = &TextFormatter{
DisableColors: true,
}
log(logger)
fields := make(map[string]string)
for _, kv := range strings.Split(buffer.String(), " ") {
if !strings.Contains(kv, "=") {
continue
}
kvArr := strings.Split(kv, "=")
key := strings.TrimSpace(kvArr[0])
val := kvArr[1]
if kvArr[1][0] == '"' {
var err error
val, err = strconv.Unquote(val)
assert.NoError(t, err)
}
fields[key] = val
}
assertions(fields)
}
func TestPrint(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Print("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
assert.Equal(t, fields["level"], "info")
})
}
func TestInfo(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
assert.Equal(t, fields["level"], "info")
})
}
func TestWarn(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Warn("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
assert.Equal(t, fields["level"], "warning")
})
}
func TestInfolnShouldAddSpacesBetweenStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Infoln("test", "test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test test")
})
}
func TestInfolnShouldAddSpacesBetweenStringAndNonstring(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Infoln("test", 10)
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test 10")
})
}
func TestInfolnShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Infoln(10, 10)
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "10 10")
})
}
func TestInfoShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Infoln(10, 10)
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "10 10")
})
}
func TestInfoShouldNotAddSpacesBetweenStringAndNonstring(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Info("test", 10)
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test10")
})
}
func TestInfoShouldNotAddSpacesBetweenStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Info("test", "test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "testtest")
})
}
func TestWithFieldsShouldAllowAssignments(t *testing.T) {
var buffer bytes.Buffer
var fields Fields
logger := New()
logger.Out = &buffer
logger.Formatter = new(JSONFormatter)
localLog := logger.WithFields(Fields{
"key1": "value1",
})
localLog.WithField("key2", "value2").Info("test")
err := json.Unmarshal(buffer.Bytes(), &fields)
assert.Nil(t, err)
assert.Equal(t, "value2", fields["key2"])
assert.Equal(t, "value1", fields["key1"])
buffer = bytes.Buffer{}
fields = Fields{}
localLog.Info("test")
err = json.Unmarshal(buffer.Bytes(), &fields)
assert.Nil(t, err)
_, ok := fields["key2"]
assert.Equal(t, false, ok)
assert.Equal(t, "value1", fields["key1"])
}
func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("msg", "hello").Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
})
}
func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("msg", "hello").Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
assert.Equal(t, fields["fields.msg"], "hello")
})
}
func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("time", "hello").Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["fields.time"], "hello")
})
}
func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("level", 1).Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["level"], "info")
assert.Equal(t, fields["fields.level"], 1)
})
}
func TestDefaultFieldsAreNotPrefixed(t *testing.T) {
LogAndAssertText(t, func(log *Logger) {
ll := log.WithField("herp", "derp")
ll.Info("hello")
ll.Info("bye")
}, func(fields map[string]string) {
for _, fieldName := range []string{"fields.level", "fields.time", "fields.msg"} {
if _, ok := fields[fieldName]; ok {
t.Fatalf("should not have prefixed %q: %v", fieldName, fields)
}
}
})
}
func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) {
var buffer bytes.Buffer
var fields Fields
logger := New()
logger.Out = &buffer
logger.Formatter = new(JSONFormatter)
llog := logger.WithField("context", "eating raw fish")
llog.Info("looks delicious")
err := json.Unmarshal(buffer.Bytes(), &fields)
assert.NoError(t, err, "should have decoded first message")
assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields")
assert.Equal(t, fields["msg"], "looks delicious")
assert.Equal(t, fields["context"], "eating raw fish")
buffer.Reset()
llog.Warn("omg it is!")
err = json.Unmarshal(buffer.Bytes(), &fields)
assert.NoError(t, err, "should have decoded second message")
assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields")
assert.Equal(t, fields["msg"], "omg it is!")
assert.Equal(t, fields["context"], "eating raw fish")
assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry")
}
func TestConvertLevelToString(t *testing.T) {
assert.Equal(t, "debug", DebugLevel.String())
assert.Equal(t, "info", InfoLevel.String())
assert.Equal(t, "warning", WarnLevel.String())
assert.Equal(t, "error", ErrorLevel.String())
assert.Equal(t, "fatal", FatalLevel.String())
assert.Equal(t, "panic", PanicLevel.String())
}
func TestParseLevel(t *testing.T) {
l, err := ParseLevel("panic")
assert.Nil(t, err)
assert.Equal(t, PanicLevel, l)
l, err = ParseLevel("fatal")
assert.Nil(t, err)
assert.Equal(t, FatalLevel, l)
l, err = ParseLevel("error")
assert.Nil(t, err)
assert.Equal(t, ErrorLevel, l)
l, err = ParseLevel("warn")
assert.Nil(t, err)
assert.Equal(t, WarnLevel, l)
l, err = ParseLevel("warning")
assert.Nil(t, err)
assert.Equal(t, WarnLevel, l)
l, err = ParseLevel("info")
assert.Nil(t, err)
assert.Equal(t, InfoLevel, l)
l, err = ParseLevel("debug")
assert.Nil(t, err)
assert.Equal(t, DebugLevel, l)
l, err = ParseLevel("invalid")
assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error())
}

View file

@ -1,33 +0,0 @@
package logrus
import (
"bytes"
"errors"
"testing"
)
func TestQuoting(t *testing.T) {
tf := &TextFormatter{DisableColors: true}
checkQuoting := func(q bool, value interface{}) {
b, _ := tf.Format(WithField("test", value))
idx := bytes.Index(b, ([]byte)("test="))
cont := bytes.Contains(b[idx+5:], []byte{'"'})
if cont != q {
if q {
t.Errorf("quoting expected for: %#v", value)
} else {
t.Errorf("quoting not expected for: %#v", value)
}
}
}
checkQuoting(false, "abcd")
checkQuoting(false, "v1.0")
checkQuoting(true, "/foobar")
checkQuoting(true, "x y")
checkQuoting(true, "x,y")
checkQuoting(false, errors.New("invalid"))
checkQuoting(true, errors.New("invalid argument"))
}

View file

@ -1,21 +0,0 @@
Copyright (C) 2013 Jeremy Saenz
All Rights Reserved.
MIT LICENSE
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,298 +0,0 @@
[![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/codegangsta/cli)
# cli.go
cli.go is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way.
You can view the API docs here:
http://godoc.org/github.com/codegangsta/cli
## Overview
Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app.
**This is where cli.go comes into play.** cli.go makes command line programming fun, organized, and expressive!
## Installation
Make sure you have a working Go environment (go 1.1 is *required*). [See the install instructions](http://golang.org/doc/install.html).
To install `cli.go`, simply run:
```
$ go get github.com/codegangsta/cli
```
Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used:
```
export PATH=$PATH:$GOPATH/bin
```
## Getting Started
One of the philosophies behind cli.go is that an API should be playful and full of discovery. So a cli.go app can be as little as one line of code in `main()`.
``` go
package main
import (
"os"
"github.com/codegangsta/cli"
)
func main() {
cli.NewApp().Run(os.Args)
}
```
This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation:
``` go
package main
import (
"os"
"github.com/codegangsta/cli"
)
func main() {
app := cli.NewApp()
app.Name = "boom"
app.Usage = "make an explosive entrance"
app.Action = func(c *cli.Context) {
println("boom! I say!")
}
app.Run(os.Args)
}
```
Running this already gives you a ton of functionality, plus support for things like subcommands and flags, which are covered below.
## Example
Being a programmer can be a lonely job. Thankfully by the power of automation that is not the case! Let's create a greeter app to fend off our demons of loneliness!
Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it:
``` go
package main
import (
"os"
"github.com/codegangsta/cli"
)
func main() {
app := cli.NewApp()
app.Name = "greet"
app.Usage = "fight the loneliness!"
app.Action = func(c *cli.Context) {
println("Hello friend!")
}
app.Run(os.Args)
}
```
Install our command to the `$GOPATH/bin` directory:
```
$ go install
```
Finally run our new command:
```
$ greet
Hello friend!
```
cli.go also generates some bitchass help text:
```
$ greet help
NAME:
greet - fight the loneliness!
USAGE:
greet [global options] command [command options] [arguments...]
VERSION:
0.0.0
COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS
--version Shows version information
```
### Arguments
You can lookup arguments by calling the `Args` function on `cli.Context`.
``` go
...
app.Action = func(c *cli.Context) {
println("Hello", c.Args()[0])
}
...
```
### Flags
Setting and querying flags is simple.
``` go
...
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
},
}
app.Action = func(c *cli.Context) {
name := "someone"
if len(c.Args()) > 0 {
name = c.Args()[0]
}
if c.String("lang") == "spanish" {
println("Hola", name)
} else {
println("Hello", name)
}
}
...
```
#### Alternate Names
You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
},
}
```
That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error.
#### Values from the Environment
You can also have the default value set from the environment via `EnvVar`. e.g.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
EnvVar: "APP_LANG",
},
}
```
The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG",
},
}
```
### Subcommands
Subcommands can be defined for a more git-like command line app.
```go
...
app.Commands = []cli.Command{
{
Name: "add",
ShortName: "a",
Usage: "add a task to the list",
Action: func(c *cli.Context) {
println("added task: ", c.Args().First())
},
},
{
Name: "complete",
ShortName: "c",
Usage: "complete a task on the list",
Action: func(c *cli.Context) {
println("completed task: ", c.Args().First())
},
},
{
Name: "template",
ShortName: "r",
Usage: "options for task templates",
Subcommands: []cli.Command{
{
Name: "add",
Usage: "add a new template",
Action: func(c *cli.Context) {
println("new task template: ", c.Args().First())
},
},
{
Name: "remove",
Usage: "remove an existing template",
Action: func(c *cli.Context) {
println("removed task template: ", c.Args().First())
},
},
},
},
}
...
```
### Bash Completion
You can enable completion commands by setting the `EnableBashCompletion`
flag on the `App` object. By default, this setting will only auto-complete to
show an app's subcommands, but you can write your own completion methods for
the App or its subcommands.
```go
...
var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"}
app := cli.NewApp()
app.EnableBashCompletion = true
app.Commands = []cli.Command{
{
Name: "complete",
ShortName: "c",
Usage: "complete a task on the list",
Action: func(c *cli.Context) {
println("completed task: ", c.Args().First())
},
BashComplete: func(c *cli.Context) {
// This will complete if no args are passed
if len(c.Args()) > 0 {
return
}
for _, t := range tasks {
fmt.Println(t)
}
},
}
}
...
```
#### To Enable
Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while
setting the `PROG` variable to the name of your program:
`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete`
## Contribution Guidelines
Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch.
If you are have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together.
If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out.

View file

@ -1,275 +0,0 @@
package cli
import (
"fmt"
"io"
"io/ioutil"
"os"
"text/tabwriter"
"text/template"
"time"
)
// App is the main structure of a cli application. It is recomended that
// and app be created with the cli.NewApp() function
type App struct {
// The name of the program. Defaults to os.Args[0]
Name string
// Description of the program.
Usage string
// Version of the program
Version string
// List of commands to execute
Commands []Command
// List of flags to parse
Flags []Flag
// Boolean to enable bash completion commands
EnableBashCompletion bool
// Boolean to hide built-in help command
HideHelp bool
// Boolean to hide built-in version flag
HideVersion bool
// An action to execute when the bash-completion flag is set
BashComplete func(context *Context)
// An action to execute before any subcommands are run, but after the context is ready
// If a non-nil error is returned, no subcommands are run
Before func(context *Context) error
// The action to execute when no subcommands are specified
Action func(context *Context)
// Execute this function if the proper command cannot be found
CommandNotFound func(context *Context, command string)
// Compilation date
Compiled time.Time
// Author
Author string
// Author e-mail
Email string
// Writer writer to write output to
Writer io.Writer
}
// Tries to find out when this binary was compiled.
// Returns the current time if it fails to find it.
func compileTime() time.Time {
info, err := os.Stat(os.Args[0])
if err != nil {
return time.Now()
}
return info.ModTime()
}
// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action.
func NewApp() *App {
return &App{
Name: os.Args[0],
Usage: "A new cli application",
Version: "0.0.0",
BashComplete: DefaultAppComplete,
Action: helpCommand.Action,
Compiled: compileTime(),
Author: "Author",
Email: "unknown@email",
Writer: os.Stdout,
}
}
// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
func (a *App) Run(arguments []string) error {
if HelpPrinter == nil {
defer func() {
HelpPrinter = nil
}()
HelpPrinter = func(templ string, data interface{}) {
w := tabwriter.NewWriter(a.Writer, 0, 8, 1, '\t', 0)
t := template.Must(template.New("help").Parse(templ))
err := t.Execute(w, data)
if err != nil {
panic(err)
}
w.Flush()
}
}
// append help to commands
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand)
a.appendFlag(HelpFlag)
}
//append version/help flags
if a.EnableBashCompletion {
a.appendFlag(BashCompletionFlag)
}
if !a.HideVersion {
a.appendFlag(VersionFlag)
}
// parse flags
set := flagSet(a.Name, a.Flags)
set.SetOutput(ioutil.Discard)
err := set.Parse(arguments[1:])
nerr := normalizeFlags(a.Flags, set)
if nerr != nil {
fmt.Fprintln(a.Writer, nerr)
context := NewContext(a, set, set)
ShowAppHelp(context)
fmt.Fprintln(a.Writer)
return nerr
}
context := NewContext(a, set, set)
if err != nil {
fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n")
ShowAppHelp(context)
fmt.Fprintln(a.Writer)
return err
}
if checkCompletions(context) {
return nil
}
if checkHelp(context) {
return nil
}
if checkVersion(context) {
return nil
}
if a.Before != nil {
err := a.Before(context)
if err != nil {
return err
}
}
args := context.Args()
if args.Present() {
name := args.First()
c := a.Command(name)
if c != nil {
return c.Run(context)
}
}
// Run default Action
a.Action(context)
return nil
}
// Another entry point to the cli app, takes care of passing arguments and error handling
func (a *App) RunAndExitOnError() {
if err := a.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
func (a *App) RunAsSubcommand(ctx *Context) error {
// append help to commands
if len(a.Commands) > 0 {
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand)
a.appendFlag(HelpFlag)
}
}
// append flags
if a.EnableBashCompletion {
a.appendFlag(BashCompletionFlag)
}
// parse flags
set := flagSet(a.Name, a.Flags)
set.SetOutput(ioutil.Discard)
err := set.Parse(ctx.Args().Tail())
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, ctx.globalSet)
if nerr != nil {
fmt.Fprintln(a.Writer, nerr)
if len(a.Commands) > 0 {
ShowSubcommandHelp(context)
} else {
ShowCommandHelp(ctx, context.Args().First())
}
fmt.Fprintln(a.Writer)
return nerr
}
if err != nil {
fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n")
ShowSubcommandHelp(context)
return err
}
if checkCompletions(context) {
return nil
}
if len(a.Commands) > 0 {
if checkSubcommandHelp(context) {
return nil
}
} else {
if checkCommandHelp(ctx, context.Args().First()) {
return nil
}
}
if a.Before != nil {
err := a.Before(context)
if err != nil {
return err
}
}
args := context.Args()
if args.Present() {
name := args.First()
c := a.Command(name)
if c != nil {
return c.Run(context)
}
}
// Run default Action
if len(a.Commands) > 0 {
a.Action(context)
} else {
a.Action(ctx)
}
return nil
}
// Returns the named command on App. Returns nil if the command does not exist
func (a *App) Command(name string) *Command {
for _, c := range a.Commands {
if c.HasName(name) {
return &c
}
}
return nil
}
func (a *App) hasFlag(flag Flag) bool {
for _, f := range a.Flags {
if flag == f {
return true
}
}
return false
}
func (a *App) appendFlag(flag Flag) {
if !a.hasFlag(flag) {
a.Flags = append(a.Flags, flag)
}
}

View file

@ -1,467 +0,0 @@
package cli_test
import (
"fmt"
"os"
"testing"
"github.com/codegangsta/cli"
)
func ExampleApp() {
// set args for examples sake
os.Args = []string{"greet", "--name", "Jeremy"}
app := cli.NewApp()
app.Name = "greet"
app.Flags = []cli.Flag{
cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
}
app.Action = func(c *cli.Context) {
fmt.Printf("Hello %v\n", c.String("name"))
}
app.Run(os.Args)
// Output:
// Hello Jeremy
}
func ExampleAppSubcommand() {
// set args for examples sake
os.Args = []string{"say", "hi", "english", "--name", "Jeremy"}
app := cli.NewApp()
app.Name = "say"
app.Commands = []cli.Command{
{
Name: "hello",
ShortName: "hi",
Usage: "use it to see a description",
Description: "This is how we describe hello the function",
Subcommands: []cli.Command{
{
Name: "english",
ShortName: "en",
Usage: "sends a greeting in english",
Description: "greets someone in english",
Flags: []cli.Flag{
cli.StringFlag{
Name: "name",
Value: "Bob",
Usage: "Name of the person to greet",
},
},
Action: func(c *cli.Context) {
fmt.Println("Hello,", c.String("name"))
},
},
},
},
}
app.Run(os.Args)
// Output:
// Hello, Jeremy
}
func ExampleAppHelp() {
// set args for examples sake
os.Args = []string{"greet", "h", "describeit"}
app := cli.NewApp()
app.Name = "greet"
app.Flags = []cli.Flag{
cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
}
app.Commands = []cli.Command{
{
Name: "describeit",
ShortName: "d",
Usage: "use it to see a description",
Description: "This is how we describe describeit the function",
Action: func(c *cli.Context) {
fmt.Printf("i like to describe things")
},
},
}
app.Run(os.Args)
// Output:
// NAME:
// describeit - use it to see a description
//
// USAGE:
// command describeit [arguments...]
//
// DESCRIPTION:
// This is how we describe describeit the function
}
func ExampleAppBashComplete() {
// set args for examples sake
os.Args = []string{"greet", "--generate-bash-completion"}
app := cli.NewApp()
app.Name = "greet"
app.EnableBashCompletion = true
app.Commands = []cli.Command{
{
Name: "describeit",
ShortName: "d",
Usage: "use it to see a description",
Description: "This is how we describe describeit the function",
Action: func(c *cli.Context) {
fmt.Printf("i like to describe things")
},
}, {
Name: "next",
Usage: "next example",
Description: "more stuff to see when generating bash completion",
Action: func(c *cli.Context) {
fmt.Printf("the next example")
},
},
}
app.Run(os.Args)
// Output:
// describeit
// d
// next
// help
// h
}
func TestApp_Run(t *testing.T) {
s := ""
app := cli.NewApp()
app.Action = func(c *cli.Context) {
s = s + c.Args().First()
}
err := app.Run([]string{"command", "foo"})
expect(t, err, nil)
err = app.Run([]string{"command", "bar"})
expect(t, err, nil)
expect(t, s, "foobar")
}
var commandAppTests = []struct {
name string
expected bool
}{
{"foobar", true},
{"batbaz", true},
{"b", true},
{"f", true},
{"bat", false},
{"nothing", false},
}
func TestApp_Command(t *testing.T) {
app := cli.NewApp()
fooCommand := cli.Command{Name: "foobar", ShortName: "f"}
batCommand := cli.Command{Name: "batbaz", ShortName: "b"}
app.Commands = []cli.Command{
fooCommand,
batCommand,
}
for _, test := range commandAppTests {
expect(t, app.Command(test.name) != nil, test.expected)
}
}
func TestApp_CommandWithArgBeforeFlags(t *testing.T) {
var parsedOption, firstArg string
app := cli.NewApp()
command := cli.Command{
Name: "cmd",
Flags: []cli.Flag{
cli.StringFlag{Name: "option", Value: "", Usage: "some option"},
},
Action: func(c *cli.Context) {
parsedOption = c.String("option")
firstArg = c.Args().First()
},
}
app.Commands = []cli.Command{command}
app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"})
expect(t, parsedOption, "my-option")
expect(t, firstArg, "my-arg")
}
func TestApp_Float64Flag(t *testing.T) {
var meters float64
app := cli.NewApp()
app.Flags = []cli.Flag{
cli.Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"},
}
app.Action = func(c *cli.Context) {
meters = c.Float64("height")
}
app.Run([]string{"", "--height", "1.93"})
expect(t, meters, 1.93)
}
func TestApp_ParseSliceFlags(t *testing.T) {
var parsedOption, firstArg string
var parsedIntSlice []int
var parsedStringSlice []string
app := cli.NewApp()
command := cli.Command{
Name: "cmd",
Flags: []cli.Flag{
cli.IntSliceFlag{Name: "p", Value: &cli.IntSlice{}, Usage: "set one or more ip addr"},
cli.StringSliceFlag{Name: "ip", Value: &cli.StringSlice{}, Usage: "set one or more ports to open"},
},
Action: func(c *cli.Context) {
parsedIntSlice = c.IntSlice("p")
parsedStringSlice = c.StringSlice("ip")
parsedOption = c.String("option")
firstArg = c.Args().First()
},
}
app.Commands = []cli.Command{command}
app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"})
IntsEquals := func(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
StrsEquals := func(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
var expectedIntSlice = []int{22, 80}
var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"}
if !IntsEquals(parsedIntSlice, expectedIntSlice) {
t.Errorf("%v does not match %v", parsedIntSlice, expectedIntSlice)
}
if !StrsEquals(parsedStringSlice, expectedStringSlice) {
t.Errorf("%v does not match %v", parsedStringSlice, expectedStringSlice)
}
}
func TestApp_DefaultStdout(t *testing.T) {
app := cli.NewApp()
if app.Writer != os.Stdout {
t.Error("Default output writer not set.")
}
}
type mockWriter struct {
written []byte
}
func (fw *mockWriter) Write(p []byte) (n int, err error) {
if fw.written == nil {
fw.written = p
} else {
fw.written = append(fw.written, p...)
}
return len(p), nil
}
func (fw *mockWriter) GetWritten() (b []byte) {
return fw.written
}
func TestApp_SetStdout(t *testing.T) {
w := &mockWriter{}
app := cli.NewApp()
app.Name = "test"
app.Writer = w
err := app.Run([]string{"help"})
if err != nil {
t.Fatalf("Run error: %s", err)
}
if len(w.written) == 0 {
t.Error("App did not write output to desired writer.")
}
}
func TestApp_BeforeFunc(t *testing.T) {
beforeRun, subcommandRun := false, false
beforeError := fmt.Errorf("fail")
var err error
app := cli.NewApp()
app.Before = func(c *cli.Context) error {
beforeRun = true
s := c.String("opt")
if s == "fail" {
return beforeError
}
return nil
}
app.Commands = []cli.Command{
cli.Command{
Name: "sub",
Action: func(c *cli.Context) {
subcommandRun = true
},
},
}
app.Flags = []cli.Flag{
cli.StringFlag{Name: "opt"},
}
// run with the Before() func succeeding
err = app.Run([]string{"command", "--opt", "succeed", "sub"})
if err != nil {
t.Fatalf("Run error: %s", err)
}
if beforeRun == false {
t.Errorf("Before() not executed when expected")
}
if subcommandRun == false {
t.Errorf("Subcommand not executed when expected")
}
// reset
beforeRun, subcommandRun = false, false
// run with the Before() func failing
err = app.Run([]string{"command", "--opt", "fail", "sub"})
// should be the same error produced by the Before func
if err != beforeError {
t.Errorf("Run error expected, but not received")
}
if beforeRun == false {
t.Errorf("Before() not executed when expected")
}
if subcommandRun == true {
t.Errorf("Subcommand executed when NOT expected")
}
}
func TestAppHelpPrinter(t *testing.T) {
oldPrinter := cli.HelpPrinter
defer func() {
cli.HelpPrinter = oldPrinter
}()
var wasCalled = false
cli.HelpPrinter = func(template string, data interface{}) {
wasCalled = true
}
app := cli.NewApp()
app.Run([]string{"-h"})
if wasCalled == false {
t.Errorf("Help printer expected to be called, but was not")
}
}
func TestAppVersionPrinter(t *testing.T) {
oldPrinter := cli.VersionPrinter
defer func() {
cli.VersionPrinter = oldPrinter
}()
var wasCalled = false
cli.VersionPrinter = func(c *cli.Context) {
wasCalled = true
}
app := cli.NewApp()
ctx := cli.NewContext(app, nil, nil)
cli.ShowVersion(ctx)
if wasCalled == false {
t.Errorf("Version printer expected to be called, but was not")
}
}
func TestAppCommandNotFound(t *testing.T) {
beforeRun, subcommandRun := false, false
app := cli.NewApp()
app.CommandNotFound = func(c *cli.Context, command string) {
beforeRun = true
}
app.Commands = []cli.Command{
cli.Command{
Name: "bar",
Action: func(c *cli.Context) {
subcommandRun = true
},
},
}
app.Run([]string{"command", "foo"})
expect(t, beforeRun, true)
expect(t, subcommandRun, false)
}
func TestGlobalFlagsInSubcommands(t *testing.T) {
subcommandRun := false
app := cli.NewApp()
app.Flags = []cli.Flag{
cli.BoolFlag{Name: "debug, d", Usage: "Enable debugging"},
}
app.Commands = []cli.Command{
cli.Command{
Name: "foo",
Subcommands: []cli.Command{
{
Name: "bar",
Action: func(c *cli.Context) {
if c.GlobalBool("debug") {
subcommandRun = true
}
},
},
},
},
}
app.Run([]string{"command", "-d", "foo", "bar"})
expect(t, subcommandRun, true)
}

View file

@ -1,13 +0,0 @@
#! /bin/bash
_cli_bash_autocomplete() {
local cur prev opts base
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
}
complete -F _cli_bash_autocomplete $PROG

View file

@ -1,5 +0,0 @@
autoload -U compinit && compinit
autoload -U bashcompinit && bashcompinit
script_dir=$(dirname $0)
source ${script_dir}/bash_autocomplete

View file

@ -1,19 +0,0 @@
// Package cli provides a minimal framework for creating and organizing command line
// Go applications. cli is designed to be easy to understand and write, the most simple
// cli application can be written as follows:
// func main() {
// cli.NewApp().Run(os.Args)
// }
//
// Of course this application does not do much, so let's make this an actual application:
// func main() {
// app := cli.NewApp()
// app.Name = "greet"
// app.Usage = "say a greeting"
// app.Action = func(c *cli.Context) {
// println("Greetings")
// }
//
// app.Run(os.Args)
// }
package cli

View file

@ -1,100 +0,0 @@
package cli_test
import (
"os"
"github.com/codegangsta/cli"
)
func Example() {
app := cli.NewApp()
app.Name = "todo"
app.Usage = "task list on the command line"
app.Commands = []cli.Command{
{
Name: "add",
ShortName: "a",
Usage: "add a task to the list",
Action: func(c *cli.Context) {
println("added task: ", c.Args().First())
},
},
{
Name: "complete",
ShortName: "c",
Usage: "complete a task on the list",
Action: func(c *cli.Context) {
println("completed task: ", c.Args().First())
},
},
}
app.Run(os.Args)
}
func ExampleSubcommand() {
app := cli.NewApp()
app.Name = "say"
app.Commands = []cli.Command{
{
Name: "hello",
ShortName: "hi",
Usage: "use it to see a description",
Description: "This is how we describe hello the function",
Subcommands: []cli.Command{
{
Name: "english",
ShortName: "en",
Usage: "sends a greeting in english",
Description: "greets someone in english",
Flags: []cli.Flag{
cli.StringFlag{
Name: "name",
Value: "Bob",
Usage: "Name of the person to greet",
},
},
Action: func(c *cli.Context) {
println("Hello, ", c.String("name"))
},
}, {
Name: "spanish",
ShortName: "sp",
Usage: "sends a greeting in spanish",
Flags: []cli.Flag{
cli.StringFlag{
Name: "surname",
Value: "Jones",
Usage: "Surname of the person to greet",
},
},
Action: func(c *cli.Context) {
println("Hola, ", c.String("surname"))
},
}, {
Name: "french",
ShortName: "fr",
Usage: "sends a greeting in french",
Flags: []cli.Flag{
cli.StringFlag{
Name: "nickname",
Value: "Stevie",
Usage: "Nickname of the person to greet",
},
},
Action: func(c *cli.Context) {
println("Bonjour, ", c.String("nickname"))
},
},
},
}, {
Name: "bye",
Usage: "says goodbye",
Action: func(c *cli.Context) {
println("bye")
},
},
}
app.Run(os.Args)
}

Some files were not shown because too many files have changed in this diff Show more