├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── MAINTAINERS ├── MAINTAINERS_GUIDE.md ├── Makefile ├── NOTICE ├── PRINCIPLES.md ├── README.md ├── ROADMAP.md ├── SPEC.md ├── apparmor ├── apparmor.go └── apparmor_disabled.go ├── capabilities_linux.go ├── cgroups ├── cgroups.go ├── cgroups_test.go ├── cgroups_unsupported.go ├── fs │ ├── apply_raw.go │ ├── blkio.go │ ├── blkio_test.go │ ├── cpu.go │ ├── cpu_test.go │ ├── cpuacct.go │ ├── cpuset.go │ ├── cpuset_test.go │ ├── devices.go │ ├── devices_test.go │ ├── freezer.go │ ├── freezer_test.go │ ├── fs_unsupported.go │ ├── hugetlb.go │ ├── hugetlb_test.go │ ├── memory.go │ ├── memory_test.go │ ├── net_cls.go │ ├── net_cls_test.go │ ├── net_prio.go │ ├── net_prio_test.go │ ├── perf_event.go │ ├── stats_util_test.go │ ├── util_test.go │ ├── utils.go │ └── utils_test.go ├── stats.go ├── systemd │ ├── apply_nosystemd.go │ └── apply_systemd.go └── utils.go ├── configs ├── cgroup.go ├── config.go ├── config_test.go ├── config_unix.go ├── device.go ├── device_defaults.go ├── hugepage_limit.go ├── interface_priority_map.go ├── mount.go ├── namespaces.go ├── namespaces_syscall.go ├── namespaces_syscall_unsupported.go ├── namespaces_unix.go ├── namespaces_windows.go ├── network.go └── validate │ └── config.go ├── console.go ├── console_freebsd.go ├── console_linux.go ├── console_windows.go ├── container.go ├── container_linux.go ├── container_linux_test.go ├── container_nouserns_linux.go ├── container_userns_linux.go ├── criu_opts.go ├── criurpc ├── Makefile ├── criurpc.pb.go └── criurpc.proto ├── devices ├── devices_test.go ├── devices_unix.go ├── devices_windows.go └── number.go ├── docs └── man │ └── nsinit.1.md ├── error.go ├── error_test.go ├── factory.go ├── factory_linux.go ├── factory_linux_test.go ├── generic_error.go ├── generic_error_test.go ├── hack └── validate.sh ├── init_linux.go ├── integration ├── checkpoint_test.go ├── doc.go ├── exec_test.go ├── execin_test.go ├── init_test.go ├── template_test.go └── utils_test.go ├── label ├── label.go ├── label_selinux.go └── label_selinux_test.go ├── netlink ├── MAINTAINERS ├── netlink.go ├── netlink_linux.go ├── netlink_linux_armppc64.go ├── netlink_linux_notarm.go ├── netlink_linux_test.go └── netlink_unsupported.go ├── network_linux.go ├── notify_linux.go ├── notify_linux_test.go ├── nsenter ├── README.md ├── nsenter.go ├── nsenter_gccgo.go ├── nsenter_test.go ├── nsenter_unsupported.go └── nsexec.c ├── nsinit ├── Makefile ├── README.md ├── checkpoint.go ├── config.go ├── exec.go ├── init.go ├── main.go ├── oom.go ├── pause.go ├── restore.go ├── security.go ├── state.go ├── stats.go ├── tty.go └── utils.go ├── process.go ├── process_linux.go ├── restored_process.go ├── rootfs_linux.go ├── rootfs_linux_test.go ├── sample_configs ├── README.md ├── apparmor.json ├── attach_to_bridge.json ├── host-pid.json ├── minimal.json ├── selinux.json └── userns.json ├── seccomp ├── bpf.go ├── context.go ├── filter.go ├── jump_amd64.go └── seccomp.go ├── selinux ├── selinux.go └── selinux_test.go ├── setns_init_linux.go ├── stacktrace ├── capture.go ├── capture_test.go ├── frame.go ├── frame_test.go └── stacktrace.go ├── standard_init_linux.go ├── stats.go ├── stats_freebsd.go ├── stats_linux.go ├── stats_windows.go ├── system ├── linux.go ├── proc.go ├── setns_linux.go ├── syscall_linux_386.go ├── syscall_linux_64.go ├── syscall_linux_arm.go ├── sysconfig.go ├── sysconfig_notcgo.go └── xattrs_linux.go ├── update-vendor.sh ├── user ├── MAINTAINERS ├── lookup.go ├── lookup_unix.go ├── lookup_unsupported.go ├── user.go └── user_test.go ├── utils ├── utils.go └── utils_test.go ├── vendor └── src │ └── github.com │ ├── Sirupsen │ └── logrus │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── entry.go │ │ ├── entry_test.go │ │ ├── examples │ │ ├── basic │ │ │ └── basic.go │ │ └── hook │ │ │ └── hook.go │ │ ├── exported.go │ │ ├── formatter.go │ │ ├── formatter_bench_test.go │ │ ├── formatters │ │ └── logstash │ │ │ ├── logstash.go │ │ │ └── logstash_test.go │ │ ├── hook_test.go │ │ ├── hooks.go │ │ ├── hooks │ │ ├── airbrake │ │ │ ├── airbrake.go │ │ │ └── airbrake_test.go │ │ ├── bugsnag │ │ │ ├── bugsnag.go │ │ │ └── bugsnag_test.go │ │ ├── papertrail │ │ │ ├── README.md │ │ │ ├── papertrail.go │ │ │ └── papertrail_test.go │ │ ├── sentry │ │ │ ├── README.md │ │ │ ├── sentry.go │ │ │ └── sentry_test.go │ │ └── syslog │ │ │ ├── README.md │ │ │ ├── syslog.go │ │ │ └── syslog_test.go │ │ ├── json_formatter.go │ │ ├── json_formatter_test.go │ │ ├── logger.go │ │ ├── logrus.go │ │ ├── logrus_test.go │ │ ├── terminal_darwin.go │ │ ├── terminal_freebsd.go │ │ ├── terminal_linux.go │ │ ├── terminal_notwindows.go │ │ ├── terminal_openbsd.go │ │ ├── terminal_windows.go │ │ ├── text_formatter.go │ │ ├── text_formatter_test.go │ │ └── writer.go │ ├── codegangsta │ └── cli │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── app.go │ │ ├── app_test.go │ │ ├── autocomplete │ │ └── bash_autocomplete │ │ ├── cli.go │ │ ├── cli_test.go │ │ ├── command.go │ │ ├── command_test.go │ │ ├── context.go │ │ ├── context_test.go │ │ ├── flag.go │ │ ├── flag_test.go │ │ ├── help.go │ │ └── helpers_test.go │ ├── coreos │ └── go-systemd │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── activation │ │ ├── files.go │ │ ├── files_test.go │ │ ├── listeners.go │ │ └── listeners_test.go │ │ ├── dbus │ │ ├── dbus.go │ │ ├── dbus_test.go │ │ ├── methods.go │ │ ├── methods_test.go │ │ ├── properties.go │ │ ├── set.go │ │ ├── set_test.go │ │ ├── subscription.go │ │ ├── subscription_set.go │ │ ├── subscription_set_test.go │ │ └── subscription_test.go │ │ ├── examples │ │ └── activation │ │ │ ├── activation.go │ │ │ ├── httpserver │ │ │ ├── README.md │ │ │ ├── hello.service │ │ │ ├── hello.socket │ │ │ └── httpserver.go │ │ │ └── listen.go │ │ ├── fixtures │ │ ├── enable-disable.service │ │ ├── start-stop.service │ │ ├── subscribe-events-set.service │ │ └── subscribe-events.service │ │ ├── journal │ │ └── send.go │ │ ├── login1 │ │ ├── dbus.go │ │ └── dbus_test.go │ │ └── test │ ├── godbus │ └── dbus │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── MAINTAINERS │ │ ├── README.markdown │ │ ├── _examples │ │ ├── eavesdrop.go │ │ ├── introspect.go │ │ ├── list-names.go │ │ ├── notification.go │ │ ├── prop.go │ │ ├── server.go │ │ └── signal.go │ │ ├── auth.go │ │ ├── auth_external.go │ │ ├── auth_sha1.go │ │ ├── call.go │ │ ├── conn.go │ │ ├── conn_darwin.go │ │ ├── conn_other.go │ │ ├── conn_test.go │ │ ├── dbus.go │ │ ├── decoder.go │ │ ├── doc.go │ │ ├── encoder.go │ │ ├── examples_test.go │ │ ├── export.go │ │ ├── homedir.go │ │ ├── homedir_dynamic.go │ │ ├── homedir_static.go │ │ ├── introspect │ │ ├── call.go │ │ ├── introspect.go │ │ └── introspectable.go │ │ ├── message.go │ │ ├── prop │ │ └── prop.go │ │ ├── proto_test.go │ │ ├── sig.go │ │ ├── sig_test.go │ │ ├── transport_darwin.go │ │ ├── transport_generic.go │ │ ├── transport_unix.go │ │ ├── transport_unix_test.go │ │ ├── transport_unixcred_dragonfly.go │ │ ├── transport_unixcred_linux.go │ │ ├── variant.go │ │ ├── variant_lexer.go │ │ ├── variant_parser.go │ │ └── variant_test.go │ ├── golang │ └── protobuf │ │ ├── .gitignore │ │ ├── AUTHORS │ │ ├── CONTRIBUTORS │ │ ├── LICENSE │ │ ├── Make.protobuf │ │ ├── Makefile │ │ ├── README │ │ ├── proto │ │ ├── Makefile │ │ ├── all_test.go │ │ ├── clone.go │ │ ├── clone_test.go │ │ ├── decode.go │ │ ├── encode.go │ │ ├── equal.go │ │ ├── equal_test.go │ │ ├── extensions.go │ │ ├── extensions_test.go │ │ ├── lib.go │ │ ├── message_set.go │ │ ├── message_set_test.go │ │ ├── pointer_reflect.go │ │ ├── pointer_unsafe.go │ │ ├── properties.go │ │ ├── proto3_proto │ │ │ └── proto3.proto │ │ ├── proto3_test.go │ │ ├── size2_test.go │ │ ├── size_test.go │ │ ├── testdata │ │ │ ├── Makefile │ │ │ ├── golden_test.go │ │ │ └── test.proto │ │ ├── text.go │ │ ├── text_parser.go │ │ ├── text_parser_test.go │ │ └── text_test.go │ │ └── protoc-gen-go │ │ ├── Makefile │ │ ├── descriptor │ │ ├── Makefile │ │ └── descriptor.pb.golden │ │ ├── doc.go │ │ ├── generator │ │ ├── Makefile │ │ ├── generator.go │ │ └── name_test.go │ │ ├── internal │ │ └── grpc │ │ │ └── grpc.go │ │ ├── link_grpc.go │ │ ├── main.go │ │ ├── plugin │ │ ├── Makefile │ │ └── plugin.pb.golden │ │ └── testdata │ │ ├── Makefile │ │ ├── extension_base.proto │ │ ├── extension_extra.proto │ │ ├── extension_test.go │ │ ├── extension_user.proto │ │ ├── golden_test.go │ │ ├── grpc.proto │ │ ├── imp.pb.go.golden │ │ ├── imp.proto │ │ ├── imp2.proto │ │ ├── imp3.proto │ │ ├── main_test.go │ │ ├── multi │ │ ├── multi1.proto │ │ ├── multi2.proto │ │ └── multi3.proto │ │ ├── my_test │ │ ├── test.pb.go.golden │ │ └── test.proto │ │ └── proto3.proto │ └── syndtr │ └── gocapability │ ├── LICENSE │ └── capability │ ├── capability.go │ ├── capability_linux.go │ ├── capability_noop.go │ ├── capability_test.go │ ├── enum.go │ ├── enum_gen.go │ ├── enumgen │ └── gen.go │ └── syscall_linux.go └── xattr ├── errors.go ├── xattr_linux.go ├── xattr_test.go └── xattr_unsupported.go /.gitignore: -------------------------------------------------------------------------------- 1 | bundles 2 | nsinit/nsinit 3 | vendor/pkg 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.4 2 | 3 | RUN echo "deb http://ftp.us.debian.org/debian testing main contrib" >> /etc/apt/sources.list 4 | RUN apt-get update && apt-get install -y iptables criu=1.5.2-1 && rm -rf /var/lib/apt/lists/* 5 | 6 | RUN go get golang.org/x/tools/cmd/cover 7 | 8 | ENV GOPATH $GOPATH:/go/src/github.com/docker/libcontainer/vendor 9 | RUN go get github.com/docker/docker/pkg/term 10 | 11 | # setup a playground for us to spawn containers in 12 | RUN mkdir /busybox && \ 13 | curl -sSL 'https://github.com/jpetazzo/docker-busybox/raw/buildroot-2014.11/rootfs.tar' | tar -xC /busybox 14 | 15 | RUN curl -sSL https://raw.githubusercontent.com/docker/docker/master/hack/dind -o /dind && \ 16 | chmod +x /dind 17 | 18 | COPY . /go/src/github.com/docker/libcontainer 19 | WORKDIR /go/src/github.com/docker/libcontainer 20 | RUN cp sample_configs/minimal.json /busybox/container.json 21 | 22 | RUN make direct-install 23 | 24 | ENTRYPOINT ["/dind"] 25 | CMD ["make", "direct-test"] 26 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Michael Crosby (@crosbymichael) 2 | Rohit Jnagal (@rjnagal) 3 | Victor Marmol (@vmarmol) 4 | Mrunal Patel (@mrunalp) 5 | Alexandr Morozov (@LK4D4) 6 | Daniel, Dao Quang Minh (@dqminh) 7 | update-vendor.sh: Tianon Gravi (@tianon) 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: 3 | docker build -t dockercore/libcontainer . 4 | 5 | test: 6 | # we need NET_ADMIN for the netlink tests and SYS_ADMIN for mounting 7 | docker run --rm -it --privileged dockercore/libcontainer 8 | 9 | sh: 10 | docker run --rm -it --privileged -w /busybox dockercore/libcontainer nsinit exec sh 11 | 12 | GO_PACKAGES = $(shell find . -not \( -wholename ./vendor -prune -o -wholename ./.git -prune \) -name '*.go' -print0 | xargs -0n1 dirname | sort -u) 13 | 14 | direct-test: 15 | go test $(TEST_TAGS) -cover -v $(GO_PACKAGES) 16 | 17 | direct-test-short: 18 | go test $(TEST_TAGS) -cover -test.short -v $(GO_PACKAGES) 19 | 20 | direct-build: 21 | go build -v $(GO_PACKAGES) 22 | 23 | direct-install: 24 | go install -v $(GO_PACKAGES) 25 | 26 | local: 27 | go test -v 28 | 29 | validate: 30 | hack/validate.sh 31 | 32 | binary: all 33 | docker run --rm --privileged -v $(CURDIR)/bundles:/go/bin dockercore/libcontainer make direct-install 34 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | libcontainer 2 | Copyright 2012-2015 Docker, Inc. 3 | 4 | This product includes software developed at Docker, Inc. (http://www.docker.com). 5 | 6 | The following is courtesy of our legal counsel: 7 | 8 | 9 | Use and transfer of Docker may be subject to certain restrictions by the 10 | United States and other governments. 11 | It is your responsibility to ensure that your use and/or transfer does not 12 | violate applicable laws. 13 | 14 | For more information, please see http://www.bis.doc.gov 15 | 16 | See also http://www.apache.org/dev/crypto.html and/or seek legal counsel. 17 | -------------------------------------------------------------------------------- /PRINCIPLES.md: -------------------------------------------------------------------------------- 1 | # libcontainer Principles 2 | 3 | In the design and development of libcontainer we try to follow these principles: 4 | 5 | (Work in progress) 6 | 7 | * Don't try to replace every tool. Instead, be an ingredient to improve them. 8 | * Less code is better. 9 | * Fewer components are better. Do you really need to add one more class? 10 | * 50 lines of straightforward, readable code is better than 10 lines of magic that nobody can understand. 11 | * Don't do later what you can do now. "//TODO: refactor" is not acceptable in new code. 12 | * When hesitating between two options, choose the one that is easier to reverse. 13 | * "No" is temporary; "Yes" is forever. If you're not sure about a new feature, say no. You can change your mind later. 14 | * Containers must be portable to the greatest possible number of machines. Be suspicious of any change which makes machines less interchangeable. 15 | * The fewer moving parts in a container, the better. 16 | * Don't merge it unless you document it. 17 | * Don't document it unless you can keep it up-to-date. 18 | * Don't merge it unless you test it! 19 | * Everyone's problem is slightly different. Focus on the part that is the same for everyone, and solve that. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NOTICE 2 | 3 | Please refer to https://github.com/opencontainers/runc/tree/master/libcontainer to make contributions to libcontainer and `runc`. 4 | 5 | For more information about the open container project and foundation please refer to: 6 | 7 | http://blog.docker.com/2015/06/open-container-project-foundation/ 8 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # libcontainer: what's next? 2 | 3 | This document is a high-level overview of where we want to take libcontainer next. 4 | It is a curated selection of planned improvements which are either important, difficult, or both. 5 | 6 | For a more complete view of planned and requested improvements, see [the Github issues](https://github.com/docker/libcontainer/issues). 7 | 8 | To suggest changes to the roadmap, including additions, please write the change as if it were already in effect, and make a pull request. 9 | 10 | ## Broader kernel support 11 | 12 | Our goal is to make libcontainer run everywhere, but currently libcontainer requires Linux version 3.8 or higher. If you’re deploying new machines for the purpose of running libcontainer, this is a fairly easy requirement to meet. However, if you’re adding libcontainer to an existing deployment, you may not have the flexibility to update and patch the kernel. 13 | 14 | ## Cross-architecture support 15 | 16 | Our goal is to make libcontainer run everywhere. Recently libcontainer has 17 | expanded from its initial support for x86_64 systems to include POWER (ppc64 18 | little and big endian variants), IBM System z (s390x 64-bit), and ARM. We plan 19 | to continue expanding architecture support such that libcontainer containers 20 | can be created and used on more architectures. 21 | -------------------------------------------------------------------------------- /apparmor/apparmor.go: -------------------------------------------------------------------------------- 1 | // +build apparmor,linux 2 | 3 | package apparmor 4 | 5 | // #cgo LDFLAGS: -lapparmor 6 | // #include 7 | // #include 8 | import "C" 9 | import ( 10 | "io/ioutil" 11 | "os" 12 | "unsafe" 13 | ) 14 | 15 | func IsEnabled() bool { 16 | if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" { 17 | buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled") 18 | return err == nil && len(buf) > 1 && buf[0] == 'Y' 19 | } 20 | return false 21 | } 22 | 23 | func ApplyProfile(name string) error { 24 | if name == "" { 25 | return nil 26 | } 27 | cName := C.CString(name) 28 | defer C.free(unsafe.Pointer(cName)) 29 | 30 | if _, err := C.aa_change_onexec(cName); err != nil { 31 | return err 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /apparmor/apparmor_disabled.go: -------------------------------------------------------------------------------- 1 | // +build !apparmor !linux 2 | 3 | package apparmor 4 | 5 | func IsEnabled() bool { 6 | return false 7 | } 8 | 9 | func ApplyProfile(name string) error { 10 | return nil 11 | } 12 | -------------------------------------------------------------------------------- /cgroups/cgroups.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package cgroups 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/docker/libcontainer/configs" 9 | ) 10 | 11 | type Manager interface { 12 | // Apply cgroup configuration to the process with the specified pid 13 | Apply(pid int) error 14 | 15 | // Returns the PIDs inside the cgroup set 16 | GetPids() ([]int, error) 17 | 18 | // Returns statistics for the cgroup set 19 | GetStats() (*Stats, error) 20 | 21 | // Toggles the freezer cgroup according with specified state 22 | Freeze(state configs.FreezerState) error 23 | 24 | // Destroys the cgroup set 25 | Destroy() error 26 | 27 | // NewCgroupManager() and LoadCgroupManager() require following attributes: 28 | // Paths map[string]string 29 | // Cgroups *cgroups.Cgroup 30 | // Paths maps cgroup subsystem to path at which it is mounted. 31 | // Cgroups specifies specific cgroup settings for the various subsystems 32 | 33 | // Returns cgroup paths to save in a state file and to be able to 34 | // restore the object later. 35 | GetPaths() map[string]string 36 | 37 | // Set the cgroup as configured. 38 | Set(container *configs.Config) error 39 | } 40 | 41 | type NotFoundError struct { 42 | Subsystem string 43 | } 44 | 45 | func (e *NotFoundError) Error() string { 46 | return fmt.Sprintf("mountpoint for %s not found", e.Subsystem) 47 | } 48 | 49 | func NewNotFoundError(sub string) error { 50 | return &NotFoundError{ 51 | Subsystem: sub, 52 | } 53 | } 54 | 55 | func IsNotFound(err error) bool { 56 | if err == nil { 57 | return false 58 | } 59 | _, ok := err.(*NotFoundError) 60 | return ok 61 | } 62 | -------------------------------------------------------------------------------- /cgroups/cgroups_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package cgroups 4 | 5 | import ( 6 | "bytes" 7 | "testing" 8 | ) 9 | 10 | const ( 11 | cgroupsContents = `11:hugetlb:/ 12 | 10:perf_event:/ 13 | 9:blkio:/ 14 | 8:net_cls:/ 15 | 7:freezer:/ 16 | 6:devices:/ 17 | 5:memory:/ 18 | 4:cpuacct,cpu:/ 19 | 3:cpuset:/ 20 | 2:name=systemd:/user.slice/user-1000.slice/session-16.scope` 21 | ) 22 | 23 | func TestParseCgroups(t *testing.T) { 24 | r := bytes.NewBuffer([]byte(cgroupsContents)) 25 | _, err := ParseCgroupFile("blkio", r) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cgroups/cgroups_unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package cgroups 4 | -------------------------------------------------------------------------------- /cgroups/fs/cpuset_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs 4 | 5 | import ( 6 | "testing" 7 | ) 8 | 9 | func TestCpusetSetCpus(t *testing.T) { 10 | helper := NewCgroupTestUtil("cpuset", t) 11 | defer helper.cleanup() 12 | 13 | const ( 14 | cpusBefore = "0" 15 | cpusAfter = "1-3" 16 | ) 17 | 18 | helper.writeFileContents(map[string]string{ 19 | "cpuset.cpus": cpusBefore, 20 | }) 21 | 22 | helper.CgroupData.c.CpusetCpus = cpusAfter 23 | cpuset := &CpusetGroup{} 24 | if err := cpuset.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { 25 | t.Fatal(err) 26 | } 27 | 28 | value, err := getCgroupParamString(helper.CgroupPath, "cpuset.cpus") 29 | if err != nil { 30 | t.Fatalf("Failed to parse cpuset.cpus - %s", err) 31 | } 32 | 33 | if value != cpusAfter { 34 | t.Fatal("Got the wrong value, set cpuset.cpus failed.") 35 | } 36 | } 37 | 38 | func TestCpusetSetMems(t *testing.T) { 39 | helper := NewCgroupTestUtil("cpuset", t) 40 | defer helper.cleanup() 41 | 42 | const ( 43 | memsBefore = "0" 44 | memsAfter = "1" 45 | ) 46 | 47 | helper.writeFileContents(map[string]string{ 48 | "cpuset.mems": memsBefore, 49 | }) 50 | 51 | helper.CgroupData.c.CpusetMems = memsAfter 52 | cpuset := &CpusetGroup{} 53 | if err := cpuset.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { 54 | t.Fatal(err) 55 | } 56 | 57 | value, err := getCgroupParamString(helper.CgroupPath, "cpuset.mems") 58 | if err != nil { 59 | t.Fatalf("Failed to parse cpuset.mems - %s", err) 60 | } 61 | 62 | if value != memsAfter { 63 | t.Fatal("Got the wrong value, set cpuset.mems failed.") 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /cgroups/fs/devices.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs 4 | 5 | import ( 6 | "github.com/docker/libcontainer/cgroups" 7 | "github.com/docker/libcontainer/configs" 8 | ) 9 | 10 | type DevicesGroup struct { 11 | } 12 | 13 | func (s *DevicesGroup) Apply(d *data) error { 14 | dir, err := d.join("devices") 15 | if err != nil { 16 | // We will return error even it's `not found` error, devices 17 | // cgroup is hard requirement for container's security. 18 | return err 19 | } 20 | 21 | if err := s.Set(dir, d.c); err != nil { 22 | return err 23 | } 24 | 25 | return nil 26 | } 27 | 28 | func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error { 29 | if !cgroup.AllowAllDevices { 30 | if err := writeFile(path, "devices.deny", "a"); err != nil { 31 | return err 32 | } 33 | 34 | for _, dev := range cgroup.AllowedDevices { 35 | if err := writeFile(path, "devices.allow", dev.CgroupString()); err != nil { 36 | return err 37 | } 38 | } 39 | return nil 40 | } 41 | 42 | if err := writeFile(path, "devices.allow", "a"); err != nil { 43 | return err 44 | } 45 | 46 | for _, dev := range cgroup.DeniedDevices { 47 | if err := writeFile(path, "devices.deny", dev.CgroupString()); err != nil { 48 | return err 49 | } 50 | } 51 | 52 | return nil 53 | } 54 | 55 | func (s *DevicesGroup) Remove(d *data) error { 56 | return removePath(d.path("devices")) 57 | } 58 | 59 | func (s *DevicesGroup) GetStats(path string, stats *cgroups.Stats) error { 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /cgroups/fs/devices_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/docker/libcontainer/configs" 9 | ) 10 | 11 | var ( 12 | allowedDevices = []*configs.Device{ 13 | { 14 | Path: "/dev/zero", 15 | Type: 'c', 16 | Major: 1, 17 | Minor: 5, 18 | Permissions: "rwm", 19 | FileMode: 0666, 20 | }, 21 | } 22 | allowedList = "c 1:5 rwm" 23 | deniedDevices = []*configs.Device{ 24 | { 25 | Path: "/dev/null", 26 | Type: 'c', 27 | Major: 1, 28 | Minor: 3, 29 | Permissions: "rwm", 30 | FileMode: 0666, 31 | }, 32 | } 33 | deniedList = "c 1:3 rwm" 34 | ) 35 | 36 | func TestDevicesSetAllow(t *testing.T) { 37 | helper := NewCgroupTestUtil("devices", t) 38 | defer helper.cleanup() 39 | 40 | helper.writeFileContents(map[string]string{ 41 | "devices.deny": "a", 42 | }) 43 | 44 | helper.CgroupData.c.AllowAllDevices = false 45 | helper.CgroupData.c.AllowedDevices = allowedDevices 46 | devices := &DevicesGroup{} 47 | if err := devices.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | value, err := getCgroupParamString(helper.CgroupPath, "devices.allow") 52 | if err != nil { 53 | t.Fatalf("Failed to parse devices.allow - %s", err) 54 | } 55 | 56 | if value != allowedList { 57 | t.Fatal("Got the wrong value, set devices.allow failed.") 58 | } 59 | } 60 | 61 | func TestDevicesSetDeny(t *testing.T) { 62 | helper := NewCgroupTestUtil("devices", t) 63 | defer helper.cleanup() 64 | 65 | helper.writeFileContents(map[string]string{ 66 | "devices.allow": "a", 67 | }) 68 | 69 | helper.CgroupData.c.AllowAllDevices = true 70 | helper.CgroupData.c.DeniedDevices = deniedDevices 71 | devices := &DevicesGroup{} 72 | if err := devices.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { 73 | t.Fatal(err) 74 | } 75 | 76 | value, err := getCgroupParamString(helper.CgroupPath, "devices.deny") 77 | if err != nil { 78 | t.Fatalf("Failed to parse devices.deny - %s", err) 79 | } 80 | 81 | if value != deniedList { 82 | t.Fatal("Got the wrong value, set devices.deny failed.") 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /cgroups/fs/freezer.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | "time" 9 | 10 | "github.com/docker/libcontainer/cgroups" 11 | "github.com/docker/libcontainer/configs" 12 | ) 13 | 14 | type FreezerGroup struct { 15 | } 16 | 17 | func (s *FreezerGroup) Apply(d *data) error { 18 | dir, err := d.join("freezer") 19 | if err != nil && !cgroups.IsNotFound(err) { 20 | return err 21 | } 22 | 23 | if err := s.Set(dir, d.c); err != nil { 24 | return err 25 | } 26 | 27 | return nil 28 | } 29 | 30 | func (s *FreezerGroup) Set(path string, cgroup *configs.Cgroup) error { 31 | switch cgroup.Freezer { 32 | case configs.Frozen, configs.Thawed: 33 | if err := writeFile(path, "freezer.state", string(cgroup.Freezer)); err != nil { 34 | return err 35 | } 36 | 37 | for { 38 | state, err := readFile(path, "freezer.state") 39 | if err != nil { 40 | return err 41 | } 42 | if strings.TrimSpace(state) == string(cgroup.Freezer) { 43 | break 44 | } 45 | time.Sleep(1 * time.Millisecond) 46 | } 47 | case configs.Undefined: 48 | return nil 49 | default: 50 | return fmt.Errorf("Invalid argument '%s' to freezer.state", string(cgroup.Freezer)) 51 | } 52 | 53 | return nil 54 | } 55 | 56 | func (s *FreezerGroup) Remove(d *data) error { 57 | return removePath(d.path("freezer")) 58 | } 59 | 60 | func (s *FreezerGroup) GetStats(path string, stats *cgroups.Stats) error { 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /cgroups/fs/freezer_test.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/docker/libcontainer/configs" 7 | ) 8 | 9 | func TestFreezerSetState(t *testing.T) { 10 | helper := NewCgroupTestUtil("freezer", t) 11 | defer helper.cleanup() 12 | 13 | helper.writeFileContents(map[string]string{ 14 | "freezer.state": string(configs.Frozen), 15 | }) 16 | 17 | helper.CgroupData.c.Freezer = configs.Thawed 18 | freezer := &FreezerGroup{} 19 | if err := freezer.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { 20 | t.Fatal(err) 21 | } 22 | 23 | value, err := getCgroupParamString(helper.CgroupPath, "freezer.state") 24 | if err != nil { 25 | t.Fatalf("Failed to parse freezer.state - %s", err) 26 | } 27 | if value != string(configs.Thawed) { 28 | t.Fatal("Got the wrong value, set freezer.state failed.") 29 | } 30 | } 31 | 32 | func TestFreezerSetInvalidState(t *testing.T) { 33 | helper := NewCgroupTestUtil("freezer", t) 34 | defer helper.cleanup() 35 | 36 | const ( 37 | invalidArg configs.FreezerState = "Invalid" 38 | ) 39 | 40 | helper.CgroupData.c.Freezer = invalidArg 41 | freezer := &FreezerGroup{} 42 | if err := freezer.Set(helper.CgroupPath, helper.CgroupData.c); err == nil { 43 | t.Fatal("Failed to return invalid argument error") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /cgroups/fs/fs_unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package fs 4 | -------------------------------------------------------------------------------- /cgroups/fs/hugetlb.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs 4 | 5 | import ( 6 | "fmt" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/docker/libcontainer/cgroups" 11 | "github.com/docker/libcontainer/configs" 12 | ) 13 | 14 | type HugetlbGroup struct { 15 | } 16 | 17 | func (s *HugetlbGroup) Apply(d *data) error { 18 | dir, err := d.join("hugetlb") 19 | if err != nil && !cgroups.IsNotFound(err) { 20 | return err 21 | } 22 | 23 | if err := s.Set(dir, d.c); err != nil { 24 | return err 25 | } 26 | 27 | return nil 28 | } 29 | 30 | func (s *HugetlbGroup) Set(path string, cgroup *configs.Cgroup) error { 31 | for _, hugetlb := range cgroup.HugetlbLimit { 32 | if err := writeFile(path, strings.Join([]string{"hugetlb", hugetlb.Pagesize, "limit_in_bytes"}, "."), strconv.Itoa(hugetlb.Limit)); err != nil { 33 | return err 34 | } 35 | } 36 | 37 | return nil 38 | } 39 | 40 | func (s *HugetlbGroup) Remove(d *data) error { 41 | return removePath(d.path("hugetlb")) 42 | } 43 | 44 | func (s *HugetlbGroup) GetStats(path string, stats *cgroups.Stats) error { 45 | hugetlbStats := cgroups.HugetlbStats{} 46 | for _, pageSize := range HugePageSizes { 47 | usage := strings.Join([]string{"hugetlb", pageSize, "usage_in_bytes"}, ".") 48 | value, err := getCgroupParamUint(path, usage) 49 | if err != nil { 50 | return fmt.Errorf("failed to parse %s - %v", usage, err) 51 | } 52 | hugetlbStats.Usage = value 53 | 54 | maxUsage := strings.Join([]string{"hugetlb", pageSize, "max_usage_in_bytes"}, ".") 55 | value, err = getCgroupParamUint(path, maxUsage) 56 | if err != nil { 57 | return fmt.Errorf("failed to parse %s - %v", maxUsage, err) 58 | } 59 | hugetlbStats.MaxUsage = value 60 | 61 | failcnt := strings.Join([]string{"hugetlb", pageSize, "failcnt"}, ".") 62 | value, err = getCgroupParamUint(path, failcnt) 63 | if err != nil { 64 | return fmt.Errorf("failed to parse %s - %v", failcnt, err) 65 | } 66 | hugetlbStats.Failcnt = value 67 | 68 | stats.HugetlbStats[pageSize] = hugetlbStats 69 | } 70 | 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /cgroups/fs/net_cls.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "github.com/docker/libcontainer/cgroups" 5 | "github.com/docker/libcontainer/configs" 6 | ) 7 | 8 | type NetClsGroup struct { 9 | } 10 | 11 | func (s *NetClsGroup) Apply(d *data) error { 12 | dir, err := d.join("net_cls") 13 | if err != nil && !cgroups.IsNotFound(err) { 14 | return err 15 | } 16 | 17 | if err := s.Set(dir, d.c); err != nil { 18 | return err 19 | } 20 | 21 | return nil 22 | } 23 | 24 | func (s *NetClsGroup) Set(path string, cgroup *configs.Cgroup) error { 25 | if cgroup.NetClsClassid != "" { 26 | if err := writeFile(path, "net_cls.classid", cgroup.NetClsClassid); err != nil { 27 | return err 28 | } 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func (s *NetClsGroup) Remove(d *data) error { 35 | return removePath(d.path("net_cls")) 36 | } 37 | 38 | func (s *NetClsGroup) GetStats(path string, stats *cgroups.Stats) error { 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /cgroups/fs/net_cls_test.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | const ( 8 | classidBefore = "0x100002" 9 | classidAfter = "0x100001" 10 | ) 11 | 12 | func TestNetClsSetClassid(t *testing.T) { 13 | helper := NewCgroupTestUtil("net_cls", t) 14 | defer helper.cleanup() 15 | 16 | helper.writeFileContents(map[string]string{ 17 | "net_cls.classid": classidBefore, 18 | }) 19 | 20 | helper.CgroupData.c.NetClsClassid = classidAfter 21 | netcls := &NetClsGroup{} 22 | if err := netcls.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { 23 | t.Fatal(err) 24 | } 25 | 26 | // As we are in mock environment, we can't get correct value of classid from 27 | // net_cls.classid. 28 | // So. we just judge if we successfully write classid into file 29 | value, err := getCgroupParamString(helper.CgroupPath, "net_cls.classid") 30 | if err != nil { 31 | t.Fatalf("Failed to parse net_cls.classid - %s", err) 32 | } 33 | if value != classidAfter { 34 | t.Fatal("Got the wrong value, set net_cls.classid failed.") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /cgroups/fs/net_prio.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "github.com/docker/libcontainer/cgroups" 5 | "github.com/docker/libcontainer/configs" 6 | ) 7 | 8 | type NetPrioGroup struct { 9 | } 10 | 11 | func (s *NetPrioGroup) Apply(d *data) error { 12 | dir, err := d.join("net_prio") 13 | if err != nil && !cgroups.IsNotFound(err) { 14 | return err 15 | } 16 | 17 | if err := s.Set(dir, d.c); err != nil { 18 | return err 19 | } 20 | 21 | return nil 22 | } 23 | 24 | func (s *NetPrioGroup) Set(path string, cgroup *configs.Cgroup) error { 25 | for _, prioMap := range cgroup.NetPrioIfpriomap { 26 | if err := writeFile(path, "net_prio.ifpriomap", prioMap.CgroupString()); err != nil { 27 | return err 28 | } 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func (s *NetPrioGroup) Remove(d *data) error { 35 | return removePath(d.path("net_prio")) 36 | } 37 | 38 | func (s *NetPrioGroup) GetStats(path string, stats *cgroups.Stats) error { 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /cgroups/fs/net_prio_test.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/docker/libcontainer/configs" 8 | ) 9 | 10 | var ( 11 | prioMap = []*configs.IfPrioMap{ 12 | { 13 | Interface: "test", 14 | Priority: 5, 15 | }, 16 | } 17 | ) 18 | 19 | func TestNetPrioSetIfPrio(t *testing.T) { 20 | helper := NewCgroupTestUtil("net_prio", t) 21 | defer helper.cleanup() 22 | 23 | helper.CgroupData.c.NetPrioIfpriomap = prioMap 24 | netPrio := &NetPrioGroup{} 25 | if err := netPrio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { 26 | t.Fatal(err) 27 | } 28 | 29 | value, err := getCgroupParamString(helper.CgroupPath, "net_prio.ifpriomap") 30 | if err != nil { 31 | t.Fatalf("Failed to parse net_prio.ifpriomap - %s", err) 32 | } 33 | if !strings.Contains(value, "test 5") { 34 | t.Fatal("Got the wrong value, set net_prio.ifpriomap failed.") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /cgroups/fs/perf_event.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs 4 | 5 | import ( 6 | "github.com/docker/libcontainer/cgroups" 7 | "github.com/docker/libcontainer/configs" 8 | ) 9 | 10 | type PerfEventGroup struct { 11 | } 12 | 13 | func (s *PerfEventGroup) Apply(d *data) error { 14 | // we just want to join this group even though we don't set anything 15 | if _, err := d.join("perf_event"); err != nil && !cgroups.IsNotFound(err) { 16 | return err 17 | } 18 | return nil 19 | } 20 | 21 | func (s *PerfEventGroup) Set(path string, cgroup *configs.Cgroup) error { 22 | return nil 23 | } 24 | 25 | func (s *PerfEventGroup) Remove(d *data) error { 26 | return removePath(d.path("perf_event")) 27 | } 28 | 29 | func (s *PerfEventGroup) GetStats(path string, stats *cgroups.Stats) error { 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /cgroups/fs/util_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | /* 4 | Utility for testing cgroup operations. 5 | 6 | Creates a mock of the cgroup filesystem for the duration of the test. 7 | */ 8 | package fs 9 | 10 | import ( 11 | "io/ioutil" 12 | "os" 13 | "path/filepath" 14 | "testing" 15 | 16 | "github.com/docker/libcontainer/configs" 17 | ) 18 | 19 | type cgroupTestUtil struct { 20 | // data to use in tests. 21 | CgroupData *data 22 | 23 | // Path to the mock cgroup directory. 24 | CgroupPath string 25 | 26 | // Temporary directory to store mock cgroup filesystem. 27 | tempDir string 28 | t *testing.T 29 | } 30 | 31 | // Creates a new test util for the specified subsystem 32 | func NewCgroupTestUtil(subsystem string, t *testing.T) *cgroupTestUtil { 33 | d := &data{ 34 | c: &configs.Cgroup{}, 35 | } 36 | tempDir, err := ioutil.TempDir("", "cgroup_test") 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | d.root = tempDir 41 | testCgroupPath := filepath.Join(d.root, subsystem) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | 46 | // Ensure the full mock cgroup path exists. 47 | err = os.MkdirAll(testCgroupPath, 0755) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | return &cgroupTestUtil{CgroupData: d, CgroupPath: testCgroupPath, tempDir: tempDir, t: t} 52 | } 53 | 54 | func (c *cgroupTestUtil) cleanup() { 55 | os.RemoveAll(c.tempDir) 56 | } 57 | 58 | // Write the specified contents on the mock of the specified cgroup files. 59 | func (c *cgroupTestUtil) writeFileContents(fileContents map[string]string) { 60 | for file, contents := range fileContents { 61 | err := writeFile(c.CgroupPath, file, contents) 62 | if err != nil { 63 | c.t.Fatal(err) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /cgroups/systemd/apply_nosystemd.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package systemd 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/docker/libcontainer/cgroups" 9 | "github.com/docker/libcontainer/configs" 10 | ) 11 | 12 | type Manager struct { 13 | Cgroups *configs.Cgroup 14 | Paths map[string]string 15 | } 16 | 17 | func UseSystemd() bool { 18 | return false 19 | } 20 | 21 | func (m *Manager) Apply(pid int) error { 22 | return fmt.Errorf("Systemd not supported") 23 | } 24 | 25 | func (m *Manager) GetPids() ([]int, error) { 26 | return nil, fmt.Errorf("Systemd not supported") 27 | } 28 | 29 | func (m *Manager) Destroy() error { 30 | return fmt.Errorf("Systemd not supported") 31 | } 32 | 33 | func (m *Manager) GetPaths() map[string]string { 34 | return nil 35 | } 36 | 37 | func (m *Manager) GetStats() (*cgroups.Stats, error) { 38 | return nil, fmt.Errorf("Systemd not supported") 39 | } 40 | 41 | func (m *Manager) Set(container *configs.Config) error { 42 | return nil, fmt.Errorf("Systemd not supported") 43 | } 44 | 45 | func (m *Manager) Freeze(state configs.FreezerState) error { 46 | return fmt.Errorf("Systemd not supported") 47 | } 48 | 49 | func Freeze(c *configs.Cgroup, state configs.FreezerState) error { 50 | return fmt.Errorf("Systemd not supported") 51 | } 52 | -------------------------------------------------------------------------------- /configs/config_unix.go: -------------------------------------------------------------------------------- 1 | // +build freebsd linux 2 | 3 | package configs 4 | 5 | import "fmt" 6 | 7 | // Gets the root uid for the process on host which could be non-zero 8 | // when user namespaces are enabled. 9 | func (c Config) HostUID() (int, error) { 10 | if c.Namespaces.Contains(NEWUSER) { 11 | if c.UidMappings == nil { 12 | return -1, fmt.Errorf("User namespaces enabled, but no user mappings found.") 13 | } 14 | id, found := c.hostIDFromMapping(0, c.UidMappings) 15 | if !found { 16 | return -1, fmt.Errorf("User namespaces enabled, but no root user mapping found.") 17 | } 18 | return id, nil 19 | } 20 | // Return default root uid 0 21 | return 0, nil 22 | } 23 | 24 | // Gets the root uid for the process on host which could be non-zero 25 | // when user namespaces are enabled. 26 | func (c Config) HostGID() (int, error) { 27 | if c.Namespaces.Contains(NEWUSER) { 28 | if c.GidMappings == nil { 29 | return -1, fmt.Errorf("User namespaces enabled, but no gid mappings found.") 30 | } 31 | id, found := c.hostIDFromMapping(0, c.GidMappings) 32 | if !found { 33 | return -1, fmt.Errorf("User namespaces enabled, but no root user mapping found.") 34 | } 35 | return id, nil 36 | } 37 | // Return default root uid 0 38 | return 0, nil 39 | } 40 | 41 | // Utility function that gets a host ID for a container ID from user namespace map 42 | // if that ID is present in the map. 43 | func (c Config) hostIDFromMapping(containerID int, uMap []IDMap) (int, bool) { 44 | for _, m := range uMap { 45 | if (containerID >= m.ContainerID) && (containerID <= (m.ContainerID + m.Size - 1)) { 46 | hostID := m.HostID + (containerID - m.ContainerID) 47 | return hostID, true 48 | } 49 | } 50 | return -1, false 51 | } 52 | -------------------------------------------------------------------------------- /configs/device.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | const ( 9 | Wildcard = -1 10 | ) 11 | 12 | // TODO Windows: This can be factored out in the future 13 | 14 | type Device struct { 15 | // Device type, block, char, etc. 16 | Type rune `json:"type"` 17 | 18 | // Path to the device. 19 | Path string `json:"path"` 20 | 21 | // Major is the device's major number. 22 | Major int64 `json:"major"` 23 | 24 | // Minor is the device's minor number. 25 | Minor int64 `json:"minor"` 26 | 27 | // Cgroup permissions format, rwm. 28 | Permissions string `json:"permissions"` 29 | 30 | // FileMode permission bits for the device. 31 | FileMode os.FileMode `json:"file_mode"` 32 | 33 | // Uid of the device. 34 | Uid uint32 `json:"uid"` 35 | 36 | // Gid of the device. 37 | Gid uint32 `json:"gid"` 38 | } 39 | 40 | func (d *Device) CgroupString() string { 41 | return fmt.Sprintf("%c %s:%s %s", d.Type, deviceNumberString(d.Major), deviceNumberString(d.Minor), d.Permissions) 42 | } 43 | 44 | func (d *Device) Mkdev() int { 45 | return int((d.Major << 8) | (d.Minor & 0xff) | ((d.Minor & 0xfff00) << 12)) 46 | } 47 | 48 | // deviceNumberString converts the device number to a string return result. 49 | func deviceNumberString(number int64) string { 50 | if number == Wildcard { 51 | return "*" 52 | } 53 | return fmt.Sprint(number) 54 | } 55 | -------------------------------------------------------------------------------- /configs/hugepage_limit.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | type HugepageLimit struct { 4 | // which type of hugepage to limit. 5 | Pagesize string `json:"page_size"` 6 | 7 | // usage limit for hugepage. 8 | Limit int `json:"limit"` 9 | } 10 | -------------------------------------------------------------------------------- /configs/interface_priority_map.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type IfPrioMap struct { 8 | Interface string `json:"interface"` 9 | Priority int64 `json:"priority"` 10 | } 11 | 12 | func (i *IfPrioMap) CgroupString() string { 13 | return fmt.Sprintf("%s %d", i.Interface, i.Priority) 14 | } 15 | -------------------------------------------------------------------------------- /configs/mount.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | type Mount struct { 4 | // Source path for the mount. 5 | Source string `json:"source"` 6 | 7 | // Destination path for the mount inside the container. 8 | Destination string `json:"destination"` 9 | 10 | // Device the mount is for. 11 | Device string `json:"device"` 12 | 13 | // Mount flags. 14 | Flags int `json:"flags"` 15 | 16 | // Mount data applied to the mount. 17 | Data string `json:"data"` 18 | 19 | // Relabel source if set, "z" indicates shared, "Z" indicates unshared. 20 | Relabel string `json:"relabel"` 21 | 22 | // Optional Command to be run before Source is mounted. 23 | PremountCmds []Command `json:"premount_cmds"` 24 | 25 | // Optional Command to be run after Source is mounted. 26 | PostmountCmds []Command `json:"postmount_cmds"` 27 | } 28 | 29 | type Command struct { 30 | Path string `json:"path"` 31 | Args []string `json:"args"` 32 | Env []string `json:"env"` 33 | Dir string `json:"dir"` 34 | } 35 | -------------------------------------------------------------------------------- /configs/namespaces.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | type NamespaceType string 4 | 5 | type Namespaces []Namespace 6 | -------------------------------------------------------------------------------- /configs/namespaces_syscall.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package configs 4 | 5 | import "syscall" 6 | 7 | func (n *Namespace) Syscall() int { 8 | return namespaceInfo[n.Type] 9 | } 10 | 11 | var namespaceInfo = map[NamespaceType]int{ 12 | NEWNET: syscall.CLONE_NEWNET, 13 | NEWNS: syscall.CLONE_NEWNS, 14 | NEWUSER: syscall.CLONE_NEWUSER, 15 | NEWIPC: syscall.CLONE_NEWIPC, 16 | NEWUTS: syscall.CLONE_NEWUTS, 17 | NEWPID: syscall.CLONE_NEWPID, 18 | } 19 | 20 | // CloneFlags parses the container's Namespaces options to set the correct 21 | // flags on clone, unshare. This functions returns flags only for new namespaces. 22 | func (n *Namespaces) CloneFlags() uintptr { 23 | var flag int 24 | for _, v := range *n { 25 | if v.Path != "" { 26 | continue 27 | } 28 | flag |= namespaceInfo[v.Type] 29 | } 30 | return uintptr(flag) 31 | } 32 | -------------------------------------------------------------------------------- /configs/namespaces_syscall_unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !linux,!windows 2 | 3 | package configs 4 | 5 | func (n *Namespace) Syscall() int { 6 | panic("No namespace syscall support") 7 | return 0 8 | } 9 | 10 | // CloneFlags parses the container's Namespaces options to set the correct 11 | // flags on clone, unshare. This functions returns flags only for new namespaces. 12 | func (n *Namespaces) CloneFlags() uintptr { 13 | panic("No namespace syscall support") 14 | return uintptr(0) 15 | } 16 | -------------------------------------------------------------------------------- /configs/namespaces_unix.go: -------------------------------------------------------------------------------- 1 | // +build linux freebsd 2 | 3 | package configs 4 | 5 | import "fmt" 6 | 7 | const ( 8 | NEWNET NamespaceType = "NEWNET" 9 | NEWPID NamespaceType = "NEWPID" 10 | NEWNS NamespaceType = "NEWNS" 11 | NEWUTS NamespaceType = "NEWUTS" 12 | NEWIPC NamespaceType = "NEWIPC" 13 | NEWUSER NamespaceType = "NEWUSER" 14 | ) 15 | 16 | func NamespaceTypes() []NamespaceType { 17 | return []NamespaceType{ 18 | NEWNET, 19 | NEWPID, 20 | NEWNS, 21 | NEWUTS, 22 | NEWIPC, 23 | NEWUSER, 24 | } 25 | } 26 | 27 | // Namespace defines configuration for each namespace. It specifies an 28 | // alternate path that is able to be joined via setns. 29 | type Namespace struct { 30 | Type NamespaceType `json:"type"` 31 | Path string `json:"path"` 32 | } 33 | 34 | func (n *Namespace) GetPath(pid int) string { 35 | if n.Path != "" { 36 | return n.Path 37 | } 38 | return fmt.Sprintf("/proc/%d/ns/%s", pid, n.file()) 39 | } 40 | 41 | func (n *Namespace) file() string { 42 | file := "" 43 | switch n.Type { 44 | case NEWNET: 45 | file = "net" 46 | case NEWNS: 47 | file = "mnt" 48 | case NEWPID: 49 | file = "pid" 50 | case NEWIPC: 51 | file = "ipc" 52 | case NEWUSER: 53 | file = "user" 54 | case NEWUTS: 55 | file = "uts" 56 | } 57 | return file 58 | } 59 | 60 | func (n *Namespaces) Remove(t NamespaceType) bool { 61 | i := n.index(t) 62 | if i == -1 { 63 | return false 64 | } 65 | *n = append((*n)[:i], (*n)[i+1:]...) 66 | return true 67 | } 68 | 69 | func (n *Namespaces) Add(t NamespaceType, path string) { 70 | i := n.index(t) 71 | if i == -1 { 72 | *n = append(*n, Namespace{Type: t, Path: path}) 73 | return 74 | } 75 | (*n)[i].Path = path 76 | } 77 | 78 | func (n *Namespaces) index(t NamespaceType) int { 79 | for i, ns := range *n { 80 | if ns.Type == t { 81 | return i 82 | } 83 | } 84 | return -1 85 | } 86 | 87 | func (n *Namespaces) Contains(t NamespaceType) bool { 88 | return n.index(t) != -1 89 | } 90 | -------------------------------------------------------------------------------- /configs/namespaces_windows.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | // Namespace defines configuration for each namespace. It specifies an 4 | // alternate path that is able to be joined via setns. 5 | type Namespace struct { 6 | } 7 | -------------------------------------------------------------------------------- /console.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | import "io" 4 | 5 | // Console represents a pseudo TTY. 6 | type Console interface { 7 | io.ReadWriter 8 | io.Closer 9 | 10 | // Path returns the filesystem path to the slave side of the pty. 11 | Path() string 12 | 13 | // Fd returns the fd for the master of the pty. 14 | Fd() uintptr 15 | } 16 | -------------------------------------------------------------------------------- /console_freebsd.go: -------------------------------------------------------------------------------- 1 | // +build freebsd 2 | 3 | package libcontainer 4 | 5 | import ( 6 | "errors" 7 | ) 8 | 9 | // newConsole returns an initalized console that can be used within a container by copying bytes 10 | // from the master side to the slave that is attached as the tty for the container's init process. 11 | func newConsole(uid, gid int) (Console, error) { 12 | return nil, errors.New("libcontainer console is not supported on FreeBSD") 13 | } 14 | -------------------------------------------------------------------------------- /console_windows.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | // newConsole returns an initalized console that can be used within a container 4 | func newConsole(uid, gid int) (Console, error) { 5 | return &windowsConsole{}, nil 6 | } 7 | 8 | // windowsConsole is a Windows psuedo TTY for use within a container. 9 | type windowsConsole struct { 10 | } 11 | 12 | func (c *windowsConsole) Fd() uintptr { 13 | return 0 14 | } 15 | 16 | func (c *windowsConsole) Path() string { 17 | return "" 18 | } 19 | 20 | func (c *windowsConsole) Read(b []byte) (int, error) { 21 | return 0, nil 22 | } 23 | 24 | func (c *windowsConsole) Write(b []byte) (int, error) { 25 | return 0, nil 26 | } 27 | 28 | func (c *windowsConsole) Close() error { 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /container_nouserns_linux.go: -------------------------------------------------------------------------------- 1 | // +build !go1.4 2 | 3 | package libcontainer 4 | 5 | import ( 6 | "fmt" 7 | "syscall" 8 | ) 9 | 10 | // not available before go 1.4 11 | func (c *linuxContainer) addUidGidMappings(sys *syscall.SysProcAttr) error { 12 | return fmt.Errorf("User namespace is not supported in golang < 1.4") 13 | } 14 | -------------------------------------------------------------------------------- /container_userns_linux.go: -------------------------------------------------------------------------------- 1 | // +build go1.4 2 | 3 | package libcontainer 4 | 5 | import "syscall" 6 | 7 | // Converts IDMap to SysProcIDMap array and adds it to SysProcAttr. 8 | func (c *linuxContainer) addUidGidMappings(sys *syscall.SysProcAttr) error { 9 | if c.config.UidMappings != nil { 10 | sys.UidMappings = make([]syscall.SysProcIDMap, len(c.config.UidMappings)) 11 | for i, um := range c.config.UidMappings { 12 | sys.UidMappings[i].ContainerID = um.ContainerID 13 | sys.UidMappings[i].HostID = um.HostID 14 | sys.UidMappings[i].Size = um.Size 15 | } 16 | } 17 | if c.config.GidMappings != nil { 18 | sys.GidMappings = make([]syscall.SysProcIDMap, len(c.config.GidMappings)) 19 | for i, gm := range c.config.GidMappings { 20 | sys.GidMappings[i].ContainerID = gm.ContainerID 21 | sys.GidMappings[i].HostID = gm.HostID 22 | sys.GidMappings[i].Size = gm.Size 23 | } 24 | } 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /criu_opts.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | type CriuPageServerInfo struct { 4 | Address string // IP address of CRIU page server 5 | Port int32 // port number of CRIU page server 6 | } 7 | 8 | type CriuOpts struct { 9 | ImagesDirectory string // directory for storing image files 10 | WorkDirectory string // directory to cd and write logs/pidfiles/stats to 11 | LeaveRunning bool // leave container in running state after checkpoint 12 | TcpEstablished bool // checkpoint/restore established TCP connections 13 | ExternalUnixConnections bool // allow external unix connections 14 | ShellJob bool // allow to dump and restore shell jobs 15 | PageServer CriuPageServerInfo // allow to dump to criu page server 16 | } 17 | -------------------------------------------------------------------------------- /criurpc/Makefile: -------------------------------------------------------------------------------- 1 | gen: criurpc.proto 2 | protoc --go_out=. criurpc.proto 3 | -------------------------------------------------------------------------------- /devices/devices_test.go: -------------------------------------------------------------------------------- 1 | package devices 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func TestDeviceFromPathLstatFailure(t *testing.T) { 10 | testError := errors.New("test error") 11 | 12 | // Override os.Lstat to inject error. 13 | osLstat = func(path string) (os.FileInfo, error) { 14 | return nil, testError 15 | } 16 | 17 | _, err := DeviceFromPath("", "") 18 | if err != testError { 19 | t.Fatalf("Unexpected error %v, expected %v", err, testError) 20 | } 21 | } 22 | 23 | func TestHostDevicesIoutilReadDirFailure(t *testing.T) { 24 | testError := errors.New("test error") 25 | 26 | // Override ioutil.ReadDir to inject error. 27 | ioutilReadDir = func(dirname string) ([]os.FileInfo, error) { 28 | return nil, testError 29 | } 30 | 31 | _, err := HostDevices() 32 | if err != testError { 33 | t.Fatalf("Unexpected error %v, expected %v", err, testError) 34 | } 35 | } 36 | 37 | func TestHostDevicesIoutilReadDirDeepFailure(t *testing.T) { 38 | testError := errors.New("test error") 39 | called := false 40 | 41 | // Override ioutil.ReadDir to inject error after the first call. 42 | ioutilReadDir = func(dirname string) ([]os.FileInfo, error) { 43 | if called { 44 | return nil, testError 45 | } 46 | called = true 47 | 48 | // Provoke a second call. 49 | fi, err := os.Lstat("/tmp") 50 | if err != nil { 51 | t.Fatalf("Unexpected error %v", err) 52 | } 53 | 54 | return []os.FileInfo{fi}, nil 55 | } 56 | 57 | _, err := HostDevices() 58 | if err != testError { 59 | t.Fatalf("Unexpected error %v, expected %v", err, testError) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /devices/devices_windows.go: -------------------------------------------------------------------------------- 1 | package devices 2 | 3 | import ( 4 | "github.com/docker/libcontainer/configs" 5 | ) 6 | 7 | // TODO Windows. This can be factored out further - Devices are not supported 8 | // by Windows Containers. 9 | 10 | func DeviceFromPath(path, permissions string) (*configs.Device, error) { 11 | return nil, nil 12 | } 13 | 14 | func HostDevices() ([]*configs.Device, error) { 15 | return nil, nil 16 | } 17 | -------------------------------------------------------------------------------- /devices/number.go: -------------------------------------------------------------------------------- 1 | // +build linux freebsd 2 | 3 | package devices 4 | 5 | /* 6 | 7 | This code provides support for manipulating linux device numbers. It should be replaced by normal syscall functions once http://code.google.com/p/go/issues/detail?id=8106 is solved. 8 | 9 | You can read what they are here: 10 | 11 | - http://www.makelinux.net/ldd3/chp-3-sect-2 12 | - http://www.linux-tutorial.info/modules.php?name=MContent&pageid=94 13 | 14 | Note! These are NOT the same as the MAJOR(dev_t device);, MINOR(dev_t device); and MKDEV(int major, int minor); functions as defined in as the representation of device numbers used by go is different than the one used internally to the kernel! - https://github.com/torvalds/linux/blob/master/include/linux/kdev_t.h#L9 15 | 16 | */ 17 | 18 | func Major(devNumber int) int64 { 19 | return int64((devNumber >> 8) & 0xfff) 20 | } 21 | 22 | func Minor(devNumber int) int64 { 23 | return int64((devNumber & 0xff) | ((devNumber >> 12) & 0xfff00)) 24 | } 25 | -------------------------------------------------------------------------------- /docs/man/nsinit.1.md: -------------------------------------------------------------------------------- 1 | % nsinit User Manual 2 | % docker/libcontainer 3 | % JAN 2015 4 | 5 | NAME: 6 | nsinit - A low-level utility for managing containers. 7 | It is used to spawn new containers or join existing containers. 8 | 9 | USAGE: 10 | nsinit [global options] command [command options] [arguments...] 11 | 12 | VERSION: 13 | 0.1 14 | 15 | COMMANDS: 16 | config display the container configuration 17 | exec execute a new command inside a container 18 | init runs the init process inside the namespace 19 | oom display oom notifications for a container 20 | pause pause the container's processes 21 | stats display statistics for the container 22 | unpause unpause the container's processes 23 | help, h shows a list of commands or help for one command 24 | 25 | EXAMPLES: 26 | 27 | Get the of an already running docker container. 28 | `sudo docker ps` will return the list of all the running containers. 29 | 30 | take the (e.g. 4addb0b2d307) and go to its config directory 31 | `/var/lib/docker/execdriver/native/4addb0b2d307` and here you can run the nsinit 32 | command line utility. 33 | 34 | e.g. `nsinit exec /bin/bash` will start a shell on the already running container. 35 | 36 | # HISTORY 37 | Jan 2015, Originally compiled by Shishir Mahajan (shishir dot mahajan at redhat dot com) 38 | based on nsinit source material and internal work. 39 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | import "io" 4 | 5 | // API error code type. 6 | type ErrorCode int 7 | 8 | // API error codes. 9 | const ( 10 | // Factory errors 11 | IdInUse ErrorCode = iota 12 | InvalidIdFormat 13 | 14 | // Container errors 15 | ContainerNotExists 16 | ContainerPaused 17 | ContainerNotStopped 18 | ContainerNotRunning 19 | 20 | // Process errors 21 | ProcessNotExecuted 22 | 23 | // Common errors 24 | ConfigInvalid 25 | SystemError 26 | ) 27 | 28 | func (c ErrorCode) String() string { 29 | switch c { 30 | case IdInUse: 31 | return "Id already in use" 32 | case InvalidIdFormat: 33 | return "Invalid format" 34 | case ContainerPaused: 35 | return "Container paused" 36 | case ConfigInvalid: 37 | return "Invalid configuration" 38 | case SystemError: 39 | return "System error" 40 | case ContainerNotExists: 41 | return "Container does not exist" 42 | case ContainerNotStopped: 43 | return "Container is not stopped" 44 | case ContainerNotRunning: 45 | return "Container is not running" 46 | default: 47 | return "Unknown error" 48 | } 49 | } 50 | 51 | // API Error type. 52 | type Error interface { 53 | error 54 | 55 | // Returns a verbose string including the error message 56 | // and a representation of the stack trace suitable for 57 | // printing. 58 | Detail(w io.Writer) error 59 | 60 | // Returns the error code for this error. 61 | Code() ErrorCode 62 | } 63 | -------------------------------------------------------------------------------- /error_test.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | import "testing" 4 | 5 | func TestErrorCode(t *testing.T) { 6 | codes := map[ErrorCode]string{ 7 | IdInUse: "Id already in use", 8 | InvalidIdFormat: "Invalid format", 9 | ContainerPaused: "Container paused", 10 | ConfigInvalid: "Invalid configuration", 11 | SystemError: "System error", 12 | ContainerNotExists: "Container does not exist", 13 | } 14 | 15 | for code, expected := range codes { 16 | if actual := code.String(); actual != expected { 17 | t.Fatalf("expected string %q but received %q", expected, actual) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /factory.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | import ( 4 | "github.com/docker/libcontainer/configs" 5 | ) 6 | 7 | type Factory interface { 8 | // Creates a new container with the given id and starts the initial process inside it. 9 | // id must be a string containing only letters, digits and underscores and must contain 10 | // between 1 and 1024 characters, inclusive. 11 | // 12 | // The id must not already be in use by an existing container. Containers created using 13 | // a factory with the same path (and file system) must have distinct ids. 14 | // 15 | // Returns the new container with a running process. 16 | // 17 | // errors: 18 | // IdInUse - id is already in use by a container 19 | // InvalidIdFormat - id has incorrect format 20 | // ConfigInvalid - config is invalid 21 | // Systemerror - System error 22 | // 23 | // On error, any partially created container parts are cleaned up (the operation is atomic). 24 | Create(id string, config *configs.Config) (Container, error) 25 | 26 | // Load takes an ID for an existing container and returns the container information 27 | // from the state. This presents a read only view of the container. 28 | // 29 | // errors: 30 | // Path does not exist 31 | // Container is stopped 32 | // System error 33 | Load(id string) (Container, error) 34 | 35 | // StartInitialization is an internal API to libcontainer used during the reexec of the 36 | // container. 37 | // 38 | // Errors: 39 | // Pipe connection error 40 | // System error 41 | StartInitialization() error 42 | 43 | // Type returns info string about factory type (e.g. lxc, libcontainer...) 44 | Type() string 45 | } 46 | -------------------------------------------------------------------------------- /generic_error.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "text/template" 7 | "time" 8 | 9 | "github.com/docker/libcontainer/stacktrace" 10 | ) 11 | 12 | var errorTemplate = template.Must(template.New("error").Parse(`Timestamp: {{.Timestamp}} 13 | Code: {{.ECode}} 14 | {{if .Message }} 15 | Message: {{.Message}} 16 | {{end}} 17 | Frames:{{range $i, $frame := .Stack.Frames}} 18 | --- 19 | {{$i}}: {{$frame.Function}} 20 | Package: {{$frame.Package}} 21 | File: {{$frame.File}}@{{$frame.Line}}{{end}} 22 | `)) 23 | 24 | func newGenericError(err error, c ErrorCode) Error { 25 | if le, ok := err.(Error); ok { 26 | return le 27 | } 28 | gerr := &genericError{ 29 | Timestamp: time.Now(), 30 | Err: err, 31 | ECode: c, 32 | Stack: stacktrace.Capture(1), 33 | } 34 | if err != nil { 35 | gerr.Message = err.Error() 36 | } 37 | return gerr 38 | } 39 | 40 | func newSystemError(err error) Error { 41 | if le, ok := err.(Error); ok { 42 | return le 43 | } 44 | gerr := &genericError{ 45 | Timestamp: time.Now(), 46 | Err: err, 47 | ECode: SystemError, 48 | Stack: stacktrace.Capture(1), 49 | } 50 | if err != nil { 51 | gerr.Message = err.Error() 52 | } 53 | return gerr 54 | } 55 | 56 | type genericError struct { 57 | Timestamp time.Time 58 | ECode ErrorCode 59 | Err error `json:"-"` 60 | Message string 61 | Stack stacktrace.Stacktrace 62 | } 63 | 64 | func (e *genericError) Error() string { 65 | return fmt.Sprintf("[%d] %s: %s", e.ECode, e.ECode, e.Message) 66 | } 67 | 68 | func (e *genericError) Code() ErrorCode { 69 | return e.ECode 70 | } 71 | 72 | func (e *genericError) Detail(w io.Writer) error { 73 | return errorTemplate.Execute(w, e) 74 | } 75 | -------------------------------------------------------------------------------- /generic_error_test.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "testing" 7 | ) 8 | 9 | func TestErrorDetail(t *testing.T) { 10 | err := newGenericError(fmt.Errorf("test error"), SystemError) 11 | if derr := err.Detail(ioutil.Discard); derr != nil { 12 | t.Fatal(derr) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /hack/validate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | # This script runs all validations 5 | 6 | validate() { 7 | export MAKEDIR=/go/src/github.com/docker/docker/hack/make 8 | sed -i 's!docker/docker!docker/libcontainer!' /go/src/github.com/docker/docker/hack/make/.validate 9 | bash /go/src/github.com/docker/docker/hack/make/validate-dco 10 | bash /go/src/github.com/docker/docker/hack/make/validate-gofmt 11 | go get golang.org/x/tools/cmd/vet 12 | bash /go/src/github.com/docker/docker/hack/make/validate-vet 13 | } 14 | 15 | # run validations 16 | validate 17 | -------------------------------------------------------------------------------- /integration/doc.go: -------------------------------------------------------------------------------- 1 | // integration is used for integration testing of libcontainer 2 | package integration 3 | -------------------------------------------------------------------------------- /integration/init_test.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | "testing" 7 | 8 | "github.com/Sirupsen/logrus" 9 | "github.com/docker/libcontainer" 10 | "github.com/docker/libcontainer/cgroups/systemd" 11 | _ "github.com/docker/libcontainer/nsenter" 12 | ) 13 | 14 | // init runs the libcontainer initialization code because of the busybox style needs 15 | // to work around the go runtime and the issues with forking 16 | func init() { 17 | if len(os.Args) < 2 || os.Args[1] != "init" { 18 | return 19 | } 20 | runtime.GOMAXPROCS(1) 21 | runtime.LockOSThread() 22 | factory, err := libcontainer.New("") 23 | if err != nil { 24 | logrus.Fatalf("unable to initialize for container: %s", err) 25 | } 26 | if err := factory.StartInitialization(); err != nil { 27 | logrus.Fatal(err) 28 | } 29 | } 30 | 31 | var ( 32 | factory libcontainer.Factory 33 | systemdFactory libcontainer.Factory 34 | ) 35 | 36 | func TestMain(m *testing.M) { 37 | var ( 38 | err error 39 | ret int = 0 40 | ) 41 | 42 | logrus.SetOutput(os.Stderr) 43 | logrus.SetLevel(logrus.InfoLevel) 44 | 45 | factory, err = libcontainer.New(".", libcontainer.Cgroupfs) 46 | if err != nil { 47 | logrus.Error(err) 48 | os.Exit(1) 49 | } 50 | if systemd.UseSystemd() { 51 | systemdFactory, err = libcontainer.New(".", libcontainer.SystemdCgroups) 52 | if err != nil { 53 | logrus.Error(err) 54 | os.Exit(1) 55 | } 56 | } 57 | 58 | ret = m.Run() 59 | os.Exit(ret) 60 | } 61 | -------------------------------------------------------------------------------- /label/label.go: -------------------------------------------------------------------------------- 1 | // +build !selinux !linux 2 | 3 | package label 4 | 5 | // InitLabels returns the process label and file labels to be used within 6 | // the container. A list of options can be passed into this function to alter 7 | // the labels. 8 | func InitLabels(options []string) (string, string, error) { 9 | return "", "", nil 10 | } 11 | 12 | func GenLabels(options string) (string, string, error) { 13 | return "", "", nil 14 | } 15 | 16 | func FormatMountLabel(src string, mountLabel string) string { 17 | return src 18 | } 19 | 20 | func SetProcessLabel(processLabel string) error { 21 | return nil 22 | } 23 | 24 | func SetFileLabel(path string, fileLabel string) error { 25 | return nil 26 | } 27 | 28 | func SetFileCreateLabel(fileLabel string) error { 29 | return nil 30 | } 31 | 32 | func Relabel(path string, fileLabel string, relabel string) error { 33 | return nil 34 | } 35 | 36 | func GetPidLabel(pid int) (string, error) { 37 | return "", nil 38 | } 39 | 40 | func Init() { 41 | } 42 | 43 | func ReserveLabel(label string) error { 44 | return nil 45 | } 46 | 47 | func UnreserveLabel(label string) error { 48 | return nil 49 | } 50 | 51 | // DupSecOpt takes an process label and returns security options that 52 | // can be used to set duplicate labels on future container processes 53 | func DupSecOpt(src string) []string { 54 | return nil 55 | } 56 | 57 | // DisableSecOpt returns a security opt that can disable labeling 58 | // support for future container processes 59 | func DisableSecOpt() []string { 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /netlink/MAINTAINERS: -------------------------------------------------------------------------------- 1 | Michael Crosby (@crosbymichael) 2 | Guillaume J. Charmes (@creack) 3 | -------------------------------------------------------------------------------- /netlink/netlink.go: -------------------------------------------------------------------------------- 1 | // Packet netlink provide access to low level Netlink sockets and messages. 2 | // 3 | // Actual implementations are in: 4 | // netlink_linux.go 5 | // netlink_darwin.go 6 | package netlink 7 | 8 | import ( 9 | "errors" 10 | "net" 11 | ) 12 | 13 | var ( 14 | ErrWrongSockType = errors.New("Wrong socket type") 15 | ErrShortResponse = errors.New("Got short response from netlink") 16 | ErrInterfaceExists = errors.New("Network interface already exists") 17 | ) 18 | 19 | // A Route is a subnet associated with the interface to reach it. 20 | type Route struct { 21 | *net.IPNet 22 | Iface *net.Interface 23 | Default bool 24 | } 25 | 26 | // An IfAddr defines IP network settings for a given network interface 27 | type IfAddr struct { 28 | Iface *net.Interface 29 | IP net.IP 30 | IPNet *net.IPNet 31 | } 32 | -------------------------------------------------------------------------------- /netlink/netlink_linux_armppc64.go: -------------------------------------------------------------------------------- 1 | // +build arm ppc64 ppc64le 2 | 3 | package netlink 4 | 5 | func ifrDataByte(b byte) uint8 { 6 | return uint8(b) 7 | } 8 | -------------------------------------------------------------------------------- /netlink/netlink_linux_notarm.go: -------------------------------------------------------------------------------- 1 | // +build !arm,!ppc64,!ppc64le 2 | 3 | package netlink 4 | 5 | func ifrDataByte(b byte) int8 { 6 | return int8(b) 7 | } 8 | -------------------------------------------------------------------------------- /netlink/netlink_unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package netlink 4 | 5 | import ( 6 | "errors" 7 | "net" 8 | ) 9 | 10 | var ( 11 | ErrNotImplemented = errors.New("not implemented") 12 | ) 13 | 14 | func NetworkGetRoutes() ([]Route, error) { 15 | return nil, ErrNotImplemented 16 | } 17 | 18 | func NetworkLinkAdd(name string, linkType string) error { 19 | return ErrNotImplemented 20 | } 21 | 22 | func NetworkLinkDel(name string) error { 23 | return ErrNotImplemented 24 | } 25 | 26 | func NetworkLinkUp(iface *net.Interface) error { 27 | return ErrNotImplemented 28 | } 29 | 30 | func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error { 31 | return ErrNotImplemented 32 | } 33 | 34 | func NetworkLinkDelIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error { 35 | return ErrNotImplemented 36 | } 37 | 38 | func AddRoute(destination, source, gateway, device string) error { 39 | return ErrNotImplemented 40 | } 41 | 42 | func AddDefaultGw(ip, device string) error { 43 | return ErrNotImplemented 44 | } 45 | 46 | func NetworkSetMTU(iface *net.Interface, mtu int) error { 47 | return ErrNotImplemented 48 | } 49 | 50 | func NetworkSetTxQueueLen(iface *net.Interface, txQueueLen int) error { 51 | return ErrNotImplemented 52 | } 53 | 54 | func NetworkCreateVethPair(name1, name2 string, txQueueLen int) error { 55 | return ErrNotImplemented 56 | } 57 | 58 | func NetworkChangeName(iface *net.Interface, newName string) error { 59 | return ErrNotImplemented 60 | } 61 | 62 | func NetworkSetNsFd(iface *net.Interface, fd int) error { 63 | return ErrNotImplemented 64 | } 65 | 66 | func NetworkSetNsPid(iface *net.Interface, nspid int) error { 67 | return ErrNotImplemented 68 | } 69 | 70 | func NetworkSetMaster(iface, master *net.Interface) error { 71 | return ErrNotImplemented 72 | } 73 | 74 | func NetworkLinkDown(iface *net.Interface) error { 75 | return ErrNotImplemented 76 | } 77 | 78 | func CreateBridge(name string, setMacAddr bool) error { 79 | return ErrNotImplemented 80 | } 81 | 82 | func DeleteBridge(name string) error { 83 | return ErrNotImplemented 84 | } 85 | 86 | func AddToBridge(iface, master *net.Interface) error { 87 | return ErrNotImplemented 88 | } 89 | -------------------------------------------------------------------------------- /notify_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package libcontainer 4 | 5 | import ( 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "syscall" 11 | ) 12 | 13 | const oomCgroupName = "memory" 14 | 15 | // notifyOnOOM returns channel on which you can expect event about OOM, 16 | // if process died without OOM this channel will be closed. 17 | // s is current *libcontainer.State for container. 18 | func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) { 19 | dir := paths[oomCgroupName] 20 | if dir == "" { 21 | return nil, fmt.Errorf("There is no path for %q in state", oomCgroupName) 22 | } 23 | oomControl, err := os.Open(filepath.Join(dir, "memory.oom_control")) 24 | if err != nil { 25 | return nil, err 26 | } 27 | fd, _, syserr := syscall.RawSyscall(syscall.SYS_EVENTFD2, 0, syscall.FD_CLOEXEC, 0) 28 | if syserr != 0 { 29 | oomControl.Close() 30 | return nil, syserr 31 | } 32 | 33 | eventfd := os.NewFile(fd, "eventfd") 34 | 35 | eventControlPath := filepath.Join(dir, "cgroup.event_control") 36 | data := fmt.Sprintf("%d %d", eventfd.Fd(), oomControl.Fd()) 37 | if err := ioutil.WriteFile(eventControlPath, []byte(data), 0700); err != nil { 38 | eventfd.Close() 39 | oomControl.Close() 40 | return nil, err 41 | } 42 | ch := make(chan struct{}) 43 | go func() { 44 | defer func() { 45 | close(ch) 46 | eventfd.Close() 47 | oomControl.Close() 48 | }() 49 | buf := make([]byte, 8) 50 | for { 51 | if _, err := eventfd.Read(buf); err != nil { 52 | return 53 | } 54 | // When a cgroup is destroyed, an event is sent to eventfd. 55 | // So if the control path is gone, return instead of notifying. 56 | if _, err := os.Lstat(eventControlPath); os.IsNotExist(err) { 57 | return 58 | } 59 | ch <- struct{}{} 60 | } 61 | }() 62 | return ch, nil 63 | } 64 | -------------------------------------------------------------------------------- /nsenter/README.md: -------------------------------------------------------------------------------- 1 | ## nsenter 2 | 3 | The `nsenter` package registers a special init constructor that is called before 4 | the Go runtime has a chance to boot. This provides us the ability to `setns` on 5 | existing namespaces and avoid the issues that the Go runtime has with multiple 6 | threads. This constructor will be called if this package is registered, 7 | imported, in your go application. 8 | 9 | The `nsenter` package will `import "C"` and it uses [cgo](https://golang.org/cmd/cgo/) 10 | package. In cgo, if the import of "C" is immediately preceded by a comment, that comment, 11 | called the preamble, is used as a header when compiling the C parts of the package. 12 | So every time we import package `nsenter`, the C code function `nsexec()` would be 13 | called. And package `nsenter` is now only imported in Docker execdriver, so every time 14 | before we call `execdriver.Exec()`, that C code would run. 15 | 16 | `nsexec()` will first check the environment variable `_LIBCONTAINER_INITPID` 17 | which will give the process of the container that should be joined. Namespaces fd will 18 | be found from `/proc/[pid]/ns` and set by `setns` syscall. 19 | 20 | And then get the pipe number from `_LIBCONTAINER_INITPIPE`, error message could 21 | be transfered through it. If tty is added, `_LIBCONTAINER_CONSOLE_PATH` will 22 | have value and start a console for output. 23 | 24 | Finally, `nsexec()` will clone a child process , exit the parent process and let 25 | the Go runtime take over. 26 | -------------------------------------------------------------------------------- /nsenter/nsenter.go: -------------------------------------------------------------------------------- 1 | // +build linux,!gccgo 2 | 3 | package nsenter 4 | 5 | /* 6 | #cgo CFLAGS: -Wall 7 | extern void nsexec(); 8 | void __attribute__((constructor)) init(void) { 9 | nsexec(); 10 | } 11 | */ 12 | import "C" 13 | -------------------------------------------------------------------------------- /nsenter/nsenter_gccgo.go: -------------------------------------------------------------------------------- 1 | // +build linux,gccgo 2 | 3 | package nsenter 4 | 5 | /* 6 | #cgo CFLAGS: -Wall 7 | extern void nsexec(); 8 | void __attribute__((constructor)) init(void) { 9 | nsexec(); 10 | } 11 | */ 12 | import "C" 13 | 14 | // AlwaysFalse is here to stay false 15 | // (and be exported so the compiler doesn't optimize out its reference) 16 | var AlwaysFalse bool 17 | 18 | func init() { 19 | if AlwaysFalse { 20 | // by referencing this C init() in a noop test, it will ensure the compiler 21 | // links in the C function. 22 | // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65134 23 | C.init() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /nsenter/nsenter_test.go: -------------------------------------------------------------------------------- 1 | package nsenter 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | type pid struct { 13 | Pid int `json:"Pid"` 14 | } 15 | 16 | func TestNsenterAlivePid(t *testing.T) { 17 | args := []string{"nsenter-exec"} 18 | r, w, err := os.Pipe() 19 | if err != nil { 20 | t.Fatalf("failed to create pipe %v", err) 21 | } 22 | 23 | cmd := &exec.Cmd{ 24 | Path: os.Args[0], 25 | Args: args, 26 | ExtraFiles: []*os.File{w}, 27 | Env: []string{fmt.Sprintf("_LIBCONTAINER_INITPID=%d", os.Getpid()), "_LIBCONTAINER_INITPIPE=3"}, 28 | } 29 | 30 | if err := cmd.Start(); err != nil { 31 | t.Fatalf("nsenter failed to start %v", err) 32 | } 33 | w.Close() 34 | 35 | decoder := json.NewDecoder(r) 36 | var pid *pid 37 | 38 | if err := decoder.Decode(&pid); err != nil { 39 | t.Fatalf("%v", err) 40 | } 41 | 42 | if err := cmd.Wait(); err != nil { 43 | t.Fatalf("nsenter exits with a non-zero exit status") 44 | } 45 | p, err := os.FindProcess(pid.Pid) 46 | if err != nil { 47 | t.Fatalf("%v", err) 48 | } 49 | p.Wait() 50 | } 51 | 52 | func TestNsenterInvalidPid(t *testing.T) { 53 | args := []string{"nsenter-exec"} 54 | 55 | cmd := &exec.Cmd{ 56 | Path: os.Args[0], 57 | Args: args, 58 | Env: []string{"_LIBCONTAINER_INITPID=-1"}, 59 | } 60 | 61 | err := cmd.Run() 62 | if err == nil { 63 | t.Fatal("nsenter exits with a zero exit status") 64 | } 65 | } 66 | 67 | func TestNsenterDeadPid(t *testing.T) { 68 | dead_cmd := exec.Command("true") 69 | if err := dead_cmd.Run(); err != nil { 70 | t.Fatal(err) 71 | } 72 | args := []string{"nsenter-exec"} 73 | 74 | cmd := &exec.Cmd{ 75 | Path: os.Args[0], 76 | Args: args, 77 | Env: []string{fmt.Sprintf("_LIBCONTAINER_INITPID=%d", dead_cmd.Process.Pid)}, 78 | } 79 | 80 | err := cmd.Run() 81 | if err == nil { 82 | t.Fatal("nsenter exits with a zero exit status") 83 | } 84 | } 85 | 86 | func init() { 87 | if strings.HasPrefix(os.Args[0], "nsenter-") { 88 | os.Exit(0) 89 | } 90 | return 91 | } 92 | -------------------------------------------------------------------------------- /nsenter/nsenter_unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !linux !cgo 2 | 3 | package nsenter 4 | 5 | import "C" 6 | -------------------------------------------------------------------------------- /nsinit/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | go build -o nsinit . 3 | -------------------------------------------------------------------------------- /nsinit/init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | 6 | "github.com/Sirupsen/logrus" 7 | "github.com/codegangsta/cli" 8 | "github.com/docker/libcontainer" 9 | _ "github.com/docker/libcontainer/nsenter" 10 | ) 11 | 12 | var initCommand = cli.Command{ 13 | Name: "init", 14 | Usage: "runs the init process inside the namespace", 15 | Action: func(context *cli.Context) { 16 | logrus.SetLevel(logrus.DebugLevel) 17 | runtime.GOMAXPROCS(1) 18 | runtime.LockOSThread() 19 | factory, err := libcontainer.New("") 20 | if err != nil { 21 | fatal(err) 22 | } 23 | if err := factory.StartInitialization(); err != nil { 24 | fatal(err) 25 | } 26 | panic("This line should never been executed") 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /nsinit/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/Sirupsen/logrus" 7 | "github.com/codegangsta/cli" 8 | ) 9 | 10 | func main() { 11 | app := cli.NewApp() 12 | app.Name = "nsinit" 13 | app.Version = "2" 14 | app.Author = "libcontainer maintainers" 15 | app.Flags = []cli.Flag{ 16 | cli.BoolFlag{Name: "debug", Usage: "enable debug output in the logs"}, 17 | cli.StringFlag{Name: "root", Value: "/var/run/nsinit", Usage: "root directory for containers"}, 18 | cli.StringFlag{Name: "log-file", Usage: "set the log file to output logs to"}, 19 | cli.StringFlag{Name: "criu", Value: "criu", Usage: "path to the criu binary for checkpoint and restore"}, 20 | } 21 | app.Commands = []cli.Command{ 22 | checkpointCommand, 23 | configCommand, 24 | execCommand, 25 | initCommand, 26 | oomCommand, 27 | pauseCommand, 28 | stateCommand, 29 | statsCommand, 30 | unpauseCommand, 31 | restoreCommand, 32 | } 33 | app.Before = func(context *cli.Context) error { 34 | if context.GlobalBool("debug") { 35 | logrus.SetLevel(logrus.DebugLevel) 36 | } 37 | if path := context.GlobalString("log-file"); path != "" { 38 | f, err := os.Create(path) 39 | if err != nil { 40 | return err 41 | } 42 | logrus.SetOutput(f) 43 | } 44 | return nil 45 | } 46 | if err := app.Run(os.Args); err != nil { 47 | logrus.Fatal(err) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /nsinit/oom.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/Sirupsen/logrus" 5 | "github.com/codegangsta/cli" 6 | ) 7 | 8 | var oomCommand = cli.Command{ 9 | Name: "oom", 10 | Usage: "display oom notifications for a container", 11 | Flags: []cli.Flag{ 12 | cli.StringFlag{Name: "id", Value: "nsinit", Usage: "specify the ID for a container"}, 13 | }, 14 | Action: func(context *cli.Context) { 15 | container, err := getContainer(context) 16 | if err != nil { 17 | logrus.Fatal(err) 18 | } 19 | n, err := container.NotifyOOM() 20 | if err != nil { 21 | logrus.Fatal(err) 22 | } 23 | for x := range n { 24 | // hack for calm down go1.4 gofmt 25 | _ = x 26 | logrus.Printf("OOM notification received") 27 | } 28 | }, 29 | } 30 | -------------------------------------------------------------------------------- /nsinit/pause.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/Sirupsen/logrus" 5 | "github.com/codegangsta/cli" 6 | ) 7 | 8 | var pauseCommand = cli.Command{ 9 | Name: "pause", 10 | Usage: "pause the container's processes", 11 | Flags: []cli.Flag{ 12 | cli.StringFlag{Name: "id", Value: "nsinit", Usage: "specify the ID for a container"}, 13 | }, 14 | Action: func(context *cli.Context) { 15 | container, err := getContainer(context) 16 | if err != nil { 17 | logrus.Fatal(err) 18 | } 19 | if err = container.Pause(); err != nil { 20 | logrus.Fatal(err) 21 | } 22 | }, 23 | } 24 | 25 | var unpauseCommand = cli.Command{ 26 | Name: "unpause", 27 | Usage: "unpause the container's processes", 28 | Flags: []cli.Flag{ 29 | cli.StringFlag{Name: "id", Value: "nsinit", Usage: "specify the ID for a container"}, 30 | }, 31 | Action: func(context *cli.Context) { 32 | container, err := getContainer(context) 33 | if err != nil { 34 | logrus.Fatal(err) 35 | } 36 | if err = container.Resume(); err != nil { 37 | logrus.Fatal(err) 38 | } 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /nsinit/state.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/codegangsta/cli" 8 | ) 9 | 10 | var stateCommand = cli.Command{ 11 | Name: "state", 12 | Usage: "get the container's current state", 13 | Flags: []cli.Flag{ 14 | cli.StringFlag{Name: "id", Value: "nsinit", Usage: "specify the ID for a container"}, 15 | }, 16 | Action: func(context *cli.Context) { 17 | container, err := getContainer(context) 18 | if err != nil { 19 | fatal(err) 20 | } 21 | state, err := container.State() 22 | if err != nil { 23 | fatal(err) 24 | } 25 | data, err := json.MarshalIndent(state, "", "\t") 26 | if err != nil { 27 | fatal(err) 28 | } 29 | fmt.Printf("%s", data) 30 | }, 31 | } 32 | -------------------------------------------------------------------------------- /nsinit/stats.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/codegangsta/cli" 8 | ) 9 | 10 | var statsCommand = cli.Command{ 11 | Name: "stats", 12 | Usage: "display statistics for the container", 13 | Flags: []cli.Flag{ 14 | cli.StringFlag{Name: "id", Value: "nsinit", Usage: "specify the ID for a container"}, 15 | }, 16 | Action: func(context *cli.Context) { 17 | container, err := getContainer(context) 18 | if err != nil { 19 | fatal(err) 20 | } 21 | stats, err := container.Stats() 22 | if err != nil { 23 | fatal(err) 24 | } 25 | data, err := json.MarshalIndent(stats, "", "\t") 26 | if err != nil { 27 | fatal(err) 28 | } 29 | fmt.Printf("%s", data) 30 | }, 31 | } 32 | -------------------------------------------------------------------------------- /nsinit/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/Sirupsen/logrus" 10 | 11 | "github.com/codegangsta/cli" 12 | "github.com/docker/libcontainer" 13 | "github.com/docker/libcontainer/cgroups/systemd" 14 | "github.com/docker/libcontainer/configs" 15 | ) 16 | 17 | func loadConfig(context *cli.Context) (*configs.Config, error) { 18 | if path := context.String("config"); path != "" { 19 | f, err := os.Open(path) 20 | if err != nil { 21 | return nil, err 22 | } 23 | defer f.Close() 24 | var config *configs.Config 25 | if err := json.NewDecoder(f).Decode(&config); err != nil { 26 | return nil, err 27 | } 28 | return config, nil 29 | } 30 | config := getTemplate() 31 | modify(config, context) 32 | return config, nil 33 | } 34 | 35 | func loadFactory(context *cli.Context) (libcontainer.Factory, error) { 36 | cgm := libcontainer.Cgroupfs 37 | if context.Bool("systemd") { 38 | if systemd.UseSystemd() { 39 | cgm = libcontainer.SystemdCgroups 40 | } else { 41 | logrus.Warn("systemd cgroup flag passed, but systemd support for managing cgroups is not available.") 42 | } 43 | } 44 | root := context.GlobalString("root") 45 | abs, err := filepath.Abs(root) 46 | if err != nil { 47 | return nil, err 48 | } 49 | return libcontainer.New(abs, cgm, func(l *libcontainer.LinuxFactory) error { 50 | l.CriuPath = context.GlobalString("criu") 51 | return nil 52 | }) 53 | } 54 | 55 | func getContainer(context *cli.Context) (libcontainer.Container, error) { 56 | factory, err := loadFactory(context) 57 | if err != nil { 58 | return nil, err 59 | } 60 | container, err := factory.Load(context.String("id")) 61 | if err != nil { 62 | return nil, err 63 | } 64 | return container, nil 65 | } 66 | 67 | func fatal(err error) { 68 | if lerr, ok := err.(libcontainer.Error); ok { 69 | lerr.Detail(os.Stderr) 70 | os.Exit(1) 71 | } 72 | fmt.Fprintln(os.Stderr, err) 73 | os.Exit(1) 74 | } 75 | 76 | func fatalf(t string, v ...interface{}) { 77 | fmt.Fprintf(os.Stderr, t, v...) 78 | os.Exit(1) 79 | } 80 | -------------------------------------------------------------------------------- /rootfs_linux_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package libcontainer 4 | 5 | import "testing" 6 | 7 | func TestCheckMountDestOnProc(t *testing.T) { 8 | dest := "/rootfs/proc/" 9 | err := checkMountDestination("/rootfs", dest) 10 | if err == nil { 11 | t.Fatal("destination inside proc should return an error") 12 | } 13 | } 14 | 15 | func TestCheckMountDestInSys(t *testing.T) { 16 | dest := "/rootfs//sys/fs/cgroup" 17 | err := checkMountDestination("/rootfs", dest) 18 | if err != nil { 19 | t.Fatal("destination inside /sys should not return an error") 20 | } 21 | } 22 | 23 | func TestCheckMountDestFalsePositive(t *testing.T) { 24 | dest := "/rootfs/sysfiles/fs/cgroup" 25 | err := checkMountDestination("/rootfs", dest) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | } 30 | 31 | func TestCheckMountRoot(t *testing.T) { 32 | dest := "/rootfs" 33 | err := checkMountDestination("/rootfs", dest) 34 | if err == nil { 35 | t.Fatal(err) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sample_configs/README.md: -------------------------------------------------------------------------------- 1 | These configuration files can be used with `nsinit` to quickly develop, test, 2 | and experiment with features of libcontainer. 3 | 4 | When consuming these configuration files, copy them into your rootfs and rename 5 | the file to `container.json` for use with `nsinit`. 6 | -------------------------------------------------------------------------------- /seccomp/bpf.go: -------------------------------------------------------------------------------- 1 | package seccomp 2 | 3 | import "strings" 4 | 5 | type bpfLabel struct { 6 | label string 7 | location uint32 8 | } 9 | 10 | type bpfLabels []bpfLabel 11 | 12 | // labelIndex returns the index for the label if it exists in the slice. 13 | // if it does not exist in the slice it appends the label lb to the end 14 | // of the slice and returns the index. 15 | func labelIndex(labels *bpfLabels, lb string) uint32 { 16 | var id uint32 17 | for id = 0; id < uint32(len(*labels)); id++ { 18 | if strings.EqualFold(lb, (*labels)[id].label) { 19 | return id 20 | } 21 | } 22 | *labels = append(*labels, bpfLabel{lb, 0xffffffff}) 23 | return id 24 | } 25 | 26 | func scmpBpfStmt(code uint16, k uint32) sockFilter { 27 | return sockFilter{code, 0, 0, k} 28 | } 29 | 30 | func scmpBpfJump(code uint16, k uint32, jt, jf uint8) sockFilter { 31 | return sockFilter{code, jt, jf, k} 32 | } 33 | -------------------------------------------------------------------------------- /selinux/selinux_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package selinux_test 4 | 5 | import ( 6 | "os" 7 | "testing" 8 | 9 | "github.com/docker/libcontainer/selinux" 10 | ) 11 | 12 | func testSetfilecon(t *testing.T) { 13 | if selinux.SelinuxEnabled() { 14 | tmp := "selinux_test" 15 | out, _ := os.OpenFile(tmp, os.O_WRONLY, 0) 16 | out.Close() 17 | err := selinux.Setfilecon(tmp, "system_u:object_r:bin_t:s0") 18 | if err != nil { 19 | t.Log("Setfilecon failed") 20 | t.Fatal(err) 21 | } 22 | os.Remove(tmp) 23 | } 24 | } 25 | 26 | func TestSELinux(t *testing.T) { 27 | var ( 28 | err error 29 | plabel, flabel string 30 | ) 31 | 32 | if selinux.SelinuxEnabled() { 33 | t.Log("Enabled") 34 | plabel, flabel = selinux.GetLxcContexts() 35 | t.Log(plabel) 36 | t.Log(flabel) 37 | selinux.FreeLxcContexts(plabel) 38 | plabel, flabel = selinux.GetLxcContexts() 39 | t.Log(plabel) 40 | t.Log(flabel) 41 | selinux.FreeLxcContexts(plabel) 42 | t.Log("getenforce ", selinux.SelinuxGetEnforce()) 43 | t.Log("getenforcemode ", selinux.SelinuxGetEnforceMode()) 44 | pid := os.Getpid() 45 | t.Logf("PID:%d MCS:%s\n", pid, selinux.IntToMcs(pid, 1023)) 46 | err = selinux.Setfscreatecon("unconfined_u:unconfined_r:unconfined_t:s0") 47 | if err == nil { 48 | t.Log(selinux.Getfscreatecon()) 49 | } else { 50 | t.Log("setfscreatecon failed", err) 51 | t.Fatal(err) 52 | } 53 | err = selinux.Setfscreatecon("") 54 | if err == nil { 55 | t.Log(selinux.Getfscreatecon()) 56 | } else { 57 | t.Log("setfscreatecon failed", err) 58 | t.Fatal(err) 59 | } 60 | t.Log(selinux.Getpidcon(1)) 61 | } else { 62 | t.Log("Disabled") 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /setns_init_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package libcontainer 4 | 5 | import ( 6 | "os" 7 | 8 | "github.com/docker/libcontainer/apparmor" 9 | "github.com/docker/libcontainer/label" 10 | "github.com/docker/libcontainer/system" 11 | ) 12 | 13 | // linuxSetnsInit performs the container's initialization for running a new process 14 | // inside an existing container. 15 | type linuxSetnsInit struct { 16 | config *initConfig 17 | } 18 | 19 | func (l *linuxSetnsInit) Init() error { 20 | if err := setupRlimits(l.config.Config); err != nil { 21 | return err 22 | } 23 | if err := finalizeNamespace(l.config); err != nil { 24 | return err 25 | } 26 | if err := apparmor.ApplyProfile(l.config.Config.AppArmorProfile); err != nil { 27 | return err 28 | } 29 | if l.config.Config.ProcessLabel != "" { 30 | if err := label.SetProcessLabel(l.config.Config.ProcessLabel); err != nil { 31 | return err 32 | } 33 | } 34 | return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ()) 35 | } 36 | -------------------------------------------------------------------------------- /stacktrace/capture.go: -------------------------------------------------------------------------------- 1 | package stacktrace 2 | 3 | import "runtime" 4 | 5 | // Caputure captures a stacktrace for the current calling go program 6 | // 7 | // skip is the number of frames to skip 8 | func Capture(userSkip int) Stacktrace { 9 | var ( 10 | skip = userSkip + 1 // add one for our own function 11 | frames []Frame 12 | ) 13 | for i := skip; ; i++ { 14 | pc, file, line, ok := runtime.Caller(i) 15 | if !ok { 16 | break 17 | } 18 | frames = append(frames, NewFrame(pc, file, line)) 19 | } 20 | return Stacktrace{ 21 | Frames: frames, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /stacktrace/capture_test.go: -------------------------------------------------------------------------------- 1 | package stacktrace 2 | 3 | import "testing" 4 | 5 | func captureFunc() Stacktrace { 6 | return Capture(0) 7 | } 8 | 9 | func TestCaptureTestFunc(t *testing.T) { 10 | stack := captureFunc() 11 | 12 | if len(stack.Frames) == 0 { 13 | t.Fatal("expected stack frames to be returned") 14 | } 15 | 16 | // the first frame is the caller 17 | frame := stack.Frames[0] 18 | if expected := "captureFunc"; frame.Function != expected { 19 | t.Fatalf("expteced function %q but recevied %q", expected, frame.Function) 20 | } 21 | if expected := "github.com/docker/libcontainer/stacktrace"; frame.Package != expected { 22 | t.Fatalf("expected package %q but received %q", expected, frame.Package) 23 | } 24 | if expected := "capture_test.go"; frame.File != expected { 25 | t.Fatalf("expected file %q but received %q", expected, frame.File) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /stacktrace/frame.go: -------------------------------------------------------------------------------- 1 | package stacktrace 2 | 3 | import ( 4 | "path/filepath" 5 | "runtime" 6 | "strings" 7 | ) 8 | 9 | // NewFrame returns a new stack frame for the provided information 10 | func NewFrame(pc uintptr, file string, line int) Frame { 11 | fn := runtime.FuncForPC(pc) 12 | if fn == nil { 13 | return Frame{} 14 | } 15 | pack, name := parseFunctionName(fn.Name()) 16 | return Frame{ 17 | Line: line, 18 | File: filepath.Base(file), 19 | Package: pack, 20 | Function: name, 21 | } 22 | } 23 | 24 | func parseFunctionName(name string) (string, string) { 25 | i := strings.LastIndex(name, ".") 26 | if i == -1 { 27 | return "", name 28 | } 29 | return name[:i], name[i+1:] 30 | } 31 | 32 | // Frame contains all the information for a stack frame within a go program 33 | type Frame struct { 34 | File string 35 | Function string 36 | Package string 37 | Line int 38 | } 39 | -------------------------------------------------------------------------------- /stacktrace/frame_test.go: -------------------------------------------------------------------------------- 1 | package stacktrace 2 | 3 | import "testing" 4 | 5 | func TestParsePackageName(t *testing.T) { 6 | var ( 7 | name = "github.com/docker/libcontainer/stacktrace.captureFunc" 8 | expectedPackage = "github.com/docker/libcontainer/stacktrace" 9 | expectedFunction = "captureFunc" 10 | ) 11 | 12 | pack, funcName := parseFunctionName(name) 13 | if pack != expectedPackage { 14 | t.Fatalf("expected package %q but received %q", expectedPackage, pack) 15 | } 16 | 17 | if funcName != expectedFunction { 18 | t.Fatalf("expected function %q but received %q", expectedFunction, funcName) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /stacktrace/stacktrace.go: -------------------------------------------------------------------------------- 1 | package stacktrace 2 | 3 | type Stacktrace struct { 4 | Frames []Frame 5 | } 6 | -------------------------------------------------------------------------------- /stats.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | type NetworkInterface struct { 4 | // Name is the name of the network interface. 5 | Name string 6 | 7 | RxBytes uint64 8 | RxPackets uint64 9 | RxErrors uint64 10 | RxDropped uint64 11 | TxBytes uint64 12 | TxPackets uint64 13 | TxErrors uint64 14 | TxDropped uint64 15 | } 16 | -------------------------------------------------------------------------------- /stats_freebsd.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | type Stats struct { 4 | Interfaces []*NetworkInterface 5 | } 6 | -------------------------------------------------------------------------------- /stats_linux.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | import "github.com/docker/libcontainer/cgroups" 4 | 5 | type Stats struct { 6 | Interfaces []*NetworkInterface 7 | CgroupStats *cgroups.Stats 8 | } 9 | -------------------------------------------------------------------------------- /stats_windows.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | type Stats struct { 4 | Interfaces []*NetworkInterface 5 | } 6 | -------------------------------------------------------------------------------- /system/linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package system 4 | 5 | import ( 6 | "os/exec" 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | type ParentDeathSignal int 12 | 13 | func (p ParentDeathSignal) Restore() error { 14 | if p == 0 { 15 | return nil 16 | } 17 | current, err := GetParentDeathSignal() 18 | if err != nil { 19 | return err 20 | } 21 | if p == current { 22 | return nil 23 | } 24 | return p.Set() 25 | } 26 | 27 | func (p ParentDeathSignal) Set() error { 28 | return SetParentDeathSignal(uintptr(p)) 29 | } 30 | 31 | func Execv(cmd string, args []string, env []string) error { 32 | name, err := exec.LookPath(cmd) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | return syscall.Exec(name, args, env) 38 | } 39 | 40 | func SetParentDeathSignal(sig uintptr) error { 41 | if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, sig, 0); err != 0 { 42 | return err 43 | } 44 | return nil 45 | } 46 | 47 | func GetParentDeathSignal() (ParentDeathSignal, error) { 48 | var sig int 49 | _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_GET_PDEATHSIG, uintptr(unsafe.Pointer(&sig)), 0) 50 | if err != 0 { 51 | return -1, err 52 | } 53 | return ParentDeathSignal(sig), nil 54 | } 55 | 56 | func SetKeepCaps() error { 57 | if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_KEEPCAPS, 1, 0); err != 0 { 58 | return err 59 | } 60 | 61 | return nil 62 | } 63 | 64 | func ClearKeepCaps() error { 65 | if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_KEEPCAPS, 0, 0); err != 0 { 66 | return err 67 | } 68 | 69 | return nil 70 | } 71 | 72 | func Setctty() error { 73 | if _, _, err := syscall.RawSyscall(syscall.SYS_IOCTL, 0, uintptr(syscall.TIOCSCTTY), 0); err != 0 { 74 | return err 75 | } 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /system/proc.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import ( 4 | "io/ioutil" 5 | "path/filepath" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // look in /proc to find the process start time so that we can verify 11 | // that this pid has started after ourself 12 | func GetProcessStartTime(pid int) (string, error) { 13 | data, err := ioutil.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "stat")) 14 | if err != nil { 15 | return "", err 16 | } 17 | 18 | parts := strings.Split(string(data), " ") 19 | // the starttime is located at pos 22 20 | // from the man page 21 | // 22 | // starttime %llu (was %lu before Linux 2.6) 23 | // (22) The time the process started after system boot. In kernels before Linux 2.6, this 24 | // value was expressed in jiffies. Since Linux 2.6, the value is expressed in clock ticks 25 | // (divide by sysconf(_SC_CLK_TCK)). 26 | return parts[22-1], nil // starts at 1 27 | } 28 | -------------------------------------------------------------------------------- /system/setns_linux.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "syscall" 7 | ) 8 | 9 | // Via http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=7b21fddd087678a70ad64afc0f632e0f1071b092 10 | // 11 | // We need different setns values for the different platforms and arch 12 | // We are declaring the macro here because the SETNS syscall does not exist in th stdlib 13 | var setNsMap = map[string]uintptr{ 14 | "linux/386": 346, 15 | "linux/arm64": 268, 16 | "linux/amd64": 308, 17 | "linux/arm": 375, 18 | "linux/ppc": 350, 19 | "linux/ppc64": 350, 20 | "linux/ppc64le": 350, 21 | "linux/s390x": 339, 22 | } 23 | 24 | var sysSetns = setNsMap[fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)] 25 | 26 | func SysSetns() uint32 { 27 | return uint32(sysSetns) 28 | } 29 | 30 | func Setns(fd uintptr, flags uintptr) error { 31 | ns, exists := setNsMap[fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)] 32 | if !exists { 33 | return fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime.GOARCH) 34 | } 35 | _, _, err := syscall.RawSyscall(ns, fd, flags, 0) 36 | if err != 0 { 37 | return err 38 | } 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /system/syscall_linux_386.go: -------------------------------------------------------------------------------- 1 | // +build linux,386 2 | package system 3 | 4 | import ( 5 | "syscall" 6 | ) 7 | 8 | // Setuid sets the uid of the calling thread to the specified uid. 9 | func Setuid(uid int) (err error) { 10 | _, _, e1 := syscall.RawSyscall(syscall.SYS_SETUID, uintptr(uid), 0, 0) 11 | if e1 != 0 { 12 | err = e1 13 | } 14 | return 15 | } 16 | 17 | // Setgid sets the gid of the calling thread to the specified gid. 18 | func Setgid(gid int) (err error) { 19 | _, _, e1 := syscall.RawSyscall(syscall.SYS_SETGID32, uintptr(gid), 0, 0) 20 | if e1 != 0 { 21 | err = e1 22 | } 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /system/syscall_linux_64.go: -------------------------------------------------------------------------------- 1 | // +build linux,arm64 linux,amd64 linux,ppc linux,ppc64 linux,ppc64le linux,s390x 2 | 3 | package system 4 | 5 | import ( 6 | "syscall" 7 | ) 8 | 9 | // Setuid sets the uid of the calling thread to the specified uid. 10 | func Setuid(uid int) (err error) { 11 | _, _, e1 := syscall.RawSyscall(syscall.SYS_SETUID, uintptr(uid), 0, 0) 12 | if e1 != 0 { 13 | err = e1 14 | } 15 | return 16 | } 17 | 18 | // Setgid sets the gid of the calling thread to the specified gid. 19 | func Setgid(gid int) (err error) { 20 | _, _, e1 := syscall.RawSyscall(syscall.SYS_SETGID, uintptr(gid), 0, 0) 21 | if e1 != 0 { 22 | err = e1 23 | } 24 | return 25 | } 26 | -------------------------------------------------------------------------------- /system/syscall_linux_arm.go: -------------------------------------------------------------------------------- 1 | // +build linux,arm 2 | package system 3 | 4 | import ( 5 | "syscall" 6 | ) 7 | 8 | // Setuid sets the uid of the calling thread to the specified uid. 9 | func Setuid(uid int) (err error) { 10 | _, _, e1 := syscall.RawSyscall(syscall.SYS_SETUID32, uintptr(uid), 0, 0) 11 | if e1 != 0 { 12 | err = e1 13 | } 14 | return 15 | } 16 | 17 | // Setgid sets the gid of the calling thread to the specified gid. 18 | func Setgid(gid int) (err error) { 19 | _, _, e1 := syscall.RawSyscall(syscall.SYS_SETGID32, uintptr(gid), 0, 0) 20 | if e1 != 0 { 21 | err = e1 22 | } 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /system/sysconfig.go: -------------------------------------------------------------------------------- 1 | // +build cgo,linux cgo,freebsd 2 | 3 | package system 4 | 5 | /* 6 | #include 7 | */ 8 | import "C" 9 | 10 | func GetClockTicks() int { 11 | return int(C.sysconf(C._SC_CLK_TCK)) 12 | } 13 | -------------------------------------------------------------------------------- /system/sysconfig_notcgo.go: -------------------------------------------------------------------------------- 1 | // +build !cgo windows 2 | 3 | package system 4 | 5 | func GetClockTicks() int { 6 | // TODO figure out a better alternative for platforms where we're missing cgo 7 | // 8 | // TODO Windows. This could be implemented using Win32 QueryPerformanceFrequency(). 9 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms644905(v=vs.85).aspx 10 | // 11 | // An example of its usage can be found here. 12 | // https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx 13 | 14 | return 100 15 | } 16 | -------------------------------------------------------------------------------- /update-vendor.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | cd "$(dirname "$BASH_SOURCE")" 5 | 6 | # Downloads dependencies into vendor/ directory 7 | mkdir -p vendor 8 | cd vendor 9 | 10 | clone() { 11 | vcs=$1 12 | pkg=$2 13 | rev=$3 14 | 15 | pkg_url=https://$pkg 16 | target_dir=src/$pkg 17 | 18 | echo -n "$pkg @ $rev: " 19 | 20 | if [ -d $target_dir ]; then 21 | echo -n 'rm old, ' 22 | rm -fr $target_dir 23 | fi 24 | 25 | echo -n 'clone, ' 26 | case $vcs in 27 | git) 28 | git clone --quiet --no-checkout $pkg_url $target_dir 29 | ( cd $target_dir && git reset --quiet --hard $rev ) 30 | ;; 31 | hg) 32 | hg clone --quiet --updaterev $rev $pkg_url $target_dir 33 | ;; 34 | esac 35 | 36 | echo -n 'rm VCS, ' 37 | ( cd $target_dir && rm -rf .{git,hg} ) 38 | 39 | echo done 40 | } 41 | 42 | # the following lines are in sorted order, FYI 43 | clone git github.com/codegangsta/cli 1.1.0 44 | clone git github.com/coreos/go-systemd v2 45 | clone git github.com/godbus/dbus v2 46 | clone git github.com/Sirupsen/logrus v0.7.3 47 | clone git github.com/syndtr/gocapability 66ef2aa 48 | clone git github.com/golang/protobuf 655cdfa588ea 49 | 50 | # intentionally not vendoring Docker itself... that'd be a circle :) 51 | -------------------------------------------------------------------------------- /user/MAINTAINERS: -------------------------------------------------------------------------------- 1 | Tianon Gravi (@tianon) 2 | Aleksa Sarai (@cyphar) 3 | -------------------------------------------------------------------------------- /user/lookup_unix.go: -------------------------------------------------------------------------------- 1 | // +build darwin dragonfly freebsd linux netbsd openbsd solaris 2 | 3 | package user 4 | 5 | import ( 6 | "io" 7 | "os" 8 | ) 9 | 10 | // Unix-specific path to the passwd and group formatted files. 11 | const ( 12 | unixPasswdPath = "/etc/passwd" 13 | unixGroupPath = "/etc/group" 14 | ) 15 | 16 | func GetPasswdPath() (string, error) { 17 | return unixPasswdPath, nil 18 | } 19 | 20 | func GetPasswd() (io.ReadCloser, error) { 21 | return os.Open(unixPasswdPath) 22 | } 23 | 24 | func GetGroupPath() (string, error) { 25 | return unixGroupPath, nil 26 | } 27 | 28 | func GetGroup() (io.ReadCloser, error) { 29 | return os.Open(unixGroupPath) 30 | } 31 | -------------------------------------------------------------------------------- /user/lookup_unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris 2 | 3 | package user 4 | 5 | import "io" 6 | 7 | func GetPasswdPath() (string, error) { 8 | return "", ErrUnsupported 9 | } 10 | 11 | func GetPasswd() (io.ReadCloser, error) { 12 | return nil, ErrUnsupported 13 | } 14 | 15 | func GetGroupPath() (string, error) { 16 | return "", ErrUnsupported 17 | } 18 | 19 | func GetGroup() (io.ReadCloser, error) { 20 | return nil, ErrUnsupported 21 | } 22 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/hex" 6 | "io" 7 | "io/ioutil" 8 | "path/filepath" 9 | "strconv" 10 | "syscall" 11 | ) 12 | 13 | const ( 14 | exitSignalOffset = 128 15 | ) 16 | 17 | // GenerateRandomName returns a new name joined with a prefix. This size 18 | // specified is used to truncate the randomly generated value 19 | func GenerateRandomName(prefix string, size int) (string, error) { 20 | id := make([]byte, 32) 21 | if _, err := io.ReadFull(rand.Reader, id); err != nil { 22 | return "", err 23 | } 24 | if size > 64 { 25 | size = 64 26 | } 27 | return prefix + hex.EncodeToString(id)[:size], nil 28 | } 29 | 30 | // ResolveRootfs ensures that the current working directory is 31 | // not a symlink and returns the absolute path to the rootfs 32 | func ResolveRootfs(uncleanRootfs string) (string, error) { 33 | rootfs, err := filepath.Abs(uncleanRootfs) 34 | if err != nil { 35 | return "", err 36 | } 37 | return filepath.EvalSymlinks(rootfs) 38 | } 39 | 40 | func CloseExecFrom(minFd int) error { 41 | fdList, err := ioutil.ReadDir("/proc/self/fd") 42 | if err != nil { 43 | return err 44 | } 45 | for _, fi := range fdList { 46 | fd, err := strconv.Atoi(fi.Name()) 47 | if err != nil { 48 | // ignore non-numeric file names 49 | continue 50 | } 51 | 52 | if fd < minFd { 53 | // ignore descriptors lower than our specified minimum 54 | continue 55 | } 56 | 57 | // intentionally ignore errors from syscall.CloseOnExec 58 | syscall.CloseOnExec(fd) 59 | // the cases where this might fail are basically file descriptors that have already been closed (including and especially the one that was created when ioutil.ReadDir did the "opendir" syscall) 60 | } 61 | return nil 62 | } 63 | 64 | // ExitStatus returns the correct exit status for a process based on if it 65 | // was signaled or existed cleanly. 66 | func ExitStatus(status syscall.WaitStatus) int { 67 | if status.Signaled() { 68 | return exitSignalOffset + int(status.Signal()) 69 | } 70 | return status.ExitStatus() 71 | } 72 | -------------------------------------------------------------------------------- /utils/utils_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "testing" 4 | 5 | func TestGenerateName(t *testing.T) { 6 | name, err := GenerateRandomName("veth", 5) 7 | if err != nil { 8 | t.Fatal(err) 9 | } 10 | 11 | expected := 5 + len("veth") 12 | if len(name) != expected { 13 | t.Fatalf("expected name to be %d chars but received %d", expected, len(name)) 14 | } 15 | 16 | name, err = GenerateRandomName("veth", 65) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | expected = 64 + len("veth") 22 | if len(name) != expected { 23 | t.Fatalf("expected name to be %d chars but received %d", expected, len(name)) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/.gitignore: -------------------------------------------------------------------------------- 1 | logrus 2 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.2 4 | - 1.3 5 | - 1.4 6 | - tip 7 | install: 8 | - go get -t ./... 9 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.7.3 2 | 3 | formatter/\*: allow configuration of timestamp layout 4 | 5 | # 0.7.2 6 | 7 | formatter/text: Add configuration option for time format (#158) 8 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Simon Eskildsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/entry_test.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestEntryPanicln(t *testing.T) { 12 | errBoom := fmt.Errorf("boom time") 13 | 14 | defer func() { 15 | p := recover() 16 | assert.NotNil(t, p) 17 | 18 | switch pVal := p.(type) { 19 | case *Entry: 20 | assert.Equal(t, "kaboom", pVal.Message) 21 | assert.Equal(t, errBoom, pVal.Data["err"]) 22 | default: 23 | t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal) 24 | } 25 | }() 26 | 27 | logger := New() 28 | logger.Out = &bytes.Buffer{} 29 | entry := NewEntry(logger) 30 | entry.WithField("err", errBoom).Panicln("kaboom") 31 | } 32 | 33 | func TestEntryPanicf(t *testing.T) { 34 | errBoom := fmt.Errorf("boom again") 35 | 36 | defer func() { 37 | p := recover() 38 | assert.NotNil(t, p) 39 | 40 | switch pVal := p.(type) { 41 | case *Entry: 42 | assert.Equal(t, "kaboom true", pVal.Message) 43 | assert.Equal(t, errBoom, pVal.Data["err"]) 44 | default: 45 | t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal) 46 | } 47 | }() 48 | 49 | logger := New() 50 | logger.Out = &bytes.Buffer{} 51 | entry := NewEntry(logger) 52 | entry.WithField("err", errBoom).Panicf("kaboom %v", true) 53 | } 54 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/examples/basic/basic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/Sirupsen/logrus" 5 | ) 6 | 7 | var log = logrus.New() 8 | 9 | func init() { 10 | log.Formatter = new(logrus.JSONFormatter) 11 | log.Formatter = new(logrus.TextFormatter) // default 12 | log.Level = logrus.DebugLevel 13 | } 14 | 15 | func main() { 16 | defer func() { 17 | err := recover() 18 | if err != nil { 19 | log.WithFields(logrus.Fields{ 20 | "omg": true, 21 | "err": err, 22 | "number": 100, 23 | }).Fatal("The ice breaks!") 24 | } 25 | }() 26 | 27 | log.WithFields(logrus.Fields{ 28 | "animal": "walrus", 29 | "number": 8, 30 | }).Debug("Started observing beach") 31 | 32 | log.WithFields(logrus.Fields{ 33 | "animal": "walrus", 34 | "size": 10, 35 | }).Info("A group of walrus emerges from the ocean") 36 | 37 | log.WithFields(logrus.Fields{ 38 | "omg": true, 39 | "number": 122, 40 | }).Warn("The group's number increased tremendously!") 41 | 42 | log.WithFields(logrus.Fields{ 43 | "temperature": -4, 44 | }).Debug("Temperature changes") 45 | 46 | log.WithFields(logrus.Fields{ 47 | "animal": "orca", 48 | "size": 9009, 49 | }).Panic("It's over 9000!") 50 | } 51 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/examples/hook/hook.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/Sirupsen/logrus" 5 | "github.com/Sirupsen/logrus/hooks/airbrake" 6 | ) 7 | 8 | var log = logrus.New() 9 | 10 | func init() { 11 | log.Formatter = new(logrus.TextFormatter) // default 12 | log.Hooks.Add(airbrake.NewHook("https://example.com", "xyz", "development")) 13 | } 14 | 15 | func main() { 16 | log.WithFields(logrus.Fields{ 17 | "animal": "walrus", 18 | "size": 10, 19 | }).Info("A group of walrus emerges from the ocean") 20 | 21 | log.WithFields(logrus.Fields{ 22 | "omg": true, 23 | "number": 122, 24 | }).Warn("The group's number increased tremendously!") 25 | 26 | log.WithFields(logrus.Fields{ 27 | "omg": true, 28 | "number": 100, 29 | }).Fatal("The ice breaks!") 30 | } 31 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/formatter.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | import "time" 4 | 5 | const DefaultTimestampFormat = time.RFC3339 6 | 7 | // The Formatter interface is used to implement a custom Formatter. It takes an 8 | // `Entry`. It exposes all the fields, including the default ones: 9 | // 10 | // * `entry.Data["msg"]`. The message passed from Info, Warn, Error .. 11 | // * `entry.Data["time"]`. The timestamp. 12 | // * `entry.Data["level"]. The level the entry was logged at. 13 | // 14 | // Any additional fields added with `WithField` or `WithFields` are also in 15 | // `entry.Data`. Format is expected to return an array of bytes which are then 16 | // logged to `logger.Out`. 17 | type Formatter interface { 18 | Format(*Entry) ([]byte, error) 19 | } 20 | 21 | // This is to not silently overwrite `time`, `msg` and `level` fields when 22 | // dumping it. If this code wasn't there doing: 23 | // 24 | // logrus.WithField("level", 1).Info("hello") 25 | // 26 | // Would just silently drop the user provided level. Instead with this code 27 | // it'll logged as: 28 | // 29 | // {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."} 30 | // 31 | // It's not exported because it's still using Data in an opinionated way. It's to 32 | // avoid code duplication between the two default formatters. 33 | func prefixFieldClashes(data Fields) { 34 | _, ok := data["time"] 35 | if ok { 36 | data["fields.time"] = data["time"] 37 | } 38 | 39 | _, ok = data["msg"] 40 | if ok { 41 | data["fields.msg"] = data["msg"] 42 | } 43 | 44 | _, ok = data["level"] 45 | if ok { 46 | data["fields.level"] = data["level"] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go: -------------------------------------------------------------------------------- 1 | package logstash 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/Sirupsen/logrus" 8 | ) 9 | 10 | // Formatter generates json in logstash format. 11 | // Logstash site: http://logstash.net/ 12 | type LogstashFormatter struct { 13 | Type string // if not empty use for logstash type field. 14 | 15 | // TimestampFormat sets the format used for timestamps. 16 | TimestampFormat string 17 | } 18 | 19 | func (f *LogstashFormatter) Format(entry *logrus.Entry) ([]byte, error) { 20 | entry.Data["@version"] = 1 21 | 22 | if f.TimestampFormat == "" { 23 | f.TimestampFormat = logrus.DefaultTimestampFormat 24 | } 25 | 26 | entry.Data["@timestamp"] = entry.Time.Format(f.TimestampFormat) 27 | 28 | // set message field 29 | v, ok := entry.Data["message"] 30 | if ok { 31 | entry.Data["fields.message"] = v 32 | } 33 | entry.Data["message"] = entry.Message 34 | 35 | // set level field 36 | v, ok = entry.Data["level"] 37 | if ok { 38 | entry.Data["fields.level"] = v 39 | } 40 | entry.Data["level"] = entry.Level.String() 41 | 42 | // set type field 43 | if f.Type != "" { 44 | v, ok = entry.Data["type"] 45 | if ok { 46 | entry.Data["fields.type"] = v 47 | } 48 | entry.Data["type"] = f.Type 49 | } 50 | 51 | serialized, err := json.Marshal(entry.Data) 52 | if err != nil { 53 | return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) 54 | } 55 | return append(serialized, '\n'), nil 56 | } 57 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/formatters/logstash/logstash_test.go: -------------------------------------------------------------------------------- 1 | package logstash 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "github.com/Sirupsen/logrus" 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func TestLogstashFormatter(t *testing.T) { 12 | assert := assert.New(t) 13 | 14 | lf := LogstashFormatter{Type: "abc"} 15 | 16 | fields := logrus.Fields{ 17 | "message": "def", 18 | "level": "ijk", 19 | "type": "lmn", 20 | "one": 1, 21 | "pi": 3.14, 22 | "bool": true, 23 | } 24 | 25 | entry := logrus.WithFields(fields) 26 | entry.Message = "msg" 27 | entry.Level = logrus.InfoLevel 28 | 29 | b, _ := lf.Format(entry) 30 | 31 | var data map[string]interface{} 32 | dec := json.NewDecoder(bytes.NewReader(b)) 33 | dec.UseNumber() 34 | dec.Decode(&data) 35 | 36 | // base fields 37 | assert.Equal(json.Number("1"), data["@version"]) 38 | assert.NotEmpty(data["@timestamp"]) 39 | assert.Equal("abc", data["type"]) 40 | assert.Equal("msg", data["message"]) 41 | assert.Equal("info", data["level"]) 42 | 43 | // substituted fields 44 | assert.Equal("def", data["fields.message"]) 45 | assert.Equal("ijk", data["fields.level"]) 46 | assert.Equal("lmn", data["fields.type"]) 47 | 48 | // formats 49 | assert.Equal(json.Number("1"), data["one"]) 50 | assert.Equal(json.Number("3.14"), data["pi"]) 51 | assert.Equal(true, data["bool"]) 52 | } 53 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/hooks.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | // A hook to be fired when logging on the logging levels returned from 4 | // `Levels()` on your implementation of the interface. Note that this is not 5 | // fired in a goroutine or a channel with workers, you should handle such 6 | // functionality yourself if your call is non-blocking and you don't wish for 7 | // the logging calls for levels returned from `Levels()` to block. 8 | type Hook interface { 9 | Levels() []Level 10 | Fire(*Entry) error 11 | } 12 | 13 | // Internal type for storing the hooks on a logger instance. 14 | type levelHooks map[Level][]Hook 15 | 16 | // Add a hook to an instance of logger. This is called with 17 | // `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface. 18 | func (hooks levelHooks) Add(hook Hook) { 19 | for _, level := range hook.Levels() { 20 | hooks[level] = append(hooks[level], hook) 21 | } 22 | } 23 | 24 | // Fire all the hooks for the passed level. Used by `entry.log` to fire 25 | // appropriate hooks for a log entry. 26 | func (hooks levelHooks) Fire(level Level, entry *Entry) error { 27 | for _, hook := range hooks[level] { 28 | if err := hook.Fire(entry); err != nil { 29 | return err 30 | } 31 | } 32 | 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go: -------------------------------------------------------------------------------- 1 | package airbrake 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/Sirupsen/logrus" 8 | "github.com/tobi/airbrake-go" 9 | ) 10 | 11 | // AirbrakeHook to send exceptions to an exception-tracking service compatible 12 | // with the Airbrake API. 13 | type airbrakeHook struct { 14 | APIKey string 15 | Endpoint string 16 | Environment string 17 | } 18 | 19 | func NewHook(endpoint, apiKey, env string) *airbrakeHook { 20 | return &airbrakeHook{ 21 | APIKey: apiKey, 22 | Endpoint: endpoint, 23 | Environment: env, 24 | } 25 | } 26 | 27 | func (hook *airbrakeHook) Fire(entry *logrus.Entry) error { 28 | airbrake.ApiKey = hook.APIKey 29 | airbrake.Endpoint = hook.Endpoint 30 | airbrake.Environment = hook.Environment 31 | 32 | var notifyErr error 33 | err, ok := entry.Data["error"].(error) 34 | if ok { 35 | notifyErr = err 36 | } else { 37 | notifyErr = errors.New(entry.Message) 38 | } 39 | 40 | airErr := airbrake.Notify(notifyErr) 41 | if airErr != nil { 42 | return fmt.Errorf("Failed to send error to Airbrake: %s", airErr) 43 | } 44 | 45 | return nil 46 | } 47 | 48 | func (hook *airbrakeHook) Levels() []logrus.Level { 49 | return []logrus.Level{ 50 | logrus.ErrorLevel, 51 | logrus.FatalLevel, 52 | logrus.PanicLevel, 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag_test.go: -------------------------------------------------------------------------------- 1 | package logrus_bugsnag 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "io/ioutil" 7 | "net/http" 8 | "net/http/httptest" 9 | "testing" 10 | "time" 11 | 12 | "github.com/Sirupsen/logrus" 13 | "github.com/bugsnag/bugsnag-go" 14 | ) 15 | 16 | type notice struct { 17 | Events []struct { 18 | Exceptions []struct { 19 | Message string `json:"message"` 20 | } `json:"exceptions"` 21 | } `json:"events"` 22 | } 23 | 24 | func TestNoticeReceived(t *testing.T) { 25 | msg := make(chan string, 1) 26 | expectedMsg := "foo" 27 | 28 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 29 | var notice notice 30 | data, _ := ioutil.ReadAll(r.Body) 31 | if err := json.Unmarshal(data, ¬ice); err != nil { 32 | t.Error(err) 33 | } 34 | _ = r.Body.Close() 35 | 36 | msg <- notice.Events[0].Exceptions[0].Message 37 | })) 38 | defer ts.Close() 39 | 40 | hook := &bugsnagHook{} 41 | 42 | bugsnag.Configure(bugsnag.Configuration{ 43 | Endpoint: ts.URL, 44 | ReleaseStage: "production", 45 | APIKey: "12345678901234567890123456789012", 46 | Synchronous: true, 47 | }) 48 | 49 | log := logrus.New() 50 | log.Hooks.Add(hook) 51 | 52 | log.WithFields(logrus.Fields{ 53 | "error": errors.New(expectedMsg), 54 | }).Error("Bugsnag will not see this string") 55 | 56 | select { 57 | case received := <-msg: 58 | if received != expectedMsg { 59 | t.Errorf("Unexpected message received: %s", received) 60 | } 61 | case <-time.After(time.Second): 62 | t.Error("Timed out; no notice received by Bugsnag API") 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/hooks/papertrail/README.md: -------------------------------------------------------------------------------- 1 | # Papertrail Hook for Logrus :walrus: 2 | 3 | [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). 4 | 5 | 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. 6 | 7 | ## Usage 8 | 9 | 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`. 10 | 11 | For `YOUR_APP_NAME`, substitute a short string that will readily identify your application or service in the logs. 12 | 13 | ```go 14 | import ( 15 | "log/syslog" 16 | "github.com/Sirupsen/logrus" 17 | "github.com/Sirupsen/logrus/hooks/papertrail" 18 | ) 19 | 20 | func main() { 21 | log := logrus.New() 22 | hook, err := logrus_papertrail.NewPapertrailHook("logs.papertrailapp.com", YOUR_PAPERTRAIL_UDP_PORT, YOUR_APP_NAME) 23 | 24 | if err == nil { 25 | log.Hooks.Add(hook) 26 | } 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go: -------------------------------------------------------------------------------- 1 | package logrus_papertrail 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "time" 8 | 9 | "github.com/Sirupsen/logrus" 10 | ) 11 | 12 | const ( 13 | format = "Jan 2 15:04:05" 14 | ) 15 | 16 | // PapertrailHook to send logs to a logging service compatible with the Papertrail API. 17 | type PapertrailHook struct { 18 | Host string 19 | Port int 20 | AppName string 21 | UDPConn net.Conn 22 | } 23 | 24 | // NewPapertrailHook creates a hook to be added to an instance of logger. 25 | func NewPapertrailHook(host string, port int, appName string) (*PapertrailHook, error) { 26 | conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", host, port)) 27 | return &PapertrailHook{host, port, appName, conn}, err 28 | } 29 | 30 | // Fire is called when a log event is fired. 31 | func (hook *PapertrailHook) Fire(entry *logrus.Entry) error { 32 | date := time.Now().Format(format) 33 | msg, _ := entry.String() 34 | payload := fmt.Sprintf("<22> %s %s: %s", date, hook.AppName, msg) 35 | 36 | bytesWritten, err := hook.UDPConn.Write([]byte(payload)) 37 | if err != nil { 38 | fmt.Fprintf(os.Stderr, "Unable to send log line to Papertrail via UDP. Wrote %d bytes before error: %v", bytesWritten, err) 39 | return err 40 | } 41 | 42 | return nil 43 | } 44 | 45 | // Levels returns the available logging levels. 46 | func (hook *PapertrailHook) Levels() []logrus.Level { 47 | return []logrus.Level{ 48 | logrus.PanicLevel, 49 | logrus.FatalLevel, 50 | logrus.ErrorLevel, 51 | logrus.WarnLevel, 52 | logrus.InfoLevel, 53 | logrus.DebugLevel, 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail_test.go: -------------------------------------------------------------------------------- 1 | package logrus_papertrail 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/Sirupsen/logrus" 8 | "github.com/stvp/go-udp-testing" 9 | ) 10 | 11 | func TestWritingToUDP(t *testing.T) { 12 | port := 16661 13 | udp.SetAddr(fmt.Sprintf(":%d", port)) 14 | 15 | hook, err := NewPapertrailHook("localhost", port, "test") 16 | if err != nil { 17 | t.Errorf("Unable to connect to local UDP server.") 18 | } 19 | 20 | log := logrus.New() 21 | log.Hooks.Add(hook) 22 | 23 | udp.ShouldReceive(t, "foo", func() { 24 | log.Info("foo") 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/hooks/sentry/README.md: -------------------------------------------------------------------------------- 1 | # Sentry Hook for Logrus :walrus: 2 | 3 | [Sentry](https://getsentry.com) provides both self-hosted and hosted 4 | solutions for exception tracking. 5 | Both client and server are 6 | [open source](https://github.com/getsentry/sentry). 7 | 8 | ## Usage 9 | 10 | Every sentry application defined on the server gets a different 11 | [DSN](https://www.getsentry.com/docs/). In the example below replace 12 | `YOUR_DSN` with the one created for your application. 13 | 14 | ```go 15 | import ( 16 | "github.com/Sirupsen/logrus" 17 | "github.com/Sirupsen/logrus/hooks/sentry" 18 | ) 19 | 20 | func main() { 21 | log := logrus.New() 22 | hook, err := logrus_sentry.NewSentryHook(YOUR_DSN, []logrus.Level{ 23 | logrus.PanicLevel, 24 | logrus.FatalLevel, 25 | logrus.ErrorLevel, 26 | }) 27 | 28 | if err == nil { 29 | log.Hooks.Add(hook) 30 | } 31 | } 32 | ``` 33 | 34 | ## Special fields 35 | 36 | Some logrus fields have a special meaning in this hook, 37 | these are server_name and logger. 38 | When logs are sent to sentry these fields are treated differently. 39 | - server_name (also known as hostname) is the name of the server which 40 | is logging the event (hostname.example.com) 41 | - logger is the part of the application which is logging the event. 42 | In go this usually means setting it to the name of the package. 43 | 44 | ## Timeout 45 | 46 | `Timeout` is the time the sentry hook will wait for a response 47 | from the sentry server. 48 | 49 | If this time elapses with no response from 50 | the server an error will be returned. 51 | 52 | If `Timeout` is set to 0 the SentryHook will not wait for a reply 53 | and will assume a correct delivery. 54 | 55 | The SentryHook has a default timeout of `100 milliseconds` when created 56 | with a call to `NewSentryHook`. This can be changed by assigning a value to the `Timeout` field: 57 | 58 | ```go 59 | hook, _ := logrus_sentry.NewSentryHook(...) 60 | hook.Timeout = 20*time.Second 61 | ``` 62 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/hooks/syslog/README.md: -------------------------------------------------------------------------------- 1 | # Syslog Hooks for Logrus :walrus: 2 | 3 | ## Usage 4 | 5 | ```go 6 | import ( 7 | "log/syslog" 8 | "github.com/Sirupsen/logrus" 9 | logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog" 10 | ) 11 | 12 | func main() { 13 | log := logrus.New() 14 | hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") 15 | 16 | if err == nil { 17 | log.Hooks.Add(hook) 18 | } 19 | } 20 | ``` 21 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go: -------------------------------------------------------------------------------- 1 | package logrus_syslog 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Sirupsen/logrus" 6 | "log/syslog" 7 | "os" 8 | ) 9 | 10 | // SyslogHook to send logs via syslog. 11 | type SyslogHook struct { 12 | Writer *syslog.Writer 13 | SyslogNetwork string 14 | SyslogRaddr string 15 | } 16 | 17 | // Creates a hook to be added to an instance of logger. This is called with 18 | // `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")` 19 | // `if err == nil { log.Hooks.Add(hook) }` 20 | func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) { 21 | w, err := syslog.Dial(network, raddr, priority, tag) 22 | return &SyslogHook{w, network, raddr}, err 23 | } 24 | 25 | func (hook *SyslogHook) Fire(entry *logrus.Entry) error { 26 | line, err := entry.String() 27 | if err != nil { 28 | fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err) 29 | return err 30 | } 31 | 32 | switch entry.Level { 33 | case logrus.PanicLevel: 34 | return hook.Writer.Crit(line) 35 | case logrus.FatalLevel: 36 | return hook.Writer.Crit(line) 37 | case logrus.ErrorLevel: 38 | return hook.Writer.Err(line) 39 | case logrus.WarnLevel: 40 | return hook.Writer.Warning(line) 41 | case logrus.InfoLevel: 42 | return hook.Writer.Info(line) 43 | case logrus.DebugLevel: 44 | return hook.Writer.Debug(line) 45 | default: 46 | return nil 47 | } 48 | } 49 | 50 | func (hook *SyslogHook) Levels() []logrus.Level { 51 | return []logrus.Level{ 52 | logrus.PanicLevel, 53 | logrus.FatalLevel, 54 | logrus.ErrorLevel, 55 | logrus.WarnLevel, 56 | logrus.InfoLevel, 57 | logrus.DebugLevel, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/hooks/syslog/syslog_test.go: -------------------------------------------------------------------------------- 1 | package logrus_syslog 2 | 3 | import ( 4 | "github.com/Sirupsen/logrus" 5 | "log/syslog" 6 | "testing" 7 | ) 8 | 9 | func TestLocalhostAddAndPrint(t *testing.T) { 10 | log := logrus.New() 11 | hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") 12 | 13 | if err != nil { 14 | t.Errorf("Unable to connect to local syslog.") 15 | } 16 | 17 | log.Hooks.Add(hook) 18 | 19 | for _, level := range hook.Levels() { 20 | if len(log.Hooks[level]) != 1 { 21 | t.Errorf("SyslogHook was not added. The length of log.Hooks[%v]: %v", level, len(log.Hooks[level])) 22 | } 23 | } 24 | 25 | log.Info("Congratulations!") 26 | } 27 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/json_formatter.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | type JSONFormatter struct { 9 | // TimestampFormat sets the format used for marshaling timestamps. 10 | TimestampFormat string 11 | } 12 | 13 | func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { 14 | data := make(Fields, len(entry.Data)+3) 15 | for k, v := range entry.Data { 16 | switch v := v.(type) { 17 | case error: 18 | // Otherwise errors are ignored by `encoding/json` 19 | // https://github.com/Sirupsen/logrus/issues/137 20 | data[k] = v.Error() 21 | default: 22 | data[k] = v 23 | } 24 | } 25 | prefixFieldClashes(data) 26 | 27 | if f.TimestampFormat == "" { 28 | f.TimestampFormat = DefaultTimestampFormat 29 | } 30 | 31 | data["time"] = entry.Time.Format(f.TimestampFormat) 32 | data["msg"] = entry.Message 33 | data["level"] = entry.Level.String() 34 | 35 | serialized, err := json.Marshal(data) 36 | if err != nil { 37 | return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) 38 | } 39 | return append(serialized, '\n'), nil 40 | } 41 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/terminal_darwin.go: -------------------------------------------------------------------------------- 1 | // Based on ssh/terminal: 2 | // Copyright 2013 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package logrus 7 | 8 | import "syscall" 9 | 10 | const ioctlReadTermios = syscall.TIOCGETA 11 | 12 | type Termios syscall.Termios 13 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/terminal_freebsd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin. 3 | */ 4 | package logrus 5 | 6 | import ( 7 | "syscall" 8 | ) 9 | 10 | const ioctlReadTermios = syscall.TIOCGETA 11 | 12 | type Termios struct { 13 | Iflag uint32 14 | Oflag uint32 15 | Cflag uint32 16 | Lflag uint32 17 | Cc [20]uint8 18 | Ispeed uint32 19 | Ospeed uint32 20 | } 21 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/terminal_linux.go: -------------------------------------------------------------------------------- 1 | // Based on ssh/terminal: 2 | // Copyright 2013 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package logrus 7 | 8 | import "syscall" 9 | 10 | const ioctlReadTermios = syscall.TCGETS 11 | 12 | type Termios syscall.Termios 13 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/terminal_notwindows.go: -------------------------------------------------------------------------------- 1 | // Based on ssh/terminal: 2 | // Copyright 2011 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // +build linux darwin freebsd openbsd 7 | 8 | package logrus 9 | 10 | import ( 11 | "syscall" 12 | "unsafe" 13 | ) 14 | 15 | // IsTerminal returns true if the given file descriptor is a terminal. 16 | func IsTerminal() bool { 17 | fd := syscall.Stdout 18 | var termios Termios 19 | _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) 20 | return err == 0 21 | } 22 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/terminal_openbsd.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | import "syscall" 4 | 5 | const ioctlReadTermios = syscall.TIOCGETA 6 | 7 | type Termios syscall.Termios 8 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/terminal_windows.go: -------------------------------------------------------------------------------- 1 | // Based on ssh/terminal: 2 | // Copyright 2011 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // +build windows 7 | 8 | package logrus 9 | 10 | import ( 11 | "syscall" 12 | "unsafe" 13 | ) 14 | 15 | var kernel32 = syscall.NewLazyDLL("kernel32.dll") 16 | 17 | var ( 18 | procGetConsoleMode = kernel32.NewProc("GetConsoleMode") 19 | ) 20 | 21 | // IsTerminal returns true if the given file descriptor is a terminal. 22 | func IsTerminal() bool { 23 | fd := syscall.Stdout 24 | var st uint32 25 | r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) 26 | return r != 0 && e == 0 27 | } 28 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestQuoting(t *testing.T) { 11 | tf := &TextFormatter{DisableColors: true} 12 | 13 | checkQuoting := func(q bool, value interface{}) { 14 | b, _ := tf.Format(WithField("test", value)) 15 | idx := bytes.Index(b, ([]byte)("test=")) 16 | cont := bytes.Contains(b[idx+5:], []byte{'"'}) 17 | if cont != q { 18 | if q { 19 | t.Errorf("quoting expected for: %#v", value) 20 | } else { 21 | t.Errorf("quoting not expected for: %#v", value) 22 | } 23 | } 24 | } 25 | 26 | checkQuoting(false, "abcd") 27 | checkQuoting(false, "v1.0") 28 | checkQuoting(false, "1234567890") 29 | checkQuoting(true, "/foobar") 30 | checkQuoting(true, "x y") 31 | checkQuoting(true, "x,y") 32 | checkQuoting(false, errors.New("invalid")) 33 | checkQuoting(true, errors.New("invalid argument")) 34 | } 35 | 36 | func TestTimestampFormat(t *testing.T) { 37 | checkTimeStr := func(format string) { 38 | customFormatter := &TextFormatter{DisableColors: true, TimestampFormat: format} 39 | customStr, _ := customFormatter.Format(WithField("test", "test")) 40 | timeStart := bytes.Index(customStr, ([]byte)("time=")) 41 | timeEnd := bytes.Index(customStr, ([]byte)("level=")) 42 | timeStr := customStr[timeStart+5 : timeEnd-1] 43 | if timeStr[0] == '"' && timeStr[len(timeStr)-1] == '"' { 44 | timeStr = timeStr[1 : len(timeStr)-1] 45 | } 46 | if format == "" { 47 | format = time.RFC3339 48 | } 49 | _, e := time.Parse(format, (string)(timeStr)) 50 | if e != nil { 51 | t.Errorf("time string \"%s\" did not match provided time format \"%s\": %s", timeStr, format, e) 52 | } 53 | } 54 | 55 | checkTimeStr("2006-01-02T15:04:05.000000000Z07:00") 56 | checkTimeStr("Mon Jan _2 15:04:05 2006") 57 | checkTimeStr("") 58 | } 59 | 60 | // TODO add tests for sorting etc., this requires a parser for the text 61 | // formatter output. 62 | -------------------------------------------------------------------------------- /vendor/src/github.com/Sirupsen/logrus/writer.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "runtime" 7 | ) 8 | 9 | func (logger *Logger) Writer() *io.PipeWriter { 10 | reader, writer := io.Pipe() 11 | 12 | go logger.writerScanner(reader) 13 | runtime.SetFinalizer(writer, writerFinalizer) 14 | 15 | return writer 16 | } 17 | 18 | func (logger *Logger) writerScanner(reader *io.PipeReader) { 19 | scanner := bufio.NewScanner(reader) 20 | for scanner.Scan() { 21 | logger.Print(scanner.Text()) 22 | } 23 | if err := scanner.Err(); err != nil { 24 | logger.Errorf("Error while reading from Writer: %s", err) 25 | } 26 | reader.Close() 27 | } 28 | 29 | func writerFinalizer(writer *io.PipeWriter) { 30 | writer.Close() 31 | } 32 | -------------------------------------------------------------------------------- /vendor/src/github.com/codegangsta/cli/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 1.1 3 | 4 | script: 5 | - go vet ./... 6 | - go test -v ./... 7 | -------------------------------------------------------------------------------- /vendor/src/github.com/codegangsta/cli/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 Jeremy Saenz 2 | All Rights Reserved. 3 | 4 | MIT LICENSE 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | _cli_bash_autocomplete() { 4 | local cur prev opts base 5 | COMPREPLY=() 6 | cur="${COMP_WORDS[COMP_CWORD]}" 7 | prev="${COMP_WORDS[COMP_CWORD-1]}" 8 | opts=$( ${COMP_WORDS[@]:0:COMP_CWORD} --generate-bash-completion ) 9 | COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) 10 | return 0 11 | } 12 | 13 | complete -F _cli_bash_autocomplete $PROG -------------------------------------------------------------------------------- /vendor/src/github.com/codegangsta/cli/cli.go: -------------------------------------------------------------------------------- 1 | // Package cli provides a minimal framework for creating and organizing command line 2 | // Go applications. cli is designed to be easy to understand and write, the most simple 3 | // cli application can be written as follows: 4 | // func main() { 5 | // cli.NewApp().Run(os.Args) 6 | // } 7 | // 8 | // Of course this application does not do much, so let's make this an actual application: 9 | // func main() { 10 | // app := cli.NewApp() 11 | // app.Name = "greet" 12 | // app.Usage = "say a greeting" 13 | // app.Action = func(c *cli.Context) { 14 | // println("Greetings") 15 | // } 16 | // 17 | // app.Run(os.Args) 18 | // } 19 | package cli 20 | -------------------------------------------------------------------------------- /vendor/src/github.com/codegangsta/cli/command_test.go: -------------------------------------------------------------------------------- 1 | package cli_test 2 | 3 | import ( 4 | "flag" 5 | "github.com/codegangsta/cli" 6 | "testing" 7 | ) 8 | 9 | func TestCommandDoNotIgnoreFlags(t *testing.T) { 10 | app := cli.NewApp() 11 | set := flag.NewFlagSet("test", 0) 12 | test := []string{"blah", "blah", "-break"} 13 | set.Parse(test) 14 | 15 | c := cli.NewContext(app, set, set) 16 | 17 | command := cli.Command { 18 | Name: "test-cmd", 19 | ShortName: "tc", 20 | Usage: "this is for testing", 21 | Description: "testing", 22 | Action: func(_ *cli.Context) { }, 23 | } 24 | err := command.Run(c) 25 | 26 | expect(t, err.Error(), "flag provided but not defined: -break") 27 | } 28 | 29 | func TestCommandIgnoreFlags(t *testing.T) { 30 | app := cli.NewApp() 31 | set := flag.NewFlagSet("test", 0) 32 | test := []string{"blah", "blah"} 33 | set.Parse(test) 34 | 35 | c := cli.NewContext(app, set, set) 36 | 37 | command := cli.Command { 38 | Name: "test-cmd", 39 | ShortName: "tc", 40 | Usage: "this is for testing", 41 | Description: "testing", 42 | Action: func(_ *cli.Context) { }, 43 | SkipFlagParsing: true, 44 | } 45 | err := command.Run(c) 46 | 47 | expect(t, err, nil) 48 | } 49 | -------------------------------------------------------------------------------- /vendor/src/github.com/codegangsta/cli/context_test.go: -------------------------------------------------------------------------------- 1 | package cli_test 2 | 3 | import ( 4 | "flag" 5 | "github.com/codegangsta/cli" 6 | "testing" 7 | ) 8 | 9 | func TestNewContext(t *testing.T) { 10 | set := flag.NewFlagSet("test", 0) 11 | set.Int("myflag", 12, "doc") 12 | globalSet := flag.NewFlagSet("test", 0) 13 | globalSet.Int("myflag", 42, "doc") 14 | command := cli.Command{Name: "mycommand"} 15 | c := cli.NewContext(nil, set, globalSet) 16 | c.Command = command 17 | expect(t, c.Int("myflag"), 12) 18 | expect(t, c.GlobalInt("myflag"), 42) 19 | expect(t, c.Command.Name, "mycommand") 20 | } 21 | 22 | func TestContext_Int(t *testing.T) { 23 | set := flag.NewFlagSet("test", 0) 24 | set.Int("myflag", 12, "doc") 25 | c := cli.NewContext(nil, set, set) 26 | expect(t, c.Int("myflag"), 12) 27 | } 28 | 29 | func TestContext_String(t *testing.T) { 30 | set := flag.NewFlagSet("test", 0) 31 | set.String("myflag", "hello world", "doc") 32 | c := cli.NewContext(nil, set, set) 33 | expect(t, c.String("myflag"), "hello world") 34 | } 35 | 36 | func TestContext_Bool(t *testing.T) { 37 | set := flag.NewFlagSet("test", 0) 38 | set.Bool("myflag", false, "doc") 39 | c := cli.NewContext(nil, set, set) 40 | expect(t, c.Bool("myflag"), false) 41 | } 42 | 43 | func TestContext_BoolT(t *testing.T) { 44 | set := flag.NewFlagSet("test", 0) 45 | set.Bool("myflag", true, "doc") 46 | c := cli.NewContext(nil, set, set) 47 | expect(t, c.BoolT("myflag"), true) 48 | } 49 | 50 | func TestContext_Args(t *testing.T) { 51 | set := flag.NewFlagSet("test", 0) 52 | set.Bool("myflag", false, "doc") 53 | c := cli.NewContext(nil, set, set) 54 | set.Parse([]string{"--myflag", "bat", "baz"}) 55 | expect(t, len(c.Args()), 2) 56 | expect(t, c.Bool("myflag"), true) 57 | } 58 | 59 | func TestContext_IsSet(t *testing.T) { 60 | set := flag.NewFlagSet("test", 0) 61 | set.Bool("myflag", false, "doc") 62 | set.String("otherflag", "hello world", "doc") 63 | c := cli.NewContext(nil, set, set) 64 | set.Parse([]string{"--myflag", "bat", "baz"}) 65 | expect(t, c.IsSet("myflag"), true) 66 | expect(t, c.IsSet("otherflag"), false) 67 | expect(t, c.IsSet("bogusflag"), false) 68 | } 69 | -------------------------------------------------------------------------------- /vendor/src/github.com/codegangsta/cli/helpers_test.go: -------------------------------------------------------------------------------- 1 | package cli_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | /* Test Helpers */ 9 | func expect(t *testing.T, a interface{}, b interface{}) { 10 | if a != b { 11 | t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) 12 | } 13 | } 14 | 15 | func refute(t *testing.T, a interface{}, b interface{}) { 16 | if a == b { 17 | t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 1.2 3 | 4 | install: 5 | - echo "Skip install" 6 | 7 | script: 8 | - ./test 9 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/README.md: -------------------------------------------------------------------------------- 1 | # go-systemd 2 | 3 | Go bindings to systemd. The project has three packages: 4 | 5 | - activation - for writing and using socket activation from Go 6 | - journal - for writing to systemd's logging service, journal 7 | - dbus - for starting/stopping/inspecting running services and units 8 | 9 | Go docs for the entire project are here: 10 | 11 | http://godoc.org/github.com/coreos/go-systemd 12 | 13 | ## Socket Activation 14 | 15 | An example HTTP server using socket activation can be quickly setup by 16 | following this README on a Linux machine running systemd: 17 | 18 | https://github.com/coreos/go-systemd/tree/master/examples/activation/httpserver 19 | 20 | ## Journal 21 | 22 | Using this package you can submit journal entries directly to systemd's journal taking advantage of features like indexed key/value pairs for each log entry. 23 | 24 | ## D-Bus 25 | 26 | The D-Bus API lets you start, stop and introspect systemd units. The API docs are here: 27 | 28 | http://godoc.org/github.com/coreos/go-systemd/dbus 29 | 30 | ### Debugging 31 | 32 | Create `/etc/dbus-1/system-local.conf` that looks like this: 33 | 34 | ``` 35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ``` 45 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/activation/files.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 CoreOS Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package activation implements primitives for systemd socket activation. 18 | package activation 19 | 20 | import ( 21 | "os" 22 | "strconv" 23 | "syscall" 24 | ) 25 | 26 | // based on: https://gist.github.com/alberts/4640792 27 | const ( 28 | listenFdsStart = 3 29 | ) 30 | 31 | func Files(unsetEnv bool) []*os.File { 32 | if unsetEnv { 33 | // there is no way to unset env in golang os package for now 34 | // https://code.google.com/p/go/issues/detail?id=6423 35 | defer os.Setenv("LISTEN_PID", "") 36 | defer os.Setenv("LISTEN_FDS", "") 37 | } 38 | 39 | pid, err := strconv.Atoi(os.Getenv("LISTEN_PID")) 40 | if err != nil || pid != os.Getpid() { 41 | return nil 42 | } 43 | 44 | nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS")) 45 | if err != nil || nfds == 0 { 46 | return nil 47 | } 48 | 49 | var files []*os.File 50 | for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ { 51 | syscall.CloseOnExec(fd) 52 | files = append(files, os.NewFile(uintptr(fd), "LISTEN_FD_"+strconv.Itoa(fd))) 53 | } 54 | 55 | return files 56 | } 57 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/activation/listeners.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 CoreOS Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package activation 18 | 19 | import ( 20 | "fmt" 21 | "net" 22 | ) 23 | 24 | // Listeners returns net.Listeners for all socket activated fds passed to this process. 25 | func Listeners(unsetEnv bool) ([]net.Listener, error) { 26 | files := Files(unsetEnv) 27 | listeners := make([]net.Listener, len(files)) 28 | 29 | for i, f := range files { 30 | var err error 31 | listeners[i], err = net.FileListener(f) 32 | if err != nil { 33 | return nil, fmt.Errorf("Error setting up FileListener for fd %d: %s", f.Fd(), err.Error()) 34 | } 35 | } 36 | 37 | return listeners, nil 38 | } 39 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/dbus/dbus_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 CoreOS Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package dbus 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | // TestObjectPath ensures path encoding of the systemd rules works. 24 | func TestObjectPath(t *testing.T) { 25 | input := "/silly-path/to@a/unit..service" 26 | output := ObjectPath(input) 27 | expected := "/silly_2dpath/to_40a/unit_2e_2eservice" 28 | 29 | if string(output) != expected { 30 | t.Fatalf("Output '%s' did not match expected '%s'", output, expected) 31 | } 32 | } 33 | 34 | // TestNew ensures that New() works without errors. 35 | func TestNew(t *testing.T) { 36 | _, err := New() 37 | 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/dbus/set.go: -------------------------------------------------------------------------------- 1 | package dbus 2 | 3 | type set struct { 4 | data map[string]bool 5 | } 6 | 7 | func (s *set) Add(value string) { 8 | s.data[value] = true 9 | } 10 | 11 | func (s *set) Remove(value string) { 12 | delete(s.data, value) 13 | } 14 | 15 | func (s *set) Contains(value string) (exists bool) { 16 | _, exists = s.data[value] 17 | return 18 | } 19 | 20 | func (s *set) Length() (int) { 21 | return len(s.data) 22 | } 23 | 24 | func (s *set) Values() (values []string) { 25 | for val, _ := range s.data { 26 | values = append(values, val) 27 | } 28 | return 29 | } 30 | 31 | func newSet() (*set) { 32 | return &set{make(map[string] bool)} 33 | } 34 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/dbus/set_test.go: -------------------------------------------------------------------------------- 1 | package dbus 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // TestBasicSetActions asserts that Add & Remove behavior is correct 8 | func TestBasicSetActions(t *testing.T) { 9 | s := newSet() 10 | 11 | if s.Contains("foo") { 12 | t.Fatal("set should not contain 'foo'") 13 | } 14 | 15 | s.Add("foo") 16 | 17 | if !s.Contains("foo") { 18 | t.Fatal("set should contain 'foo'") 19 | } 20 | 21 | v := s.Values() 22 | if len(v) != 1 { 23 | t.Fatal("set.Values did not report correct number of values") 24 | } 25 | if v[0] != "foo" { 26 | t.Fatal("set.Values did not report value") 27 | } 28 | 29 | s.Remove("foo") 30 | 31 | if s.Contains("foo") { 32 | t.Fatal("set should not contain 'foo'") 33 | } 34 | 35 | v = s.Values() 36 | if len(v) != 0 { 37 | t.Fatal("set.Values did not report correct number of values") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/dbus/subscription_set.go: -------------------------------------------------------------------------------- 1 | package dbus 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // SubscriptionSet returns a subscription set which is like conn.Subscribe but 8 | // can filter to only return events for a set of units. 9 | type SubscriptionSet struct { 10 | *set 11 | conn *Conn 12 | } 13 | 14 | 15 | func (s *SubscriptionSet) filter(unit string) bool { 16 | return !s.Contains(unit) 17 | } 18 | 19 | // Subscribe starts listening for dbus events for all of the units in the set. 20 | // Returns channels identical to conn.SubscribeUnits. 21 | func (s *SubscriptionSet) Subscribe() (<-chan map[string]*UnitStatus, <-chan error) { 22 | // TODO: Make fully evented by using systemd 209 with properties changed values 23 | return s.conn.SubscribeUnitsCustom(time.Second, 0, 24 | func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, 25 | func(unit string) bool { return s.filter(unit) }, 26 | ) 27 | } 28 | 29 | // NewSubscriptionSet returns a new subscription set. 30 | func (conn *Conn) NewSubscriptionSet() (*SubscriptionSet) { 31 | return &SubscriptionSet{newSet(), conn} 32 | } 33 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go: -------------------------------------------------------------------------------- 1 | package dbus 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | // TestSubscribeUnit exercises the basics of subscription of a particular unit. 9 | func TestSubscriptionSetUnit(t *testing.T) { 10 | target := "subscribe-events-set.service" 11 | 12 | conn, err := New() 13 | 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | 18 | err = conn.Subscribe() 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | 23 | subSet := conn.NewSubscriptionSet() 24 | evChan, errChan := subSet.Subscribe() 25 | 26 | subSet.Add(target) 27 | setupUnit(target, conn, t) 28 | linkUnit(target, conn, t) 29 | 30 | job, err := conn.StartUnit(target, "replace") 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | if job != "done" { 36 | t.Fatal("Couldn't start", target) 37 | } 38 | 39 | timeout := make(chan bool, 1) 40 | go func() { 41 | time.Sleep(3 * time.Second) 42 | close(timeout) 43 | }() 44 | 45 | for { 46 | select { 47 | case changes := <-evChan: 48 | tCh, ok := changes[target] 49 | 50 | if !ok { 51 | t.Fatal("Unexpected event:", changes) 52 | } 53 | 54 | if tCh.ActiveState == "active" && tCh.Name == target { 55 | goto success 56 | } 57 | case err = <-errChan: 58 | t.Fatal(err) 59 | case <-timeout: 60 | t.Fatal("Reached timeout") 61 | } 62 | } 63 | 64 | success: 65 | return 66 | } 67 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/dbus/subscription_test.go: -------------------------------------------------------------------------------- 1 | package dbus 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | // TestSubscribe exercises the basics of subscription 9 | func TestSubscribe(t *testing.T) { 10 | conn, err := New() 11 | 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | 16 | err = conn.Subscribe() 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | err = conn.Unsubscribe() 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | } 26 | 27 | // TestSubscribeUnit exercises the basics of subscription of a particular unit. 28 | func TestSubscribeUnit(t *testing.T) { 29 | target := "subscribe-events.service" 30 | 31 | conn, err := New() 32 | 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | 37 | err = conn.Subscribe() 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | 42 | err = conn.Unsubscribe() 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | 47 | evChan, errChan := conn.SubscribeUnits(time.Second) 48 | 49 | setupUnit(target, conn, t) 50 | linkUnit(target, conn, t) 51 | 52 | job, err := conn.StartUnit(target, "replace") 53 | if err != nil { 54 | t.Fatal(err) 55 | } 56 | 57 | if job != "done" { 58 | t.Fatal("Couldn't start", target) 59 | } 60 | 61 | timeout := make(chan bool, 1) 62 | go func() { 63 | time.Sleep(3 * time.Second) 64 | close(timeout) 65 | }() 66 | 67 | for { 68 | select { 69 | case changes := <-evChan: 70 | tCh, ok := changes[target] 71 | 72 | // Just continue until we see our event. 73 | if !ok { 74 | continue 75 | } 76 | 77 | if tCh.ActiveState == "active" && tCh.Name == target { 78 | goto success 79 | } 80 | case err = <-errChan: 81 | t.Fatal(err) 82 | case <-timeout: 83 | t.Fatal("Reached timeout") 84 | } 85 | } 86 | 87 | success: 88 | return 89 | } 90 | 91 | 92 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/examples/activation/activation.go: -------------------------------------------------------------------------------- 1 | // Activation example used by the activation unit tests. 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coreos/go-systemd/activation" 9 | ) 10 | 11 | func fixListenPid() { 12 | if os.Getenv("FIX_LISTEN_PID") != "" { 13 | // HACK: real systemd would set LISTEN_PID before exec'ing but 14 | // this is too difficult in golang for the purpose of a test. 15 | // Do not do this in real code. 16 | os.Setenv("LISTEN_PID", fmt.Sprintf("%d", os.Getpid())) 17 | } 18 | } 19 | 20 | func main() { 21 | fixListenPid() 22 | 23 | files := activation.Files(false) 24 | 25 | if len(files) == 0 { 26 | panic("No files") 27 | } 28 | 29 | if os.Getenv("LISTEN_PID") == "" || os.Getenv("LISTEN_FDS") == "" { 30 | panic("Should not unset envs") 31 | } 32 | 33 | files = activation.Files(true) 34 | 35 | if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" { 36 | panic("Can not unset envs") 37 | } 38 | 39 | // Write out the expected strings to the two pipes 40 | files[0].Write([]byte("Hello world")) 41 | files[1].Write([]byte("Goodbye world")) 42 | 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/README.md: -------------------------------------------------------------------------------- 1 | ## socket activated http server 2 | 3 | This is a simple example of using socket activation with systemd to serve a 4 | simple HTTP server on http://127.0.0.1:8076 5 | 6 | To try it out `go get` the httpserver and run it under the systemd-activate helper 7 | 8 | ``` 9 | export GOPATH=`pwd` 10 | go get github.com/coreos/go-systemd/examples/activation/httpserver 11 | sudo /usr/lib/systemd/systemd-activate -l 127.0.0.1:8076 ./bin/httpserver 12 | ``` 13 | 14 | Then curl the URL and you will notice that it starts up: 15 | 16 | ``` 17 | curl 127.0.0.1:8076 18 | hello socket activated world! 19 | ``` 20 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Hello World HTTP 3 | Requires=network.target 4 | After=multi-user.target 5 | 6 | [Service] 7 | Type=simple 8 | ExecStart=/usr/local/bin/httpserver 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket: -------------------------------------------------------------------------------- 1 | [Socket] 2 | ListenStream=127.0.0.1:8076 3 | 4 | [Install] 5 | WantedBy=sockets.target 6 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | 7 | "github.com/coreos/go-systemd/activation" 8 | ) 9 | 10 | func HelloServer(w http.ResponseWriter, req *http.Request) { 11 | io.WriteString(w, "hello socket activated world!\n") 12 | } 13 | 14 | func main() { 15 | listeners, err := activation.Listeners(true) 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | if len(listeners) != 1 { 21 | panic("Unexpected number of socket activation fds") 22 | } 23 | 24 | http.HandleFunc("/", HelloServer) 25 | http.Serve(listeners[0], nil) 26 | } 27 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/examples/activation/listen.go: -------------------------------------------------------------------------------- 1 | // Activation example used by the activation unit tests. 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coreos/go-systemd/activation" 9 | ) 10 | 11 | func fixListenPid() { 12 | if os.Getenv("FIX_LISTEN_PID") != "" { 13 | // HACK: real systemd would set LISTEN_PID before exec'ing but 14 | // this is too difficult in golang for the purpose of a test. 15 | // Do not do this in real code. 16 | os.Setenv("LISTEN_PID", fmt.Sprintf("%d", os.Getpid())) 17 | } 18 | } 19 | 20 | func main() { 21 | fixListenPid() 22 | 23 | listeners, _ := activation.Listeners(false) 24 | 25 | if len(listeners) == 0 { 26 | panic("No listeners") 27 | } 28 | 29 | if os.Getenv("LISTEN_PID") == "" || os.Getenv("LISTEN_FDS") == "" { 30 | panic("Should not unset envs") 31 | } 32 | 33 | listeners, err := activation.Listeners(true) 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" { 39 | panic("Can not unset envs") 40 | } 41 | 42 | c0, _ := listeners[0].Accept() 43 | c1, _ := listeners[1].Accept() 44 | 45 | // Write out the expected strings to the two pipes 46 | c0.Write([]byte("Hello world")) 47 | c1.Write([]byte("Goodbye world")) 48 | 49 | return 50 | } 51 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/fixtures/enable-disable.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=enable disable test 3 | 4 | [Service] 5 | ExecStart=/bin/sleep 400 6 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/fixtures/start-stop.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=start stop test 3 | 4 | [Service] 5 | ExecStart=/bin/sleep 400 6 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/fixtures/subscribe-events-set.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=start stop test 3 | 4 | [Service] 5 | ExecStart=/bin/sleep 400 6 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/fixtures/subscribe-events.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=start stop test 3 | 4 | [Service] 5 | ExecStart=/bin/sleep 400 6 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/login1/dbus_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 CoreOS Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package login1 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | // TestNew ensures that New() works without errors. 24 | func TestNew(t *testing.T) { 25 | _, err := New() 26 | 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /vendor/src/github.com/coreos/go-systemd/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | go test -v ./... 4 | -------------------------------------------------------------------------------- /vendor/src/github.com/godbus/dbus/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | ## Getting Started 4 | 5 | - Fork the repository on GitHub 6 | - Read the [README](README.markdown) for build and test instructions 7 | - Play with the project, submit bugs, submit patches! 8 | 9 | ## Contribution Flow 10 | 11 | This is a rough outline of what a contributor's workflow looks like: 12 | 13 | - Create a topic branch from where you want to base your work (usually master). 14 | - Make commits of logical units. 15 | - Make sure your commit messages are in the proper format (see below). 16 | - Push your changes to a topic branch in your fork of the repository. 17 | - Make sure the tests pass, and add any new tests as appropriate. 18 | - Submit a pull request to the original repository. 19 | 20 | Thanks for your contributions! 21 | 22 | ### Format of the Commit Message 23 | 24 | We follow a rough convention for commit messages that is designed to answer two 25 | questions: what changed and why. The subject line should feature the what and 26 | the body of the commit should describe the why. 27 | 28 | ``` 29 | scripts: add the test-cluster command 30 | 31 | this uses tmux to setup a test cluster that you can easily kill and 32 | start for debugging. 33 | 34 | Fixes #38 35 | ``` 36 | 37 | The format can be described more formally as follows: 38 | 39 | ``` 40 | : 41 | 42 | 43 | 44 |