Squashed 'k8smain/' content from commit 719158d
git-subtree-dir: k8smain git-subtree-split: 719158d2c8788c30cc9e1c51bbebb53c5801524e
This commit is contained in:
+108
@@ -0,0 +1,108 @@
|
||||
.generated_docs
|
||||
contrib/completions/bash/kubectl
|
||||
docs/admin/kube-apiserver.md
|
||||
docs/admin/kube-controller-manager.md
|
||||
docs/admin/kube-proxy.md
|
||||
docs/admin/kube-scheduler.md
|
||||
docs/admin/kubelet.md
|
||||
docs/man/man1/kubectl-annotate.1
|
||||
docs/man/man1/kubectl-api-versions.1
|
||||
docs/man/man1/kubectl-apply.1
|
||||
docs/man/man1/kubectl-attach.1
|
||||
docs/man/man1/kubectl-autoscale.1
|
||||
docs/man/man1/kubectl-cluster-info.1
|
||||
docs/man/man1/kubectl-config-current-context.1
|
||||
docs/man/man1/kubectl-config-set-cluster.1
|
||||
docs/man/man1/kubectl-config-set-context.1
|
||||
docs/man/man1/kubectl-config-set-credentials.1
|
||||
docs/man/man1/kubectl-config-set.1
|
||||
docs/man/man1/kubectl-config-unset.1
|
||||
docs/man/man1/kubectl-config-use-context.1
|
||||
docs/man/man1/kubectl-config-view.1
|
||||
docs/man/man1/kubectl-config.1
|
||||
docs/man/man1/kubectl-convert.1
|
||||
docs/man/man1/kubectl-cordon.1
|
||||
docs/man/man1/kubectl-create-configmap.1
|
||||
docs/man/man1/kubectl-create-namespace.1
|
||||
docs/man/man1/kubectl-create-secret-docker-registry.1
|
||||
docs/man/man1/kubectl-create-secret-generic.1
|
||||
docs/man/man1/kubectl-create-secret.1
|
||||
docs/man/man1/kubectl-create-serviceaccount.1
|
||||
docs/man/man1/kubectl-create.1
|
||||
docs/man/man1/kubectl-delete.1
|
||||
docs/man/man1/kubectl-describe.1
|
||||
docs/man/man1/kubectl-drain.1
|
||||
docs/man/man1/kubectl-edit.1
|
||||
docs/man/man1/kubectl-exec.1
|
||||
docs/man/man1/kubectl-explain.1
|
||||
docs/man/man1/kubectl-expose.1
|
||||
docs/man/man1/kubectl-get.1
|
||||
docs/man/man1/kubectl-label.1
|
||||
docs/man/man1/kubectl-logs.1
|
||||
docs/man/man1/kubectl-namespace.1
|
||||
docs/man/man1/kubectl-patch.1
|
||||
docs/man/man1/kubectl-port-forward.1
|
||||
docs/man/man1/kubectl-proxy.1
|
||||
docs/man/man1/kubectl-replace.1
|
||||
docs/man/man1/kubectl-rolling-update.1
|
||||
docs/man/man1/kubectl-rollout-history.1
|
||||
docs/man/man1/kubectl-rollout-pause.1
|
||||
docs/man/man1/kubectl-rollout-resume.1
|
||||
docs/man/man1/kubectl-rollout-undo.1
|
||||
docs/man/man1/kubectl-rollout.1
|
||||
docs/man/man1/kubectl-run.1
|
||||
docs/man/man1/kubectl-scale.1
|
||||
docs/man/man1/kubectl-stop.1
|
||||
docs/man/man1/kubectl-uncordon.1
|
||||
docs/man/man1/kubectl-version.1
|
||||
docs/man/man1/kubectl.1
|
||||
docs/user-guide/kubectl/kubectl.md
|
||||
docs/user-guide/kubectl/kubectl_annotate.md
|
||||
docs/user-guide/kubectl/kubectl_api-versions.md
|
||||
docs/user-guide/kubectl/kubectl_apply.md
|
||||
docs/user-guide/kubectl/kubectl_attach.md
|
||||
docs/user-guide/kubectl/kubectl_autoscale.md
|
||||
docs/user-guide/kubectl/kubectl_cluster-info.md
|
||||
docs/user-guide/kubectl/kubectl_config.md
|
||||
docs/user-guide/kubectl/kubectl_config_current-context.md
|
||||
docs/user-guide/kubectl/kubectl_config_set-cluster.md
|
||||
docs/user-guide/kubectl/kubectl_config_set-context.md
|
||||
docs/user-guide/kubectl/kubectl_config_set-credentials.md
|
||||
docs/user-guide/kubectl/kubectl_config_set.md
|
||||
docs/user-guide/kubectl/kubectl_config_unset.md
|
||||
docs/user-guide/kubectl/kubectl_config_use-context.md
|
||||
docs/user-guide/kubectl/kubectl_config_view.md
|
||||
docs/user-guide/kubectl/kubectl_convert.md
|
||||
docs/user-guide/kubectl/kubectl_cordon.md
|
||||
docs/user-guide/kubectl/kubectl_create.md
|
||||
docs/user-guide/kubectl/kubectl_create_configmap.md
|
||||
docs/user-guide/kubectl/kubectl_create_namespace.md
|
||||
docs/user-guide/kubectl/kubectl_create_secret.md
|
||||
docs/user-guide/kubectl/kubectl_create_secret_docker-registry.md
|
||||
docs/user-guide/kubectl/kubectl_create_secret_generic.md
|
||||
docs/user-guide/kubectl/kubectl_create_serviceaccount.md
|
||||
docs/user-guide/kubectl/kubectl_delete.md
|
||||
docs/user-guide/kubectl/kubectl_describe.md
|
||||
docs/user-guide/kubectl/kubectl_drain.md
|
||||
docs/user-guide/kubectl/kubectl_edit.md
|
||||
docs/user-guide/kubectl/kubectl_exec.md
|
||||
docs/user-guide/kubectl/kubectl_explain.md
|
||||
docs/user-guide/kubectl/kubectl_expose.md
|
||||
docs/user-guide/kubectl/kubectl_get.md
|
||||
docs/user-guide/kubectl/kubectl_label.md
|
||||
docs/user-guide/kubectl/kubectl_logs.md
|
||||
docs/user-guide/kubectl/kubectl_namespace.md
|
||||
docs/user-guide/kubectl/kubectl_patch.md
|
||||
docs/user-guide/kubectl/kubectl_port-forward.md
|
||||
docs/user-guide/kubectl/kubectl_proxy.md
|
||||
docs/user-guide/kubectl/kubectl_replace.md
|
||||
docs/user-guide/kubectl/kubectl_rolling-update.md
|
||||
docs/user-guide/kubectl/kubectl_rollout.md
|
||||
docs/user-guide/kubectl/kubectl_rollout_history.md
|
||||
docs/user-guide/kubectl/kubectl_rollout_pause.md
|
||||
docs/user-guide/kubectl/kubectl_rollout_resume.md
|
||||
docs/user-guide/kubectl/kubectl_rollout_undo.md
|
||||
docs/user-guide/kubectl/kubectl_run.md
|
||||
docs/user-guide/kubectl/kubectl_scale.md
|
||||
docs/user-guide/kubectl/kubectl_uncordon.md
|
||||
docs/user-guide/kubectl/kubectl_version.md
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
# OSX leaves these everywhere on SMB shares
|
||||
._*
|
||||
|
||||
# OSX trash
|
||||
.DS_Store
|
||||
|
||||
# Eclipse files
|
||||
.classpath
|
||||
.project
|
||||
.settings/**
|
||||
|
||||
# Files generated by JetBrains IDEs, e.g. IntelliJ IDEA
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
# Vscode files
|
||||
.vscode/**
|
||||
|
||||
# This is where the result of the go build goes
|
||||
/output/**
|
||||
/output
|
||||
/_output/**
|
||||
/_output
|
||||
|
||||
# Emacs save files
|
||||
*~
|
||||
\#*\#
|
||||
.\#*
|
||||
|
||||
# Vim-related files
|
||||
[._]*.s[a-w][a-z]
|
||||
[._]s[a-w][a-z]
|
||||
*.un~
|
||||
Session.vim
|
||||
.netrwhist
|
||||
|
||||
# Go test binaries
|
||||
*.test
|
||||
/hack/.test-cmd-auth
|
||||
|
||||
# JUnit test output from ginkgo e2e tests
|
||||
/junit*.xml
|
||||
|
||||
# Mercurial files
|
||||
**/.hg
|
||||
**/.hg*
|
||||
|
||||
# Vagrant
|
||||
.vagrant
|
||||
network_closure.sh
|
||||
|
||||
# Local cluster env variables
|
||||
/cluster/env.sh
|
||||
|
||||
# Compiled binaries in third_party
|
||||
/third_party/pkg
|
||||
|
||||
# Also ignore etcd installed by hack/install-etcd.sh
|
||||
/third_party/etcd*
|
||||
|
||||
# User cluster configs
|
||||
.kubeconfig
|
||||
|
||||
.tags*
|
||||
|
||||
# Web UI
|
||||
www/master/node_modules/
|
||||
www/master/npm-debug.log
|
||||
www/master/shared/config/development.json
|
||||
|
||||
# Karma output
|
||||
www/test_out
|
||||
|
||||
# precommit temporary directories created by ./hack/verify-generated-docs.sh and ./hack/lib/util.sh
|
||||
_tmp/
|
||||
doc_tmp/
|
||||
|
||||
# Test artifacts produced by Jenkins jobs
|
||||
/_artifacts/
|
||||
|
||||
# Go dependencies installed on Jenkins
|
||||
/_gopath/
|
||||
|
||||
# Config directories created by gcloud and gsutil on Jenkins
|
||||
/.config/gcloud/
|
||||
/.gsutil/
|
||||
|
||||
# CoreOS stuff
|
||||
cluster/libvirt-coreos/coreos_*.img
|
||||
|
||||
# Downloaded Kubernetes binary release
|
||||
kubernetes/
|
||||
|
||||
# direnv .envrc files
|
||||
.envrc
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
sudo: required
|
||||
|
||||
# trusty required for docker
|
||||
# https://github.com/travis-ci/travis-ci/issues/5448
|
||||
dist: trusty
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
language: go
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.5.3
|
||||
|
||||
install:
|
||||
- go get github.com/tools/godep
|
||||
- ./hack/install-etcd.sh
|
||||
- ./hack/build-go.sh
|
||||
- export PATH=$GOPATH/bin:./third_party/etcd:$PATH
|
||||
|
||||
script:
|
||||
- make verify BRANCH=$TRAVIS_BRANCH
|
||||
|
||||
notifications:
|
||||
irc: "chat.freenode.net#kubernetes-dev"
|
||||
slack: kubernetes:PHPPkljtEsHXED6xl9IfOvKe
|
||||
@@ -0,0 +1,4 @@
|
||||
Please see the [Releases Page](https://github.com/kubernetes/kubernetes/releases)
|
||||
|
||||
|
||||
[]()
|
||||
@@ -0,0 +1,3 @@
|
||||
[Moved to CONTRIBUTING.md](CONTRIBUTING.md)
|
||||
|
||||
[]()
|
||||
@@ -0,0 +1,40 @@
|
||||
# Contributing guidelines
|
||||
|
||||
## Filing issues
|
||||
|
||||
If you have a question about Kubernetes or have a problem using Kubernetes, before filing an issue, please read the [troubleshooting guide](docs/troubleshooting.md).
|
||||
|
||||
## How to become a contributor and submit your own code
|
||||
|
||||
### Contributor License Agreements
|
||||
|
||||
We'd love to accept your patches! Before we can take them, we have to jump a couple of legal hurdles.
|
||||
|
||||
Please fill out either the individual or corporate Contributor License Agreement (CLA).
|
||||
|
||||
* If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA](http://code.google.com/legal/individual-cla-v1.0.html).
|
||||
* If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA](http://code.google.com/legal/corporate-cla-v1.0.html).
|
||||
|
||||
Follow either of the two links above to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to accept your pull requests.
|
||||
|
||||
***NOTE***: Only original source code from you and other people that have signed the CLA can be accepted into the main repository. This policy does not apply to [third_party](https://github.com/kubernetes/kubernetes/tree/master/third_party).
|
||||
|
||||
### Contributing A Patch
|
||||
|
||||
1. Submit an issue describing your proposed change to the repo in question.
|
||||
1. The repo owner will respond to your issue promptly.
|
||||
1. If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above).
|
||||
1. Fork the desired repo, develop and test your code changes.
|
||||
1. Submit a pull request.
|
||||
|
||||
### Protocols for Collaborative Development
|
||||
|
||||
Please read [this doc](docs/devel/collab.md) for information on how we're running development for the project.
|
||||
Also take a look at the [development guide](docs/devel/development.md) for information on how to set up your environment, run tests, manage dependencies, etc.
|
||||
|
||||
### Adding dependencies
|
||||
|
||||
If your patch depends on new packages, add that package with [`godep`](https://github.com/tools/godep). Follow the [instructions to add a dependency](docs/devel/development.md#godep-and-dependency-management).
|
||||
|
||||
|
||||
[]()
|
||||
@@ -0,0 +1,10 @@
|
||||
# Kubernetes Overview
|
||||
|
||||
See the [user guide overview](docs/user-guide/overview.md) for an introduction to Kubernetes and its core concepts.
|
||||
|
||||
See the [design overview](docs/design) for an overview of the system design.
|
||||
|
||||
See the [API overview](docs/api.md) and [conventions](docs/devel/api-conventions.md) for an overview of the API design.
|
||||
|
||||
|
||||
[]()
|
||||
Generated
+1059
File diff suppressed because it is too large
Load Diff
Generated
+112
@@ -0,0 +1,112 @@
|
||||
Dependency Licenses
|
||||
-------------------
|
||||
|
||||
Package | License
|
||||
------- | -------
|
||||
bitbucket.org/bertimus9/systemstat | MITname
|
||||
bitbucket.org/ww/goautoneg | spdxBSD3
|
||||
github.com/abbot/go-http-auth | Apache-2
|
||||
github.com/appc/cni | Apache-2
|
||||
github.com/appc/spec | Apache-2
|
||||
github.com/armon/go-metrics | MITname
|
||||
github.com/aws/aws-sdk-go | Apache-2
|
||||
github.com/beorn7/perks/quantile | MIT?
|
||||
github.com/blang/semver | MITname
|
||||
github.com/boltdb/bolt | MITname
|
||||
github.com/camlistore/go4 | Apache-2
|
||||
github.com/ClusterHQ/flocker-go | Apache-2
|
||||
github.com/codegangsta/negroni | MITname
|
||||
github.com/coreos/etcd | Apache-2
|
||||
github.com/coreos/go-etcd | Apache-2
|
||||
github.com/coreos/go-oidc | Apache-2
|
||||
github.com/coreos/go-semver | Apache-2
|
||||
github.com/coreos/go-systemd | Apache-2
|
||||
github.com/coreos/pkg | Apache-2
|
||||
github.com/coreos/rkt | MITname
|
||||
github.com/cpuguy83/go-md2man | MITname
|
||||
github.com/davecgh/go-spew | MIToldwithoutSellandNoDocumentationRequi
|
||||
github.com/daviddengcn/go-colortext | BSD?
|
||||
github.com/dgrijalva/jwt-go | spdxMIT
|
||||
github.com/docker/docker | Apache-2
|
||||
github.com/docker/docker/pkg/symlink | spdxBSD3
|
||||
github.com/docker/go-units | Apache-2
|
||||
github.com/docker/spdystream | SeeFile
|
||||
github.com/elazarl/go-bindata-assetfs | spdxBSD2
|
||||
github.com/elazarl/goproxy | BSDWarr
|
||||
github.com/emicklei/go-restful | MITname
|
||||
github.com/evanphx/json-patch | BSDWarr
|
||||
github.com/fsouza/go-dockerclient | spdxBSD2
|
||||
github.com/garyburd/redigo/internal | ApachesPermLim
|
||||
github.com/garyburd/redigo/redis | ApachesPermLim
|
||||
github.com/ghodss/yaml | MITname
|
||||
github.com/go-ini/ini | Apache-2
|
||||
github.com/godbus/dbus | spdxBSD2
|
||||
github.com/gogo/protobuf | spdxBSD3
|
||||
github.com/golang/glog | Apache-2
|
||||
github.com/golang/groupcache | Apache-2
|
||||
github.com/golang/protobuf | spdxBSD3
|
||||
github.com/google/btree | Apache-2
|
||||
github.com/google/cadvisor | Apache-2
|
||||
github.com/google/gofuzz | Apache-2
|
||||
github.com/gorilla/context | spdxBSD3
|
||||
github.com/gorilla/mux | spdxBSD3
|
||||
github.com/hashicorp/go-msgpack | spdxBSD3
|
||||
github.com/hashicorp/golang-lru | IntelPart08
|
||||
github.com/hashicorp/raft | IntelPart08
|
||||
github.com/hashicorp/raft-boltdb | IntelPart08
|
||||
github.com/hawkular/hawkular-client-go | Apache-2
|
||||
github.com/imdario/mergo | spdxBSD3
|
||||
github.com/inconshreveable/mousetrap | Apache-2
|
||||
github.com/influxdb/influxdb | MITname
|
||||
github.com/jmespath/go-jmespath | Apache-2
|
||||
github.com/jonboulle/clockwork | Apache-2
|
||||
github.com/juju/ratelimit | LesserExceptionGPLVer3-TOOLONG
|
||||
github.com/kardianos/osext | spdxBSD3
|
||||
github.com/kr/pty | spdxMIT
|
||||
github.com/matttproud/golang_protobuf_extensions | Apache-2
|
||||
github.com/mesos/mesos-go | Apache-2
|
||||
github.com/miekg/dns | spdxBSD3
|
||||
github.com/mistifyio/go-zfs | Apache-2
|
||||
github.com/mitchellh/mapstructure | MITname
|
||||
github.com/mvdan/xurls | spdxBSD3
|
||||
github.com/mxk/go-flowrate | spdxBSD3
|
||||
github.com/onsi/ginkgo | spdxMIT
|
||||
github.com/onsi/gomega | spdxMIT
|
||||
github.com/opencontainers/runc | Apache-2
|
||||
github.com/pborman/uuid | spdxBSD3
|
||||
github.com/pmezard/go-difflib | BSD3
|
||||
github.com/prometheus/client_golang | Apache-2
|
||||
github.com/prometheus/client_model | Apache-2
|
||||
github.com/prometheus/common/expfmt | Apache-2
|
||||
github.com/prometheus/common/model | Apache-2
|
||||
github.com/prometheus/procfs | Apache-2
|
||||
github.com/rackspace/gophercloud | Apache-2
|
||||
github.com/russross/blackfriday | AsIsVariant2-TOOLONG
|
||||
github.com/samuel/go-zookeeper | spdxBSD3
|
||||
github.com/scalingdata/gcfg | spdxBSD2
|
||||
github.com/seccomp/libseccomp-golang | AllRights-TOOLONG
|
||||
github.com/shurcooL/sanitized_anchor_name | MIT?
|
||||
github.com/Sirupsen/logrus | MITname
|
||||
github.com/skynetservices/skydns | MITname
|
||||
github.com/spf13/cobra | Apache-2
|
||||
github.com/spf13/pflag | spdxBSD3
|
||||
github.com/stretchr/objx | MIT?
|
||||
github.com/stretchr/testify | spdxMIT
|
||||
github.com/syndtr/gocapability | spdxBSD2
|
||||
github.com/ugorji/go | MITname
|
||||
github.com/vishvananda/netlink | Apache-2
|
||||
github.com/xiang90/probing | MITname
|
||||
github.com/xyproto/simpleredis | MITname
|
||||
golang.org/x/crypto | spdxBSD3
|
||||
golang.org/x/exp | spdxBSD3
|
||||
golang.org/x/net | spdxBSD3
|
||||
golang.org/x/oauth2 | spdxBSD3
|
||||
golang.org/x/sys | spdxBSD3
|
||||
golang.org/x/tools | spdxBSD3
|
||||
google.golang.org/api | spdxBSD3
|
||||
google.golang.org/cloud | Apache-2
|
||||
google.golang.org/grpc | spdxBSD3
|
||||
gopkg.in/natefinch/lumberjack.v2 | MITname
|
||||
gopkg.in/yaml.v2 | LesserExceptionGPLVer3-TOOLONG
|
||||
k8s.io/heapster | Apache-2
|
||||
speter.net/go/exp/math/dec/inf | spdxBSD2
|
||||
Generated
+5
@@ -0,0 +1,5 @@
|
||||
This directory tree is generated automatically by godep.
|
||||
|
||||
Please do not edit.
|
||||
|
||||
See https://github.com/tools/godep for more information.
|
||||
Generated
+2
@@ -0,0 +1,2 @@
|
||||
/pkg
|
||||
/bin
|
||||
@@ -0,0 +1,25 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
|
||||
*.swp
|
||||
*.swo
|
||||
+1
@@ -0,0 +1 @@
|
||||
[Phillip](http://bitbucket.org/bertimus9) <bertimus9@gmail.com>
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Phillip Bond
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
test:
|
||||
gofmt -s -w .
|
||||
go test ./...
|
||||
go get bitbucket.org/bertimus9/systemstat
|
||||
|
||||
coverage:
|
||||
go get github.com/axw/gocov/gocov
|
||||
gocov test . | gocov report
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
# systemstat
|
||||
|
||||
[Documentation online](http://godoc.org/bitbucket.org/bertimus9/systemstat)
|
||||
|
||||
**systemstat** is a package written in Go generated automatically by `gobi`.
|
||||
|
||||
**systemstat** allows you to add system statistics to your go program; it
|
||||
currently polls the linux kernel for CPU usage, free/used memory and swap
|
||||
sizes, and uptime for your go process, as well as the system you're running it
|
||||
on, and the system load. It can be used to make a crippled version of top that
|
||||
monitors the current go process and ignores other processes and the number of
|
||||
users with ttys. See the examples directory for go-top.go, which is my attempt
|
||||
at a top clone. Bear in mind that the intention of **systemstat** is to allow
|
||||
your process to monitor itself and it's environment, not to replace top.
|
||||
|
||||
## Install (with GOPATH set on your machine)
|
||||
----------
|
||||
|
||||
* Step 1: Get the `systemstat` package
|
||||
|
||||
```
|
||||
go get bitbucket.org/bertimus9/systemstat
|
||||
```
|
||||
|
||||
* Step 2 (Optional): Run tests
|
||||
|
||||
```
|
||||
$ go test -v bitbucket.org/bertimus9/systemstat
|
||||
```
|
||||
|
||||
* Step 3 (Optional): Run example
|
||||
|
||||
```bash
|
||||
$ cd to the first directory in your $GOPATH
|
||||
$ cd src/bitbucket.org/bertimus9/systemstat
|
||||
$ go run examples/go-top.go
|
||||
```
|
||||
|
||||
##Usage
|
||||
----------
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"bitbucket.org/bertimus9/systemstat"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var sample systemstat.MemSample
|
||||
|
||||
// This example shows how easy it is to get memory information
|
||||
func main() {
|
||||
sample = systemstat.GetMemSample()
|
||||
fmt.Println("Total available RAM in kb:", sample.MemTotal, "k total")
|
||||
fmt.Println("Used RAM in kb:", sample.MemUsed, "k used")
|
||||
fmt.Println("Free RAM in kb:", sample.MemFree, "k free")
|
||||
fmt.Printf("The output is similar to, but somewhat different than:\n\ttop -n1 | grep Mem:\n")
|
||||
}
|
||||
```
|
||||
|
||||
##License
|
||||
----------
|
||||
|
||||
Copyright (c) 2013 Phillip Bond
|
||||
|
||||
Licensed under the MIT License
|
||||
|
||||
see file LICENSE
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
0.0.1
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
// Copyright (c) 2013 Phillip Bond
|
||||
// Licensed under the MIT License
|
||||
// see file LICENSE
|
||||
|
||||
package main
|
||||
|
||||
// go-top
|
||||
//
|
||||
// A sample program that emulates the way gnu top gets most of its
|
||||
// information. It does not get information about other processes, just the
|
||||
// calling process.
|
||||
//
|
||||
// To demonstrate how the output changes, you can invoke with the
|
||||
// -coresToPeg=N option. For example:
|
||||
//
|
||||
// go run go-top.go -coresToPeg=2
|
||||
//
|
||||
// will run two concurrent infinte loops and max out up to two cores (assuming
|
||||
// you have more than one core). Note that the loops are not tuned to always
|
||||
// hit 100% on all machines, but they get close. Also note that each core you
|
||||
// want to max out will add up to 100% CPU usage to this process, but you will
|
||||
// get less than 100% per core if there are other processes using the CPU, or
|
||||
// if the kernel is suffering high load averages, etc.
|
||||
//
|
||||
// %CCPU measures cumulative CPU usage. It is useful when you have a daemon
|
||||
// that only runs periodically, but does intense calculations. You can use
|
||||
// long sample times, on the order of minutes, but still get an accurate
|
||||
// measure of how much CPU time has been used over the life of the process,
|
||||
// even if your samples occur when the CPU is temporarily idle.
|
||||
|
||||
import (
|
||||
"bitbucket.org/bertimus9/systemstat"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
var coresToPegPtr *int64
|
||||
|
||||
type stats struct {
|
||||
startTime time.Time
|
||||
|
||||
// stats this process
|
||||
ProcUptime float64 //seconds
|
||||
ProcMemUsedPct float64
|
||||
ProcCPUAvg systemstat.ProcCPUAverage
|
||||
LastProcCPUSample systemstat.ProcCPUSample `json:"-"`
|
||||
CurProcCPUSample systemstat.ProcCPUSample `json:"-"`
|
||||
|
||||
// stats for whole system
|
||||
LastCPUSample systemstat.CPUSample `json:"-"`
|
||||
CurCPUSample systemstat.CPUSample `json:"-"`
|
||||
SysCPUAvg systemstat.CPUAverage
|
||||
SysMemK systemstat.MemSample
|
||||
LoadAverage systemstat.LoadAvgSample
|
||||
SysUptime systemstat.UptimeSample
|
||||
|
||||
// bookkeeping
|
||||
procCPUSampled bool
|
||||
sysCPUSampled bool
|
||||
}
|
||||
|
||||
func NewStats() *stats {
|
||||
s := stats{}
|
||||
s.startTime = time.Now()
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *stats) PrintStats() {
|
||||
up, err := time.ParseDuration(fmt.Sprintf("%fs", s.SysUptime.Uptime))
|
||||
upstring := "SysUptime Error"
|
||||
if err == nil {
|
||||
updays := up.Hours() / 24
|
||||
switch {
|
||||
case updays >= 365:
|
||||
upstring = fmt.Sprintf("%.0f years", updays/365)
|
||||
case updays >= 1:
|
||||
upstring = fmt.Sprintf("%.0f days", updays)
|
||||
default: // less than a day
|
||||
upstring = up.String()
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("*********************************************************")
|
||||
fmt.Printf("go-top - %s up %s,\t\tload average: %.2f, %.2f, %.2f\n",
|
||||
s.LoadAverage.Time.Format("15:04:05"), upstring, s.LoadAverage.One, s.LoadAverage.Five, s.LoadAverage.Fifteen)
|
||||
|
||||
fmt.Printf("Cpu(s): %.1f%%us, %.1f%%sy, %.1f%%ni, %.1f%%id, %.1f%%wa, %.1f%%hi, %.1f%%si, %.1f%%st %.1f%%gu\n",
|
||||
s.SysCPUAvg.UserPct, s.SysCPUAvg.SystemPct, s.SysCPUAvg.NicePct, s.SysCPUAvg.IdlePct,
|
||||
s.SysCPUAvg.IowaitPct, s.SysCPUAvg.IrqPct, s.SysCPUAvg.SoftIrqPct, s.SysCPUAvg.StealPct,
|
||||
s.SysCPUAvg.GuestPct)
|
||||
|
||||
fmt.Printf("Mem: %9dk total, %9dk used, %9dk free, %9dk buffers\n", s.SysMemK.MemTotal,
|
||||
s.SysMemK.MemUsed, s.SysMemK.MemFree, s.SysMemK.Buffers)
|
||||
fmt.Printf("Swap: %9dk total, %9dk used, %9dk free, %9dk cached\n", s.SysMemK.SwapTotal,
|
||||
s.SysMemK.SwapUsed, s.SysMemK.SwapFree, s.SysMemK.Cached)
|
||||
|
||||
fmt.Println("************************************************************")
|
||||
if s.ProcCPUAvg.PossiblePct > 0 {
|
||||
cpuHelpText := "[see -help flag to change %cpu]"
|
||||
if *coresToPegPtr > 0 {
|
||||
cpuHelpText = ""
|
||||
}
|
||||
fmt.Printf("ProcessName\tRES(k)\t%%CPU\t%%CCPU\t%%MEM\n")
|
||||
fmt.Printf("this-process\t%d\t%3.1f\t%2.1f\t%3.1f\t%s\n",
|
||||
s.CurProcCPUSample.ProcMemUsedK,
|
||||
s.ProcCPUAvg.TotalPct,
|
||||
100*s.CurProcCPUSample.Total/s.ProcUptime/float64(1),
|
||||
100*float64(s.CurProcCPUSample.ProcMemUsedK)/float64(s.SysMemK.MemTotal),
|
||||
cpuHelpText)
|
||||
fmt.Println("%CCPU is cumulative CPU usage over this process' life.")
|
||||
fmt.Printf("Max this-process CPU possible: %3.f%%\n", s.ProcCPUAvg.PossiblePct)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stats) GatherStats(percent bool) {
|
||||
s.SysUptime = systemstat.GetUptime()
|
||||
s.ProcUptime = time.Since(s.startTime).Seconds()
|
||||
|
||||
s.SysMemK = systemstat.GetMemSample()
|
||||
s.LoadAverage = systemstat.GetLoadAvgSample()
|
||||
|
||||
s.LastCPUSample = s.CurCPUSample
|
||||
s.CurCPUSample = systemstat.GetCPUSample()
|
||||
|
||||
if s.sysCPUSampled { // we need 2 samples to get an average
|
||||
s.SysCPUAvg = systemstat.GetCPUAverage(s.LastCPUSample, s.CurCPUSample)
|
||||
}
|
||||
// we have at least one sample, subsequent rounds will give us an average
|
||||
s.sysCPUSampled = true
|
||||
|
||||
s.ProcMemUsedPct = 100 * float64(s.CurProcCPUSample.ProcMemUsedK) / float64(s.SysMemK.MemTotal)
|
||||
|
||||
s.LastProcCPUSample = s.CurProcCPUSample
|
||||
s.CurProcCPUSample = systemstat.GetProcCPUSample()
|
||||
if s.procCPUSampled {
|
||||
s.ProcCPUAvg = systemstat.GetProcCPUAverage(s.LastProcCPUSample, s.CurProcCPUSample, s.ProcUptime)
|
||||
}
|
||||
s.procCPUSampled = true
|
||||
}
|
||||
|
||||
func main() {
|
||||
// get command line flags
|
||||
coresToPegPtr = flag.Int64("coresToPeg", 0, "how many CPU cores would you like to artificially peg to 100% usage")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
// this will help us poll the OS to get system statistics
|
||||
stats := NewStats()
|
||||
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
// WARNING: each call to burnCPU() will peg one core
|
||||
// of your machine to 100%
|
||||
// If you have code you'd like to drop in to this example,
|
||||
// just run "go yourCode()" instead of "go burnCPU()
|
||||
for i := *coresToPegPtr; i > 0; i-- {
|
||||
fmt.Println("pegging one more CPU core.")
|
||||
go burnCPU()
|
||||
}
|
||||
|
||||
for {
|
||||
stats.GatherStats(true)
|
||||
stats.PrintStats()
|
||||
|
||||
// This next line lets out see the jsonified object
|
||||
// produced by systemstat
|
||||
// printJson(stats, false)
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func printJson(s *stats, indent bool) {
|
||||
b, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
fmt.Println("error:", err)
|
||||
}
|
||||
dst := new(bytes.Buffer)
|
||||
if indent {
|
||||
json.Indent(dst, b, "", " ")
|
||||
} else {
|
||||
dst.Write(b)
|
||||
}
|
||||
fmt.Println(dst.String())
|
||||
time.Sleep(time.Second * 3)
|
||||
}
|
||||
|
||||
func burnCPU() {
|
||||
time.Sleep(4 * time.Second)
|
||||
for {
|
||||
b := 1.0
|
||||
c := 1.0
|
||||
d := 1.0
|
||||
for j := 1; j < 1000; j++ {
|
||||
b *= float64(j)
|
||||
for k := 1; k < 700000; k++ {
|
||||
|
||||
c *= float64(k)
|
||||
d = (28 + b*b/3.23412) / math.Sqrt(c*c)
|
||||
c *= d
|
||||
}
|
||||
time.Sleep(500 * time.Nanosecond)
|
||||
runtime.Gosched()
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) 2013 Phillip Bond
|
||||
// Licensed under the MIT License
|
||||
// see file LICENSE
|
||||
|
||||
package systemstat
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// CPUSample is an object that represents the breakdown of time spent by the
|
||||
// CPU in various types of tasks. Two CPUSamples are required to find the
|
||||
// average usage over time, represented by the CPUAverage object. The CPUSample
|
||||
// is taken from the line "cpu" from /proc/stat in the Linux kernel.
|
||||
//
|
||||
// Summarized from the proc(5) man page:
|
||||
// /proc/stat :
|
||||
// kernel/system statistics. Varies with architecture.
|
||||
type CPUSample struct {
|
||||
User uint64 // time spent in user mode
|
||||
Nice uint64 // time spent in user mode with low priority (nice)
|
||||
System uint64 // time spent in system mode
|
||||
Idle uint64 // time spent in the idle task
|
||||
Iowait uint64 // time spent waiting for I/O to complete (since Linux 2.5.41)
|
||||
Irq uint64 // time spent servicing interrupts (since 2.6.0-test4)
|
||||
SoftIrq uint64 // time spent servicing softirqs (since 2.6.0-test4)
|
||||
Steal uint64 // time spent in other OSes when running in a virtualized environment
|
||||
Guest uint64 // time spent running a virtual CPU for guest operating systems under the control of the Linux kernel.
|
||||
Name string // name of the line in /proc/stat; cpu, cpu1, etc
|
||||
Time time.Time // when the sample was taken
|
||||
Total uint64 // total of all time fields
|
||||
}
|
||||
|
||||
type ProcCPUSample struct {
|
||||
User float64 // time spent in user mode
|
||||
System float64 // time spent in system mode
|
||||
Time time.Time // when the sample was taken
|
||||
Total float64 // total of all time fields
|
||||
ProcMemUsedK int64
|
||||
}
|
||||
|
||||
type ProcCPUAverage struct {
|
||||
UserPct float64 // time spent in user mode
|
||||
SystemPct float64 // time spent in system mode
|
||||
TotalPct float64 // total of all time fields
|
||||
PossiblePct float64 // total of all time fields
|
||||
CumulativeTotalPct float64 // total of all time throughout process life
|
||||
Time time.Time // when the sample was taken
|
||||
Seconds float64 // how many seconds between the two samples
|
||||
}
|
||||
|
||||
// SimpleCPUAverage is an object that represents the average cpu usage over a
|
||||
// time period. It is calculated by taking the difference between two
|
||||
// CPUSamples (whose units are clock ticks), dividing by the number of elapsed
|
||||
// ticks between the samples, and converting to a percent. It is a simplified version of the CPUAverage in that it only accounts for time in the Idle task and all other time (Busy).
|
||||
type SimpleCPUAverage struct {
|
||||
BusyPct float64 // percent of time spent by CPU performing all non-idle tasks
|
||||
IdlePct float64 // percent of time spent by CPU in the idle task
|
||||
}
|
||||
|
||||
// CPUAverage is an object that represents the average cpu usage over a
|
||||
// time period. It is calculated by taking the difference between two
|
||||
// CPUSamples (whose units are clock ticks), dividing by the number of elapsed
|
||||
// ticks between the samples, and converting to a percent.
|
||||
type CPUAverage struct {
|
||||
UserPct float64
|
||||
NicePct float64
|
||||
SystemPct float64
|
||||
IdlePct float64
|
||||
IowaitPct float64
|
||||
IrqPct float64
|
||||
SoftIrqPct float64
|
||||
StealPct float64
|
||||
GuestPct float64
|
||||
Time time.Time
|
||||
Seconds float64 // how many seconds between the two samples
|
||||
}
|
||||
|
||||
type MemSample struct {
|
||||
Buffers uint64
|
||||
Cached uint64
|
||||
MemTotal uint64
|
||||
MemUsed uint64
|
||||
MemFree uint64
|
||||
SwapTotal uint64
|
||||
SwapUsed uint64
|
||||
SwapFree uint64
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
type LoadAvgSample struct {
|
||||
One float64
|
||||
Five float64
|
||||
Fifteen float64
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
type UptimeSample struct {
|
||||
Uptime float64
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
// GetCPUAverage returns the average cpu usage between two CPUSamples.
|
||||
func GetCPUAverage(first CPUSample, second CPUSample) CPUAverage {
|
||||
return getCPUAverage(first, second)
|
||||
}
|
||||
|
||||
// GetSimpleCPUAverage returns an aggregated average cpu usage between two CPUSamples.
|
||||
func GetSimpleCPUAverage(first CPUSample, second CPUSample) SimpleCPUAverage {
|
||||
return getSimpleCPUAverage(first, second)
|
||||
}
|
||||
|
||||
// GetProcCPUAverage returns the average cpu usage of this running process
|
||||
func GetProcCPUAverage(first ProcCPUSample, second ProcCPUSample, procUptime float64) (avg ProcCPUAverage) {
|
||||
return getProcCPUAverage(first, second, procUptime)
|
||||
}
|
||||
|
||||
// GetCPUSample takes a snapshot of kernel statistics from the /proc/stat file.
|
||||
func GetCPUSample() (samp CPUSample) {
|
||||
return getCPUSample("/proc/stat")
|
||||
}
|
||||
|
||||
// GetProcCPUSample takes a snapshot of kernel statistics from the /proc/stat file.
|
||||
func GetProcCPUSample() (samp ProcCPUSample) {
|
||||
return getProcCPUSample()
|
||||
}
|
||||
|
||||
// GetUptime takes a snapshot of load info from the /proc/loadavg file.
|
||||
func GetUptime() (samp UptimeSample) {
|
||||
return getUptime("/proc/uptime")
|
||||
}
|
||||
|
||||
// GetLoadAvgSample takes a snapshot of load info from the /proc/loadavg file.
|
||||
func GetLoadAvgSample() (samp LoadAvgSample) {
|
||||
return getLoadAvgSample("/proc/loadavg")
|
||||
}
|
||||
|
||||
// GetMemSample takes a snapshot of memory info from the /proc/meminfo file.
|
||||
func GetMemSample() (samp MemSample) {
|
||||
return getMemSample("/proc/meminfo")
|
||||
}
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
// Copyright (c) 2013 Phillip Bond
|
||||
// Licensed under the MIT License
|
||||
// see file LICENSE
|
||||
|
||||
// +build linux
|
||||
|
||||
package systemstat
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func getUptime(procfile string) (uptime UptimeSample) {
|
||||
// read in whole uptime file with cpu usage information ;"/proc/uptime"
|
||||
contents, err := ioutil.ReadFile(procfile)
|
||||
uptime.Time = time.Now()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(bytes.NewBuffer(contents))
|
||||
line, _, err := reader.ReadLine()
|
||||
fields := strings.Fields(string(line))
|
||||
|
||||
val, numerr := strconv.ParseFloat(fields[0], 64)
|
||||
if numerr != nil {
|
||||
return
|
||||
}
|
||||
uptime.Uptime = val
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getLoadAvgSample(procfile string) (samp LoadAvgSample) {
|
||||
// read in whole loadavg file with cpu usage information ;"/proc/loadavg"
|
||||
contents, err := ioutil.ReadFile(procfile)
|
||||
samp.Time = time.Now()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(bytes.NewBuffer(contents))
|
||||
line, _, err := reader.ReadLine()
|
||||
fields := strings.Fields(string(line))
|
||||
for i := 0; i < 3; i++ {
|
||||
val, numerr := strconv.ParseFloat(fields[i], 64)
|
||||
if numerr != nil {
|
||||
return
|
||||
}
|
||||
switch i {
|
||||
case 0:
|
||||
samp.One = val
|
||||
case 1:
|
||||
samp.Five = val
|
||||
case 2:
|
||||
samp.Fifteen = val
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getMemSample(procfile string) (samp MemSample) {
|
||||
want := map[string]bool{
|
||||
"Buffers:": true,
|
||||
"Cached:": true,
|
||||
"MemTotal:": true,
|
||||
"MemFree:": true,
|
||||
"MemUsed:": true,
|
||||
"SwapTotal:": true,
|
||||
"SwapFree:": true,
|
||||
"SwapUsed:": true}
|
||||
|
||||
// read in whole meminfo file with cpu usage information ;"/proc/meminfo"
|
||||
contents, err := ioutil.ReadFile(procfile)
|
||||
samp.Time = time.Now()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(bytes.NewBuffer(contents))
|
||||
for {
|
||||
line, _, err := reader.ReadLine()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
fields := strings.Fields(string(line))
|
||||
fieldName := fields[0]
|
||||
|
||||
_, ok := want[fieldName]
|
||||
if ok && len(fields) == 3 {
|
||||
val, numerr := strconv.ParseUint(fields[1], 10, 64)
|
||||
if numerr != nil {
|
||||
return
|
||||
}
|
||||
switch fieldName {
|
||||
case "Buffers:":
|
||||
samp.Buffers = val
|
||||
case "Cached:":
|
||||
samp.Cached = val
|
||||
case "MemTotal:":
|
||||
samp.MemTotal = val
|
||||
case "MemFree:":
|
||||
samp.MemFree = val
|
||||
case "SwapTotal:":
|
||||
samp.SwapTotal = val
|
||||
case "SwapFree:":
|
||||
samp.SwapFree = val
|
||||
}
|
||||
}
|
||||
}
|
||||
samp.MemUsed = samp.MemTotal - samp.MemFree
|
||||
samp.SwapUsed = samp.SwapTotal - samp.SwapFree
|
||||
return
|
||||
}
|
||||
|
||||
func getProcCPUSample() (s ProcCPUSample) {
|
||||
var processInfo syscall.Rusage
|
||||
syscall.Getrusage(syscall.RUSAGE_SELF, &processInfo)
|
||||
|
||||
s.Time = time.Now()
|
||||
s.ProcMemUsedK = int64(processInfo.Maxrss)
|
||||
s.User = float64(processInfo.Utime.Usec)/1000000 + float64(processInfo.Utime.Sec)
|
||||
s.System = float64(processInfo.Stime.Usec)/1000000 + float64(processInfo.Stime.Sec)
|
||||
s.Total = s.User + s.System
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getCPUSample(procfile string) (samp CPUSample) {
|
||||
// read in whole proc file with cpu usage information ; "/proc/stat"
|
||||
contents, err := ioutil.ReadFile(procfile)
|
||||
samp.Time = time.Now()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(bytes.NewBuffer(contents))
|
||||
for {
|
||||
line, _, err := reader.ReadLine()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
fields := strings.Fields(string(line))
|
||||
|
||||
if len(fields) > 0 {
|
||||
fieldName := fields[0]
|
||||
if fieldName == "cpu" {
|
||||
parseCPUFields(fields, &samp)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getSimpleCPUAverage(first CPUSample, second CPUSample) (avg SimpleCPUAverage) {
|
||||
//walltimediff := second.Time.Sub(first.Time)
|
||||
//dT := float64(first.Total - second.Total)
|
||||
|
||||
dI := float64(second.Idle - first.Idle)
|
||||
dTot := float64(second.Total - first.Total)
|
||||
avg.IdlePct = dI / dTot * 100
|
||||
avg.BusyPct = (dTot - dI) * 100 / dTot
|
||||
//log.Printf("cpu idle ticks %f, total ticks %f, idle pct %f, busy pct %f\n", dI, dTot, avg.IdlePct, avg.BusyPct)
|
||||
return
|
||||
}
|
||||
|
||||
func subtractAndConvertTicks(first uint64, second uint64) float64 {
|
||||
return float64(first - second)
|
||||
}
|
||||
|
||||
func getCPUAverage(first CPUSample, second CPUSample) (avg CPUAverage) {
|
||||
dTot := float64(second.Total - first.Total)
|
||||
invQuotient := 100.00 / dTot
|
||||
|
||||
avg.UserPct = subtractAndConvertTicks(second.User, first.User) * invQuotient
|
||||
avg.NicePct = subtractAndConvertTicks(second.Nice, first.Nice) * invQuotient
|
||||
avg.SystemPct = subtractAndConvertTicks(second.System, first.System) * invQuotient
|
||||
avg.IdlePct = subtractAndConvertTicks(second.Idle, first.Idle) * invQuotient
|
||||
avg.IowaitPct = subtractAndConvertTicks(second.Iowait, first.Iowait) * invQuotient
|
||||
avg.IrqPct = subtractAndConvertTicks(second.Irq, first.Irq) * invQuotient
|
||||
avg.SoftIrqPct = subtractAndConvertTicks(second.SoftIrq, first.SoftIrq) * invQuotient
|
||||
avg.StealPct = subtractAndConvertTicks(second.Steal, first.Steal) * invQuotient
|
||||
avg.GuestPct = subtractAndConvertTicks(second.Guest, first.Guest) * invQuotient
|
||||
avg.Time = second.Time
|
||||
avg.Seconds = second.Time.Sub(first.Time).Seconds()
|
||||
return
|
||||
}
|
||||
|
||||
func getProcCPUAverage(first ProcCPUSample, second ProcCPUSample, procUptime float64) (avg ProcCPUAverage) {
|
||||
dT := second.Time.Sub(first.Time).Seconds()
|
||||
|
||||
avg.UserPct = 100 * (second.User - first.User) / dT
|
||||
avg.SystemPct = 100 * (second.System - first.System) / dT
|
||||
avg.TotalPct = 100 * (second.Total - first.Total) / dT
|
||||
avg.PossiblePct = 100.0 * float64(runtime.NumCPU())
|
||||
avg.CumulativeTotalPct = 100 * second.Total / procUptime
|
||||
avg.Time = second.Time
|
||||
avg.Seconds = dT
|
||||
return
|
||||
}
|
||||
|
||||
func parseCPUFields(fields []string, stat *CPUSample) {
|
||||
numFields := len(fields)
|
||||
stat.Name = fields[0]
|
||||
for i := 1; i < numFields; i++ {
|
||||
val, numerr := strconv.ParseUint(fields[i], 10, 64)
|
||||
if numerr != nil {
|
||||
log.Println("systemstat.parseCPUFields(): Error parsing (field, value): ", i, fields[i])
|
||||
}
|
||||
stat.Total += val
|
||||
switch i {
|
||||
case 1:
|
||||
stat.User = val
|
||||
case 2:
|
||||
stat.Nice = val
|
||||
case 3:
|
||||
stat.System = val
|
||||
case 4:
|
||||
stat.Idle = val
|
||||
case 5:
|
||||
stat.Iowait = val
|
||||
case 6:
|
||||
stat.Irq = val
|
||||
case 7:
|
||||
stat.SoftIrq = val
|
||||
case 8:
|
||||
stat.Steal = val
|
||||
case 9:
|
||||
stat.Guest = val
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
include $(GOROOT)/src/Make.inc
|
||||
|
||||
TARG=bitbucket.org/ww/goautoneg
|
||||
GOFILES=autoneg.go
|
||||
|
||||
include $(GOROOT)/src/Make.pkg
|
||||
|
||||
format:
|
||||
gofmt -w *.go
|
||||
|
||||
docs:
|
||||
gomake clean
|
||||
godoc ${TARG} > README.txt
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
PACKAGE
|
||||
|
||||
package goautoneg
|
||||
import "bitbucket.org/ww/goautoneg"
|
||||
|
||||
HTTP Content-Type Autonegotiation.
|
||||
|
||||
The functions in this package implement the behaviour specified in
|
||||
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
||||
|
||||
Copyright (c) 2011, Open Knowledge Foundation Ltd.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
Neither the name of the Open Knowledge Foundation Ltd. nor the
|
||||
names of its contributors may be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
FUNCTIONS
|
||||
|
||||
func Negotiate(header string, alternatives []string) (content_type string)
|
||||
Negotiate the most appropriate content_type given the accept header
|
||||
and a list of alternatives.
|
||||
|
||||
func ParseAccept(header string) (accept []Accept)
|
||||
Parse an Accept Header string returning a sorted list
|
||||
of clauses
|
||||
|
||||
|
||||
TYPES
|
||||
|
||||
type Accept struct {
|
||||
Type, SubType string
|
||||
Q float32
|
||||
Params map[string]string
|
||||
}
|
||||
Structure to represent a clause in an HTTP Accept Header
|
||||
|
||||
|
||||
SUBDIRECTORIES
|
||||
|
||||
.hg
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
HTTP Content-Type Autonegotiation.
|
||||
|
||||
The functions in this package implement the behaviour specified in
|
||||
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
||||
|
||||
Copyright (c) 2011, Open Knowledge Foundation Ltd.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
Neither the name of the Open Knowledge Foundation Ltd. nor the
|
||||
names of its contributors may be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
*/
|
||||
package goautoneg
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Structure to represent a clause in an HTTP Accept Header
|
||||
type Accept struct {
|
||||
Type, SubType string
|
||||
Q float64
|
||||
Params map[string]string
|
||||
}
|
||||
|
||||
// For internal use, so that we can use the sort interface
|
||||
type accept_slice []Accept
|
||||
|
||||
func (accept accept_slice) Len() int {
|
||||
slice := []Accept(accept)
|
||||
return len(slice)
|
||||
}
|
||||
|
||||
func (accept accept_slice) Less(i, j int) bool {
|
||||
slice := []Accept(accept)
|
||||
ai, aj := slice[i], slice[j]
|
||||
if ai.Q > aj.Q {
|
||||
return true
|
||||
}
|
||||
if ai.Type != "*" && aj.Type == "*" {
|
||||
return true
|
||||
}
|
||||
if ai.SubType != "*" && aj.SubType == "*" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (accept accept_slice) Swap(i, j int) {
|
||||
slice := []Accept(accept)
|
||||
slice[i], slice[j] = slice[j], slice[i]
|
||||
}
|
||||
|
||||
// Parse an Accept Header string returning a sorted list
|
||||
// of clauses
|
||||
func ParseAccept(header string) (accept []Accept) {
|
||||
parts := strings.Split(header, ",")
|
||||
accept = make([]Accept, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
part := strings.Trim(part, " ")
|
||||
|
||||
a := Accept{}
|
||||
a.Params = make(map[string]string)
|
||||
a.Q = 1.0
|
||||
|
||||
mrp := strings.Split(part, ";")
|
||||
|
||||
media_range := mrp[0]
|
||||
sp := strings.Split(media_range, "/")
|
||||
a.Type = strings.Trim(sp[0], " ")
|
||||
|
||||
switch {
|
||||
case len(sp) == 1 && a.Type == "*":
|
||||
a.SubType = "*"
|
||||
case len(sp) == 2:
|
||||
a.SubType = strings.Trim(sp[1], " ")
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
if len(mrp) == 1 {
|
||||
accept = append(accept, a)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, param := range mrp[1:] {
|
||||
sp := strings.SplitN(param, "=", 2)
|
||||
if len(sp) != 2 {
|
||||
continue
|
||||
}
|
||||
token := strings.Trim(sp[0], " ")
|
||||
if token == "q" {
|
||||
a.Q, _ = strconv.ParseFloat(sp[1], 32)
|
||||
} else {
|
||||
a.Params[token] = strings.Trim(sp[1], " ")
|
||||
}
|
||||
}
|
||||
|
||||
accept = append(accept, a)
|
||||
}
|
||||
|
||||
slice := accept_slice(accept)
|
||||
sort.Sort(slice)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Negotiate the most appropriate content_type given the accept header
|
||||
// and a list of alternatives.
|
||||
func Negotiate(header string, alternatives []string) (content_type string) {
|
||||
asp := make([][]string, 0, len(alternatives))
|
||||
for _, ctype := range alternatives {
|
||||
asp = append(asp, strings.SplitN(ctype, "/", 2))
|
||||
}
|
||||
for _, clause := range ParseAccept(header) {
|
||||
for i, ctsp := range asp {
|
||||
if clause.Type == ctsp[0] && clause.SubType == ctsp[1] {
|
||||
content_type = alternatives[i]
|
||||
return
|
||||
}
|
||||
if clause.Type == ctsp[0] && clause.SubType == "*" {
|
||||
content_type = alternatives[i]
|
||||
return
|
||||
}
|
||||
if clause.Type == "*" && clause.SubType == "*" {
|
||||
content_type = alternatives[i]
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2014-2016 ClusterHQ
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
flocker-go
|
||||
==========
|
||||
|
||||
[](https://circleci.com/gh/ClusterHQ/flocker-go)
|
||||
|
||||
flocker-go implements the package `flocker` that will let you easily interact
|
||||
with a Flocker Control Service.
|
||||
|
||||
What can it do?
|
||||
---------------
|
||||
|
||||
You can check the package documentation here: https://godoc.org/github.com/ClusterHQ/flocker-go
|
||||
|
||||
TODO
|
||||
----
|
||||
|
||||
- Define a proper interface `flockerClientable` with all the needed methods for
|
||||
wrapping the Flocker API.
|
||||
+324
@@ -0,0 +1,324 @@
|
||||
package flocker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// From https://github.com/ClusterHQ/flocker-docker-plugin/blob/master/flockerdockerplugin/adapter.py#L18
|
||||
const defaultVolumeSize = json.Number("107374182400")
|
||||
|
||||
var (
|
||||
// A volume can take a long time to be available, if we don't want
|
||||
// Kubernetes to wait forever we need to stop trying after some time, that
|
||||
// time is defined here
|
||||
timeoutWaitingForVolume = 2 * time.Minute
|
||||
tickerWaitingForVolume = 5 * time.Second
|
||||
|
||||
errStateNotFound = errors.New("State not found by Dataset ID")
|
||||
errConfigurationNotFound = errors.New("Configuration not found by Name")
|
||||
|
||||
errFlockerControlServiceHost = errors.New("The volume config must have a key CONTROL_SERVICE_HOST defined in the OtherAttributes field")
|
||||
errFlockerControlServicePort = errors.New("The volume config must have a key CONTROL_SERVICE_PORT defined in the OtherAttributes field")
|
||||
|
||||
errVolumeAlreadyExists = errors.New("The volume already exists")
|
||||
errVolumeDoesNotExist = errors.New("The volume does not exist")
|
||||
|
||||
errUpdatingDataset = errors.New("It was impossible to update the dataset")
|
||||
)
|
||||
|
||||
// Clientable exposes the needed methods to implement your own Flocker Client.
|
||||
type Clientable interface {
|
||||
CreateDataset(metaName string) (*DatasetState, error)
|
||||
|
||||
GetDatasetState(datasetID string) (*DatasetState, error)
|
||||
GetDatasetID(metaName string) (datasetID string, err error)
|
||||
GetPrimaryUUID() (primaryUUID string, err error)
|
||||
|
||||
UpdatePrimaryForDataset(primaryUUID, datasetID string) (*DatasetState, error)
|
||||
}
|
||||
|
||||
// Client is a default Flocker Client.
|
||||
type Client struct {
|
||||
*http.Client
|
||||
|
||||
schema string
|
||||
host string
|
||||
port int
|
||||
version string
|
||||
|
||||
clientIP string
|
||||
|
||||
maximumSize json.Number
|
||||
}
|
||||
|
||||
// NewClient creates a wrapper over http.Client to communicate with the flocker control service.
|
||||
func NewClient(host string, port int, clientIP string, caCertPath, keyPath, certPath string) (*Client, error) {
|
||||
client, err := newTLSClient(caCertPath, keyPath, certPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{
|
||||
Client: client,
|
||||
schema: "https",
|
||||
host: host,
|
||||
port: port,
|
||||
version: "v1",
|
||||
maximumSize: defaultVolumeSize,
|
||||
clientIP: clientIP,
|
||||
}, nil
|
||||
}
|
||||
|
||||
/*
|
||||
request do a request using the http.Client embedded to the control service
|
||||
and returns the response or an error in case it happens.
|
||||
|
||||
Note: you will need to deal with the response body call to Close if you
|
||||
don't want to deal with problems later.
|
||||
*/
|
||||
func (c Client) request(method, url string, payload interface{}) (*http.Response, error) {
|
||||
var (
|
||||
b []byte
|
||||
err error
|
||||
)
|
||||
|
||||
if method == "POST" { // Just allow payload on POST
|
||||
b, err = json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, bytes.NewBuffer(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// REMEMBER TO CLOSE THE BODY IN THE OUTSIDE FUNCTION
|
||||
return c.Do(req)
|
||||
}
|
||||
|
||||
// post performs a post request with the indicated payload
|
||||
func (c Client) post(url string, payload interface{}) (*http.Response, error) {
|
||||
return c.request("POST", url, payload)
|
||||
}
|
||||
|
||||
// get performs a get request
|
||||
func (c Client) get(url string) (*http.Response, error) {
|
||||
return c.request("GET", url, nil)
|
||||
}
|
||||
|
||||
// getURL returns a full URI to the control service
|
||||
func (c Client) getURL(path string) string {
|
||||
return fmt.Sprintf("%s://%s:%d/%s/%s", c.schema, c.host, c.port, c.version, path)
|
||||
}
|
||||
|
||||
type configurationPayload struct {
|
||||
Deleted bool `json:"deleted"`
|
||||
Primary string `json:"primary"`
|
||||
DatasetID string `json:"dataset_id,omitempty"`
|
||||
MaximumSize json.Number `json:"maximum_size,omitempty"`
|
||||
Metadata metadataPayload `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type metadataPayload struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
type DatasetState struct {
|
||||
Path string `json:"path"`
|
||||
DatasetID string `json:"dataset_id"`
|
||||
Primary string `json:"primary,omitempty"`
|
||||
MaximumSize json.Number `json:"maximum_size,omitempty"`
|
||||
}
|
||||
|
||||
type datasetStatePayload struct {
|
||||
*DatasetState
|
||||
}
|
||||
|
||||
type nodeStatePayload struct {
|
||||
UUID string `json:"uuid"`
|
||||
Host string `json:"host"`
|
||||
}
|
||||
|
||||
// findIDInConfigurationsPayload returns the datasetID if it was found in the
|
||||
// configurations payload, otherwise it will return an error.
|
||||
func (c Client) findIDInConfigurationsPayload(body io.ReadCloser, name string) (datasetID string, err error) {
|
||||
var configurations []configurationPayload
|
||||
if err = json.NewDecoder(body).Decode(&configurations); err == nil {
|
||||
for _, r := range configurations {
|
||||
if r.Metadata.Name == name {
|
||||
return r.DatasetID, nil
|
||||
}
|
||||
}
|
||||
return "", errConfigurationNotFound
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// GetPrimaryUUID returns the UUID of the primary Flocker Control Service for
|
||||
// the given host.
|
||||
func (c Client) GetPrimaryUUID() (uuid string, err error) {
|
||||
resp, err := c.get(c.getURL("state/nodes"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var states []nodeStatePayload
|
||||
if err = json.NewDecoder(resp.Body).Decode(&states); err == nil {
|
||||
for _, s := range states {
|
||||
if s.Host == c.clientIP {
|
||||
return s.UUID, nil
|
||||
}
|
||||
}
|
||||
return "", errStateNotFound
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// GetDatasetState performs a get request to get the state of the given datasetID, if
|
||||
// something goes wrong or the datasetID was not found it returns an error.
|
||||
func (c Client) GetDatasetState(datasetID string) (*DatasetState, error) {
|
||||
resp, err := c.get(c.getURL("state/datasets"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var states []datasetStatePayload
|
||||
if err = json.NewDecoder(resp.Body).Decode(&states); err == nil {
|
||||
for _, s := range states {
|
||||
if s.DatasetID == datasetID {
|
||||
return s.DatasetState, nil
|
||||
}
|
||||
}
|
||||
return nil, errStateNotFound
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
/*
|
||||
CreateDataset creates a volume in Flocker, waits for it to be ready and
|
||||
returns the dataset id.
|
||||
|
||||
This process is a little bit complex but follows this flow:
|
||||
|
||||
1. Find the Flocker Control Service UUID
|
||||
2. Try to create the dataset
|
||||
3. If it already exists an error is returned
|
||||
4. If it didn't previously exist, wait for it to be ready
|
||||
*/
|
||||
func (c Client) CreateDataset(metaName string) (*DatasetState, error) {
|
||||
// 1) Find the primary Flocker UUID
|
||||
// Note: it could be cached, but doing this query we health check it
|
||||
primary, err := c.GetPrimaryUUID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2) Try to create the dataset in the given Primary
|
||||
payload := configurationPayload{
|
||||
Primary: primary,
|
||||
MaximumSize: json.Number(c.maximumSize),
|
||||
Metadata: metadataPayload{
|
||||
Name: metaName,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := c.post(c.getURL("configuration/datasets"), payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 3) Return if the dataset was previously created
|
||||
if resp.StatusCode == http.StatusConflict {
|
||||
return nil, errVolumeAlreadyExists
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
return nil, fmt.Errorf("Expected: {1,2}xx creating the volume, got: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var p configurationPayload
|
||||
if err := json.NewDecoder(resp.Body).Decode(&p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 4) Wait until the dataset is ready for usage. In case it never gets
|
||||
// ready there is a timeoutChan that will return an error
|
||||
timeoutChan := time.NewTimer(timeoutWaitingForVolume).C
|
||||
tickChan := time.NewTicker(tickerWaitingForVolume).C
|
||||
|
||||
for {
|
||||
if s, err := c.GetDatasetState(p.DatasetID); err == nil {
|
||||
return s, nil
|
||||
} else if err != errStateNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-timeoutChan:
|
||||
return nil, err
|
||||
case <-tickChan:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdatePrimaryForDataset will update the Primary for the given dataset
|
||||
// returning the current DatasetState.
|
||||
func (c Client) UpdatePrimaryForDataset(newPrimaryUUID, datasetID string) (*DatasetState, error) {
|
||||
payload := struct {
|
||||
Primary string `json:"primary"`
|
||||
}{
|
||||
Primary: newPrimaryUUID,
|
||||
}
|
||||
|
||||
url := c.getURL(fmt.Sprintf("configuration/datasets/%s", datasetID))
|
||||
resp, err := c.post(url, payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
return nil, errUpdatingDataset
|
||||
}
|
||||
|
||||
var s DatasetState
|
||||
if err := json.NewDecoder(resp.Body).Decode(&s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
// GetDatasetID will return the DatasetID found for the given metadata name.
|
||||
func (c Client) GetDatasetID(metaName string) (datasetID string, err error) {
|
||||
resp, err := c.get(c.getURL("configuration/datasets"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var configurations []configurationPayload
|
||||
if err = json.NewDecoder(resp.Body).Decode(&configurations); err == nil {
|
||||
for _, c := range configurations {
|
||||
if c.Metadata.Name == metaName && c.Deleted == false {
|
||||
return c.DatasetID, nil
|
||||
}
|
||||
}
|
||||
return "", errConfigurationNotFound
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// flocker package allows you to easily interact with a Flocker Control Service.
|
||||
package flocker
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package flocker
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// newTLSClient returns a new TLS http client
|
||||
func newTLSClient(caCertPath, keyPath, certPath string) (*http.Client, error) {
|
||||
// Client certificate
|
||||
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// CA certificate
|
||||
caCert, err := ioutil.ReadFile(caCertPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
caCertPool.AppendCertsFromPEM(caCert)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: caCertPool,
|
||||
}
|
||||
tlsConfig.BuildNameToCertificate()
|
||||
transport := &http.Transport{TLSClientConfig: tlsConfig}
|
||||
|
||||
return &http.Client{Transport: transport}, nil
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
logrus
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- tip
|
||||
install:
|
||||
- go get github.com/stretchr/testify
|
||||
- go get github.com/stvp/go-udp-testing
|
||||
- go get github.com/tobi/airbrake-go
|
||||
- go get github.com/getsentry/raven-go
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Simon Eskildsen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+352
@@ -0,0 +1,352 @@
|
||||
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [](https://travis-ci.org/Sirupsen/logrus)
|
||||
|
||||
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||
the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
|
||||
yet stable (pre 1.0), the core API is unlikely change much but please version
|
||||
control your Logrus to make sure you aren't fetching latest `master` on every
|
||||
build.**
|
||||
|
||||
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||
plain text):
|
||||
|
||||

|
||||
|
||||
With `log.Formatter = new(logrus.JSONFormatter)`, for easy parsing by logstash
|
||||
or Splunk:
|
||||
|
||||
```json
|
||||
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
||||
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
||||
|
||||
{"level":"warning","msg":"The group's number increased tremendously!",
|
||||
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
||||
|
||||
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
||||
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
||||
|
||||
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
||||
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
||||
|
||||
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
||||
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
||||
```
|
||||
|
||||
With the default `log.Formatter = new(logrus.TextFormatter)` when a TTY is not
|
||||
attached, the output is compatible with the
|
||||
[l2met](http://r.32k.io/l2met-introduction) format:
|
||||
|
||||
```text
|
||||
time="2014-04-20 15:36:23.830442383 -0400 EDT" level="info" msg="A group of walrus emerges from the ocean" animal="walrus" size=10
|
||||
time="2014-04-20 15:36:23.830584199 -0400 EDT" level="warning" msg="The group's number increased tremendously!" omg=true number=122
|
||||
time="2014-04-20 15:36:23.830596521 -0400 EDT" level="info" msg="A giant walrus appears!" animal="walrus" size=10
|
||||
time="2014-04-20 15:36:23.830611837 -0400 EDT" level="info" msg="Tremendously sized cow enters the ocean." animal="walrus" size=9
|
||||
time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks!" omg=true number=100
|
||||
```
|
||||
|
||||
#### Example
|
||||
|
||||
The simplest way to use Logrus is simply the package-level exported logger:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
}).Info("A walrus appears")
|
||||
}
|
||||
```
|
||||
|
||||
Note that it's completely api-compatible with the stdlib logger, so you can
|
||||
replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"`
|
||||
and you'll now have the flexibility of Logrus. You can customize it all you
|
||||
want:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/Sirupsen/logrus/hooks/airbrake"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Log as JSON instead of the default ASCII formatter.
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
|
||||
// Use the Airbrake hook to report errors that have Error severity or above to
|
||||
// an exception tracker. You can create custom hooks, see the Hooks section.
|
||||
log.AddHook(&logrus_airbrake.AirbrakeHook{})
|
||||
|
||||
// Output to stderr instead of stdout, could also be a file.
|
||||
log.SetOutput(os.Stderr)
|
||||
|
||||
// Only log the warning severity or above.
|
||||
log.SetLevel(log.WarnLevel)
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
}
|
||||
```
|
||||
|
||||
For more advanced usage such as logging to multiple locations from the same
|
||||
application, you can also create an instance of the `logrus` Logger:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Create a new instance of the logger. You can have any number of instances.
|
||||
var log = logrus.New()
|
||||
|
||||
func main() {
|
||||
// The API for setting attributes is a little different than the package level
|
||||
// exported logger. See Godoc.
|
||||
log.Out = os.Stderr
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
}
|
||||
```
|
||||
|
||||
#### Fields
|
||||
|
||||
Logrus encourages careful, structured logging though logging fields instead of
|
||||
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
||||
to send event %s to topic %s with key %d")`, you should log the much more
|
||||
discoverable:
|
||||
|
||||
```go
|
||||
log.WithFields(log.Fields{
|
||||
"event": event,
|
||||
"topic": topic,
|
||||
"key": key,
|
||||
}).Fatal("Failed to send event")
|
||||
```
|
||||
|
||||
We've found this API forces you to think about logging in a way that produces
|
||||
much more useful logging messages. We've been in countless situations where just
|
||||
a single added field to a log statement that was already there would've saved us
|
||||
hours. The `WithFields` call is optional.
|
||||
|
||||
In general, with Logrus using any of the `printf`-family functions should be
|
||||
seen as a hint you should add a field, however, you can still use the
|
||||
`printf`-family functions with Logrus.
|
||||
|
||||
#### Hooks
|
||||
|
||||
You can add hooks for logging levels. For example to send errors to an exception
|
||||
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
|
||||
multiple places simultaneously, e.g. syslog.
|
||||
|
||||
```go
|
||||
// Not the real implementation of the Airbrake hook. Just a simple sample.
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.AddHook(new(AirbrakeHook))
|
||||
}
|
||||
|
||||
type AirbrakeHook struct{}
|
||||
|
||||
// `Fire()` takes the entry that the hook is fired for. `entry.Data[]` contains
|
||||
// the fields for the entry. See the Fields section of the README.
|
||||
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
|
||||
err := airbrake.Notify(entry.Data["error"].(error))
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"source": "airbrake",
|
||||
"endpoint": airbrake.Endpoint,
|
||||
}).Info("Failed to send error to Airbrake")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// `Levels()` returns a slice of `Levels` the hook is fired for.
|
||||
func (hook *AirbrakeHook) Levels() []log.Level {
|
||||
return []log.Level{
|
||||
log.ErrorLevel,
|
||||
log.FatalLevel,
|
||||
log.PanicLevel,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Logrus comes with built-in hooks. Add those, or your custom hook, in `init`:
|
||||
|
||||
```go
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/Sirupsen/logrus/hooks/airbrake"
|
||||
"github.com/Sirupsen/logrus/hooks/syslog"
|
||||
"log/syslog"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.AddHook(new(logrus_airbrake.AirbrakeHook))
|
||||
|
||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||
if err != nil {
|
||||
log.Error("Unable to connect to local syslog daemon")
|
||||
} else {
|
||||
log.AddHook(hook)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go)
|
||||
Send errors to an exception tracking service compatible with the Airbrake API.
|
||||
Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes.
|
||||
|
||||
* [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go)
|
||||
Send errors to the Papertrail hosted logging service via UDP.
|
||||
|
||||
* [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go)
|
||||
Send errors to remote syslog server.
|
||||
Uses standard library `log/syslog` behind the scenes.
|
||||
|
||||
* [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus)
|
||||
Send errors to a channel in hipchat.
|
||||
|
||||
* [`github.com/sebest/logrusly`](https://github.com/sebest/logrusly)
|
||||
Send logs to Loggly (https://www.loggly.com/)
|
||||
|
||||
#### Level logging
|
||||
|
||||
Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
|
||||
|
||||
```go
|
||||
log.Debug("Useful debugging information.")
|
||||
log.Info("Something noteworthy happened!")
|
||||
log.Warn("You should probably take a look at this.")
|
||||
log.Error("Something failed but I'm not quitting.")
|
||||
// Calls os.Exit(1) after logging
|
||||
log.Fatal("Bye.")
|
||||
// Calls panic() after logging
|
||||
log.Panic("I'm bailing.")
|
||||
```
|
||||
|
||||
You can set the logging level on a `Logger`, then it will only log entries with
|
||||
that severity or anything above it:
|
||||
|
||||
```go
|
||||
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
||||
log.SetLevel(log.InfoLevel)
|
||||
```
|
||||
|
||||
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
|
||||
environment if your application has that.
|
||||
|
||||
#### Entries
|
||||
|
||||
Besides the fields added with `WithField` or `WithFields` some fields are
|
||||
automatically added to all logging events:
|
||||
|
||||
1. `time`. The timestamp when the entry was created.
|
||||
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
|
||||
the `AddFields` call. E.g. `Failed to send event.`
|
||||
3. `level`. The logging level. E.g. `info`.
|
||||
|
||||
#### Environments
|
||||
|
||||
Logrus has no notion of environment.
|
||||
|
||||
If you wish for hooks and formatters to only be used in specific environments,
|
||||
you should handle that yourself. For example, if your application has a global
|
||||
variable `Environment`, which is a string representation of the environment you
|
||||
could do:
|
||||
|
||||
```go
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
init() {
|
||||
// do something here to set environment depending on an environment variable
|
||||
// or command-line flag
|
||||
if Environment == "production" {
|
||||
log.SetFormatter(logrus.JSONFormatter)
|
||||
} else {
|
||||
// The TextFormatter is default, you don't actually have to do this.
|
||||
log.SetFormatter(logrus.TextFormatter)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This configuration is how `logrus` was intended to be used, but JSON in
|
||||
production is mostly only useful if you do log aggregation with tools like
|
||||
Splunk or Logstash.
|
||||
|
||||
#### Formatters
|
||||
|
||||
The built-in logging formatters are:
|
||||
|
||||
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
||||
without colors.
|
||||
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
||||
field to `true`. To force no colored output even if there is a TTY set the
|
||||
`DisableColors` field to `true`
|
||||
* `logrus.JSONFormatter`. Logs fields as JSON.
|
||||
|
||||
Third party logging formatters:
|
||||
|
||||
* [`zalgo`](https://github.com/aybabtme/logzalgo): invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
||||
|
||||
You can define your formatter by implementing the `Formatter` interface,
|
||||
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
||||
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
||||
default ones (see Entries section above):
|
||||
|
||||
```go
|
||||
type MyJSONFormatter struct {
|
||||
}
|
||||
|
||||
log.SetFormatter(new(MyJSONFormatter))
|
||||
|
||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
// Note this doesn't include Time, Level and Message which are available on
|
||||
// the Entry. Consult `godoc` on information about those fields or read the
|
||||
// source of the official loggers.
|
||||
serialized, err := json.Marshal(entry.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||
}
|
||||
return append(serialized, '\n'), nil
|
||||
}
|
||||
```
|
||||
|
||||
#### Rotation
|
||||
|
||||
Log rotation is not provided with Logrus. Log rotation should be done by an
|
||||
external program (like `logrotated(8)`) that can compress and delete old log
|
||||
entries. It should not be a feature of the application-level logger.
|
||||
|
||||
|
||||
[godoc]: https://godoc.org/github.com/Sirupsen/logrus
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// An entry is the final or intermediate Logrus logging entry. It contains all
|
||||
// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
|
||||
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
|
||||
// passed around as much as you wish to avoid field duplication.
|
||||
type Entry struct {
|
||||
Logger *Logger
|
||||
|
||||
// Contains all the fields set by the user.
|
||||
Data Fields
|
||||
|
||||
// Time at which the log entry was created
|
||||
Time time.Time
|
||||
|
||||
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
||||
Level Level
|
||||
|
||||
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
||||
Message string
|
||||
}
|
||||
|
||||
func NewEntry(logger *Logger) *Entry {
|
||||
return &Entry{
|
||||
Logger: logger,
|
||||
// Default is three fields, give a little extra room
|
||||
Data: make(Fields, 5),
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a reader for the entry, which is a proxy to the formatter.
|
||||
func (entry *Entry) Reader() (*bytes.Buffer, error) {
|
||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
||||
return bytes.NewBuffer(serialized), err
|
||||
}
|
||||
|
||||
// Returns the string representation from the reader and ultimately the
|
||||
// formatter.
|
||||
func (entry *Entry) String() (string, error) {
|
||||
reader, err := entry.Reader()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return reader.String(), err
|
||||
}
|
||||
|
||||
// Add a single field to the Entry.
|
||||
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
||||
return entry.WithFields(Fields{key: value})
|
||||
}
|
||||
|
||||
// Add a map of fields to the Entry.
|
||||
func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||
data := Fields{}
|
||||
for k, v := range entry.Data {
|
||||
data[k] = v
|
||||
}
|
||||
for k, v := range fields {
|
||||
data[k] = v
|
||||
}
|
||||
return &Entry{Logger: entry.Logger, Data: data}
|
||||
}
|
||||
|
||||
func (entry *Entry) log(level Level, msg string) {
|
||||
entry.Time = time.Now()
|
||||
entry.Level = level
|
||||
entry.Message = msg
|
||||
|
||||
if err := entry.Logger.Hooks.Fire(level, entry); err != nil {
|
||||
entry.Logger.mu.Lock()
|
||||
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||
entry.Logger.mu.Unlock()
|
||||
}
|
||||
|
||||
reader, err := entry.Reader()
|
||||
if err != nil {
|
||||
entry.Logger.mu.Lock()
|
||||
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
||||
entry.Logger.mu.Unlock()
|
||||
}
|
||||
|
||||
entry.Logger.mu.Lock()
|
||||
defer entry.Logger.mu.Unlock()
|
||||
|
||||
_, err = io.Copy(entry.Logger.Out, reader)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
||||
}
|
||||
|
||||
// To avoid Entry#log() returning a value that only would make sense for
|
||||
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
||||
// directly here.
|
||||
if level <= PanicLevel {
|
||||
panic(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Debug(args ...interface{}) {
|
||||
if entry.Logger.Level >= DebugLevel {
|
||||
entry.log(DebugLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Print(args ...interface{}) {
|
||||
entry.Info(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Info(args ...interface{}) {
|
||||
if entry.Logger.Level >= InfoLevel {
|
||||
entry.log(InfoLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Warn(args ...interface{}) {
|
||||
if entry.Logger.Level >= WarnLevel {
|
||||
entry.log(WarnLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Error(args ...interface{}) {
|
||||
if entry.Logger.Level >= ErrorLevel {
|
||||
entry.log(ErrorLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatal(args ...interface{}) {
|
||||
if entry.Logger.Level >= FatalLevel {
|
||||
entry.log(FatalLevel, fmt.Sprint(args...))
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panic(args ...interface{}) {
|
||||
if entry.Logger.Level >= PanicLevel {
|
||||
entry.log(PanicLevel, fmt.Sprint(args...))
|
||||
}
|
||||
panic(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
// Entry Printf family functions
|
||||
|
||||
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||
if entry.Logger.Level >= DebugLevel {
|
||||
entry.Debug(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Infof(format string, args ...interface{}) {
|
||||
if entry.Logger.Level >= InfoLevel {
|
||||
entry.Info(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Printf(format string, args ...interface{}) {
|
||||
entry.Infof(format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
||||
if entry.Logger.Level >= WarnLevel {
|
||||
entry.Warn(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
||||
entry.Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
||||
if entry.Logger.Level >= ErrorLevel {
|
||||
entry.Error(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
||||
if entry.Logger.Level >= FatalLevel {
|
||||
entry.Fatal(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||
if entry.Logger.Level >= PanicLevel {
|
||||
entry.Panic(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
// Entry Println family functions
|
||||
|
||||
func (entry *Entry) Debugln(args ...interface{}) {
|
||||
if entry.Logger.Level >= DebugLevel {
|
||||
entry.Debug(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Infoln(args ...interface{}) {
|
||||
if entry.Logger.Level >= InfoLevel {
|
||||
entry.Info(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Println(args ...interface{}) {
|
||||
entry.Infoln(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warnln(args ...interface{}) {
|
||||
if entry.Logger.Level >= WarnLevel {
|
||||
entry.Warn(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Warningln(args ...interface{}) {
|
||||
entry.Warnln(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Errorln(args ...interface{}) {
|
||||
if entry.Logger.Level >= ErrorLevel {
|
||||
entry.Error(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatalln(args ...interface{}) {
|
||||
if entry.Logger.Level >= FatalLevel {
|
||||
entry.Fatal(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Panicln(args ...interface{}) {
|
||||
if entry.Logger.Level >= PanicLevel {
|
||||
entry.Panic(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
// Sprintlnn => Sprint no newline. This is to get the behavior of how
|
||||
// fmt.Sprintln where spaces are always added between operands, regardless of
|
||||
// their type. Instead of vendoring the Sprintln implementation to spare a
|
||||
// string allocation, we do the simplest thing.
|
||||
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
||||
msg := fmt.Sprintln(args...)
|
||||
return msg[:len(msg)-1]
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
var log = logrus.New()
|
||||
|
||||
func init() {
|
||||
log.Formatter = new(logrus.JSONFormatter)
|
||||
log.Formatter = new(logrus.TextFormatter) // default
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"err": err,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
}
|
||||
}()
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "orca",
|
||||
"size": 9009,
|
||||
}).Panic("It's over 9000!")
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/Sirupsen/logrus/hooks/airbrake"
|
||||
"github.com/tobi/airbrake-go"
|
||||
)
|
||||
|
||||
var log = logrus.New()
|
||||
|
||||
func init() {
|
||||
log.Formatter = new(logrus.TextFormatter) // default
|
||||
log.Hooks.Add(new(logrus_airbrake.AirbrakeHook))
|
||||
}
|
||||
|
||||
func main() {
|
||||
airbrake.Endpoint = "https://exceptions.whatever.com/notifier_api/v2/notices.xml"
|
||||
airbrake.ApiKey = "whatever"
|
||||
airbrake.Environment = "production"
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
// std is the name of the standard logger in stdlib `log`
|
||||
std = New()
|
||||
)
|
||||
|
||||
// SetOutput sets the standard logger output.
|
||||
func SetOutput(out io.Writer) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.Out = out
|
||||
}
|
||||
|
||||
// SetFormatter sets the standard logger formatter.
|
||||
func SetFormatter(formatter Formatter) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.Formatter = formatter
|
||||
}
|
||||
|
||||
// SetLevel sets the standard logger level.
|
||||
func SetLevel(level Level) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.Level = level
|
||||
}
|
||||
|
||||
// GetLevel returns the standard logger level.
|
||||
func GetLevel() Level {
|
||||
return std.Level
|
||||
}
|
||||
|
||||
// AddHook adds a hook to the standard logger hooks.
|
||||
func AddHook(hook Hook) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.Hooks.Add(hook)
|
||||
}
|
||||
|
||||
// WithField creates an entry from the standard logger and adds a field to
|
||||
// it. If you want multiple fields, use `WithFields`.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithField(key string, value interface{}) *Entry {
|
||||
return std.WithField(key, value)
|
||||
}
|
||||
|
||||
// WithFields creates an entry from the standard logger and adds multiple
|
||||
// fields to it. This is simply a helper for `WithField`, invoking it
|
||||
// once for each field.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithFields(fields Fields) *Entry {
|
||||
return std.WithFields(fields)
|
||||
}
|
||||
|
||||
// Debug logs a message at level Debug on the standard logger.
|
||||
func Debug(args ...interface{}) {
|
||||
std.Debug(args...)
|
||||
}
|
||||
|
||||
// Print logs a message at level Info on the standard logger.
|
||||
func Print(args ...interface{}) {
|
||||
std.Print(args...)
|
||||
}
|
||||
|
||||
// Info logs a message at level Info on the standard logger.
|
||||
func Info(args ...interface{}) {
|
||||
std.Info(args...)
|
||||
}
|
||||
|
||||
// Warn logs a message at level Warn on the standard logger.
|
||||
func Warn(args ...interface{}) {
|
||||
std.Warn(args...)
|
||||
}
|
||||
|
||||
// Warning logs a message at level Warn on the standard logger.
|
||||
func Warning(args ...interface{}) {
|
||||
std.Warning(args...)
|
||||
}
|
||||
|
||||
// Error logs a message at level Error on the standard logger.
|
||||
func Error(args ...interface{}) {
|
||||
std.Error(args...)
|
||||
}
|
||||
|
||||
// Panic logs a message at level Panic on the standard logger.
|
||||
func Panic(args ...interface{}) {
|
||||
std.Panic(args...)
|
||||
}
|
||||
|
||||
// Fatal logs a message at level Fatal on the standard logger.
|
||||
func Fatal(args ...interface{}) {
|
||||
std.Fatal(args...)
|
||||
}
|
||||
|
||||
// Debugf logs a message at level Debug on the standard logger.
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
std.Debugf(format, args...)
|
||||
}
|
||||
|
||||
// Printf logs a message at level Info on the standard logger.
|
||||
func Printf(format string, args ...interface{}) {
|
||||
std.Printf(format, args...)
|
||||
}
|
||||
|
||||
// Infof logs a message at level Info on the standard logger.
|
||||
func Infof(format string, args ...interface{}) {
|
||||
std.Infof(format, args...)
|
||||
}
|
||||
|
||||
// Warnf logs a message at level Warn on the standard logger.
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
std.Warnf(format, args...)
|
||||
}
|
||||
|
||||
// Warningf logs a message at level Warn on the standard logger.
|
||||
func Warningf(format string, args ...interface{}) {
|
||||
std.Warningf(format, args...)
|
||||
}
|
||||
|
||||
// Errorf logs a message at level Error on the standard logger.
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
std.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// Panicf logs a message at level Panic on the standard logger.
|
||||
func Panicf(format string, args ...interface{}) {
|
||||
std.Panicf(format, args...)
|
||||
}
|
||||
|
||||
// Fatalf logs a message at level Fatal on the standard logger.
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
std.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
// Debugln logs a message at level Debug on the standard logger.
|
||||
func Debugln(args ...interface{}) {
|
||||
std.Debugln(args...)
|
||||
}
|
||||
|
||||
// Println logs a message at level Info on the standard logger.
|
||||
func Println(args ...interface{}) {
|
||||
std.Println(args...)
|
||||
}
|
||||
|
||||
// Infoln logs a message at level Info on the standard logger.
|
||||
func Infoln(args ...interface{}) {
|
||||
std.Infoln(args...)
|
||||
}
|
||||
|
||||
// Warnln logs a message at level Warn on the standard logger.
|
||||
func Warnln(args ...interface{}) {
|
||||
std.Warnln(args...)
|
||||
}
|
||||
|
||||
// Warningln logs a message at level Warn on the standard logger.
|
||||
func Warningln(args ...interface{}) {
|
||||
std.Warningln(args...)
|
||||
}
|
||||
|
||||
// Errorln logs a message at level Error on the standard logger.
|
||||
func Errorln(args ...interface{}) {
|
||||
std.Errorln(args...)
|
||||
}
|
||||
|
||||
// Panicln logs a message at level Panic on the standard logger.
|
||||
func Panicln(args ...interface{}) {
|
||||
std.Panicln(args...)
|
||||
}
|
||||
|
||||
// Fatalln logs a message at level Fatal on the standard logger.
|
||||
func Fatalln(args ...interface{}) {
|
||||
std.Fatalln(args...)
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package logrus
|
||||
|
||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||
// `Entry`. It exposes all the fields, including the default ones:
|
||||
//
|
||||
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
||||
// * `entry.Data["time"]`. The timestamp.
|
||||
// * `entry.Data["level"]. The level the entry was logged at.
|
||||
//
|
||||
// Any additional fields added with `WithField` or `WithFields` are also in
|
||||
// `entry.Data`. Format is expected to return an array of bytes which are then
|
||||
// logged to `logger.Out`.
|
||||
type Formatter interface {
|
||||
Format(*Entry) ([]byte, error)
|
||||
}
|
||||
|
||||
// This is to not silently overwrite `time`, `msg` and `level` fields when
|
||||
// dumping it. If this code wasn't there doing:
|
||||
//
|
||||
// logrus.WithField("level", 1).Info("hello")
|
||||
//
|
||||
// Would just silently drop the user provided level. Instead with this code
|
||||
// it'll logged as:
|
||||
//
|
||||
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
||||
//
|
||||
// It's not exported because it's still using Data in an opinionated way. It's to
|
||||
// avoid code duplication between the two default formatters.
|
||||
func prefixFieldClashes(data Fields) {
|
||||
_, ok := data["time"]
|
||||
if ok {
|
||||
data["fields.time"] = data["time"]
|
||||
}
|
||||
|
||||
_, ok = data["msg"]
|
||||
if ok {
|
||||
data["fields.msg"] = data["msg"]
|
||||
}
|
||||
|
||||
_, ok = data["level"]
|
||||
if ok {
|
||||
data["fields.level"] = data["level"]
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package logrus
|
||||
|
||||
// A hook to be fired when logging on the logging levels returned from
|
||||
// `Levels()` on your implementation of the interface. Note that this is not
|
||||
// fired in a goroutine or a channel with workers, you should handle such
|
||||
// functionality yourself if your call is non-blocking and you don't wish for
|
||||
// the logging calls for levels returned from `Levels()` to block.
|
||||
type Hook interface {
|
||||
Levels() []Level
|
||||
Fire(*Entry) error
|
||||
}
|
||||
|
||||
// Internal type for storing the hooks on a logger instance.
|
||||
type levelHooks map[Level][]Hook
|
||||
|
||||
// Add a hook to an instance of logger. This is called with
|
||||
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
|
||||
func (hooks levelHooks) Add(hook Hook) {
|
||||
for _, level := range hook.Levels() {
|
||||
hooks[level] = append(hooks[level], hook)
|
||||
}
|
||||
}
|
||||
|
||||
// Fire all the hooks for the passed level. Used by `entry.log` to fire
|
||||
// appropriate hooks for a log entry.
|
||||
func (hooks levelHooks) Fire(level Level, entry *Entry) error {
|
||||
for _, hook := range hooks[level] {
|
||||
if err := hook.Fire(entry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package logrus_airbrake
|
||||
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/tobi/airbrake-go"
|
||||
)
|
||||
|
||||
// AirbrakeHook to send exceptions to an exception-tracking service compatible
|
||||
// with the Airbrake API. You must set:
|
||||
// * airbrake.Endpoint
|
||||
// * airbrake.ApiKey
|
||||
// * airbrake.Environment (only sends exceptions when set to "production")
|
||||
//
|
||||
// Before using this hook, to send an error. Entries that trigger an Error,
|
||||
// Fatal or Panic should now include an "error" field to send to Airbrake.
|
||||
type AirbrakeHook struct{}
|
||||
|
||||
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
|
||||
if entry.Data["error"] == nil {
|
||||
entry.Logger.WithFields(logrus.Fields{
|
||||
"source": "airbrake",
|
||||
"endpoint": airbrake.Endpoint,
|
||||
}).Warn("Exceptions sent to Airbrake must have an 'error' key with the error")
|
||||
return nil
|
||||
}
|
||||
|
||||
err, ok := entry.Data["error"].(error)
|
||||
if !ok {
|
||||
entry.Logger.WithFields(logrus.Fields{
|
||||
"source": "airbrake",
|
||||
"endpoint": airbrake.Endpoint,
|
||||
}).Warn("Exceptions sent to Airbrake must have an `error` key of type `error`")
|
||||
return nil
|
||||
}
|
||||
|
||||
airErr := airbrake.Notify(err)
|
||||
if airErr != nil {
|
||||
entry.Logger.WithFields(logrus.Fields{
|
||||
"source": "airbrake",
|
||||
"endpoint": airbrake.Endpoint,
|
||||
"error": airErr,
|
||||
}).Warn("Failed to send error to Airbrake")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hook *AirbrakeHook) Levels() []logrus.Level {
|
||||
return []logrus.Level{
|
||||
logrus.ErrorLevel,
|
||||
logrus.FatalLevel,
|
||||
logrus.PanicLevel,
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# Papertrail Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
|
||||
|
||||
[Papertrail](https://papertrailapp.com) provides hosted log management. Once stored in Papertrail, you can [group](http://help.papertrailapp.com/kb/how-it-works/groups/) your logs on various dimensions, [search](http://help.papertrailapp.com/kb/how-it-works/search-syntax) them, and trigger [alerts](http://help.papertrailapp.com/kb/how-it-works/alerts).
|
||||
|
||||
In most deployments, you'll want to send logs to Papertrail via their [remote_syslog](http://help.papertrailapp.com/kb/configuration/configuring-centralized-logging-from-text-log-files-in-unix/) daemon, which requires no application-specific configuration. This hook is intended for relatively low-volume logging, likely in managed cloud hosting deployments where installing `remote_syslog` is not possible.
|
||||
|
||||
## Usage
|
||||
|
||||
You can find your Papertrail UDP port on your [Papertrail account page](https://papertrailapp.com/account/destinations). Substitute it below for `YOUR_PAPERTRAIL_UDP_PORT`.
|
||||
|
||||
For `YOUR_APP_NAME`, substitute a short string that will readily identify your application or service in the logs.
|
||||
|
||||
```go
|
||||
import (
|
||||
"log/syslog"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/Sirupsen/logrus/hooks/papertrail"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log := logrus.New()
|
||||
hook, err := logrus_papertrail.NewPapertrailHook("logs.papertrailapp.com", YOUR_PAPERTRAIL_UDP_PORT, YOUR_APP_NAME)
|
||||
|
||||
if err == nil {
|
||||
log.Hooks.Add(hook)
|
||||
}
|
||||
}
|
||||
```
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package logrus_papertrail
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
format = "Jan 2 15:04:05"
|
||||
)
|
||||
|
||||
// PapertrailHook to send logs to a logging service compatible with the Papertrail API.
|
||||
type PapertrailHook struct {
|
||||
Host string
|
||||
Port int
|
||||
AppName string
|
||||
UDPConn net.Conn
|
||||
}
|
||||
|
||||
// NewPapertrailHook creates a hook to be added to an instance of logger.
|
||||
func NewPapertrailHook(host string, port int, appName string) (*PapertrailHook, error) {
|
||||
conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", host, port))
|
||||
return &PapertrailHook{host, port, appName, conn}, err
|
||||
}
|
||||
|
||||
// Fire is called when a log event is fired.
|
||||
func (hook *PapertrailHook) Fire(entry *logrus.Entry) error {
|
||||
date := time.Now().Format(format)
|
||||
payload := fmt.Sprintf("<22> %s %s: [%s] %s", date, hook.AppName, entry.Level, entry.Message)
|
||||
|
||||
bytesWritten, err := hook.UDPConn.Write([]byte(payload))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to send log line to Papertrail via UDP. Wrote %d bytes before error: %v", bytesWritten, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Levels returns the available logging levels.
|
||||
func (hook *PapertrailHook) Levels() []logrus.Level {
|
||||
return []logrus.Level{
|
||||
logrus.PanicLevel,
|
||||
logrus.FatalLevel,
|
||||
logrus.ErrorLevel,
|
||||
logrus.WarnLevel,
|
||||
logrus.InfoLevel,
|
||||
logrus.DebugLevel,
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
# Sentry Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
|
||||
|
||||
[Sentry](https://getsentry.com) provides both self-hosted and hosted
|
||||
solutions for exception tracking.
|
||||
Both client and server are
|
||||
[open source](https://github.com/getsentry/sentry).
|
||||
|
||||
## Usage
|
||||
|
||||
Every sentry application defined on the server gets a different
|
||||
[DSN](https://www.getsentry.com/docs/). In the example below replace
|
||||
`YOUR_DSN` with the one created for your application.
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/Sirupsen/logrus/hooks/sentry"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log := logrus.New()
|
||||
hook, err := logrus_sentry.NewSentryHook(YOUR_DSN, []logrus.Level{
|
||||
logrus.PanicLevel,
|
||||
logrus.FatalLevel,
|
||||
logrus.ErrorLevel,
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
log.Hooks.Add(hook)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Special fields
|
||||
|
||||
Some logrus fields have a special meaning in this hook,
|
||||
these are server_name and logger.
|
||||
When logs are sent to sentry these fields are treated differently.
|
||||
- server_name (also known as hostname) is the name of the server which
|
||||
is logging the event (hostname.example.com)
|
||||
- logger is the part of the application which is logging the event.
|
||||
In go this usually means setting it to the name of the package.
|
||||
|
||||
## Timeout
|
||||
|
||||
`Timeout` is the time the sentry hook will wait for a response
|
||||
from the sentry server.
|
||||
|
||||
If this time elapses with no response from
|
||||
the server an error will be returned.
|
||||
|
||||
If `Timeout` is set to 0 the SentryHook will not wait for a reply
|
||||
and will assume a correct delivery.
|
||||
|
||||
The SentryHook has a default timeout of `100 milliseconds` when created
|
||||
with a call to `NewSentryHook`. This can be changed by assigning a value to the `Timeout` field:
|
||||
|
||||
```go
|
||||
hook, _ := logrus_sentry.NewSentryHook(...)
|
||||
hook.Timeout = 20*time.Seconds
|
||||
```
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package logrus_sentry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/getsentry/raven-go"
|
||||
)
|
||||
|
||||
var (
|
||||
severityMap = map[logrus.Level]raven.Severity{
|
||||
logrus.DebugLevel: raven.DEBUG,
|
||||
logrus.InfoLevel: raven.INFO,
|
||||
logrus.WarnLevel: raven.WARNING,
|
||||
logrus.ErrorLevel: raven.ERROR,
|
||||
logrus.FatalLevel: raven.FATAL,
|
||||
logrus.PanicLevel: raven.FATAL,
|
||||
}
|
||||
)
|
||||
|
||||
func getAndDel(d logrus.Fields, key string) (string, bool) {
|
||||
var (
|
||||
ok bool
|
||||
v interface{}
|
||||
val string
|
||||
)
|
||||
if v, ok = d[key]; !ok {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if val, ok = v.(string); !ok {
|
||||
return "", false
|
||||
}
|
||||
delete(d, key)
|
||||
return val, true
|
||||
}
|
||||
|
||||
// SentryHook delivers logs to a sentry server.
|
||||
type SentryHook struct {
|
||||
// Timeout sets the time to wait for a delivery error from the sentry server.
|
||||
// If this is set to zero the server will not wait for any response and will
|
||||
// consider the message correctly sent
|
||||
Timeout time.Duration
|
||||
|
||||
client *raven.Client
|
||||
levels []logrus.Level
|
||||
}
|
||||
|
||||
// NewSentryHook creates a hook to be added to an instance of logger
|
||||
// and initializes the raven client.
|
||||
// This method sets the timeout to 100 milliseconds.
|
||||
func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
|
||||
client, err := raven.NewClient(DSN, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SentryHook{100 * time.Millisecond, client, levels}, nil
|
||||
}
|
||||
|
||||
// Called when an event should be sent to sentry
|
||||
// Special fields that sentry uses to give more information to the server
|
||||
// are extracted from entry.Data (if they are found)
|
||||
// These fields are: logger and server_name
|
||||
func (hook *SentryHook) Fire(entry *logrus.Entry) error {
|
||||
packet := &raven.Packet{
|
||||
Message: entry.Message,
|
||||
Timestamp: raven.Timestamp(entry.Time),
|
||||
Level: severityMap[entry.Level],
|
||||
Platform: "go",
|
||||
}
|
||||
|
||||
d := entry.Data
|
||||
|
||||
if logger, ok := getAndDel(d, "logger"); ok {
|
||||
packet.Logger = logger
|
||||
}
|
||||
if serverName, ok := getAndDel(d, "server_name"); ok {
|
||||
packet.ServerName = serverName
|
||||
}
|
||||
packet.Extra = map[string]interface{}(d)
|
||||
|
||||
_, errCh := hook.client.Capture(packet, nil)
|
||||
timeout := hook.Timeout
|
||||
if timeout != 0 {
|
||||
timeoutCh := time.After(timeout)
|
||||
select {
|
||||
case err := <-errCh:
|
||||
return err
|
||||
case <-timeoutCh:
|
||||
return fmt.Errorf("no response from sentry server in %s", timeout)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Levels returns the available logging levels.
|
||||
func (hook *SentryHook) Levels() []logrus.Level {
|
||||
return hook.levels
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
# Syslog Hooks for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
import (
|
||||
"log/syslog"
|
||||
"github.com/Sirupsen/logrus"
|
||||
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log := logrus.New()
|
||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||
|
||||
if err == nil {
|
||||
log.Hooks.Add(hook)
|
||||
}
|
||||
}
|
||||
```
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package logrus_syslog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"log/syslog"
|
||||
"os"
|
||||
)
|
||||
|
||||
// SyslogHook to send logs via syslog.
|
||||
type SyslogHook struct {
|
||||
Writer *syslog.Writer
|
||||
SyslogNetwork string
|
||||
SyslogRaddr string
|
||||
}
|
||||
|
||||
// Creates a hook to be added to an instance of logger. This is called with
|
||||
// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")`
|
||||
// `if err == nil { log.Hooks.Add(hook) }`
|
||||
func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) {
|
||||
w, err := syslog.Dial(network, raddr, priority, tag)
|
||||
return &SyslogHook{w, network, raddr}, err
|
||||
}
|
||||
|
||||
func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
|
||||
line, err := entry.String()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
switch entry.Level {
|
||||
case logrus.PanicLevel:
|
||||
return hook.Writer.Crit(line)
|
||||
case logrus.FatalLevel:
|
||||
return hook.Writer.Crit(line)
|
||||
case logrus.ErrorLevel:
|
||||
return hook.Writer.Err(line)
|
||||
case logrus.WarnLevel:
|
||||
return hook.Writer.Warning(line)
|
||||
case logrus.InfoLevel:
|
||||
return hook.Writer.Info(line)
|
||||
case logrus.DebugLevel:
|
||||
return hook.Writer.Debug(line)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (hook *SyslogHook) Levels() []logrus.Level {
|
||||
return []logrus.Level{
|
||||
logrus.PanicLevel,
|
||||
logrus.FatalLevel,
|
||||
logrus.ErrorLevel,
|
||||
logrus.WarnLevel,
|
||||
logrus.InfoLevel,
|
||||
logrus.DebugLevel,
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type JSONFormatter struct{}
|
||||
|
||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
data := make(Fields, len(entry.Data)+3)
|
||||
for k, v := range entry.Data {
|
||||
data[k] = v
|
||||
}
|
||||
prefixFieldClashes(data)
|
||||
data["time"] = entry.Time.Format(time.RFC3339)
|
||||
data["msg"] = entry.Message
|
||||
data["level"] = entry.Level.String()
|
||||
|
||||
serialized, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||
}
|
||||
return append(serialized, '\n'), nil
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Logger struct {
|
||||
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
||||
// file, or leave it default which is `os.Stdout`. You can also set this to
|
||||
// something more adventorous, such as logging to Kafka.
|
||||
Out io.Writer
|
||||
// Hooks for the logger instance. These allow firing events based on logging
|
||||
// levels and log entries. For example, to send errors to an error tracking
|
||||
// service, log to StatsD or dump the core on fatal errors.
|
||||
Hooks levelHooks
|
||||
// All log entries pass through the formatter before logged to Out. The
|
||||
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
||||
// TextFormatter is the default. In development (when a TTY is attached) it
|
||||
// logs with colors, but to a file it wouldn't. You can easily implement your
|
||||
// own that implements the `Formatter` interface, see the `README` or included
|
||||
// formatters for examples.
|
||||
Formatter Formatter
|
||||
// The logging level the logger should log at. This is typically (and defaults
|
||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||
// logged. `logrus.Debug` is useful in
|
||||
Level Level
|
||||
// Used to sync writing to the log.
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// Creates a new logger. Configuration should be set by changing `Formatter`,
|
||||
// `Out` and `Hooks` directly on the default logger instance. You can also just
|
||||
// instantiate your own:
|
||||
//
|
||||
// var log = &Logger{
|
||||
// Out: os.Stderr,
|
||||
// Formatter: new(JSONFormatter),
|
||||
// Hooks: make(levelHooks),
|
||||
// Level: logrus.DebugLevel,
|
||||
// }
|
||||
//
|
||||
// It's recommended to make this a global instance called `log`.
|
||||
func New() *Logger {
|
||||
return &Logger{
|
||||
Out: os.Stdout,
|
||||
Formatter: new(TextFormatter),
|
||||
Hooks: make(levelHooks),
|
||||
Level: InfoLevel,
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a field to the log entry, note that you it doesn't log until you call
|
||||
// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
|
||||
// Ff you want multiple fields, use `WithFields`.
|
||||
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
||||
return NewEntry(logger).WithField(key, value)
|
||||
}
|
||||
|
||||
// Adds a struct of fields to the log entry. All it does is call `WithField` for
|
||||
// each `Field`.
|
||||
func (logger *Logger) WithFields(fields Fields) *Entry {
|
||||
return NewEntry(logger).WithFields(fields)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||
NewEntry(logger).Debugf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||
NewEntry(logger).Infof(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Printf(format string, args ...interface{}) {
|
||||
NewEntry(logger).Printf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||
NewEntry(logger).Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||
NewEntry(logger).Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||
NewEntry(logger).Errorf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||
NewEntry(logger).Fatalf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||
NewEntry(logger).Panicf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debug(args ...interface{}) {
|
||||
NewEntry(logger).Debug(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Info(args ...interface{}) {
|
||||
NewEntry(logger).Info(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Print(args ...interface{}) {
|
||||
NewEntry(logger).Info(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warn(args ...interface{}) {
|
||||
NewEntry(logger).Warn(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warning(args ...interface{}) {
|
||||
NewEntry(logger).Warn(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Error(args ...interface{}) {
|
||||
NewEntry(logger).Error(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatal(args ...interface{}) {
|
||||
NewEntry(logger).Fatal(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panic(args ...interface{}) {
|
||||
NewEntry(logger).Panic(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugln(args ...interface{}) {
|
||||
NewEntry(logger).Debugln(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Infoln(args ...interface{}) {
|
||||
NewEntry(logger).Infoln(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Println(args ...interface{}) {
|
||||
NewEntry(logger).Println(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warnln(args ...interface{}) {
|
||||
NewEntry(logger).Warnln(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningln(args ...interface{}) {
|
||||
NewEntry(logger).Warnln(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorln(args ...interface{}) {
|
||||
NewEntry(logger).Errorln(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||
NewEntry(logger).Fatalln(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicln(args ...interface{}) {
|
||||
NewEntry(logger).Panicln(args...)
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Fields type, used to pass to `WithFields`.
|
||||
type Fields map[string]interface{}
|
||||
|
||||
// Level type
|
||||
type Level uint8
|
||||
|
||||
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
||||
func (level Level) String() string {
|
||||
switch level {
|
||||
case DebugLevel:
|
||||
return "debug"
|
||||
case InfoLevel:
|
||||
return "info"
|
||||
case WarnLevel:
|
||||
return "warning"
|
||||
case ErrorLevel:
|
||||
return "error"
|
||||
case FatalLevel:
|
||||
return "fatal"
|
||||
case PanicLevel:
|
||||
return "panic"
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// ParseLevel takes a string level and returns the Logrus log level constant.
|
||||
func ParseLevel(lvl string) (Level, error) {
|
||||
switch lvl {
|
||||
case "panic":
|
||||
return PanicLevel, nil
|
||||
case "fatal":
|
||||
return FatalLevel, nil
|
||||
case "error":
|
||||
return ErrorLevel, nil
|
||||
case "warn", "warning":
|
||||
return WarnLevel, nil
|
||||
case "info":
|
||||
return InfoLevel, nil
|
||||
case "debug":
|
||||
return DebugLevel, nil
|
||||
}
|
||||
|
||||
var l Level
|
||||
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
||||
}
|
||||
|
||||
// These are the different logging levels. You can set the logging level to log
|
||||
// on your instance of logger, obtained with `logrus.New()`.
|
||||
const (
|
||||
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
||||
// message passed to Debug, Info, ...
|
||||
PanicLevel Level = iota
|
||||
// FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
|
||||
// logging level is set to Panic.
|
||||
FatalLevel
|
||||
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
||||
// Commonly used for hooks to send errors to an error tracking service.
|
||||
ErrorLevel
|
||||
// WarnLevel level. Non-critical entries that deserve eyes.
|
||||
WarnLevel
|
||||
// InfoLevel level. General operational entries about what's going on inside the
|
||||
// application.
|
||||
InfoLevel
|
||||
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
||||
DebugLevel
|
||||
)
|
||||
|
||||
// Won't compile if StdLogger can't be realized by a log.Logger
|
||||
var _ StdLogger = &log.Logger{}
|
||||
|
||||
// StdLogger is what your logrus-enabled library should take, that way
|
||||
// it'll accept a stdlib logger and a logrus logger. There's no standard
|
||||
// interface, this is the closest we get, unfortunately.
|
||||
type StdLogger interface {
|
||||
Print(...interface{})
|
||||
Printf(string, ...interface{})
|
||||
Println(...interface{})
|
||||
|
||||
Fatal(...interface{})
|
||||
Fatalf(string, ...interface{})
|
||||
Fatalln(...interface{})
|
||||
|
||||
Panic(...interface{})
|
||||
Panicf(string, ...interface{})
|
||||
Panicln(...interface{})
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Based on ssh/terminal:
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package logrus
|
||||
|
||||
import "syscall"
|
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA
|
||||
|
||||
type Termios syscall.Termios
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin.
|
||||
*/
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA
|
||||
|
||||
type Termios struct {
|
||||
Iflag uint32
|
||||
Oflag uint32
|
||||
Cflag uint32
|
||||
Lflag uint32
|
||||
Cc [20]uint8
|
||||
Ispeed uint32
|
||||
Ospeed uint32
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Based on ssh/terminal:
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package logrus
|
||||
|
||||
import "syscall"
|
||||
|
||||
const ioctlReadTermios = syscall.TCGETS
|
||||
|
||||
type Termios syscall.Termios
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// Based on ssh/terminal:
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux,!appengine darwin freebsd
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal() bool {
|
||||
fd := syscall.Stdout
|
||||
var termios Termios
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||
return err == 0
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// Based on ssh/terminal:
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build windows
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
|
||||
var (
|
||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||
)
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal() bool {
|
||||
fd := syscall.Stdout
|
||||
var st uint32
|
||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||
return r != 0 && e == 0
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
nocolor = 0
|
||||
red = 31
|
||||
green = 32
|
||||
yellow = 33
|
||||
blue = 34
|
||||
)
|
||||
|
||||
var (
|
||||
baseTimestamp time.Time
|
||||
isTerminal bool
|
||||
noQuoteNeeded *regexp.Regexp
|
||||
)
|
||||
|
||||
func init() {
|
||||
baseTimestamp = time.Now()
|
||||
isTerminal = IsTerminal()
|
||||
}
|
||||
|
||||
func miniTS() int {
|
||||
return int(time.Since(baseTimestamp) / time.Second)
|
||||
}
|
||||
|
||||
type TextFormatter struct {
|
||||
// Set to true to bypass checking for a TTY before outputting colors.
|
||||
ForceColors bool
|
||||
DisableColors bool
|
||||
// Set to true to disable timestamp logging (useful when the output
|
||||
// is redirected to a logging system already adding a timestamp)
|
||||
DisableTimestamp bool
|
||||
}
|
||||
|
||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
|
||||
var keys []string
|
||||
for k := range entry.Data {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
|
||||
prefixFieldClashes(entry.Data)
|
||||
|
||||
isColored := (f.ForceColors || isTerminal) && !f.DisableColors
|
||||
|
||||
if isColored {
|
||||
printColored(b, entry, keys)
|
||||
} else {
|
||||
if !f.DisableTimestamp {
|
||||
f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339))
|
||||
}
|
||||
f.appendKeyValue(b, "level", entry.Level.String())
|
||||
f.appendKeyValue(b, "msg", entry.Message)
|
||||
for _, key := range keys {
|
||||
f.appendKeyValue(b, key, entry.Data[key])
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func printColored(b *bytes.Buffer, entry *Entry, keys []string) {
|
||||
var levelColor int
|
||||
switch entry.Level {
|
||||
case WarnLevel:
|
||||
levelColor = yellow
|
||||
case ErrorLevel, FatalLevel, PanicLevel:
|
||||
levelColor = red
|
||||
default:
|
||||
levelColor = blue
|
||||
}
|
||||
|
||||
levelText := strings.ToUpper(entry.Level.String())[0:4]
|
||||
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
|
||||
for _, k := range keys {
|
||||
v := entry.Data[k]
|
||||
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func needsQuoting(text string) bool {
|
||||
for _, ch := range text {
|
||||
if !((ch >= 'a' && ch <= 'z') ||
|
||||
(ch >= 'A' && ch <= 'Z') ||
|
||||
(ch >= '0' && ch < '9') ||
|
||||
ch == '-' || ch == '.') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key, value interface{}) {
|
||||
switch value.(type) {
|
||||
case string:
|
||||
if needsQuoting(value.(string)) {
|
||||
fmt.Fprintf(b, "%v=%s ", key, value)
|
||||
} else {
|
||||
fmt.Fprintf(b, "%v=%q ", key, value)
|
||||
}
|
||||
case error:
|
||||
if needsQuoting(value.(error).Error()) {
|
||||
fmt.Fprintf(b, "%v=%s ", key, value)
|
||||
} else {
|
||||
fmt.Fprintf(b, "%v=%q ", key, value)
|
||||
}
|
||||
default:
|
||||
fmt.Fprintf(b, "%v=%v ", key, value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
*~
|
||||
*.a
|
||||
*.6
|
||||
*.out
|
||||
_testmain.go
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
include $(GOROOT)/src/Make.inc
|
||||
|
||||
TARG=auth_digest
|
||||
GOFILES=\
|
||||
auth.go\
|
||||
digest.go\
|
||||
basic.go\
|
||||
misc.go\
|
||||
md5crypt.go\
|
||||
users.go\
|
||||
|
||||
include $(GOROOT)/src/Make.pkg
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
HTTP Authentication implementation in Go
|
||||
========================================
|
||||
|
||||
This is an implementation of HTTP Basic and HTTP Digest authentication
|
||||
in Go language. It is designed as a simple wrapper for
|
||||
http.RequestHandler functions.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Supports HTTP Basic and HTTP Digest authentication.
|
||||
* Supports htpasswd and htdigest formatted files.
|
||||
* Automatic reloading of password files.
|
||||
* Pluggable interface for user/password storage.
|
||||
* Supports MD5 and SHA1 for Basic authentication password storage.
|
||||
* Configurable Digest nonce cache size with expiration.
|
||||
* Wrapper for legacy http handlers (http.HandlerFunc interface)
|
||||
|
||||
Example usage
|
||||
-------------
|
||||
|
||||
This is a complete working example for Basic auth:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
auth "github.com/abbot/go-http-auth"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Secret(user, realm string) string {
|
||||
if user == "john" {
|
||||
// password is "hello"
|
||||
return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func handle(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
|
||||
fmt.Fprintf(w, "<html><body><h1>Hello, %s!</h1></body></html>", r.Username)
|
||||
}
|
||||
|
||||
func main() {
|
||||
authenticator := auth.NewBasicAuthenticator("example.com", Secret)
|
||||
http.HandleFunc("/", authenticator.Wrap(handle))
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
|
||||
See more examples in the "examples" directory.
|
||||
|
||||
Legal
|
||||
-----
|
||||
|
||||
This module is developed under Apache 2.0 license, and can be used for
|
||||
open and proprietary projects.
|
||||
|
||||
Copyright 2012-2013 Lev Shamardin
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you
|
||||
may not use this file or any other part of this project except in
|
||||
compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package auth
|
||||
|
||||
import "net/http"
|
||||
|
||||
/*
|
||||
Request handlers must take AuthenticatedRequest instead of http.Request
|
||||
*/
|
||||
type AuthenticatedRequest struct {
|
||||
http.Request
|
||||
/*
|
||||
Authenticated user name. Current API implies that Username is
|
||||
never empty, which means that authentication is always done
|
||||
before calling the request handler.
|
||||
*/
|
||||
Username string
|
||||
}
|
||||
|
||||
/*
|
||||
AuthenticatedHandlerFunc is like http.HandlerFunc, but takes
|
||||
AuthenticatedRequest instead of http.Request
|
||||
*/
|
||||
type AuthenticatedHandlerFunc func(http.ResponseWriter, *AuthenticatedRequest)
|
||||
|
||||
/*
|
||||
Authenticator wraps an AuthenticatedHandlerFunc with
|
||||
authentication-checking code.
|
||||
|
||||
Typical Authenticator usage is something like:
|
||||
|
||||
authenticator := SomeAuthenticator(...)
|
||||
http.HandleFunc("/", authenticator(my_handler))
|
||||
|
||||
Authenticator wrapper checks the user authentication and calls the
|
||||
wrapped function only after authentication has succeeded. Otherwise,
|
||||
it returns a handler which initiates the authentication procedure.
|
||||
*/
|
||||
type Authenticator func(AuthenticatedHandlerFunc) http.HandlerFunc
|
||||
|
||||
type AuthenticatorInterface interface {
|
||||
Wrap(AuthenticatedHandlerFunc) http.HandlerFunc
|
||||
}
|
||||
|
||||
func JustCheck(auth AuthenticatorInterface, wrapped http.HandlerFunc) http.HandlerFunc {
|
||||
return auth.Wrap(func(w http.ResponseWriter, ar *AuthenticatedRequest) {
|
||||
ar.Header.Set("X-Authenticated-Username", ar.Username)
|
||||
wrapped(w, &ar.Request)
|
||||
})
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type BasicAuth struct {
|
||||
Realm string
|
||||
Secrets SecretProvider
|
||||
}
|
||||
|
||||
/*
|
||||
Checks the username/password combination from the request. Returns
|
||||
either an empty string (authentication failed) or the name of the
|
||||
authenticated user.
|
||||
|
||||
Supports MD5 and SHA1 password entries
|
||||
*/
|
||||
func (a *BasicAuth) CheckAuth(r *http.Request) string {
|
||||
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
|
||||
if len(s) != 2 || s[0] != "Basic" {
|
||||
return ""
|
||||
}
|
||||
|
||||
b, err := base64.StdEncoding.DecodeString(s[1])
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
pair := strings.SplitN(string(b), ":", 2)
|
||||
if len(pair) != 2 {
|
||||
return ""
|
||||
}
|
||||
passwd := a.Secrets(pair[0], a.Realm)
|
||||
if passwd == "" {
|
||||
return ""
|
||||
}
|
||||
if strings.HasPrefix(passwd, "{SHA}") {
|
||||
d := sha1.New()
|
||||
d.Write([]byte(pair[1]))
|
||||
if passwd[5:] != base64.StdEncoding.EncodeToString(d.Sum(nil)) {
|
||||
return ""
|
||||
}
|
||||
} else {
|
||||
e := NewMD5Entry(passwd)
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
if passwd != string(MD5Crypt([]byte(pair[1]), e.Salt, e.Magic)) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return pair[0]
|
||||
}
|
||||
|
||||
/*
|
||||
http.Handler for BasicAuth which initiates the authentication process
|
||||
(or requires reauthentication).
|
||||
*/
|
||||
func (a *BasicAuth) RequireAuth(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="`+a.Realm+`"`)
|
||||
w.WriteHeader(401)
|
||||
w.Write([]byte("401 Unauthorized\n"))
|
||||
}
|
||||
|
||||
/*
|
||||
BasicAuthenticator returns a function, which wraps an
|
||||
AuthenticatedHandlerFunc converting it to http.HandlerFunc. This
|
||||
wrapper function checks the authentication and either sends back
|
||||
required authentication headers, or calls the wrapped function with
|
||||
authenticated username in the AuthenticatedRequest.
|
||||
*/
|
||||
func (a *BasicAuth) Wrap(wrapped AuthenticatedHandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if username := a.CheckAuth(r); username == "" {
|
||||
a.RequireAuth(w, r)
|
||||
} else {
|
||||
ar := &AuthenticatedRequest{Request: *r, Username: username}
|
||||
wrapped(w, ar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewBasicAuthenticator(realm string, secrets SecretProvider) *BasicAuth {
|
||||
return &BasicAuth{Realm: realm, Secrets: secrets}
|
||||
}
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type digest_client struct {
|
||||
nc uint64
|
||||
last_seen int64
|
||||
}
|
||||
|
||||
type DigestAuth struct {
|
||||
Realm string
|
||||
Opaque string
|
||||
Secrets SecretProvider
|
||||
PlainTextSecrets bool
|
||||
|
||||
/*
|
||||
Approximate size of Client's Cache. When actual number of
|
||||
tracked client nonces exceeds
|
||||
ClientCacheSize+ClientCacheTolerance, ClientCacheTolerance*2
|
||||
older entries are purged.
|
||||
*/
|
||||
ClientCacheSize int
|
||||
ClientCacheTolerance int
|
||||
|
||||
clients map[string]*digest_client
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
type digest_cache_entry struct {
|
||||
nonce string
|
||||
last_seen int64
|
||||
}
|
||||
|
||||
type digest_cache []digest_cache_entry
|
||||
|
||||
func (c digest_cache) Less(i, j int) bool {
|
||||
return c[i].last_seen < c[j].last_seen
|
||||
}
|
||||
|
||||
func (c digest_cache) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
func (c digest_cache) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
|
||||
/*
|
||||
Remove count oldest entries from DigestAuth.clients
|
||||
*/
|
||||
func (a *DigestAuth) Purge(count int) {
|
||||
entries := make([]digest_cache_entry, 0, len(a.clients))
|
||||
for nonce, client := range a.clients {
|
||||
entries = append(entries, digest_cache_entry{nonce, client.last_seen})
|
||||
}
|
||||
cache := digest_cache(entries)
|
||||
sort.Sort(cache)
|
||||
for _, client := range cache[:count] {
|
||||
delete(a.clients, client.nonce)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
http.Handler for DigestAuth which initiates the authentication process
|
||||
(or requires reauthentication).
|
||||
*/
|
||||
func (a *DigestAuth) RequireAuth(w http.ResponseWriter, r *http.Request) {
|
||||
if len(a.clients) > a.ClientCacheSize+a.ClientCacheTolerance {
|
||||
a.Purge(a.ClientCacheTolerance * 2)
|
||||
}
|
||||
nonce := RandomKey()
|
||||
a.clients[nonce] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
|
||||
w.Header().Set("WWW-Authenticate",
|
||||
fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm="MD5", qop="auth"`,
|
||||
a.Realm, nonce, a.Opaque))
|
||||
w.WriteHeader(401)
|
||||
w.Write([]byte("401 Unauthorized\n"))
|
||||
}
|
||||
|
||||
/*
|
||||
Parse Authorization header from the http.Request. Returns a map of
|
||||
auth parameters or nil if the header is not a valid parsable Digest
|
||||
auth header.
|
||||
*/
|
||||
func DigestAuthParams(r *http.Request) map[string]string {
|
||||
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
|
||||
if len(s) != 2 || s[0] != "Digest" {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := map[string]string{}
|
||||
for _, kv := range strings.Split(s[1], ",") {
|
||||
parts := strings.SplitN(kv, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
result[strings.Trim(parts[0], "\" ")] = strings.Trim(parts[1], "\" ")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/*
|
||||
Check if request contains valid authentication data. Returns a pair
|
||||
of username, authinfo where username is the name of the authenticated
|
||||
user or an empty string and authinfo is the contents for the optional
|
||||
Authentication-Info response header.
|
||||
*/
|
||||
func (da *DigestAuth) CheckAuth(r *http.Request) (username string, authinfo *string) {
|
||||
da.mutex.Lock()
|
||||
defer da.mutex.Unlock()
|
||||
username = ""
|
||||
authinfo = nil
|
||||
auth := DigestAuthParams(r)
|
||||
if auth == nil || da.Opaque != auth["opaque"] || auth["algorithm"] != "MD5" || auth["qop"] != "auth" {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the requested URI matches auth header
|
||||
switch u, err := url.Parse(auth["uri"]); {
|
||||
case err != nil:
|
||||
return
|
||||
case r.URL == nil:
|
||||
return
|
||||
case len(u.Path) > len(r.URL.Path):
|
||||
return
|
||||
case !strings.HasPrefix(r.URL.Path, u.Path):
|
||||
return
|
||||
}
|
||||
|
||||
HA1 := da.Secrets(auth["username"], da.Realm)
|
||||
if da.PlainTextSecrets {
|
||||
HA1 = H(auth["username"] + ":" + da.Realm + ":" + HA1)
|
||||
}
|
||||
HA2 := H(r.Method + ":" + auth["uri"])
|
||||
KD := H(strings.Join([]string{HA1, auth["nonce"], auth["nc"], auth["cnonce"], auth["qop"], HA2}, ":"))
|
||||
|
||||
if KD != auth["response"] {
|
||||
return
|
||||
}
|
||||
|
||||
// At this point crypto checks are completed and validated.
|
||||
// Now check if the session is valid.
|
||||
|
||||
nc, err := strconv.ParseUint(auth["nc"], 16, 64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if client, ok := da.clients[auth["nonce"]]; !ok {
|
||||
return
|
||||
} else {
|
||||
if client.nc != 0 && client.nc >= nc {
|
||||
return
|
||||
}
|
||||
client.nc = nc
|
||||
client.last_seen = time.Now().UnixNano()
|
||||
}
|
||||
|
||||
resp_HA2 := H(":" + auth["uri"])
|
||||
rspauth := H(strings.Join([]string{HA1, auth["nonce"], auth["nc"], auth["cnonce"], auth["qop"], resp_HA2}, ":"))
|
||||
|
||||
info := fmt.Sprintf(`qop="auth", rspauth="%s", cnonce="%s", nc="%s"`, rspauth, auth["cnonce"], auth["nc"])
|
||||
return auth["username"], &info
|
||||
}
|
||||
|
||||
/*
|
||||
Default values for ClientCacheSize and ClientCacheTolerance for DigestAuth
|
||||
*/
|
||||
const DefaultClientCacheSize = 1000
|
||||
const DefaultClientCacheTolerance = 100
|
||||
|
||||
/*
|
||||
Wrap returns an Authenticator which uses HTTP Digest
|
||||
authentication. Arguments:
|
||||
|
||||
realm: The authentication realm.
|
||||
|
||||
secrets: SecretProvider which must return HA1 digests for the same
|
||||
realm as above.
|
||||
*/
|
||||
func (a *DigestAuth) Wrap(wrapped AuthenticatedHandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if username, authinfo := a.CheckAuth(r); username == "" {
|
||||
a.RequireAuth(w, r)
|
||||
} else {
|
||||
ar := &AuthenticatedRequest{Request: *r, Username: username}
|
||||
if authinfo != nil {
|
||||
w.Header().Set("Authentication-Info", *authinfo)
|
||||
}
|
||||
wrapped(w, ar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
JustCheck returns function which converts an http.HandlerFunc into a
|
||||
http.HandlerFunc which requires authentication. Username is passed as
|
||||
an extra X-Authenticated-Username header.
|
||||
*/
|
||||
func (a *DigestAuth) JustCheck(wrapped http.HandlerFunc) http.HandlerFunc {
|
||||
return a.Wrap(func(w http.ResponseWriter, ar *AuthenticatedRequest) {
|
||||
ar.Header.Set("X-Authenticated-Username", ar.Username)
|
||||
wrapped(w, &ar.Request)
|
||||
})
|
||||
}
|
||||
|
||||
func NewDigestAuthenticator(realm string, secrets SecretProvider) *DigestAuth {
|
||||
da := &DigestAuth{
|
||||
Opaque: RandomKey(),
|
||||
Realm: realm,
|
||||
Secrets: secrets,
|
||||
PlainTextSecrets: false,
|
||||
ClientCacheSize: DefaultClientCacheSize,
|
||||
ClientCacheTolerance: DefaultClientCacheTolerance,
|
||||
clients: map[string]*digest_client{}}
|
||||
return da
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
Example application using Basic auth
|
||||
|
||||
Build with:
|
||||
|
||||
go build basic.go
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
auth ".."
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Secret(user, realm string) string {
|
||||
if user == "john" {
|
||||
// password is "hello"
|
||||
return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func handle(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
|
||||
fmt.Fprintf(w, "<html><body><h1>Hello, %s!</h1></body></html>", r.Username)
|
||||
}
|
||||
|
||||
func main() {
|
||||
authenticator := auth.NewBasicAuthenticator("example.com", Secret)
|
||||
http.HandleFunc("/", authenticator.Wrap(handle))
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
Example application using Digest auth
|
||||
|
||||
Build with:
|
||||
|
||||
go build digest.go
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
auth ".."
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Secret(user, realm string) string {
|
||||
if user == "john" {
|
||||
// password is "hello"
|
||||
return "b98e16cbc3d01734b264adba7baa3bf9"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func handle(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
|
||||
fmt.Fprintf(w, "<html><body><h1>Hello, %s!</h1></body></html>", r.Username)
|
||||
}
|
||||
|
||||
func main() {
|
||||
authenticator := auth.NewDigestAuthenticator("example.com", Secret)
|
||||
http.HandleFunc("/", authenticator.Wrap(handle))
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
Example demonstrating how to wrap an application which is unaware of
|
||||
authenticated requests with a "pass-through" authentication
|
||||
|
||||
Build with:
|
||||
|
||||
go build wrapped.go
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
auth ".."
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Secret(user, realm string) string {
|
||||
if user == "john" {
|
||||
// password is "hello"
|
||||
return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func regular_handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "<html><body><h1>This application is unaware of authentication</h1></body></html>")
|
||||
}
|
||||
|
||||
func main() {
|
||||
authenticator := auth.NewBasicAuthenticator("example.com", Secret)
|
||||
http.HandleFunc("/", auth.JustCheck(authenticator, regular_handler))
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
package auth
|
||||
|
||||
import "crypto/md5"
|
||||
import "strings"
|
||||
|
||||
const itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
var md5_crypt_swaps = [16]int{12, 6, 0, 13, 7, 1, 14, 8, 2, 15, 9, 3, 5, 10, 4, 11}
|
||||
|
||||
type MD5Entry struct {
|
||||
Magic, Salt, Hash []byte
|
||||
}
|
||||
|
||||
func NewMD5Entry(e string) *MD5Entry {
|
||||
parts := strings.SplitN(e, "$", 4)
|
||||
if len(parts) != 4 {
|
||||
return nil
|
||||
}
|
||||
return &MD5Entry{
|
||||
Magic: []byte("$" + parts[1] + "$"),
|
||||
Salt: []byte(parts[2]),
|
||||
Hash: []byte(parts[3]),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
MD5 password crypt implementation
|
||||
*/
|
||||
func MD5Crypt(password, salt, magic []byte) []byte {
|
||||
d := md5.New()
|
||||
|
||||
d.Write(password)
|
||||
d.Write(magic)
|
||||
d.Write(salt)
|
||||
|
||||
d2 := md5.New()
|
||||
d2.Write(password)
|
||||
d2.Write(salt)
|
||||
d2.Write(password)
|
||||
|
||||
for i, mixin := 0, d2.Sum(nil); i < len(password); i++ {
|
||||
d.Write([]byte{mixin[i%16]})
|
||||
}
|
||||
|
||||
for i := len(password); i != 0; i >>= 1 {
|
||||
if i&1 == 0 {
|
||||
d.Write([]byte{password[0]})
|
||||
} else {
|
||||
d.Write([]byte{0})
|
||||
}
|
||||
}
|
||||
|
||||
final := d.Sum(nil)
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
d2 := md5.New()
|
||||
if i&1 == 0 {
|
||||
d2.Write(final)
|
||||
} else {
|
||||
d2.Write(password)
|
||||
}
|
||||
|
||||
if i%3 != 0 {
|
||||
d2.Write(salt)
|
||||
}
|
||||
|
||||
if i%7 != 0 {
|
||||
d2.Write(password)
|
||||
}
|
||||
|
||||
if i&1 == 0 {
|
||||
d2.Write(password)
|
||||
} else {
|
||||
d2.Write(final)
|
||||
}
|
||||
final = d2.Sum(nil)
|
||||
}
|
||||
|
||||
result := make([]byte, 0, 22)
|
||||
v := uint(0)
|
||||
bits := uint(0)
|
||||
for _, i := range md5_crypt_swaps {
|
||||
v |= (uint(final[i]) << bits)
|
||||
for bits = bits + 8; bits > 6; bits -= 6 {
|
||||
result = append(result, itoa64[v&0x3f])
|
||||
v >>= 6
|
||||
}
|
||||
}
|
||||
result = append(result, itoa64[v&0x3f])
|
||||
|
||||
return append(append(append(magic, salt...), '$'), result...)
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package auth
|
||||
|
||||
import "encoding/base64"
|
||||
import "crypto/md5"
|
||||
import "crypto/rand"
|
||||
import "fmt"
|
||||
|
||||
/*
|
||||
Return a random 16-byte base64 alphabet string
|
||||
*/
|
||||
func RandomKey() string {
|
||||
k := make([]byte, 12)
|
||||
for bytes := 0; bytes < len(k); {
|
||||
n, err := rand.Read(k[bytes:])
|
||||
if err != nil {
|
||||
panic("rand.Read() failed")
|
||||
}
|
||||
bytes += n
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(k)
|
||||
}
|
||||
|
||||
/*
|
||||
H function for MD5 algorithm (returns a lower-case hex MD5 digest)
|
||||
*/
|
||||
func H(data string) string {
|
||||
digest := md5.New()
|
||||
digest.Write([]byte(data))
|
||||
return fmt.Sprintf("%x", digest.Sum(nil))
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
test:example.com:aa78524fceb0e50fd8ca96dd818b8cf9
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
test:{SHA}qvTGHdzF6KLavt4PO0gs2a6pQ00=
|
||||
test2:$apr1$a0j62R97$mYqFkloXH0/UOaUnAiV2b0
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
package auth
|
||||
|
||||
import "encoding/csv"
|
||||
import "os"
|
||||
|
||||
/*
|
||||
SecretProvider is used by authenticators. Takes user name and realm
|
||||
as an argument, returns secret required for authentication (HA1 for
|
||||
digest authentication, properly encrypted password for basic).
|
||||
*/
|
||||
type SecretProvider func(user, realm string) string
|
||||
|
||||
/*
|
||||
Common functions for file auto-reloading
|
||||
*/
|
||||
type File struct {
|
||||
Path string
|
||||
Info os.FileInfo
|
||||
/* must be set in inherited types during initialization */
|
||||
Reload func()
|
||||
}
|
||||
|
||||
func (f *File) ReloadIfNeeded() {
|
||||
info, err := os.Stat(f.Path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if f.Info == nil || f.Info.ModTime() != info.ModTime() {
|
||||
f.Info = info
|
||||
f.Reload()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Structure used for htdigest file authentication. Users map realms to
|
||||
maps of users to their HA1 digests.
|
||||
*/
|
||||
type HtdigestFile struct {
|
||||
File
|
||||
Users map[string]map[string]string
|
||||
}
|
||||
|
||||
func reload_htdigest(hf *HtdigestFile) {
|
||||
r, err := os.Open(hf.Path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
csv_reader := csv.NewReader(r)
|
||||
csv_reader.Comma = ':'
|
||||
csv_reader.Comment = '#'
|
||||
csv_reader.TrimLeadingSpace = true
|
||||
|
||||
records, err := csv_reader.ReadAll()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
hf.Users = make(map[string]map[string]string)
|
||||
for _, record := range records {
|
||||
_, exists := hf.Users[record[1]]
|
||||
if !exists {
|
||||
hf.Users[record[1]] = make(map[string]string)
|
||||
}
|
||||
hf.Users[record[1]][record[0]] = record[2]
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
SecretProvider implementation based on htdigest-formated files. Will
|
||||
reload htdigest file on changes. Will panic on syntax errors in
|
||||
htdigest files.
|
||||
*/
|
||||
func HtdigestFileProvider(filename string) SecretProvider {
|
||||
hf := &HtdigestFile{File: File{Path: filename}}
|
||||
hf.Reload = func() { reload_htdigest(hf) }
|
||||
return func(user, realm string) string {
|
||||
hf.ReloadIfNeeded()
|
||||
_, exists := hf.Users[realm]
|
||||
if !exists {
|
||||
return ""
|
||||
}
|
||||
digest, exists := hf.Users[realm][user]
|
||||
if !exists {
|
||||
return ""
|
||||
}
|
||||
return digest
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Structure used for htdigest file authentication. Users map users to
|
||||
their salted encrypted password
|
||||
*/
|
||||
type HtpasswdFile struct {
|
||||
File
|
||||
Users map[string]string
|
||||
}
|
||||
|
||||
func reload_htpasswd(h *HtpasswdFile) {
|
||||
r, err := os.Open(h.Path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
csv_reader := csv.NewReader(r)
|
||||
csv_reader.Comma = ':'
|
||||
csv_reader.Comment = '#'
|
||||
csv_reader.TrimLeadingSpace = true
|
||||
|
||||
records, err := csv_reader.ReadAll()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
h.Users = make(map[string]string)
|
||||
for _, record := range records {
|
||||
h.Users[record[0]] = record[1]
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
SecretProvider implementation based on htpasswd-formated files. Will
|
||||
reload htpasswd file on changes. Will panic on syntax errors in
|
||||
htpasswd files. Realm argument of the SecretProvider is ignored.
|
||||
*/
|
||||
func HtpasswdFileProvider(filename string) SecretProvider {
|
||||
h := &HtpasswdFile{File: File{Path: filename}}
|
||||
h.Reload = func() { reload_htpasswd(h) }
|
||||
return func(user, realm string) string {
|
||||
h.ReloadIfNeeded()
|
||||
password, exists := h.Users[user]
|
||||
if !exists {
|
||||
return ""
|
||||
}
|
||||
return password
|
||||
}
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package libcni
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/appc/cni/pkg/invoke"
|
||||
"github.com/appc/cni/pkg/types"
|
||||
)
|
||||
|
||||
type RuntimeConf struct {
|
||||
ContainerID string
|
||||
NetNS string
|
||||
IfName string
|
||||
Args [][2]string
|
||||
}
|
||||
|
||||
type NetworkConfig struct {
|
||||
Network *types.NetConf
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
type CNI interface {
|
||||
AddNetwork(net *NetworkConfig, rt *RuntimeConf) (*types.Result, error)
|
||||
DelNetwork(net *NetworkConfig, rt *RuntimeConf) error
|
||||
}
|
||||
|
||||
type CNIConfig struct {
|
||||
Path []string
|
||||
}
|
||||
|
||||
func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (*types.Result, error) {
|
||||
pluginPath := invoke.FindInPath(net.Network.Type, c.Path)
|
||||
return invoke.ExecPluginWithResult(pluginPath, net.Bytes, c.args("ADD", rt))
|
||||
}
|
||||
|
||||
func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error {
|
||||
pluginPath := invoke.FindInPath(net.Network.Type, c.Path)
|
||||
return invoke.ExecPluginWithoutResult(pluginPath, net.Bytes, c.args("DEL", rt))
|
||||
}
|
||||
|
||||
// =====
|
||||
func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args {
|
||||
return &invoke.Args{
|
||||
Command: action,
|
||||
ContainerID: rt.ContainerID,
|
||||
NetNS: rt.NetNS,
|
||||
PluginArgs: rt.Args,
|
||||
IfName: rt.IfName,
|
||||
Path: strings.Join(c.Path, ":"),
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package libcni
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
|
||||
conf := &NetworkConfig{Bytes: bytes}
|
||||
if err := json.Unmarshal(bytes, &conf.Network); err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration: %s", err)
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func ConfFromFile(filename string) (*NetworkConfig, error) {
|
||||
bytes, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %s", filename, err)
|
||||
}
|
||||
return ConfFromBytes(bytes)
|
||||
}
|
||||
|
||||
func ConfFiles(dir string) ([]string, error) {
|
||||
// In part, adapted from rkt/networking/podenv.go#listFiles
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
switch {
|
||||
case err == nil: // break
|
||||
case os.IsNotExist(err):
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confFiles := []string{}
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
if filepath.Ext(f.Name()) == ".conf" {
|
||||
confFiles = append(confFiles, filepath.Join(dir, f.Name()))
|
||||
}
|
||||
}
|
||||
return confFiles, nil
|
||||
}
|
||||
|
||||
func LoadConf(dir, name string) (*NetworkConfig, error) {
|
||||
files, err := ConfFiles(dir)
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case len(files) == 0:
|
||||
return nil, fmt.Errorf("no net configurations found")
|
||||
}
|
||||
sort.Strings(files)
|
||||
|
||||
for _, confFile := range files {
|
||||
conf, err := ConfFromFile(confFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if conf.Network.Name == name {
|
||||
return conf, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf(`no net configuration with name "%s" in %s`, name, dir)
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package invoke
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CNIArgs interface {
|
||||
// For use with os/exec; i.e., return nil to inherit the
|
||||
// environment from this process
|
||||
AsEnv() []string
|
||||
}
|
||||
|
||||
type inherited struct{}
|
||||
|
||||
var inheritArgsFromEnv inherited
|
||||
|
||||
func (_ *inherited) AsEnv() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ArgsFromEnv() CNIArgs {
|
||||
return &inheritArgsFromEnv
|
||||
}
|
||||
|
||||
type Args struct {
|
||||
Command string
|
||||
ContainerID string
|
||||
NetNS string
|
||||
PluginArgs [][2]string
|
||||
PluginArgsStr string
|
||||
IfName string
|
||||
Path string
|
||||
}
|
||||
|
||||
func (args *Args) AsEnv() []string {
|
||||
env := os.Environ()
|
||||
pluginArgsStr := args.PluginArgsStr
|
||||
if pluginArgsStr == "" {
|
||||
pluginArgsStr = stringify(args.PluginArgs)
|
||||
}
|
||||
|
||||
env = append(env,
|
||||
"CNI_COMMAND="+args.Command,
|
||||
"CNI_CONTAINERID="+args.ContainerID,
|
||||
"CNI_NETNS="+args.NetNS,
|
||||
"CNI_ARGS="+pluginArgsStr,
|
||||
"CNI_IFNAME="+args.IfName,
|
||||
"CNI_PATH="+args.Path)
|
||||
return env
|
||||
}
|
||||
|
||||
// taken from rkt/networking/net_plugin.go
|
||||
func stringify(pluginArgs [][2]string) string {
|
||||
entries := make([]string, len(pluginArgs))
|
||||
|
||||
for i, kv := range pluginArgs {
|
||||
entries[i] = strings.Join(kv[:], "=")
|
||||
}
|
||||
|
||||
return strings.Join(entries, ";")
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package invoke
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/appc/cni/pkg/types"
|
||||
)
|
||||
|
||||
func pluginErr(err error, output []byte) error {
|
||||
if _, ok := err.(*exec.ExitError); ok {
|
||||
emsg := types.Error{}
|
||||
if perr := json.Unmarshal(output, &emsg); perr != nil {
|
||||
return fmt.Errorf("netplugin failed but error parsing its diagnostic message %q: %v", string(output), perr)
|
||||
}
|
||||
details := ""
|
||||
if emsg.Details != "" {
|
||||
details = fmt.Sprintf("; %v", emsg.Details)
|
||||
}
|
||||
return fmt.Errorf("%v%v", emsg.Msg, details)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (*types.Result, error) {
|
||||
stdoutBytes, err := execPlugin(pluginPath, netconf, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := &types.Result{}
|
||||
err = json.Unmarshal(stdoutBytes, res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func ExecPluginWithoutResult(pluginPath string, netconf []byte, args CNIArgs) error {
|
||||
_, err := execPlugin(pluginPath, netconf, args)
|
||||
return err
|
||||
}
|
||||
|
||||
func execPlugin(pluginPath string, netconf []byte, args CNIArgs) ([]byte, error) {
|
||||
if pluginPath == "" {
|
||||
return nil, fmt.Errorf("could not find %q plugin", filepath.Base(pluginPath))
|
||||
}
|
||||
|
||||
stdout := &bytes.Buffer{}
|
||||
|
||||
c := exec.Cmd{
|
||||
Env: args.AsEnv(),
|
||||
Path: pluginPath,
|
||||
Args: []string{pluginPath},
|
||||
Stdin: bytes.NewBuffer(netconf),
|
||||
Stdout: stdout,
|
||||
Stderr: os.Stderr,
|
||||
}
|
||||
if err := c.Run(); err != nil {
|
||||
return nil, pluginErr(err, stdout.Bytes())
|
||||
}
|
||||
|
||||
return stdout.Bytes(), nil
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package invoke
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func FindInPath(plugin string, path []string) string {
|
||||
for _, p := range path {
|
||||
fullname := filepath.Join(p, plugin)
|
||||
if fi, err := os.Stat(fullname); err == nil && fi.Mode().IsRegular() {
|
||||
return fullname
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Find returns the full path of the plugin by searching in CNI_PATH
|
||||
func Find(plugin string) string {
|
||||
paths := strings.Split(os.Getenv("CNI_PATH"), ":")
|
||||
return FindInPath(plugin, paths)
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func LoadArgs(args string, container interface{}) error {
|
||||
if args == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
containerValue := reflect.ValueOf(container)
|
||||
|
||||
pairs := strings.Split(args, ";")
|
||||
for _, pair := range pairs {
|
||||
kv := strings.Split(pair, "=")
|
||||
if len(kv) != 2 {
|
||||
return fmt.Errorf("ARGS: invalid pair %q", pair)
|
||||
}
|
||||
keyString := kv[0]
|
||||
valueString := kv[1]
|
||||
keyField := containerValue.Elem().FieldByName(keyString)
|
||||
if !keyField.IsValid() {
|
||||
return fmt.Errorf("ARGS: invalid key %q", keyString)
|
||||
}
|
||||
u := keyField.Addr().Interface().(encoding.TextUnmarshaler)
|
||||
err := u.UnmarshalText([]byte(valueString))
|
||||
if err != nil {
|
||||
return fmt.Errorf("ARGS: error parsing value of pair %q: %v)", pair, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
// like net.IPNet but adds JSON marshalling and unmarshalling
|
||||
type IPNet net.IPNet
|
||||
|
||||
// ParseCIDR takes a string like "10.2.3.1/24" and
|
||||
// return IPNet with "10.2.3.1" and /24 mask
|
||||
func ParseCIDR(s string) (*net.IPNet, error) {
|
||||
ip, ipn, err := net.ParseCIDR(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ipn.IP = ip
|
||||
return ipn, nil
|
||||
}
|
||||
|
||||
func (n IPNet) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal((*net.IPNet)(&n).String())
|
||||
}
|
||||
|
||||
func (n *IPNet) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmp, err := ParseCIDR(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*n = IPNet(*tmp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NetConf describes a network.
|
||||
type NetConf struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
IPAM struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
} `json:"ipam,omitempty"`
|
||||
}
|
||||
|
||||
// Result is what gets returned from the plugin (via stdout) to the caller
|
||||
type Result struct {
|
||||
IP4 *IPConfig `json:"ip4,omitempty"`
|
||||
IP6 *IPConfig `json:"ip6,omitempty"`
|
||||
}
|
||||
|
||||
func (r *Result) Print() error {
|
||||
return prettyPrint(r)
|
||||
}
|
||||
|
||||
// IPConfig contains values necessary to configure an interface
|
||||
type IPConfig struct {
|
||||
IP net.IPNet
|
||||
Gateway net.IP
|
||||
Routes []Route
|
||||
}
|
||||
|
||||
type Route struct {
|
||||
Dst net.IPNet
|
||||
GW net.IP
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
Code uint `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Details string `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return e.Msg
|
||||
}
|
||||
|
||||
func (e *Error) Print() error {
|
||||
return prettyPrint(e)
|
||||
}
|
||||
|
||||
// net.IPNet is not JSON (un)marshallable so this duality is needed
|
||||
// for our custom IPNet type
|
||||
|
||||
// JSON (un)marshallable types
|
||||
type ipConfig struct {
|
||||
IP IPNet `json:"ip"`
|
||||
Gateway net.IP `json:"gateway,omitempty"`
|
||||
Routes []Route `json:"routes,omitempty"`
|
||||
}
|
||||
|
||||
type route struct {
|
||||
Dst IPNet `json:"dst"`
|
||||
GW net.IP `json:"gw,omitempty"`
|
||||
}
|
||||
|
||||
func (c *IPConfig) MarshalJSON() ([]byte, error) {
|
||||
ipc := ipConfig{
|
||||
IP: IPNet(c.IP),
|
||||
Gateway: c.Gateway,
|
||||
Routes: c.Routes,
|
||||
}
|
||||
|
||||
return json.Marshal(ipc)
|
||||
}
|
||||
|
||||
func (c *IPConfig) UnmarshalJSON(data []byte) error {
|
||||
ipc := ipConfig{}
|
||||
if err := json.Unmarshal(data, &ipc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.IP = net.IPNet(ipc.IP)
|
||||
c.Gateway = ipc.Gateway
|
||||
c.Routes = ipc.Routes
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Route) UnmarshalJSON(data []byte) error {
|
||||
rt := route{}
|
||||
if err := json.Unmarshal(data, &rt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Dst = net.IPNet(rt.Dst)
|
||||
r.GW = rt.GW
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Route) MarshalJSON() ([]byte, error) {
|
||||
rt := route{
|
||||
Dst: IPNet(r.Dst),
|
||||
GW: r.GW,
|
||||
}
|
||||
|
||||
return json.Marshal(rt)
|
||||
}
|
||||
|
||||
func prettyPrint(obj interface{}) error {
|
||||
data, err := json.MarshalIndent(obj, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = os.Stdout.Write(data)
|
||||
return err
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MakeQueryString takes a comma-separated LABEL=VALUE string and returns an
|
||||
// "&"-separated string with URL escaped values.
|
||||
//
|
||||
// Examples:
|
||||
// version=1.0.0,label=v1+v2 -> version=1.0.0&label=v1%2Bv2
|
||||
// name=db,source=/tmp$1 -> name=db&source=%2Ftmp%241
|
||||
func MakeQueryString(app string) (string, error) {
|
||||
parts := strings.Split(app, ",")
|
||||
escapedParts := make([]string, len(parts))
|
||||
for i, s := range parts {
|
||||
p := strings.SplitN(s, "=", 2)
|
||||
if len(p) != 2 {
|
||||
return "", fmt.Errorf("malformed string %q - has a label without a value: %s", app, p[0])
|
||||
}
|
||||
escapedParts[i] = fmt.Sprintf("%s=%s", p[0], url.QueryEscape(p[1]))
|
||||
}
|
||||
return strings.Join(escapedParts, "&"), nil
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package schema provides definitions for the JSON schema of the different
|
||||
// manifests in the App Container Specification. The manifests are canonically
|
||||
// represented in their respective structs:
|
||||
// - `ImageManifest`
|
||||
// - `PodManifest`
|
||||
//
|
||||
// Validation is performed through serialization: if a blob of JSON data will
|
||||
// unmarshal to one of the *Manifests, it is considered a valid implementation
|
||||
// of the standard. Similarly, if a constructed *Manifest struct marshals
|
||||
// successfully to JSON, it must be valid.
|
||||
package schema
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package schema
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/appc/spec/schema/types"
|
||||
|
||||
"github.com/camlistore/go4/errorutil"
|
||||
)
|
||||
|
||||
const (
|
||||
ACIExtension = ".aci"
|
||||
ImageManifestKind = types.ACKind("ImageManifest")
|
||||
)
|
||||
|
||||
type ImageManifest struct {
|
||||
ACKind types.ACKind `json:"acKind"`
|
||||
ACVersion types.SemVer `json:"acVersion"`
|
||||
Name types.ACIdentifier `json:"name"`
|
||||
Labels types.Labels `json:"labels,omitempty"`
|
||||
App *types.App `json:"app,omitempty"`
|
||||
Annotations types.Annotations `json:"annotations,omitempty"`
|
||||
Dependencies types.Dependencies `json:"dependencies,omitempty"`
|
||||
PathWhitelist []string `json:"pathWhitelist,omitempty"`
|
||||
}
|
||||
|
||||
// imageManifest is a model to facilitate extra validation during the
|
||||
// unmarshalling of the ImageManifest
|
||||
type imageManifest ImageManifest
|
||||
|
||||
func BlankImageManifest() *ImageManifest {
|
||||
return &ImageManifest{ACKind: ImageManifestKind, ACVersion: AppContainerVersion}
|
||||
}
|
||||
|
||||
func (im *ImageManifest) UnmarshalJSON(data []byte) error {
|
||||
a := imageManifest(*im)
|
||||
err := json.Unmarshal(data, &a)
|
||||
if err != nil {
|
||||
if serr, ok := err.(*json.SyntaxError); ok {
|
||||
line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(data), serr.Offset)
|
||||
return fmt.Errorf("\nError at line %d, column %d\n%s%v", line, col, highlight, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
nim := ImageManifest(a)
|
||||
if err := nim.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*im = nim
|
||||
return nil
|
||||
}
|
||||
|
||||
func (im ImageManifest) MarshalJSON() ([]byte, error) {
|
||||
if err := im.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(imageManifest(im))
|
||||
}
|
||||
|
||||
var imKindError = types.InvalidACKindError(ImageManifestKind)
|
||||
|
||||
// assertValid performs extra assertions on an ImageManifest to ensure that
|
||||
// fields are set appropriately, etc. It is used exclusively when marshalling
|
||||
// and unmarshalling an ImageManifest. Most field-specific validation is
|
||||
// performed through the individual types being marshalled; assertValid()
|
||||
// should only deal with higher-level validation.
|
||||
func (im *ImageManifest) assertValid() error {
|
||||
if im.ACKind != ImageManifestKind {
|
||||
return imKindError
|
||||
}
|
||||
if im.ACVersion.Empty() {
|
||||
return errors.New(`acVersion must be set`)
|
||||
}
|
||||
if im.Name.Empty() {
|
||||
return errors.New(`name must be set`)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (im *ImageManifest) GetLabel(name string) (val string, ok bool) {
|
||||
return im.Labels.Get(name)
|
||||
}
|
||||
|
||||
func (im *ImageManifest) GetAnnotation(name string) (val string, ok bool) {
|
||||
return im.Annotations.Get(name)
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package schema
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/appc/spec/schema/types"
|
||||
)
|
||||
|
||||
type Kind struct {
|
||||
ACVersion types.SemVer `json:"acVersion"`
|
||||
ACKind types.ACKind `json:"acKind"`
|
||||
}
|
||||
|
||||
type kind Kind
|
||||
|
||||
func (k *Kind) UnmarshalJSON(data []byte) error {
|
||||
nk := kind{}
|
||||
err := json.Unmarshal(data, &nk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*k = Kind(nk)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k Kind) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(kind(k))
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package lastditch provides fallback redefinitions of parts of
|
||||
// schemas provided by schema package.
|
||||
//
|
||||
// Almost no validation of schemas is done (besides checking if data
|
||||
// really is `JSON`-encoded and kind is either `ImageManifest` or
|
||||
// `PodManifest`. This is to get as much data as possible from an
|
||||
// invalid manifest. The main aim of the package is to be used for the
|
||||
// better error reporting. The another aim might be to force some
|
||||
// operation (like removing a pod), which would otherwise fail because
|
||||
// of an invalid manifest.
|
||||
//
|
||||
// To avoid validation during deserialization, types provided by this
|
||||
// package use plain strings.
|
||||
package lastditch
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package lastditch
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/appc/spec/schema"
|
||||
"github.com/appc/spec/schema/types"
|
||||
)
|
||||
|
||||
type ImageManifest struct {
|
||||
ACVersion string `json:"acVersion"`
|
||||
ACKind string `json:"acKind"`
|
||||
Name string `json:"name"`
|
||||
Labels Labels `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
// a type just to avoid a recursion during unmarshalling
|
||||
type imageManifest ImageManifest
|
||||
|
||||
func (im *ImageManifest) UnmarshalJSON(data []byte) error {
|
||||
i := imageManifest(*im)
|
||||
err := json.Unmarshal(data, &i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i.ACKind != string(schema.ImageManifestKind) {
|
||||
return types.InvalidACKindError(schema.ImageManifestKind)
|
||||
}
|
||||
*im = ImageManifest(i)
|
||||
return nil
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package lastditch
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type Labels []Label
|
||||
|
||||
// a type just to avoid a recursion during unmarshalling
|
||||
type labels Labels
|
||||
|
||||
type Label struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func (l *Labels) UnmarshalJSON(data []byte) error {
|
||||
var jl labels
|
||||
if err := json.Unmarshal(data, &jl); err != nil {
|
||||
return err
|
||||
}
|
||||
*l = Labels(jl)
|
||||
return nil
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package lastditch
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/appc/spec/schema"
|
||||
"github.com/appc/spec/schema/types"
|
||||
)
|
||||
|
||||
type PodManifest struct {
|
||||
ACVersion string `json:"acVersion"`
|
||||
ACKind string `json:"acKind"`
|
||||
Apps AppList `json:"apps"`
|
||||
}
|
||||
|
||||
type AppList []RuntimeApp
|
||||
|
||||
type RuntimeApp struct {
|
||||
Name string `json:"name"`
|
||||
Image RuntimeImage `json:"image"`
|
||||
}
|
||||
|
||||
type RuntimeImage struct {
|
||||
Name string `json:"name"`
|
||||
ID string `json:"id"`
|
||||
Labels Labels `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
// a type just to avoid a recursion during unmarshalling
|
||||
type podManifest PodManifest
|
||||
|
||||
func (pm *PodManifest) UnmarshalJSON(data []byte) error {
|
||||
p := podManifest(*pm)
|
||||
err := json.Unmarshal(data, &p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p.ACKind != string(schema.PodManifestKind) {
|
||||
return types.InvalidACKindError(schema.PodManifestKind)
|
||||
}
|
||||
*pm = PodManifest(p)
|
||||
return nil
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package schema
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/appc/spec/schema/types"
|
||||
|
||||
"github.com/camlistore/go4/errorutil"
|
||||
)
|
||||
|
||||
const PodManifestKind = types.ACKind("PodManifest")
|
||||
|
||||
type PodManifest struct {
|
||||
ACVersion types.SemVer `json:"acVersion"`
|
||||
ACKind types.ACKind `json:"acKind"`
|
||||
Apps AppList `json:"apps"`
|
||||
Volumes []types.Volume `json:"volumes"`
|
||||
Isolators []types.Isolator `json:"isolators"`
|
||||
Annotations types.Annotations `json:"annotations"`
|
||||
Ports []types.ExposedPort `json:"ports"`
|
||||
}
|
||||
|
||||
// podManifest is a model to facilitate extra validation during the
|
||||
// unmarshalling of the PodManifest
|
||||
type podManifest PodManifest
|
||||
|
||||
func BlankPodManifest() *PodManifest {
|
||||
return &PodManifest{ACKind: PodManifestKind, ACVersion: AppContainerVersion}
|
||||
}
|
||||
|
||||
func (pm *PodManifest) UnmarshalJSON(data []byte) error {
|
||||
p := podManifest(*pm)
|
||||
err := json.Unmarshal(data, &p)
|
||||
if err != nil {
|
||||
if serr, ok := err.(*json.SyntaxError); ok {
|
||||
line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(data), serr.Offset)
|
||||
return fmt.Errorf("\nError at line %d, column %d\n%s%v", line, col, highlight, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
npm := PodManifest(p)
|
||||
if err := npm.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*pm = npm
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm PodManifest) MarshalJSON() ([]byte, error) {
|
||||
if err := pm.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(podManifest(pm))
|
||||
}
|
||||
|
||||
var pmKindError = types.InvalidACKindError(PodManifestKind)
|
||||
|
||||
// assertValid performs extra assertions on an PodManifest to
|
||||
// ensure that fields are set appropriately, etc. It is used exclusively when
|
||||
// marshalling and unmarshalling an PodManifest. Most
|
||||
// field-specific validation is performed through the individual types being
|
||||
// marshalled; assertValid() should only deal with higher-level validation.
|
||||
func (pm *PodManifest) assertValid() error {
|
||||
if pm.ACKind != PodManifestKind {
|
||||
return pmKindError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AppList []RuntimeApp
|
||||
|
||||
type appList AppList
|
||||
|
||||
func (al *AppList) UnmarshalJSON(data []byte) error {
|
||||
a := appList{}
|
||||
err := json.Unmarshal(data, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nal := AppList(a)
|
||||
if err := nal.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*al = nal
|
||||
return nil
|
||||
}
|
||||
|
||||
func (al AppList) MarshalJSON() ([]byte, error) {
|
||||
if err := al.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(appList(al))
|
||||
}
|
||||
|
||||
func (al AppList) assertValid() error {
|
||||
seen := map[types.ACName]bool{}
|
||||
for _, a := range al {
|
||||
if _, ok := seen[a.Name]; ok {
|
||||
return fmt.Errorf(`duplicate apps of name %q`, a.Name)
|
||||
}
|
||||
seen[a.Name] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get retrieves an app by the specified name from the AppList; if there is
|
||||
// no such app, nil is returned. The returned *RuntimeApp MUST be considered
|
||||
// read-only.
|
||||
func (al AppList) Get(name types.ACName) *RuntimeApp {
|
||||
for _, a := range al {
|
||||
if name.Equals(a.Name) {
|
||||
aa := a
|
||||
return &aa
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mount describes the mapping between a volume and the path it is mounted
|
||||
// inside of an app's filesystem.
|
||||
type Mount struct {
|
||||
Volume types.ACName `json:"volume"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
func (r Mount) assertValid() error {
|
||||
if r.Volume.Empty() {
|
||||
return errors.New("volume must be set")
|
||||
}
|
||||
if r.Path == "" {
|
||||
return errors.New("path must be set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RuntimeApp describes an application referenced in a PodManifest
|
||||
type RuntimeApp struct {
|
||||
Name types.ACName `json:"name"`
|
||||
Image RuntimeImage `json:"image"`
|
||||
App *types.App `json:"app,omitempty"`
|
||||
Mounts []Mount `json:"mounts,omitempty"`
|
||||
Annotations types.Annotations `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
// RuntimeImage describes an image referenced in a RuntimeApp
|
||||
type RuntimeImage struct {
|
||||
Name *types.ACIdentifier `json:"name,omitempty"`
|
||||
ID types.Hash `json:"id"`
|
||||
Labels types.Labels `json:"labels,omitempty"`
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// ValidACIdentifier is a regular expression that defines a valid ACIdentifier
|
||||
ValidACIdentifier = regexp.MustCompile("^[a-z0-9]+([-._~/][a-z0-9]+)*$")
|
||||
|
||||
invalidACIdentifierChars = regexp.MustCompile("[^a-z0-9-._~/]")
|
||||
invalidACIdentifierEdges = regexp.MustCompile("(^[-._~/]+)|([-._~/]+$)")
|
||||
|
||||
ErrEmptyACIdentifier = ACIdentifierError("ACIdentifier cannot be empty")
|
||||
ErrInvalidEdgeInACIdentifier = ACIdentifierError("ACIdentifier must start and end with only lower case " +
|
||||
"alphanumeric characters")
|
||||
ErrInvalidCharInACIdentifier = ACIdentifierError("ACIdentifier must contain only lower case " +
|
||||
`alphanumeric characters plus "-._~/"`)
|
||||
)
|
||||
|
||||
// ACIdentifier (an App-Container Identifier) is a format used by keys in image names
|
||||
// and image labels of the App Container Standard. An ACIdentifier is restricted to numeric
|
||||
// and lowercase URI unreserved characters defined in URI RFC[1]; all alphabetical characters
|
||||
// must be lowercase only. Furthermore, the first and last character ("edges") must be
|
||||
// alphanumeric, and an ACIdentifier cannot be empty. Programmatically, an ACIdentifier must
|
||||
// conform to the regular expression ValidACIdentifier.
|
||||
//
|
||||
// [1] http://tools.ietf.org/html/rfc3986#section-2.3
|
||||
type ACIdentifier string
|
||||
|
||||
func (n ACIdentifier) String() string {
|
||||
return string(n)
|
||||
}
|
||||
|
||||
// Set sets the ACIdentifier to the given value, if it is valid; if not,
|
||||
// an error is returned.
|
||||
func (n *ACIdentifier) Set(s string) error {
|
||||
nn, err := NewACIdentifier(s)
|
||||
if err == nil {
|
||||
*n = *nn
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Equals checks whether a given ACIdentifier is equal to this one.
|
||||
func (n ACIdentifier) Equals(o ACIdentifier) bool {
|
||||
return strings.ToLower(string(n)) == strings.ToLower(string(o))
|
||||
}
|
||||
|
||||
// Empty returns a boolean indicating whether this ACIdentifier is empty.
|
||||
func (n ACIdentifier) Empty() bool {
|
||||
return n.String() == ""
|
||||
}
|
||||
|
||||
// NewACIdentifier generates a new ACIdentifier from a string. If the given string is
|
||||
// not a valid ACIdentifier, nil and an error are returned.
|
||||
func NewACIdentifier(s string) (*ACIdentifier, error) {
|
||||
n := ACIdentifier(s)
|
||||
if err := n.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &n, nil
|
||||
}
|
||||
|
||||
// MustACIdentifier generates a new ACIdentifier from a string, If the given string is
|
||||
// not a valid ACIdentifier, it panics.
|
||||
func MustACIdentifier(s string) *ACIdentifier {
|
||||
n, err := NewACIdentifier(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (n ACIdentifier) assertValid() error {
|
||||
s := string(n)
|
||||
if len(s) == 0 {
|
||||
return ErrEmptyACIdentifier
|
||||
}
|
||||
if invalidACIdentifierChars.MatchString(s) {
|
||||
return ErrInvalidCharInACIdentifier
|
||||
}
|
||||
if invalidACIdentifierEdges.MatchString(s) {
|
||||
return ErrInvalidEdgeInACIdentifier
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface
|
||||
func (n *ACIdentifier) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
nn, err := NewACIdentifier(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*n = *nn
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface
|
||||
func (n ACIdentifier) MarshalJSON() ([]byte, error) {
|
||||
if err := n.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(n.String())
|
||||
}
|
||||
|
||||
// SanitizeACIdentifier replaces every invalid ACIdentifier character in s with an underscore
|
||||
// making it a legal ACIdentifier string. If the character is an upper case letter it
|
||||
// replaces it with its lower case. It also removes illegal edge characters
|
||||
// (hyphens, period, underscore, tilde and slash).
|
||||
//
|
||||
// This is a helper function and its algorithm is not part of the spec. It
|
||||
// should not be called without the user explicitly asking for a suggestion.
|
||||
func SanitizeACIdentifier(s string) (string, error) {
|
||||
s = strings.ToLower(s)
|
||||
s = invalidACIdentifierChars.ReplaceAllString(s, "_")
|
||||
s = invalidACIdentifierEdges.ReplaceAllString(s, "")
|
||||
|
||||
if s == "" {
|
||||
return "", errors.New("must contain at least one valid character")
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoACKind = ACKindError("ACKind must be set")
|
||||
)
|
||||
|
||||
// ACKind wraps a string to define a field which must be set with one of
|
||||
// several ACKind values. If it is unset, or has an invalid value, the field
|
||||
// will refuse to marshal/unmarshal.
|
||||
type ACKind string
|
||||
|
||||
func (a ACKind) String() string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
func (a ACKind) assertValid() error {
|
||||
s := a.String()
|
||||
switch s {
|
||||
case "ImageManifest", "PodManifest":
|
||||
return nil
|
||||
case "":
|
||||
return ErrNoACKind
|
||||
default:
|
||||
msg := fmt.Sprintf("bad ACKind: %s", s)
|
||||
return ACKindError(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (a ACKind) MarshalJSON() ([]byte, error) {
|
||||
if err := a.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(a.String())
|
||||
}
|
||||
|
||||
func (a *ACKind) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
err := json.Unmarshal(data, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
na := ACKind(s)
|
||||
if err := na.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*a = na
|
||||
return nil
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// ValidACName is a regular expression that defines a valid ACName
|
||||
ValidACName = regexp.MustCompile("^[a-z0-9]+([-][a-z0-9]+)*$")
|
||||
|
||||
invalidACNameChars = regexp.MustCompile("[^a-z0-9-]")
|
||||
invalidACNameEdges = regexp.MustCompile("(^[-]+)|([-]+$)")
|
||||
|
||||
ErrEmptyACName = ACNameError("ACName cannot be empty")
|
||||
ErrInvalidEdgeInACName = ACNameError("ACName must start and end with only lower case " +
|
||||
"alphanumeric characters")
|
||||
ErrInvalidCharInACName = ACNameError("ACName must contain only lower case " +
|
||||
`alphanumeric characters plus "-"`)
|
||||
)
|
||||
|
||||
// ACName (an App-Container Name) is a format used by keys in different formats
|
||||
// of the App Container Standard. An ACName is restricted to numeric and lowercase
|
||||
// characters accepted by the DNS RFC[1] plus "-"; all alphabetical characters must
|
||||
// be lowercase only. Furthermore, the first and last character ("edges") must be
|
||||
// alphanumeric, and an ACName cannot be empty. Programmatically, an ACName must
|
||||
// conform to the regular expression ValidACName.
|
||||
//
|
||||
// [1] http://tools.ietf.org/html/rfc1123#page-13
|
||||
type ACName string
|
||||
|
||||
func (n ACName) String() string {
|
||||
return string(n)
|
||||
}
|
||||
|
||||
// Set sets the ACName to the given value, if it is valid; if not,
|
||||
// an error is returned.
|
||||
func (n *ACName) Set(s string) error {
|
||||
nn, err := NewACName(s)
|
||||
if err == nil {
|
||||
*n = *nn
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Equals checks whether a given ACName is equal to this one.
|
||||
func (n ACName) Equals(o ACName) bool {
|
||||
return strings.ToLower(string(n)) == strings.ToLower(string(o))
|
||||
}
|
||||
|
||||
// Empty returns a boolean indicating whether this ACName is empty.
|
||||
func (n ACName) Empty() bool {
|
||||
return n.String() == ""
|
||||
}
|
||||
|
||||
// NewACName generates a new ACName from a string. If the given string is
|
||||
// not a valid ACName, nil and an error are returned.
|
||||
func NewACName(s string) (*ACName, error) {
|
||||
n := ACName(s)
|
||||
if err := n.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &n, nil
|
||||
}
|
||||
|
||||
// MustACName generates a new ACName from a string, If the given string is
|
||||
// not a valid ACName, it panics.
|
||||
func MustACName(s string) *ACName {
|
||||
n, err := NewACName(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (n ACName) assertValid() error {
|
||||
s := string(n)
|
||||
if len(s) == 0 {
|
||||
return ErrEmptyACName
|
||||
}
|
||||
if invalidACNameChars.MatchString(s) {
|
||||
return ErrInvalidCharInACName
|
||||
}
|
||||
if invalidACNameEdges.MatchString(s) {
|
||||
return ErrInvalidEdgeInACName
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface
|
||||
func (n *ACName) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
nn, err := NewACName(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*n = *nn
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface
|
||||
func (n ACName) MarshalJSON() ([]byte, error) {
|
||||
if err := n.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(n.String())
|
||||
}
|
||||
|
||||
// SanitizeACName replaces every invalid ACName character in s with a dash
|
||||
// making it a legal ACName string. If the character is an upper case letter it
|
||||
// replaces it with its lower case. It also removes illegal edge characters
|
||||
// (hyphens).
|
||||
//
|
||||
// This is a helper function and its algorithm is not part of the spec. It
|
||||
// should not be called without the user explicitly asking for a suggestion.
|
||||
func SanitizeACName(s string) (string, error) {
|
||||
s = strings.ToLower(s)
|
||||
s = invalidACNameChars.ReplaceAllString(s, "-")
|
||||
s = invalidACNameEdges.ReplaceAllString(s, "")
|
||||
|
||||
if s == "" {
|
||||
return "", errors.New("must contain at least one valid character")
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Annotations []Annotation
|
||||
|
||||
type annotations Annotations
|
||||
|
||||
type Annotation struct {
|
||||
Name ACIdentifier `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func (a Annotations) assertValid() error {
|
||||
seen := map[ACIdentifier]string{}
|
||||
for _, anno := range a {
|
||||
_, ok := seen[anno.Name]
|
||||
if ok {
|
||||
return fmt.Errorf(`duplicate annotations of name %q`, anno.Name)
|
||||
}
|
||||
seen[anno.Name] = anno.Value
|
||||
}
|
||||
if c, ok := seen["created"]; ok {
|
||||
if _, err := NewDate(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if h, ok := seen["homepage"]; ok {
|
||||
if _, err := NewURL(h); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if d, ok := seen["documentation"]; ok {
|
||||
if _, err := NewURL(d); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a Annotations) MarshalJSON() ([]byte, error) {
|
||||
if err := a.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(annotations(a))
|
||||
}
|
||||
|
||||
func (a *Annotations) UnmarshalJSON(data []byte) error {
|
||||
var ja annotations
|
||||
if err := json.Unmarshal(data, &ja); err != nil {
|
||||
return err
|
||||
}
|
||||
na := Annotations(ja)
|
||||
if err := na.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*a = na
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retrieve the value of an annotation by the given name from Annotations, if
|
||||
// it exists.
|
||||
func (a Annotations) Get(name string) (val string, ok bool) {
|
||||
for _, anno := range a {
|
||||
if anno.Name.String() == name {
|
||||
return anno.Value, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Set sets the value of an annotation by the given name, overwriting if one already exists.
|
||||
func (a *Annotations) Set(name ACIdentifier, value string) {
|
||||
for i, anno := range *a {
|
||||
if anno.Name.Equals(name) {
|
||||
(*a)[i] = Annotation{
|
||||
Name: name,
|
||||
Value: value,
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
anno := Annotation{
|
||||
Name: name,
|
||||
Value: value,
|
||||
}
|
||||
*a = append(*a, anno)
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
Exec Exec `json:"exec"`
|
||||
EventHandlers []EventHandler `json:"eventHandlers,omitempty"`
|
||||
User string `json:"user"`
|
||||
Group string `json:"group"`
|
||||
SupplementaryGIDs []int `json:"supplementaryGIDs,omitempty"`
|
||||
WorkingDirectory string `json:"workingDirectory,omitempty"`
|
||||
Environment Environment `json:"environment,omitempty"`
|
||||
MountPoints []MountPoint `json:"mountPoints,omitempty"`
|
||||
Ports []Port `json:"ports,omitempty"`
|
||||
Isolators Isolators `json:"isolators,omitempty"`
|
||||
}
|
||||
|
||||
// app is a model to facilitate extra validation during the
|
||||
// unmarshalling of the App
|
||||
type app App
|
||||
|
||||
func (a *App) UnmarshalJSON(data []byte) error {
|
||||
ja := app(*a)
|
||||
err := json.Unmarshal(data, &ja)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
na := App(ja)
|
||||
if err := na.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
if na.Environment == nil {
|
||||
na.Environment = make(Environment, 0)
|
||||
}
|
||||
*a = na
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a App) MarshalJSON() ([]byte, error) {
|
||||
if err := a.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(app(a))
|
||||
}
|
||||
|
||||
func (a *App) assertValid() error {
|
||||
if err := a.Exec.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
if a.User == "" {
|
||||
return errors.New(`user is required`)
|
||||
}
|
||||
if a.Group == "" {
|
||||
return errors.New(`group is required`)
|
||||
}
|
||||
if !path.IsAbs(a.WorkingDirectory) && a.WorkingDirectory != "" {
|
||||
return errors.New("workingDirectory must be an absolute path")
|
||||
}
|
||||
eh := make(map[string]bool)
|
||||
for _, e := range a.EventHandlers {
|
||||
name := e.Name
|
||||
if eh[name] {
|
||||
return fmt.Errorf("Only one eventHandler of name %q allowed", name)
|
||||
}
|
||||
eh[name] = true
|
||||
}
|
||||
if err := a.Environment.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Date wraps time.Time to marshal/unmarshal to/from JSON strings in strict
|
||||
// accordance with RFC3339
|
||||
// TODO(jonboulle): golang's implementation seems slightly buggy here;
|
||||
// according to http://tools.ietf.org/html/rfc3339#section-5.6 , applications
|
||||
// may choose to separate the date and time with a space instead of a T
|
||||
// character (for example, `date --rfc-3339` on GNU coreutils) - but this is
|
||||
// considered an error by go's parser. File a bug?
|
||||
type Date time.Time
|
||||
|
||||
func NewDate(s string) (*Date, error) {
|
||||
t, err := time.Parse(time.RFC3339, s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad Date: %v", err)
|
||||
}
|
||||
d := Date(t)
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
func (d Date) String() string {
|
||||
return time.Time(d).Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func (d *Date) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
nd, err := NewDate(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*d = *nd
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d Date) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(d.String())
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Dependencies []Dependency
|
||||
|
||||
type Dependency struct {
|
||||
ImageName ACIdentifier `json:"imageName"`
|
||||
ImageID *Hash `json:"imageID,omitempty"`
|
||||
Labels Labels `json:"labels,omitempty"`
|
||||
Size uint `json:"size,omitempty"`
|
||||
}
|
||||
|
||||
type dependency Dependency
|
||||
|
||||
func (d Dependency) assertValid() error {
|
||||
if len(d.ImageName) < 1 {
|
||||
return errors.New(`imageName cannot be empty`)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d Dependency) MarshalJSON() ([]byte, error) {
|
||||
if err := d.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(dependency(d))
|
||||
}
|
||||
|
||||
func (d *Dependency) UnmarshalJSON(data []byte) error {
|
||||
var jd dependency
|
||||
if err := json.Unmarshal(data, &jd); err != nil {
|
||||
return err
|
||||
}
|
||||
nd := Dependency(jd)
|
||||
if err := nd.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*d = nd
|
||||
return nil
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package types contains structs representing the various types in the app
|
||||
// container specification. It is used by the [schema manifest types](../)
|
||||
// to enforce validation.
|
||||
package types
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
envPattern = regexp.MustCompile("^[A-Za-z_][A-Za-z_0-9]*$")
|
||||
)
|
||||
|
||||
type Environment []EnvironmentVariable
|
||||
|
||||
type environment Environment
|
||||
|
||||
type EnvironmentVariable struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func (ev EnvironmentVariable) assertValid() error {
|
||||
if len(ev.Name) == 0 {
|
||||
return fmt.Errorf(`environment variable name must not be empty`)
|
||||
}
|
||||
if !envPattern.MatchString(ev.Name) {
|
||||
return fmt.Errorf(`environment variable does not have valid identifier %q`, ev.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Environment) assertValid() error {
|
||||
seen := map[string]bool{}
|
||||
for _, env := range e {
|
||||
if err := env.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, ok := seen[env.Name]
|
||||
if ok {
|
||||
return fmt.Errorf(`duplicate environment variable of name %q`, env.Name)
|
||||
}
|
||||
seen[env.Name] = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Environment) MarshalJSON() ([]byte, error) {
|
||||
if err := e.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(environment(e))
|
||||
}
|
||||
|
||||
func (e *Environment) UnmarshalJSON(data []byte) error {
|
||||
var je environment
|
||||
if err := json.Unmarshal(data, &je); err != nil {
|
||||
return err
|
||||
}
|
||||
ne := Environment(je)
|
||||
if err := ne.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*e = ne
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retrieve the value of an environment variable by the given name from
|
||||
// Environment, if it exists.
|
||||
func (e Environment) Get(name string) (value string, ok bool) {
|
||||
for _, env := range e {
|
||||
if env.Name == name {
|
||||
return env.Value, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Set sets the value of an environment variable by the given name,
|
||||
// overwriting if one already exists.
|
||||
func (e *Environment) Set(name string, value string) {
|
||||
for i, env := range *e {
|
||||
if env.Name == name {
|
||||
(*e)[i] = EnvironmentVariable{
|
||||
Name: name,
|
||||
Value: value,
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
env := EnvironmentVariable{
|
||||
Name: name,
|
||||
Value: value,
|
||||
}
|
||||
*e = append(*e, env)
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import "fmt"
|
||||
|
||||
// An ACKindError is returned when the wrong ACKind is set in a manifest
|
||||
type ACKindError string
|
||||
|
||||
func (e ACKindError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func InvalidACKindError(kind ACKind) ACKindError {
|
||||
return ACKindError(fmt.Sprintf("missing or bad ACKind (must be %#v)", kind))
|
||||
}
|
||||
|
||||
// An ACVersionError is returned when a bad ACVersion is set in a manifest
|
||||
type ACVersionError string
|
||||
|
||||
func (e ACVersionError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// An ACIdentifierError is returned when a bad value is used for an ACIdentifier
|
||||
type ACIdentifierError string
|
||||
|
||||
func (e ACIdentifierError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// An ACNameError is returned when a bad value is used for an ACName
|
||||
type ACNameError string
|
||||
|
||||
func (e ACNameError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type EventHandler struct {
|
||||
Name string `json:"name"`
|
||||
Exec Exec `json:"exec"`
|
||||
}
|
||||
|
||||
type eventHandler EventHandler
|
||||
|
||||
func (e EventHandler) assertValid() error {
|
||||
s := e.Name
|
||||
switch s {
|
||||
case "pre-start", "post-stop":
|
||||
return nil
|
||||
case "":
|
||||
return errors.New(`eventHandler "name" cannot be empty`)
|
||||
default:
|
||||
return fmt.Errorf(`bad eventHandler "name": %q`, s)
|
||||
}
|
||||
}
|
||||
|
||||
func (e EventHandler) MarshalJSON() ([]byte, error) {
|
||||
if err := e.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(eventHandler(e))
|
||||
}
|
||||
|
||||
func (e *EventHandler) UnmarshalJSON(data []byte) error {
|
||||
var je eventHandler
|
||||
err := json.Unmarshal(data, &je)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ne := EventHandler(je)
|
||||
if err := ne.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*e = ne
|
||||
return nil
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type Exec []string
|
||||
|
||||
type exec Exec
|
||||
|
||||
func (e Exec) assertValid() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Exec) MarshalJSON() ([]byte, error) {
|
||||
if err := e.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(exec(e))
|
||||
}
|
||||
|
||||
func (e *Exec) UnmarshalJSON(data []byte) error {
|
||||
var je exec
|
||||
err := json.Unmarshal(data, &je)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ne := Exec(je)
|
||||
if err := ne.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*e = ne
|
||||
return nil
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
// Copyright 2015 The appc Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
maxHashSize = (sha512.Size / 2) + len("sha512-")
|
||||
)
|
||||
|
||||
// Hash encodes a hash specified in a string of the form:
|
||||
// "<type>-<value>"
|
||||
// for example
|
||||
// "sha512-06c733b1838136838e6d2d3e8fa5aea4c7905e92[...]"
|
||||
// Valid types are currently:
|
||||
// * sha512
|
||||
type Hash struct {
|
||||
typ string
|
||||
Val string
|
||||
}
|
||||
|
||||
func NewHash(s string) (*Hash, error) {
|
||||
elems := strings.Split(s, "-")
|
||||
if len(elems) != 2 {
|
||||
return nil, errors.New("badly formatted hash string")
|
||||
}
|
||||
nh := Hash{
|
||||
typ: elems[0],
|
||||
Val: elems[1],
|
||||
}
|
||||
if err := nh.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &nh, nil
|
||||
}
|
||||
|
||||
func (h Hash) String() string {
|
||||
return fmt.Sprintf("%s-%s", h.typ, h.Val)
|
||||
}
|
||||
|
||||
func (h *Hash) Set(s string) error {
|
||||
nh, err := NewHash(s)
|
||||
if err == nil {
|
||||
*h = *nh
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (h Hash) Empty() bool {
|
||||
return reflect.DeepEqual(h, Hash{})
|
||||
}
|
||||
|
||||
func (h Hash) assertValid() error {
|
||||
switch h.typ {
|
||||
case "sha512":
|
||||
case "":
|
||||
return fmt.Errorf("unexpected empty hash type")
|
||||
default:
|
||||
return fmt.Errorf("unrecognized hash type: %v", h.typ)
|
||||
}
|
||||
if h.Val == "" {
|
||||
return fmt.Errorf("unexpected empty hash value")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Hash) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
nh, err := NewHash(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*h = *nh
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h Hash) MarshalJSON() ([]byte, error) {
|
||||
if err := h.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(h.String())
|
||||
}
|
||||
|
||||
func NewHashSHA512(b []byte) *Hash {
|
||||
h := sha512.New()
|
||||
h.Write(b)
|
||||
nh, _ := NewHash(fmt.Sprintf("sha512-%x", h.Sum(nil)))
|
||||
return nh
|
||||
}
|
||||
|
||||
func ShortHash(hash string) string {
|
||||
if len(hash) > maxHashSize {
|
||||
return hash[:maxHashSize]
|
||||
}
|
||||
return hash
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user