add feature full text search

This commit is contained in:
phachon 2020-02-16 19:03:24 +08:00
parent 1877c8dec2
commit 3c9c94a730
98 changed files with 602865 additions and 72 deletions

View File

@ -6,8 +6,10 @@ import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/fatih/color"
"github.com/go-ego/riot/types"
"github.com/phachon/mm-wiki/app/models"
"github.com/phachon/mm-wiki/app/utils"
"github.com/phachon/mm-wiki/app/work"
"github.com/phachon/mm-wiki/global"
"github.com/snail007/go-activerecord/mysql"
"log"
@ -18,7 +20,6 @@ import (
)
var (
defaultConf = "conf/mm-wiki.conf"
confPath = flag.String("conf", "", "please set mm-wiki conf path")
@ -44,6 +45,8 @@ var (
ImageAbsDir = ""
AttachmentAbsDir = ""
SearchIndexAbsDir = ""
)
func init() {
@ -53,6 +56,8 @@ func init() {
initDB()
checkUpgrade()
initDocumentDir()
initSearch()
initWork()
StartTime = time.Now().Unix()
}
@ -180,36 +185,24 @@ func initDocumentDir() {
imagesAbsDir := path.Join(documentAbsDir, "images")
// attachment save dir
attachmentAbsDir := path.Join(documentAbsDir, "attachment")
// search index dir
searchIndexAbsDir := path.Join(documentAbsDir, "search-index")
MarkdownAbsDir = markDownAbsDir
ImageAbsDir = imagesAbsDir
AttachmentAbsDir = attachmentAbsDir
SearchIndexAbsDir = searchIndexAbsDir
// create markdown dir
ok, _ = utils.File.PathIsExists(markDownAbsDir)
if !ok {
err := os.Mkdir(markDownAbsDir, 0777)
if err != nil {
logs.Error("create document markdown dir " + markDownAbsDir + " error!")
os.Exit(1)
}
}
// create image dir
ok, _ = utils.File.PathIsExists(imagesAbsDir)
if !ok {
err := os.Mkdir(imagesAbsDir, 0777)
if err != nil {
logs.Error("create document image dir " + imagesAbsDir + " error!")
os.Exit(1)
}
}
// create attachment dir
ok, _ = utils.File.PathIsExists(attachmentAbsDir)
if !ok {
err := os.Mkdir(attachmentAbsDir, 0777)
if err != nil {
logs.Error("create document attachment dir " + attachmentAbsDir + " error!")
os.Exit(1)
dirList := []string{MarkdownAbsDir, ImageAbsDir, AttachmentAbsDir, SearchIndexAbsDir}
// create dir
for _, dir := range dirList {
ok, _ = utils.File.PathIsExists(dir)
if !ok {
err := os.Mkdir(dir, 0777)
if err != nil {
logs.Error("create document dir "+dir+" error=%s", err.Error())
os.Exit(1)
}
}
}
@ -252,3 +245,39 @@ func checkUpgrade() {
os.Exit(0)
}
}
func initSearch() {
gseFile := filepath.Join(RootDir, "docs/search_dict/dictionary.txt")
stopFile := filepath.Join(RootDir, "docs/search_dict/stop_tokens.txt")
ok, _ := utils.File.PathIsExists(gseFile)
if !ok {
logs.Error("search dict file " + gseFile + " is not exists!")
os.Exit(1)
}
ok, _ = utils.File.PathIsExists(stopFile)
if !ok {
logs.Error("search stop dict file " + stopFile + " is not exists!")
os.Exit(1)
}
global.DocSearcher.Init(types.EngineOpts{
UseStore: true,
StoreFolder: SearchIndexAbsDir,
Using: 3,
//GseDict: "zh",
GseDict: gseFile,
StopTokenFile: stopFile,
IndexerOpts: &types.IndexerOpts{
IndexType: types.LocsIndex,
},
})
}
func initWork() {
// 搜索索引 work
intervalTime, _ := beego.AppConfig.Int64("search::interval_time")
if intervalTime == 0 {
intervalTime = 30
}
work.InitDocSearchIndexWork(time.Duration(intervalTime) * time.Second)
}

View File

@ -1,7 +1,11 @@
package controllers
import (
"github.com/go-ego/riot/types"
"github.com/phachon/mm-wiki/app/models"
"github.com/phachon/mm-wiki/app/utils"
"github.com/phachon/mm-wiki/global"
"strings"
)
type MainController struct {
@ -137,35 +141,86 @@ func (this *MainController) About() {
this.viewLayout("main/about", "default")
}
// 搜索,支持根据标题和内容搜索
func (this *MainController) Search() {
page, _ := this.GetInt("page", 1)
documentName := this.GetString("document_name", "")
number, _ := this.GetRangeInt("number", 20, 10, 100)
limit := (page - 1) * number
keyword := strings.TrimSpace(this.GetString("keyword", ""))
searchType := this.GetString("search_type", "content")
this.Data["search_type"] = searchType
this.Data["keyword"] = keyword
this.Data["count"] = 0
if keyword == "" {
this.viewLayout("main/search", "default")
return
}
var documents = []map[string]string{}
var err error
var count int64
if documentName != "" {
count, err = models.DocumentModel.CountDocumentsLikeName(documentName)
if err != nil {
this.ErrorLog("搜索文档总数出错:" + err.Error())
this.ViewError("搜索文档错误!")
}
if count > 0 {
documents, err = models.DocumentModel.GetDocumentsByLikeNameAndLimit(documentName, limit, number)
if err != nil {
this.ErrorLog("搜索文档列表出错:" + err.Error())
this.ViewError("搜索文档错误!")
}
// 获取该用户有权限的空间
publicSpaces, err := models.SpaceModel.GetSpacesByVisitLevel(models.Space_VisitLevel_Public)
if err != nil {
this.ErrorLog("搜索文档列表获取用户空间权限出错:" + err.Error())
this.ViewError("搜索文档错误!")
}
spaceUsers, err := models.SpaceUserModel.GetSpaceUsersByUserId(this.UserId)
if err != nil {
this.ErrorLog("搜索文档列表获取用户空间权限出错:" + err.Error())
this.ViewError("搜索文档错误!")
}
spaceIdsMap := make(map[string]bool)
for _, publicSpace := range publicSpaces {
spaceIdsMap[publicSpace["space_id"]] = true
}
for _, spaceUser := range spaceUsers {
if _, ok := spaceIdsMap[spaceUser["space_id"]]; !ok {
spaceIdsMap[spaceUser["space_id"]] = true
}
}
searchDocContents := make(map[string]string)
// 默认根据内容搜索
if searchType == "title" {
documents, err = models.DocumentModel.GetDocumentsByLikeName(keyword)
} else {
searchRes := global.DocSearcher.SearchDoc(types.SearchReq{Text: keyword})
searchDocIds := []string{}
for _, searchDoc := range searchRes.Docs {
if len(searchDoc.TokenSnippetLocs) == 0 {
continue
}
docId := searchDoc.DocId
content := searchDoc.Content
locIndex := searchDoc.TokenSnippetLocs[0]
searchContent := utils.Misc.SubStrUnicodeBySubStrIndex(content, keyword, locIndex, 30, 30)
searchDocContents[docId] = searchContent
searchDocIds = append(searchDocIds, docId)
}
documents, err = models.DocumentModel.GetDocumentsByDocumentIds(searchDocIds)
}
if err != nil {
this.ErrorLog("搜索文档出错:" + err.Error())
this.ViewError("搜索文档错误!")
}
// 过滤一下没权限的空间
realDocuments := []map[string]string{}
for _, document := range documents {
spaceId, _ := document["space_id"]
documentId, _ := document["document_id"]
if _, ok := spaceIdsMap[spaceId]; !ok {
continue
}
if searchType != "title" {
searchContent, ok := searchDocContents[documentId]
if !ok || searchContent == "" {
continue
}
document["search_content"] = searchContent
}
realDocuments = append(realDocuments, document)
}
this.Data["document_name"] = documentName
this.Data["documents"] = documents
this.Data["count"] = count
this.SetPaginator(number, count)
this.Data["search_type"] = searchType
this.Data["keyword"] = keyword
this.Data["documents"] = realDocuments
this.Data["count"] = len(realDocuments)
this.viewLayout("main/search", "default")
}

View File

@ -528,6 +528,19 @@ func (d *Document) GetAllDocumentsByDocumentIds(documentIds []string) (documents
return
}
func (d *Document) GetAllDocuments() (documents []map[string]string, err error) {
db := G.DB()
var rs *mysql.ResultSet
rs, err = db.Query(db.AR().From(Table_Document_Name).Where(map[string]interface{}{
"is_delete": Document_Delete_False,
}))
if err != nil {
return
}
documents = rs.Rows()
return
}
func (d *Document) GetParentDocumentsByDocument(document map[string]string) (parentDocuments []map[string]string, pageFile string, err error) {
if document["parent_id"] == "0" {
@ -629,3 +642,18 @@ func (d *Document) GetDocumentGroupEditUserId() (documents []map[string]string,
documents = rs.Rows()
return
}
// 根据文档信息获取文档内容和文件地址
func (d *Document) GetDocumentContentByDocument(doc map[string]string) (content string, pageFile string, err error) {
// get document page file
_, pageFile, err = DocumentModel.GetParentDocumentsByDocument(doc)
if err != nil {
return content, pageFile, err
}
// get document content
content, err = utils.Document.GetContentByPageFile(pageFile)
if err != nil {
return content, pageFile, err
}
return content, pageFile, nil
}

View File

@ -264,6 +264,23 @@ func (s *Space) GetSpaces() (spaces []map[string]string, err error) {
return
}
// get spaces by visitLevel
func (s *Space) GetSpacesByVisitLevel(visitLevel string) (spaces []map[string]string, err error) {
db := G.DB()
var rs *mysql.ResultSet
rs, err = db.Query(
db.AR().From(Table_Space_Name).Where(map[string]interface{}{
"visit_level": visitLevel,
"is_delete": Space_Delete_False,
}))
if err != nil {
return
}
spaces = rs.Rows()
return
}
// get space count
func (s *Space) CountSpaces() (count int64, err error) {

View File

@ -150,3 +150,62 @@ func (m *misc) Page(total, page, pagesize int, url string, args ...interface{})
}
return ""
}
// 获取字符串子串的位置
func (m *misc) GetStrUnicodeIndex(str string, substr string) int {
// 子串在字符串的字节位置
result := strings.Index(str, substr)
if result >= 0 {
return m.GetStrUnicodeIndexByByteIndex(str, result)
}
return -1
}
// 根据字符串字节位置获取字符串位置
func (m *misc) GetStrUnicodeIndexByByteIndex(str string, subStrByteIndex int) int {
if subStrByteIndex > len(str)-1 {
return -1
}
// 获得子串之前的字符串并转换成[]byte
prefix := []byte(str)[0:subStrByteIndex]
// 将子串之前的字符串转换成[]rune
rs := []rune(string(prefix))
// 获得子串之前的字符串的长度,便是子串在字符串的字符位置
result := len(rs)
return result
}
// 截取包含子字符串的一段字符串,前后截取
func (m *misc) SubStrUnicode(str string, subStr string, preLen int, sufLen int) string {
subStrRune := []rune(subStr)
strRune := []rune(str)
count := len(strRune)
subStrUnicodeIndex := m.GetStrUnicodeIndex(str, subStr)
startIndex := 0
endIndex := count - 1
if subStrUnicodeIndex-preLen > 0 {
startIndex = subStrUnicodeIndex - preLen
}
if subStrUnicodeIndex+len(subStrRune)+sufLen < count-1 {
endIndex = subStrUnicodeIndex + len(subStrRune) + sufLen
}
return string(strRune[startIndex:endIndex])
}
// 截取包含子字符串的一段字符串,前后截取
// subStrIndex 已只子串的字节位置
func (m *misc) SubStrUnicodeBySubStrIndex(str string, subStr string, subStrIndex int, preLen int, sufLen int) string {
subStrRune := []rune(subStr)
strRune := []rune(str)
count := len(strRune)
subStrUnicodeIndex := m.GetStrUnicodeIndexByByteIndex(str, subStrIndex)
startIndex := 0
endIndex := count - 1
if subStrUnicodeIndex-preLen > 0 {
startIndex = subStrUnicodeIndex - preLen
}
if subStrUnicodeIndex+len(subStrRune)+sufLen < count-1 {
endIndex = subStrUnicodeIndex + len(subStrRune) + sufLen
}
return string(strRune[startIndex:endIndex])
}

70
app/work/doc_search.go Normal file
View File

@ -0,0 +1,70 @@
package work
import (
"github.com/astaxie/beego/logs"
"github.com/go-ego/riot/types"
"github.com/phachon/mm-wiki/app/models"
"github.com/phachon/mm-wiki/global"
"sync"
"time"
)
// 初始化文档搜索索引 Work
func InitDocSearchIndexWork(t time.Duration) {
loadDocSearchIndex()
go func() {
defer func() {
e := recover()
if e != nil {
logs.Info("load search index panic: %v", e)
}
}()
for {
select {
case <- time.Tick(t):
loadDocSearchIndex()
}
}
}()
}
// 加载索引文件
func loadDocSearchIndex() {
logs.Info("[loadSearchIndex] start load doc index")
allDocs, err := models.DocumentModel.GetAllDocuments()
if err != nil {
logs.Error("[loadSearchIndex] getAllDocuments err: %s", err.Error())
return
}
logs.Info("[loadSearchIndex] get all doc finish")
wait := sync.WaitGroup{}
for _, doc := range allDocs {
if len(doc) == 0 {
continue
}
wait.Add(1)
go func(doc map[string]string) {
docId, _ := doc["document_id"]
defer func() {
e := recover()
if e != nil {
logs.Error("[loadSearchIndex] get documentId=%s content panic: %v", docId, e)
}
wait.Done()
}()
var err error
content, _, err := models.DocumentModel.GetDocumentContentByDocument(doc)
if err != nil {
logs.Error("[loadSearchIndex] get documentId=%s content err: %s", docId, err.Error())
return
}
// add search index
data := types.DocData{Content: content}
global.DocSearcher.IndexDoc(docId, data)
}(doc)
}
wait.Wait()
logs.Info("[loadSearchIndex] add index finish")
global.DocSearcher.Flush()
logs.Info("[loadSearchIndex] index flush finish")
}

View File

@ -89,4 +89,10 @@ file="{"level":7, "filename":"logs/mm-wiki.log"}"
# ----------------------------------------------------------------------
[document]
# 文档的根目录,最好写绝对路径
root_dir="./data"
root_dir="./data"
# 搜索配置
# ----------------------------------------------------------------------
[search]
# 搜索索引更新间隔时间(单位秒),默认为 30如果文档数过多建议增加时长
interval_time=30

View File

@ -89,4 +89,10 @@ file="{"level":7, "filename":"#log.filename#"}"
# ----------------------------------------------------------------------
[document]
# 文档的根目录,最好写绝对路径
root_dir="#document_dir#"
root_dir="#document_dir#"
# 搜索配置
# ----------------------------------------------------------------------
[search]
# 搜索索引更新间隔时间(单位秒),默认为 30如果文档数过多建议增加时长
interval_time=30

3
docs/search_dict/README.md Executable file
View File

@ -0,0 +1,3 @@
dictionary.txt 词典拷贝自 [github.com/fxsjy/jieba](https://github.com/fxsjy/jieba)
stop_tokens.txt 停用词列表来自网络

589032
docs/search_dict/dictionary.txt Executable file

File diff suppressed because it is too large Load Diff

1161
docs/search_dict/stop_tokens.txt Executable file

File diff suppressed because it is too large Load Diff

10
global/search.go Normal file
View File

@ -0,0 +1,10 @@
package global
import (
"github.com/go-ego/riot"
)
// 文档搜索
var (
DocSearcher = riot.Engine{}
)

11
go.mod
View File

@ -2,26 +2,25 @@ module github.com/phachon/mm-wiki
go 1.12
replace github.com/coreos/go-systemd => ./vendor/github.com/coreos/go-systemd
require (
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
github.com/astaxie/beego v1.12.0
github.com/fatih/color v1.7.0
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/go-ego/riot v0.0.0-20191215221243-bdbc256e4cbd
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible
github.com/go-sql-driver/mysql v1.4.1
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.10 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/onsi/ginkgo v1.10.3 // indirect
github.com/onsi/gomega v1.7.1 // indirect
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
github.com/shirou/gopsutil v2.19.11+incompatible
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/snail007/go-activerecord v0.0.0-20190813031814-2ac2f3d7cff0
github.com/stretchr/testify v1.4.0 // indirect
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect
google.golang.org/appengine v1.6.5 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/russross/blackfriday.v2 v2.0.0

218
go.sum
View File

@ -1,23 +1,57 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM=
github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/astaxie/beego v1.12.0 h1:MRhVoeeye5N+Flul5PoVfD9CslfdoH+xqC/xvSQ5u2Y=
github.com/astaxie/beego v1.12.0/go.mod h1:fysx+LZNZKnvh4GED/xND7jWtjCR6HzydR2Hh2Im57o=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger v1.6.0 h1:DshxFxZWXUcO0xX476VJC07Xsr6ZCBVRHKZ93Oh7Evo=
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20191112170834-c2139c5d712b h1:SeiGBzKrEtuDddnBABHkp4kq9sBGE9nuYmk6FPTg0zg=
github.com/dgryski/go-farm v0.0.0-20191112170834-c2139c5d712b/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
@ -25,6 +59,23 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-ego/cedar v0.0.0-20191026170511-cf63283d1a1d/go.mod h1:2tj8NEwz6OsMT56YhN7mL7dYCncJs7dOmz/Xki1ofCU=
github.com/go-ego/cedar v0.0.0-20191129121850-43a3778f11e4 h1:FjeZ5bryMJYMG5L7CX3YjlZWJnMSPfcEXU3WFQSXn3U=
github.com/go-ego/cedar v0.0.0-20191129121850-43a3778f11e4/go.mod h1:fEBCL8k9n1uTGCgRVWR/ANO+13P6wx6l9Q1tCKLzqIw=
github.com/go-ego/gpy v0.0.0-20191128165300-d7ffd622e115 h1:a/VNeZI9+jEhqhxcLXkew3bHWsJUHvi1PEyCAg7lfCs=
github.com/go-ego/gpy v0.0.0-20191128165300-d7ffd622e115/go.mod h1:XHtccmBUOwbRnjkNY/vOijl/XnNIvBH8d6gTUKWYwhA=
github.com/go-ego/gse v0.0.0-20191212182315-b30156ea952e h1:Y3PLgnqXxfnAJA1myyb2W8KHpLUqcR9HOGDTgEYoU1U=
github.com/go-ego/gse v0.0.0-20191212182315-b30156ea952e/go.mod h1:CEs4Qx+i8pnxDPG0Tlbb1GqVI4NxCbgNHcy+lQiDyTQ=
github.com/go-ego/murmur v0.0.0-20191001133222-eab2da088fb4 h1:9MiRSwErSh7DPk2M0LErU89FW4WNZokN4sTQ+fhwfc0=
github.com/go-ego/murmur v0.0.0-20191001133222-eab2da088fb4/go.mod h1:lU1gHnF7CmWCb66VY6nH4NsCavMUA4ESUMAE9iO5FSY=
github.com/go-ego/riot v0.0.0-20191215221243-bdbc256e4cbd h1:uw+EiJiRhkyM12e99tSAvSGqTPzAe2cO42HT3z+CGeQ=
github.com/go-ego/riot v0.0.0-20191215221243-bdbc256e4cbd/go.mod h1:YwkIszOT6fKxoasVtGyS1VonfABf6Xd9coks9a7umtY=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE=
@ -33,33 +84,109 @@ github.com/go-redis/redis v6.14.2+incompatible h1:UE9pLhzmWf+xHNmZsoccjXosPicuiN
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-vgo/grpclb v0.0.0-20181128171039-89526b0a742e/go.mod h1:RLBU+1pK/TEou3dxzHEhdxhgwuSFTq8UYahALbc6tN4=
github.com/go-vgo/gt v0.0.0-20191129122048-b43b4ceeec26 h1:r6LQVeALyMwvQv9VEbOiXbYoqQNxDtWfOpbUohptyrw=
github.com/go-vgo/gt v0.0.0-20191129122048-b43b4ceeec26/go.mod h1:nOd/5lXyOr4ILPGo4nO6iEgKxb8v++eaZPDLb29KooE=
github.com/go-vgo/gt/conf v0.0.0-20190822172343-e8dae7b097a1/go.mod h1:YWt7auzuo/LKrRh82hRFvmoXRSrCjgITXPbpgjM+sMo=
github.com/go-vgo/gt/conf v0.0.0-20191129122048-b43b4ceeec26 h1:SQtIeXQn3NIEyqldM1dzGN3C/kf8PBsdo79gkTpPD3o=
github.com/go-vgo/gt/conf v0.0.0-20191129122048-b43b4ceeec26/go.mod h1:Ba0HbzBCvoeeYe9/gkghpwz913uTgJg49+26G6wbV4c=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jinzhu/now v0.0.0-20181116074157-8ec929ed50c3/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY=
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.3.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20191202183732-d1d2010b5bee/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
github.com/shirou/gopsutil v2.18.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil v2.19.11+incompatible h1:lJHR0foqAjI4exXqWsU3DbH7bX1xvdhGdnXTIARA9W4=
github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=
@ -69,43 +196,118 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/snail007/go-activerecord v0.0.0-20190813031814-2ac2f3d7cff0 h1:zru9A2cEdHTi1ly8KaAMMw8sS93U2nB1LSvc+YwUCc4=
github.com/snail007/go-activerecord v0.0.0-20190813031814-2ac2f3d7cff0/go.mod h1:2oXFU20cqErcrOV0B5VjMDuEZBcT9RUUL89uBR3QjsE=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/vcaesar/tt v0.0.0-20181105181427-53fc2b08e954/go.mod h1:xKkGp+ufbz/1DQmNxdbAMFqZJOVIJEX7dGvLZMhPIWg=
github.com/vcaesar/tt v0.0.0-20190922170245-9d197389a6ac/go.mod h1:xKkGp+ufbz/1DQmNxdbAMFqZJOVIJEX7dGvLZMhPIWg=
github.com/vcaesar/tt v0.0.0-20191103173835-6896a351024b h1:psGhQitWSo4KBpLghvJPlhHxTJ8LQl1y0ekjSreqvu4=
github.com/vcaesar/tt v0.0.0-20191103173835-6896a351024b/go.mod h1:GHPxQYhn+7OgKakRusH7KJ0M5MhywoeLb8Fcffs/Gtg=
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v3.3.10+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0 h1:2mqDk8w/o6UmeUCu5Qiq2y7iMf6anbx+YA8d1JFoFrs=
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916165910-8a69140bde95/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ=
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/russross/blackfriday.v2 v2.0.0 h1:+FlnIV8DSQnT7NZ43hcVKcdJdzZoeCmJj4Ql8gq5keA=
gopkg.in/russross/blackfriday.v2 v2.0.0/go.mod h1:6sSBNz/GtOm/pJTuh5UmBK2ZHfmnxGbl2NZg1UliSOI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
@ -113,5 +315,9 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -487,4 +487,12 @@ select.pagination-select-xs:hover {
#attach_section .progress-bar {
font-size:10px;
line-height:14px;
}
mark {
padding: 1px 2px;
margin: 0 -2px;
background-color: #fff34d;
border-radius: 3px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.7);
}

1068
static/plugins/mark/mark.js Normal file

File diff suppressed because it is too large Load Diff

7
static/plugins/mark/mark.min.js vendored Normal file

File diff suppressed because one or more lines are too long

44
vendor/github.com/coreos/go-systemd/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,44 @@
dist: trusty
sudo: required
services:
- docker
language: go
go:
- "1.12.x"
- "1.13.x"
go_import_path: github.com/coreos/go-systemd
env:
global:
- IMPORTPATH=github.com/coreos/go-systemd
- GOPATH=/opt
- GO111MODULE=on
- DEP_BINDIR=/tmp
- BUILD_DIR=/opt/src/github.com/coreos/go-systemd
matrix:
- DOCKER_BASE=ubuntu:16.04
- DOCKER_BASE=ubuntu:18.04
- DOCKER_BASE=debian:stretch
before_install:
- sudo apt-get -qq update
- sudo apt-get install -y libsystemd-journal-dev libsystemd-daemon-dev
- docker pull ${DOCKER_BASE}
- docker run --privileged -e GOPATH=${GOPATH} --cidfile=/tmp/cidfile ${DOCKER_BASE} /bin/bash -c "apt-get update && apt-get install -y sudo build-essential git golang dbus libsystemd-dev libpam-systemd systemd-container"
- docker commit `cat /tmp/cidfile` go-systemd/container-tests
- rm -f /tmp/cidfile
install:
- docker run --shm-size=2gb -d --cidfile=/tmp/cidfile --privileged -e GOPATH=${GOPATH} -v ${PWD}:${BUILD_DIR} go-systemd/container-tests /bin/systemd --system
script:
- ./scripts/travis/pr-test.sh go_fmt
- ./scripts/travis/pr-test.sh build_source
- ./scripts/travis/pr-test.sh build_tests
- docker exec --privileged `cat /tmp/cidfile` /bin/bash -c "cd ${BUILD_DIR} && ./scripts/travis/pr-test.sh run_tests"
- ./scripts/travis/pr-test.sh go_vet
- ./scripts/travis/pr-test.sh license_check
after_script:
- docker kill `cat /tmp/cidfile`

77
vendor/github.com/coreos/go-systemd/CONTRIBUTING.md generated vendored Normal file
View File

@ -0,0 +1,77 @@
# How to Contribute
CoreOS projects are [Apache 2.0 licensed](LICENSE) and accept contributions via
GitHub pull requests. This document outlines some of the conventions on
development workflow, commit message formatting, contact points and other
resources to make it easier to get your contribution accepted.
# Certificate of Origin
By contributing to this project you agree to the Developer Certificate of
Origin (DCO). This document was created by the Linux Kernel community and is a
simple statement that you, as a contributor, have the legal right to make the
contribution. See the [DCO](DCO) file for details.
# Email and Chat
The project currently uses the general CoreOS email list and IRC channel:
- Email: [coreos-dev](https://groups.google.com/forum/#!forum/coreos-dev)
- IRC: #[coreos](irc://irc.freenode.org:6667/#coreos) IRC channel on freenode.org
Please avoid emailing maintainers found in the MAINTAINERS file directly. They
are very busy and read the mailing lists.
## Getting Started
- Fork the repository on GitHub
- Read the [README](README.md) for build and test instructions
- Play with the project, submit bugs, submit patches!
## Contribution Flow
This is a rough outline of what a contributor's workflow looks like:
- Create a topic branch from where you want to base your work (usually master).
- Make commits of logical units.
- Make sure your commit messages are in the proper format (see below).
- Push your changes to a topic branch in your fork of the repository.
- Make sure the tests pass, and add any new tests as appropriate.
- Submit a pull request to the original repository.
Thanks for your contributions!
### Coding Style
CoreOS projects written in Go follow a set of style guidelines that we've documented
[here](https://github.com/coreos/docs/tree/master/golang). Please follow them when
working on your contributions.
### Format of the Commit Message
We follow a rough convention for commit messages that is designed to answer two
questions: what changed and why. The subject line should feature the what and
the body of the commit should describe the why.
```
scripts: add the test-cluster command
this uses tmux to setup a test cluster that you can easily kill and
start for debugging.
Fixes #38
```
The format can be described more formally as follows:
```
<subsystem>: <what changed>
<BLANK LINE>
<why this change was made>
<BLANK LINE>
<footer>
```
The first line is the subject and should be no longer than 70 characters, the
second line is always blank, and other lines should be wrapped at 80 characters.
This allows the message to be easier to read on GitHub as well as in various
git tools.

36
vendor/github.com/coreos/go-systemd/DCO generated vendored Normal file
View File

@ -0,0 +1,36 @@
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.

38
vendor/github.com/coreos/go-systemd/Jenkinsfile generated vendored Normal file
View File

@ -0,0 +1,38 @@
matrixJob('Periodic go-systemd builder') {
label('master')
displayName('Periodic go-systemd builder (master branch)')
scm {
git {
remote {
url('https://github.com/coreos/go-systemd.git')
}
branch('master')
}
}
concurrentBuild()
triggers {
cron('@daily')
}
axes {
label('os_type', 'debian-testing', 'fedora-24', 'fedora-25')
}
wrappers {
buildNameSetter {
template('go-systemd master (periodic #${BUILD_NUMBER})')
runAtStart(true)
runAtEnd(true)
}
timeout {
absolute(25)
}
}
steps {
shell('./scripts/jenkins/periodic-go-systemd-builder.sh')
}
}

191
vendor/github.com/coreos/go-systemd/LICENSE generated vendored Normal file
View File

@ -0,0 +1,191 @@
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:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
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
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.

5
vendor/github.com/coreos/go-systemd/NOTICE generated vendored Normal file
View File

@ -0,0 +1,5 @@
CoreOS Project
Copyright 2018 CoreOS, Inc
This product includes software developed at CoreOS, Inc.
(http://www.coreos.com/).

71
vendor/github.com/coreos/go-systemd/README.md generated vendored Normal file
View File

@ -0,0 +1,71 @@
# go-systemd
[![Build Status](https://travis-ci.org/coreos/go-systemd.png?branch=master)](https://travis-ci.org/coreos/go-systemd)
[![godoc](https://godoc.org/github.com/coreos/go-systemd?status.svg)](http://godoc.org/github.com/coreos/go-systemd)
![minimum golang 1.12](https://img.shields.io/badge/golang-1.12%2B-orange.svg)
Go bindings to systemd. The project has several packages:
- `activation` - for writing and using socket activation from Go
- `daemon` - for notifying systemd of service status changes
- `dbus` - for starting/stopping/inspecting running services and units
- `journal` - for writing to systemd's logging service, journald
- `sdjournal` - for reading from journald by wrapping its C API
- `login1` - for integration with the systemd logind API
- `machine1` - for registering machines/containers with systemd
- `unit` - for (de)serialization and comparison of unit files
## Socket Activation
An example HTTP server using socket activation can be quickly set up by following this README on a Linux machine running systemd:
https://github.com/coreos/go-systemd/tree/master/examples/activation/httpserver
## systemd Service Notification
The `daemon` package is an implementation of the [sd_notify protocol](https://www.freedesktop.org/software/systemd/man/sd_notify.html#Description). It can be used to inform systemd of service start-up completion, watchdog events, and other status changes.
## D-Bus
The `dbus` package connects to the [systemd D-Bus API](http://www.freedesktop.org/wiki/Software/systemd/dbus/) and lets you start, stop and introspect systemd units. The API docs are here:
http://godoc.org/github.com/coreos/go-systemd/dbus
### Debugging
Create `/etc/dbus-1/system-local.conf` that looks like this:
```
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow eavesdrop="true"/>
<allow eavesdrop="true" send_destination="*"/>
</policy>
</busconfig>
```
## Journal
### Writing to the Journal
Using the pure-Go `journal` package you can submit journal entries directly to systemd's journal, taking advantage of features like indexed key/value pairs for each log entry.
### Reading from the Journal
The `sdjournal` package provides read access to the journal by wrapping around journald's native C API; consequently it requires cgo and the journal headers to be available.
## logind
The `login1` package provides functions to integrate with the [systemd logind API](http://www.freedesktop.org/wiki/Software/systemd/logind/).
## machined
The `machine1` package allows interaction with the [systemd machined D-Bus API](http://www.freedesktop.org/wiki/Software/systemd/machined/).
## Units
The `unit` package provides various functions for working with [systemd unit files](http://www.freedesktop.org/software/systemd/man/systemd.unit.html).

View File

@ -0,0 +1,31 @@
// Copyright 2018 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 activation
import (
"fmt"
"os"
)
// exampleCmd returns the command line for the specified example binary.
func exampleCmd(binaryName string) (string, []string) {
sourcePath := fmt.Sprintf("../examples/activation/%s.go", binaryName)
sourceCmdLine := []string{"go", "run", sourcePath}
binaryPath := fmt.Sprintf("../test_bins/%s.example", binaryName)
if _, err := os.Stat(binaryPath); err != nil && os.IsNotExist(err) {
return sourceCmdLine[0], sourceCmdLine[1:]
}
return binaryPath, []string{binaryPath}
}

View File

@ -0,0 +1,67 @@
// 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 activation implements primitives for systemd socket activation.
package activation
import (
"os"
"strconv"
"strings"
"syscall"
)
const (
// listenFdsStart corresponds to `SD_LISTEN_FDS_START`.
listenFdsStart = 3
)
// Files returns a slice containing a `os.File` object for each
// file descriptor passed to this process via systemd fd-passing protocol.
//
// The order of the file descriptors is preserved in the returned slice.
// `unsetEnv` is typically set to `true` in order to avoid clashes in
// fd usage and to avoid leaking environment flags to child processes.
func Files(unsetEnv bool) []*os.File {
if unsetEnv {
defer os.Unsetenv("LISTEN_PID")
defer os.Unsetenv("LISTEN_FDS")
defer os.Unsetenv("LISTEN_FDNAMES")
}
pid, err := strconv.Atoi(os.Getenv("LISTEN_PID"))
if err != nil || pid != os.Getpid() {
return nil
}
nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS"))
if err != nil || nfds == 0 {
return nil
}
names := strings.Split(os.Getenv("LISTEN_FDNAMES"), ":")
files := make([]*os.File, 0, nfds)
for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ {
syscall.CloseOnExec(fd)
name := "LISTEN_FD_" + strconv.Itoa(fd)
offset := fd - listenFdsStart
if offset < len(names) && len(names[offset]) > 0 {
name = names[offset]
}
files = append(files, os.NewFile(uintptr(fd), name))
}
return files
}

View File

@ -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 activation
import (
"bytes"
"io"
"os"
"os/exec"
"testing"
)
// correctStringWritten fails the text if the correct string wasn't written
// to the other side of the pipe.
func correctStringWritten(t *testing.T, r *os.File, expected string) bool {
bytes := make([]byte, len(expected))
io.ReadAtLeast(r, bytes, len(expected))
if string(bytes) != expected {
t.Fatalf("Unexpected string %s", string(bytes))
}
return true
}
// TestActivation forks out a copy of activation.go example and reads back two
// strings from the pipes that are passed in.
func TestActivation(t *testing.T) {
arg0, cmdline := exampleCmd("activation")
cmd := exec.Command(arg0, cmdline...)
r1, w1, _ := os.Pipe()
r2, w2, _ := os.Pipe()
cmd.ExtraFiles = []*os.File{
w1,
w2,
}
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "LISTEN_FDNAMES=fd1", "FIX_LISTEN_PID=1")
err := cmd.Run()
if err != nil {
t.Fatalf(err.Error())
}
correctStringWritten(t, r1, "Hello world: fd1")
correctStringWritten(t, r2, "Goodbye world: LISTEN_FD_4")
}
func TestActivationNoFix(t *testing.T) {
arg0, cmdline := exampleCmd("activation")
cmd := exec.Command(arg0, cmdline...)
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "LISTEN_FDS=2")
out, _ := cmd.CombinedOutput()
if !bytes.Contains(out, []byte("No files")) {
t.Fatalf("Child didn't error out as expected")
}
}
func TestActivationNoFiles(t *testing.T) {
arg0, cmdline := exampleCmd("activation")
cmd := exec.Command(arg0, cmdline...)
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "LISTEN_FDS=0", "FIX_LISTEN_PID=1")
out, _ := cmd.CombinedOutput()
if !bytes.Contains(out, []byte("No files")) {
t.Fatalf("Child didn't error out as expected")
}
}

View File

@ -0,0 +1,103 @@
// 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 activation
import (
"crypto/tls"
"net"
)
// Listeners returns a slice containing a net.Listener for each matching socket type
// passed to this process.
//
// The order of the file descriptors is preserved in the returned slice.
// Nil values are used to fill any gaps. For example if systemd were to return file descriptors
// corresponding with "udp, tcp, tcp", then the slice would contain {nil, net.Listener, net.Listener}
func Listeners() ([]net.Listener, error) {
files := Files(true)
listeners := make([]net.Listener, len(files))
for i, f := range files {
if pc, err := net.FileListener(f); err == nil {
listeners[i] = pc
f.Close()
}
}
return listeners, nil
}
// ListenersWithNames maps a listener name to a set of net.Listener instances.
func ListenersWithNames() (map[string][]net.Listener, error) {
files := Files(true)
listeners := map[string][]net.Listener{}
for _, f := range files {
if pc, err := net.FileListener(f); err == nil {
current, ok := listeners[f.Name()]
if !ok {
listeners[f.Name()] = []net.Listener{pc}
} else {
listeners[f.Name()] = append(current, pc)
}
f.Close()
}
}
return listeners, nil
}
// TLSListeners returns a slice containing a net.listener for each matching TCP socket type
// passed to this process.
// It uses default Listeners func and forces TCP sockets handlers to use TLS based on tlsConfig.
func TLSListeners(tlsConfig *tls.Config) ([]net.Listener, error) {
listeners, err := Listeners()
if listeners == nil || err != nil {
return nil, err
}
if tlsConfig != nil {
for i, l := range listeners {
// Activate TLS only for TCP sockets
if l.Addr().Network() == "tcp" {
listeners[i] = tls.NewListener(l, tlsConfig)
}
}
}
return listeners, err
}
// TLSListenersWithNames maps a listener name to a net.Listener with
// the associated TLS configuration.
func TLSListenersWithNames(tlsConfig *tls.Config) (map[string][]net.Listener, error) {
listeners, err := ListenersWithNames()
if listeners == nil || err != nil {
return nil, err
}
if tlsConfig != nil {
for _, ll := range listeners {
// Activate TLS only for TCP sockets
for i, l := range ll {
if l.Addr().Network() == "tcp" {
ll[i] = tls.NewListener(l, tlsConfig)
}
}
}
}
return listeners, err
}

View File

@ -0,0 +1,87 @@
// 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 activation
import (
"io"
"net"
"os"
"os/exec"
"testing"
)
// correctStringWritten fails the text if the correct string wasn't written
// to the other side of the pipe.
func correctStringWrittenNet(t *testing.T, r net.Conn, expected string) bool {
bytes := make([]byte, len(expected))
io.ReadAtLeast(r, bytes, len(expected))
if string(bytes) != expected {
t.Fatalf("Unexpected string %s", string(bytes))
}
return true
}
// TestActivation forks out a copy of activation.go example and reads back two
// strings from the pipes that are passed in.
func TestListeners(t *testing.T) {
arg0, cmdline := exampleCmd("listen")
cmd := exec.Command(arg0, cmdline...)
l1, err := net.Listen("tcp", ":9999")
if err != nil {
t.Fatalf(err.Error())
}
l2, err := net.Listen("tcp", ":1234")
if err != nil {
t.Fatalf(err.Error())
}
t1 := l1.(*net.TCPListener)
t2 := l2.(*net.TCPListener)
f1, _ := t1.File()
f2, _ := t2.File()
cmd.ExtraFiles = []*os.File{
f1,
f2,
}
r1, err := net.Dial("tcp", "127.0.0.1:9999")
if err != nil {
t.Fatalf(err.Error())
}
r1.Write([]byte("Hi"))
r2, err := net.Dial("tcp", "127.0.0.1:1234")
if err != nil {
t.Fatalf(err.Error())
}
r2.Write([]byte("Hi"))
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "LISTEN_FDNAMES=fd1:fd2", "FIX_LISTEN_PID=1")
out, err := cmd.CombinedOutput()
if err != nil {
println(string(out))
t.Fatalf(err.Error())
}
correctStringWrittenNet(t, r1, "Hello world: fd1")
correctStringWrittenNet(t, r2, "Goodbye world: fd2")
}

View File

@ -0,0 +1,38 @@
// 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 activation
import (
"net"
)
// PacketConns returns a slice containing a net.PacketConn for each matching socket type
// passed to this process.
//
// The order of the file descriptors is preserved in the returned slice.
// Nil values are used to fill any gaps. For example if systemd were to return file descriptors
// corresponding with "udp, tcp, udp", then the slice would contain {net.PacketConn, nil, net.PacketConn}
func PacketConns() ([]net.PacketConn, error) {
files := Files(true)
conns := make([]net.PacketConn, len(files))
for i, f := range files {
if pc, err := net.FilePacketConn(f); err == nil {
conns[i] = pc
f.Close()
}
}
return conns, nil
}

View File

@ -0,0 +1,69 @@
// 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 activation
import (
"net"
"os"
"os/exec"
"testing"
)
// TestActivation forks out a copy of activation.go example and reads back two
// strings from the pipes that are passed in.
func TestPacketConns(t *testing.T) {
arg0, cmdline := exampleCmd("udpconn")
cmd := exec.Command(arg0, cmdline...)
u1, err := net.ListenUDP("udp", &net.UDPAddr{Port: 9999})
if err != nil {
t.Fatalf(err.Error())
}
u2, err := net.ListenUDP("udp", &net.UDPAddr{Port: 1234})
if err != nil {
t.Fatalf(err.Error())
}
f1, _ := u1.File()
f2, _ := u2.File()
cmd.ExtraFiles = []*os.File{
f1,
f2,
}
r1, err := net.Dial("udp", "127.0.0.1:9999")
if err != nil {
t.Fatalf(err.Error())
}
r1.Write([]byte("Hi"))
r2, err := net.Dial("udp", "127.0.0.1:1234")
if err != nil {
t.Fatalf(err.Error())
}
r2.Write([]byte("Hi"))
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "LISTEN_FDNAMES=fd1:fd2", "FIX_LISTEN_PID=1")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("Cmd output '%s', err: '%s'\n", out, err)
}
correctStringWrittenNet(t, r1, "Hello world")
correctStringWrittenNet(t, r2, "Goodbye world")
}

61
vendor/github.com/coreos/go-systemd/code-of-conduct.md generated vendored Normal file
View File

@ -0,0 +1,61 @@
## CoreOS Community Code of Conduct
### Contributor Code of Conduct
As contributors and maintainers of this project, and in the interest of
fostering an open and welcoming community, we pledge to respect all people who
contribute through reporting issues, posting feature requests, updating
documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free
experience for everyone, regardless of level of experience, gender, gender
identity and expression, sexual orientation, disability, personal appearance,
body size, race, ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing others' private information, such as physical or electronic addresses, without explicit permission
* Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct. By adopting this Code of Conduct,
project maintainers commit themselves to fairly and consistently applying these
principles to every aspect of managing this project. Project maintainers who do
not follow or enforce the Code of Conduct may be permanently removed from the
project team.
This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting a project maintainer, Brandon Philips
<brandon.philips@coreos.com>, and/or Rithu John <rithu.john@coreos.com>.
This Code of Conduct is adapted from the Contributor Covenant
(http://contributor-covenant.org), version 1.2.0, available at
http://contributor-covenant.org/version/1/2/0/
### CoreOS Events Code of Conduct
CoreOS events are working conferences intended for professional networking and
collaboration in the CoreOS community. Attendees are expected to behave
according to professional standards and in accordance with their employers
policies on appropriate workplace behavior.
While at CoreOS events or related social networking opportunities, attendees
should not engage in discriminatory or offensive speech or actions including
but not limited to gender, sexuality, race, age, disability, or religion.
Speakers should be especially aware of these concerns.
CoreOS does not condone any statements by speakers contrary to these standards.
CoreOS reserves the right to deny entrance and/or eject from an event (without
refund) any individual found to be engaging in discriminatory or offensive
speech or actions.
Please bring any concerns to the immediate attention of designated on-site
staff, Brandon Philips <brandon.philips@coreos.com>, and/or Rithu John <rithu.john@coreos.com>.

84
vendor/github.com/coreos/go-systemd/daemon/sdnotify.go generated vendored Normal file
View File

@ -0,0 +1,84 @@
// Copyright 2014 Docker, Inc.
// Copyright 2015-2018 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 daemon provides a Go implementation of the sd_notify protocol.
// It can be used to inform systemd of service start-up completion, watchdog
// events, and other status changes.
//
// https://www.freedesktop.org/software/systemd/man/sd_notify.html#Description
package daemon
import (
"net"
"os"
)
const (
// SdNotifyReady tells the service manager that service startup is finished
// or the service finished loading its configuration.
SdNotifyReady = "READY=1"
// SdNotifyStopping tells the service manager that the service is beginning
// its shutdown.
SdNotifyStopping = "STOPPING=1"
// SdNotifyReloading tells the service manager that this service is
// reloading its configuration. Note that you must call SdNotifyReady when
// it completed reloading.
SdNotifyReloading = "RELOADING=1"
// SdNotifyWatchdog tells the service manager to update the watchdog
// timestamp for the service.
SdNotifyWatchdog = "WATCHDOG=1"
)
// SdNotify sends a message to the init daemon. It is common to ignore the error.
// If `unsetEnvironment` is true, the environment variable `NOTIFY_SOCKET`
// will be unconditionally unset.
//
// It returns one of the following:
// (false, nil) - notification not supported (i.e. NOTIFY_SOCKET is unset)
// (false, err) - notification supported, but failure happened (e.g. error connecting to NOTIFY_SOCKET or while sending data)
// (true, nil) - notification supported, data has been sent
func SdNotify(unsetEnvironment bool, state string) (bool, error) {
socketAddr := &net.UnixAddr{
Name: os.Getenv("NOTIFY_SOCKET"),
Net: "unixgram",
}
// NOTIFY_SOCKET not set
if socketAddr.Name == "" {
return false, nil
}
if unsetEnvironment {
if err := os.Unsetenv("NOTIFY_SOCKET"); err != nil {
return false, err
}
}
conn, err := net.DialUnix(socketAddr.Net, nil, socketAddr)
// Error connecting to NOTIFY_SOCKET
if err != nil {
return false, err
}
defer conn.Close()
if _, err = conn.Write([]byte(state)); err != nil {
return false, err
}
return true, nil
}

View File

@ -0,0 +1,79 @@
// Copyright 2016 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 daemon
import (
"fmt"
"io/ioutil"
"net"
"os"
"testing"
)
// TestSdNotify
func TestSdNotify(t *testing.T) {
testDir, e := ioutil.TempDir("/tmp/", "test-")
if e != nil {
panic(e)
}
defer os.RemoveAll(testDir)
notifySocket := testDir + "/notify-socket.sock"
laddr := net.UnixAddr{
Name: notifySocket,
Net: "unixgram",
}
_, e = net.ListenUnixgram("unixgram", &laddr)
if e != nil {
panic(e)
}
tests := []struct {
unsetEnv bool
envSocket string
wsent bool
werr bool
}{
// (true, nil) - notification supported, data has been sent
{false, notifySocket, true, false},
// (false, err) - notification supported, but failure happened
{true, testDir + "/missing.sock", false, true},
// (false, nil) - notification not supported
{true, "", false, false},
}
for i, tt := range tests {
must(os.Unsetenv("NOTIFY_SOCKET"))
if tt.envSocket != "" {
must(os.Setenv("NOTIFY_SOCKET", tt.envSocket))
}
sent, err := SdNotify(tt.unsetEnv, fmt.Sprintf("TestSdNotify test message #%d", i))
if sent != tt.wsent {
t.Errorf("#%d: expected send result %t, got %t", i, tt.wsent, sent)
}
if tt.werr && err == nil {
t.Errorf("#%d: want non-nil err, got nil", i)
} else if !tt.werr && err != nil {
t.Errorf("#%d: want nil err, got %v", i, err)
}
if tt.unsetEnv && tt.envSocket != "" && os.Getenv("NOTIFY_SOCKET") != "" {
t.Errorf("#%d: environment variable not cleaned up", i)
}
}
}

73
vendor/github.com/coreos/go-systemd/daemon/watchdog.go generated vendored Normal file
View File

@ -0,0 +1,73 @@
// Copyright 2016 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 daemon
import (
"fmt"
"os"
"strconv"
"time"
)
// SdWatchdogEnabled returns watchdog information for a service.
// Processes should call daemon.SdNotify(false, daemon.SdNotifyWatchdog) every
// time / 2.
// If `unsetEnvironment` is true, the environment variables `WATCHDOG_USEC` and
// `WATCHDOG_PID` will be unconditionally unset.
//
// It returns one of the following:
// (0, nil) - watchdog isn't enabled or we aren't the watched PID.
// (0, err) - an error happened (e.g. error converting time).
// (time, nil) - watchdog is enabled and we can send ping.
// time is delay before inactive service will be killed.
func SdWatchdogEnabled(unsetEnvironment bool) (time.Duration, error) {
wusec := os.Getenv("WATCHDOG_USEC")
wpid := os.Getenv("WATCHDOG_PID")
if unsetEnvironment {
wusecErr := os.Unsetenv("WATCHDOG_USEC")
wpidErr := os.Unsetenv("WATCHDOG_PID")
if wusecErr != nil {
return 0, wusecErr
}
if wpidErr != nil {
return 0, wpidErr
}
}
if wusec == "" {
return 0, nil
}
s, err := strconv.Atoi(wusec)
if err != nil {
return 0, fmt.Errorf("error converting WATCHDOG_USEC: %s", err)
}
if s <= 0 {
return 0, fmt.Errorf("error WATCHDOG_USEC must be a positive number")
}
interval := time.Duration(s) * time.Microsecond
if wpid == "" {
return interval, nil
}
p, err := strconv.Atoi(wpid)
if err != nil {
return 0, fmt.Errorf("error converting WATCHDOG_PID: %s", err)
}
if os.Getpid() != p {
return 0, nil
}
return interval, nil
}

View File

@ -0,0 +1,85 @@
// Copyright 2016 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 daemon
import (
"os"
"strconv"
"testing"
"time"
)
func must(err error) {
if err != nil {
panic(err)
}
}
func TestSdWatchdogEnabled(t *testing.T) {
mypid := strconv.Itoa(os.Getpid())
tests := []struct {
usec string // empty => unset
pid string // empty => unset
unsetEnv bool // arbitrarily set across testcases
werr bool
wdelay time.Duration
}{
// Success cases
{"100", mypid, true, false, 100 * time.Microsecond},
{"50", mypid, true, false, 50 * time.Microsecond},
{"1", mypid, false, false, 1 * time.Microsecond},
{"1", "", true, false, 1 * time.Microsecond},
// No-op cases
{"", mypid, true, false, 0}, // WATCHDOG_USEC not set
{"1", "0", false, false, 0}, // WATCHDOG_PID doesn't match
{"", "", true, false, 0}, // Both not set
// Failure cases
{"-1", mypid, true, true, 0}, // Negative USEC
{"string", "1", false, true, 0}, // Non-integer USEC value
{"1", "string", true, true, 0}, // Non-integer PID value
{"stringa", "stringb", false, true, 0}, // E v e r y t h i n g
{"-10239", "-eleventythree", true, true, 0}, // i s w r o n g
}
for i, tt := range tests {
if tt.usec != "" {
must(os.Setenv("WATCHDOG_USEC", tt.usec))
} else {
must(os.Unsetenv("WATCHDOG_USEC"))
}
if tt.pid != "" {
must(os.Setenv("WATCHDOG_PID", tt.pid))
} else {
must(os.Unsetenv("WATCHDOG_PID"))
}
delay, err := SdWatchdogEnabled(tt.unsetEnv)
if tt.werr && err == nil {
t.Errorf("#%d: want non-nil err, got nil", i)
} else if !tt.werr && err != nil {
t.Errorf("#%d: want nil err, got %v", i, err)
}
if tt.wdelay != delay {
t.Errorf("#%d: want delay=%d, got %d", i, tt.wdelay, delay)
}
if tt.unsetEnv && (os.Getenv("WATCHDOG_PID") != "" || os.Getenv("WATCHDOG_USEC") != "") {
t.Errorf("#%d: environment variables not cleaned up", i)
}
}
}

240
vendor/github.com/coreos/go-systemd/dbus/dbus.go generated vendored Normal file
View File

@ -0,0 +1,240 @@
// 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.
// Integration with the systemd D-Bus API. See http://www.freedesktop.org/wiki/Software/systemd/dbus/
package dbus
import (
"encoding/hex"
"fmt"
"os"
"strconv"
"strings"
"sync"
"github.com/godbus/dbus/v5"
)
const (
alpha = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`
num = `0123456789`
alphanum = alpha + num
signalBuffer = 100
)
// needsEscape checks whether a byte in a potential dbus ObjectPath needs to be escaped
func needsEscape(i int, b byte) bool {
// Escape everything that is not a-z-A-Z-0-9
// Also escape 0-9 if it's the first character
return strings.IndexByte(alphanum, b) == -1 ||
(i == 0 && strings.IndexByte(num, b) != -1)
}
// PathBusEscape sanitizes a constituent string of a dbus ObjectPath using the
// rules that systemd uses for serializing special characters.
func PathBusEscape(path string) string {
// Special case the empty string
if len(path) == 0 {
return "_"
}
n := []byte{}
for i := 0; i < len(path); i++ {
c := path[i]
if needsEscape(i, c) {
e := fmt.Sprintf("_%x", c)
n = append(n, []byte(e)...)
} else {
n = append(n, c)
}
}
return string(n)
}
// pathBusUnescape is the inverse of PathBusEscape.
func pathBusUnescape(path string) string {
if path == "_" {
return ""
}
n := []byte{}
for i := 0; i < len(path); i++ {
c := path[i]
if c == '_' && i+2 < len(path) {
res, err := hex.DecodeString(path[i+1 : i+3])
if err == nil {
n = append(n, res...)
}
i += 2
} else {
n = append(n, c)
}
}
return string(n)
}
// Conn is a connection to systemd's dbus endpoint.
type Conn struct {
// sysconn/sysobj are only used to call dbus methods
sysconn *dbus.Conn
sysobj dbus.BusObject
// sigconn/sigobj are only used to receive dbus signals
sigconn *dbus.Conn
sigobj dbus.BusObject
jobListener struct {
jobs map[dbus.ObjectPath]chan<- string
sync.Mutex
}
subStateSubscriber struct {
updateCh chan<- *SubStateUpdate
errCh chan<- error
sync.Mutex
ignore map[dbus.ObjectPath]int64
cleanIgnore int64
}
propertiesSubscriber struct {
updateCh chan<- *PropertiesUpdate
errCh chan<- error
sync.Mutex
}
}
// New establishes a connection to any available bus and authenticates.
// Callers should call Close() when done with the connection.
func New() (*Conn, error) {
conn, err := NewSystemConnection()
if err != nil && os.Geteuid() == 0 {
return NewSystemdConnection()
}
return conn, err
}
// NewSystemConnection establishes a connection to the system bus and authenticates.
// Callers should call Close() when done with the connection
func NewSystemConnection() (*Conn, error) {
return NewConnection(func() (*dbus.Conn, error) {
return dbusAuthHelloConnection(dbus.SystemBusPrivate)
})
}
// NewUserConnection establishes a connection to the session bus and
// authenticates. This can be used to connect to systemd user instances.
// Callers should call Close() when done with the connection.
func NewUserConnection() (*Conn, error) {
return NewConnection(func() (*dbus.Conn, error) {
return dbusAuthHelloConnection(dbus.SessionBusPrivate)
})
}
// NewSystemdConnection establishes a private, direct connection to systemd.
// This can be used for communicating with systemd without a dbus daemon.
// Callers should call Close() when done with the connection.
func NewSystemdConnection() (*Conn, error) {
return NewConnection(func() (*dbus.Conn, error) {
// We skip Hello when talking directly to systemd.
return dbusAuthConnection(func(opts ...dbus.ConnOption) (*dbus.Conn, error) {
return dbus.Dial("unix:path=/run/systemd/private")
})
})
}
// Close closes an established connection
func (c *Conn) Close() {
c.sysconn.Close()
c.sigconn.Close()
}
// NewConnection establishes a connection to a bus using a caller-supplied function.
// This allows connecting to remote buses through a user-supplied mechanism.
// The supplied function may be called multiple times, and should return independent connections.
// The returned connection must be fully initialised: the org.freedesktop.DBus.Hello call must have succeeded,
// and any authentication should be handled by the function.
func NewConnection(dialBus func() (*dbus.Conn, error)) (*Conn, error) {
sysconn, err := dialBus()
if err != nil {
return nil, err
}
sigconn, err := dialBus()
if err != nil {
sysconn.Close()
return nil, err
}
c := &Conn{
sysconn: sysconn,
sysobj: systemdObject(sysconn),
sigconn: sigconn,
sigobj: systemdObject(sigconn),
}
c.subStateSubscriber.ignore = make(map[dbus.ObjectPath]int64)
c.jobListener.jobs = make(map[dbus.ObjectPath]chan<- string)
// Setup the listeners on jobs so that we can get completions
c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
"type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'")
c.dispatch()
return c, nil
}
// GetManagerProperty returns the value of a property on the org.freedesktop.systemd1.Manager
// interface. The value is returned in its string representation, as defined at
// https://developer.gnome.org/glib/unstable/gvariant-text.html
func (c *Conn) GetManagerProperty(prop string) (string, error) {
variant, err := c.sysobj.GetProperty("org.freedesktop.systemd1.Manager." + prop)
if err != nil {
return "", err
}
return variant.String(), nil
}
func dbusAuthConnection(createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) {
conn, err := createBus()
if err != nil {
return nil, err
}
// Only use EXTERNAL method, and hardcode the uid (not username)
// to avoid a username lookup (which requires a dynamically linked
// libc)
methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))}
err = conn.Auth(methods)
if err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
func dbusAuthHelloConnection(createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) {
conn, err := dbusAuthConnection(createBus)
if err != nil {
return nil, err
}
if err = conn.Hello(); err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
func systemdObject(conn *dbus.Conn) dbus.BusObject {
return conn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1"))
}

96
vendor/github.com/coreos/go-systemd/dbus/dbus_test.go generated vendored Normal file
View File

@ -0,0 +1,96 @@
// 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 dbus
import (
"testing"
)
func TestNeedsEscape(t *testing.T) {
// Anything not 0-9a-zA-Z should always be escaped
for want, vals := range map[bool][]byte{
false: []byte{'a', 'b', 'z', 'A', 'Q', '1', '4', '9'},
true: []byte{'#', '%', '$', '!', '.', '_', '-', '%', '\\'},
} {
for i := 1; i < 10; i++ {
for _, b := range vals {
got := needsEscape(i, b)
if got != want {
t.Errorf("needsEscape(%d, %c) returned %t, want %t", i, b, got, want)
}
}
}
}
// 0-9 in position 0 should be escaped
for want, vals := range map[bool][]byte{
false: []byte{'A', 'a', 'e', 'x', 'Q', 'Z'},
true: []byte{'0', '4', '5', '9'},
} {
for _, b := range vals {
got := needsEscape(0, b)
if got != want {
t.Errorf("needsEscape(0, %c) returned %t, want %t", b, got, want)
}
}
}
}
func TestPathBusEscape(t *testing.T) {
for in, want := range map[string]string{
"": "_",
"foo.service": "foo_2eservice",
"foobar": "foobar",
"woof@woof.service": "woof_40woof_2eservice",
"0123456": "_30123456",
"account_db.service": "account_5fdb_2eservice",
"got-dashes": "got_2ddashes",
} {
got := PathBusEscape(in)
if got != want {
t.Errorf("bad result for PathBusEscape(%s): got %q, want %q", in, got, want)
}
}
}
func TestPathBusUnescape(t *testing.T) {
for in, want := range map[string]string{
"_": "",
"foo_2eservice": "foo.service",
"foobar": "foobar",
"woof_40woof_2eservice": "woof@woof.service",
"_30123456": "0123456",
"account_5fdb_2eservice": "account_db.service",
"got_2ddashes": "got-dashes",
"foobar_": "foobar_",
"foobar_2": "foobar_2",
} {
got := pathBusUnescape(in)
if got != want {
t.Errorf("bad result for pathBusUnescape(%s): got %q, want %q", in, got, want)
}
}
}
// TestNew ensures that New() works without errors.
func TestNew(t *testing.T) {
_, err := New()
if err != nil {
t.Fatal(err)
}
}

600
vendor/github.com/coreos/go-systemd/dbus/methods.go generated vendored Normal file
View File

@ -0,0 +1,600 @@
// Copyright 2015, 2018 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 dbus
import (
"errors"
"fmt"
"path"
"strconv"
"github.com/godbus/dbus/v5"
)
func (c *Conn) jobComplete(signal *dbus.Signal) {
var id uint32
var job dbus.ObjectPath
var unit string
var result string
dbus.Store(signal.Body, &id, &job, &unit, &result)
c.jobListener.Lock()
out, ok := c.jobListener.jobs[job]
if ok {
out <- result
delete(c.jobListener.jobs, job)
}
c.jobListener.Unlock()
}
func (c *Conn) startJob(ch chan<- string, job string, args ...interface{}) (int, error) {
if ch != nil {
c.jobListener.Lock()
defer c.jobListener.Unlock()
}
var p dbus.ObjectPath
err := c.sysobj.Call(job, 0, args...).Store(&p)
if err != nil {
return 0, err
}
if ch != nil {
c.jobListener.jobs[p] = ch
}
// ignore error since 0 is fine if conversion fails
jobID, _ := strconv.Atoi(path.Base(string(p)))
return jobID, nil
}
// StartUnit enqueues a start job and depending jobs, if any (unless otherwise
// specified by the mode string).
//
// Takes the unit to activate, plus a mode string. The mode needs to be one of
// replace, fail, isolate, ignore-dependencies, ignore-requirements. If
// "replace" the call will start the unit and its dependencies, possibly
// replacing already queued jobs that conflict with this. If "fail" the call
// will start the unit and its dependencies, but will fail if this would change
// an already queued job. If "isolate" the call will start the unit in question
// and terminate all units that aren't dependencies of it. If
// "ignore-dependencies" it will start a unit but ignore all its dependencies.
// If "ignore-requirements" it will start a unit but only ignore the
// requirement dependencies. It is not recommended to make use of the latter
// two options.
//
// If the provided channel is non-nil, a result string will be sent to it upon
// job completion: one of done, canceled, timeout, failed, dependency, skipped.
// done indicates successful execution of a job. canceled indicates that a job
// has been canceled before it finished execution. timeout indicates that the
// job timeout was reached. failed indicates that the job failed. dependency
// indicates that a job this job has been depending on failed and the job hence
// has been removed too. skipped indicates that a job was skipped because it
// didn't apply to the units current state.
//
// If no error occurs, the ID of the underlying systemd job will be returned. There
// does exist the possibility for no error to be returned, but for the returned job
// ID to be 0. In this case, the actual underlying ID is not 0 and this datapoint
// should not be considered authoritative.
//
// If an error does occur, it will be returned to the user alongside a job ID of 0.
func (c *Conn) StartUnit(name string, mode string, ch chan<- string) (int, error) {
return c.startJob(ch, "org.freedesktop.systemd1.Manager.StartUnit", name, mode)
}
// StopUnit is similar to StartUnit but stops the specified unit rather
// than starting it.
func (c *Conn) StopUnit(name string, mode string, ch chan<- string) (int, error) {
return c.startJob(ch, "org.freedesktop.systemd1.Manager.StopUnit", name, mode)
}
// ReloadUnit reloads a unit. Reloading is done only if the unit is already running and fails otherwise.
func (c *Conn) ReloadUnit(name string, mode string, ch chan<- string) (int, error) {
return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadUnit", name, mode)
}
// RestartUnit restarts a service. If a service is restarted that isn't
// running it will be started.
func (c *Conn) RestartUnit(name string, mode string, ch chan<- string) (int, error) {
return c.startJob(ch, "org.freedesktop.systemd1.Manager.RestartUnit", name, mode)
}
// TryRestartUnit is like RestartUnit, except that a service that isn't running
// is not affected by the restart.
func (c *Conn) TryRestartUnit(name string, mode string, ch chan<- string) (int, error) {
return c.startJob(ch, "org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode)
}
// ReloadOrRestartUnit attempts a reload if the unit supports it and use a restart
// otherwise.
func (c *Conn) ReloadOrRestartUnit(name string, mode string, ch chan<- string) (int, error) {
return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode)
}
// ReloadOrTryRestartUnit attempts a reload if the unit supports it and use a "Try"
// flavored restart otherwise.
func (c *Conn) ReloadOrTryRestartUnit(name string, mode string, ch chan<- string) (int, error) {
return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode)
}
// StartTransientUnit() may be used to create and start a transient unit, which
// will be released as soon as it is not running or referenced anymore or the
// system is rebooted. name is the unit name including suffix, and must be
// unique. mode is the same as in StartUnit(), properties contains properties
// of the unit.
func (c *Conn) StartTransientUnit(name string, mode string, properties []Property, ch chan<- string) (int, error) {
return c.startJob(ch, "org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0))
}
// KillUnit takes the unit name and a UNIX signal number to send. All of the unit's
// processes are killed.
func (c *Conn) KillUnit(name string, signal int32) {
c.sysobj.Call("org.freedesktop.systemd1.Manager.KillUnit", 0, name, "all", signal).Store()
}
// ResetFailedUnit resets the "failed" state of a specific unit.
func (c *Conn) ResetFailedUnit(name string) error {
return c.sysobj.Call("org.freedesktop.systemd1.Manager.ResetFailedUnit", 0, name).Store()
}
// SystemState returns the systemd state. Equivalent to `systemctl is-system-running`.
func (c *Conn) SystemState() (*Property, error) {
var err error
var prop dbus.Variant
obj := c.sysconn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1")
err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.systemd1.Manager", "SystemState").Store(&prop)
if err != nil {
return nil, err
}
return &Property{Name: "SystemState", Value: prop}, nil
}
// getProperties takes the unit path and returns all of its dbus object properties, for the given dbus interface
func (c *Conn) getProperties(path dbus.ObjectPath, dbusInterface string) (map[string]interface{}, error) {
var err error
var props map[string]dbus.Variant
if !path.IsValid() {
return nil, fmt.Errorf("invalid unit name: %v", path)
}
obj := c.sysconn.Object("org.freedesktop.systemd1", path)
err = obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, dbusInterface).Store(&props)
if err != nil {
return nil, err
}
out := make(map[string]interface{}, len(props))
for k, v := range props {
out[k] = v.Value()
}
return out, nil
}
// GetUnitProperties takes the (unescaped) unit name and returns all of its dbus object properties.
func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) {
path := unitPath(unit)
return c.getProperties(path, "org.freedesktop.systemd1.Unit")
}
// GetUnitPathProperties takes the (escaped) unit path and returns all of its dbus object properties.
func (c *Conn) GetUnitPathProperties(path dbus.ObjectPath) (map[string]interface{}, error) {
return c.getProperties(path, "org.freedesktop.systemd1.Unit")
}
// GetAllProperties takes the (unescaped) unit name and returns all of its dbus object properties.
func (c *Conn) GetAllProperties(unit string) (map[string]interface{}, error) {
path := unitPath(unit)
return c.getProperties(path, "")
}
func (c *Conn) getProperty(unit string, dbusInterface string, propertyName string) (*Property, error) {
var err error
var prop dbus.Variant
path := unitPath(unit)
if !path.IsValid() {
return nil, errors.New("invalid unit name: " + unit)
}
obj := c.sysconn.Object("org.freedesktop.systemd1", path)
err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, dbusInterface, propertyName).Store(&prop)
if err != nil {
return nil, err
}
return &Property{Name: propertyName, Value: prop}, nil
}
func (c *Conn) GetUnitProperty(unit string, propertyName string) (*Property, error) {
return c.getProperty(unit, "org.freedesktop.systemd1.Unit", propertyName)
}
// GetServiceProperty returns property for given service name and property name
func (c *Conn) GetServiceProperty(service string, propertyName string) (*Property, error) {
return c.getProperty(service, "org.freedesktop.systemd1.Service", propertyName)
}
// GetUnitTypeProperties returns the extra properties for a unit, specific to the unit type.
// Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope
// return "dbus.Error: Unknown interface" if the unitType is not the correct type of the unit
func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) {
path := unitPath(unit)
return c.getProperties(path, "org.freedesktop.systemd1."+unitType)
}
// SetUnitProperties() may be used to modify certain unit properties at runtime.
// Not all properties may be changed at runtime, but many resource management
// settings (primarily those in systemd.cgroup(5)) may. The changes are applied
// instantly, and stored on disk for future boots, unless runtime is true, in which
// case the settings only apply until the next reboot. name is the name of the unit
// to modify. properties are the settings to set, encoded as an array of property
// name and value pairs.
func (c *Conn) SetUnitProperties(name string, runtime bool, properties ...Property) error {
return c.sysobj.Call("org.freedesktop.systemd1.Manager.SetUnitProperties", 0, name, runtime, properties).Store()
}
func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) {
return c.getProperty(unit, "org.freedesktop.systemd1."+unitType, propertyName)
}
type UnitStatus struct {
Name string // The primary unit name as string
Description string // The human readable description string
LoadState string // The load state (i.e. whether the unit file has been loaded successfully)
ActiveState string // The active state (i.e. whether the unit is currently started or not)
SubState string // The sub state (a more fine-grained version of the active state that is specific to the unit type, which the active state is not)
Followed string // A unit that is being followed in its state by this unit, if there is any, otherwise the empty string.
Path dbus.ObjectPath // The unit object path
JobId uint32 // If there is a job queued for the job unit the numeric job id, 0 otherwise
JobType string // The job type as string
JobPath dbus.ObjectPath // The job object path
}
type storeFunc func(retvalues ...interface{}) error
func (c *Conn) listUnitsInternal(f storeFunc) ([]UnitStatus, error) {
result := make([][]interface{}, 0)
err := f(&result)
if err != nil {
return nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
status := make([]UnitStatus, len(result))
statusInterface := make([]interface{}, len(status))
for i := range status {
statusInterface[i] = &status[i]
}
err = dbus.Store(resultInterface, statusInterface...)
if err != nil {
return nil, err
}
return status, nil
}
// ListUnits returns an array with all currently loaded units. Note that
// units may be known by multiple names at the same time, and hence there might
// be more unit names loaded than actual units behind them.
// Also note that a unit is only loaded if it is active and/or enabled.
// Units that are both disabled and inactive will thus not be returned.
func (c *Conn) ListUnits() ([]UnitStatus, error) {
return c.listUnitsInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnits", 0).Store)
}
// ListUnitsFiltered returns an array with units filtered by state.
// It takes a list of units' statuses to filter.
func (c *Conn) ListUnitsFiltered(states []string) ([]UnitStatus, error) {
return c.listUnitsInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitsFiltered", 0, states).Store)
}
// ListUnitsByPatterns returns an array with units.
// It takes a list of units' statuses and names to filter.
// Note that units may be known by multiple names at the same time,
// and hence there might be more unit names loaded than actual units behind them.
func (c *Conn) ListUnitsByPatterns(states []string, patterns []string) ([]UnitStatus, error) {
return c.listUnitsInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitsByPatterns", 0, states, patterns).Store)
}
// ListUnitsByNames returns an array with units. It takes a list of units'
// names and returns an UnitStatus array. Comparing to ListUnitsByPatterns
// method, this method returns statuses even for inactive or non-existing
// units. Input array should contain exact unit names, but not patterns.
// Note: Requires systemd v230 or higher
func (c *Conn) ListUnitsByNames(units []string) ([]UnitStatus, error) {
return c.listUnitsInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitsByNames", 0, units).Store)
}
type UnitFile struct {
Path string
Type string
}
func (c *Conn) listUnitFilesInternal(f storeFunc) ([]UnitFile, error) {
result := make([][]interface{}, 0)
err := f(&result)
if err != nil {
return nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
files := make([]UnitFile, len(result))
fileInterface := make([]interface{}, len(files))
for i := range files {
fileInterface[i] = &files[i]
}
err = dbus.Store(resultInterface, fileInterface...)
if err != nil {
return nil, err
}
return files, nil
}
// ListUnitFiles returns an array of all available units on disk.
func (c *Conn) ListUnitFiles() ([]UnitFile, error) {
return c.listUnitFilesInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitFiles", 0).Store)
}
// ListUnitFilesByPatterns returns an array of all available units on disk matched the patterns.
func (c *Conn) ListUnitFilesByPatterns(states []string, patterns []string) ([]UnitFile, error) {
return c.listUnitFilesInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitFilesByPatterns", 0, states, patterns).Store)
}
type LinkUnitFileChange EnableUnitFileChange
// LinkUnitFiles() links unit files (that are located outside of the
// usual unit search paths) into the unit search path.
//
// It takes a list of absolute paths to unit files to link and two
// booleans. The first boolean controls whether the unit shall be
// enabled for runtime only (true, /run), or persistently (false,
// /etc).
// The second controls whether symlinks pointing to other units shall
// be replaced if necessary.
//
// This call returns a list of the changes made. The list consists of
// structures with three strings: the type of the change (one of symlink
// or unlink), the file name of the symlink and the destination of the
// symlink.
func (c *Conn) LinkUnitFiles(files []string, runtime bool, force bool) ([]LinkUnitFileChange, error) {
result := make([][]interface{}, 0)
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.LinkUnitFiles", 0, files, runtime, force).Store(&result)
if err != nil {
return nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
changes := make([]LinkUnitFileChange, len(result))
changesInterface := make([]interface{}, len(changes))
for i := range changes {
changesInterface[i] = &changes[i]
}
err = dbus.Store(resultInterface, changesInterface...)
if err != nil {
return nil, err
}
return changes, nil
}
// EnableUnitFiles() may be used to enable one or more units in the system (by
// creating symlinks to them in /etc or /run).
//
// It takes a list of unit files to enable (either just file names or full
// absolute paths if the unit files are residing outside the usual unit
// search paths), and two booleans: the first controls whether the unit shall
// be enabled for runtime only (true, /run), or persistently (false, /etc).
// The second one controls whether symlinks pointing to other units shall
// be replaced if necessary.
//
// This call returns one boolean and an array with the changes made. The
// boolean signals whether the unit files contained any enablement
// information (i.e. an [Install]) section. The changes list consists of
// structures with three strings: the type of the change (one of symlink
// or unlink), the file name of the symlink and the destination of the
// symlink.
func (c *Conn) EnableUnitFiles(files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) {
var carries_install_info bool
result := make([][]interface{}, 0)
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.EnableUnitFiles", 0, files, runtime, force).Store(&carries_install_info, &result)
if err != nil {
return false, nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
changes := make([]EnableUnitFileChange, len(result))
changesInterface := make([]interface{}, len(changes))
for i := range changes {
changesInterface[i] = &changes[i]
}
err = dbus.Store(resultInterface, changesInterface...)
if err != nil {
return false, nil, err
}
return carries_install_info, changes, nil
}
type EnableUnitFileChange struct {
Type string // Type of the change (one of symlink or unlink)
Filename string // File name of the symlink
Destination string // Destination of the symlink
}
// DisableUnitFiles() may be used to disable one or more units in the system (by
// removing symlinks to them from /etc or /run).
//
// It takes a list of unit files to disable (either just file names or full
// absolute paths if the unit files are residing outside the usual unit
// search paths), and one boolean: whether the unit was enabled for runtime
// only (true, /run), or persistently (false, /etc).
//
// This call returns an array with the changes made. The changes list
// consists of structures with three strings: the type of the change (one of
// symlink or unlink), the file name of the symlink and the destination of the
// symlink.
func (c *Conn) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFileChange, error) {
result := make([][]interface{}, 0)
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.DisableUnitFiles", 0, files, runtime).Store(&result)
if err != nil {
return nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
changes := make([]DisableUnitFileChange, len(result))
changesInterface := make([]interface{}, len(changes))
for i := range changes {
changesInterface[i] = &changes[i]
}
err = dbus.Store(resultInterface, changesInterface...)
if err != nil {
return nil, err
}
return changes, nil
}
type DisableUnitFileChange struct {
Type string // Type of the change (one of symlink or unlink)
Filename string // File name of the symlink
Destination string // Destination of the symlink
}
// MaskUnitFiles masks one or more units in the system
//
// It takes three arguments:
// * list of units to mask (either just file names or full
// absolute paths if the unit files are residing outside
// the usual unit search paths)
// * runtime to specify whether the unit was enabled for runtime
// only (true, /run/systemd/..), or persistently (false, /etc/systemd/..)
// * force flag
func (c *Conn) MaskUnitFiles(files []string, runtime bool, force bool) ([]MaskUnitFileChange, error) {
result := make([][]interface{}, 0)
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.MaskUnitFiles", 0, files, runtime, force).Store(&result)
if err != nil {
return nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
changes := make([]MaskUnitFileChange, len(result))
changesInterface := make([]interface{}, len(changes))
for i := range changes {
changesInterface[i] = &changes[i]
}
err = dbus.Store(resultInterface, changesInterface...)
if err != nil {
return nil, err
}
return changes, nil
}
type MaskUnitFileChange struct {
Type string // Type of the change (one of symlink or unlink)
Filename string // File name of the symlink
Destination string // Destination of the symlink
}
// UnmaskUnitFiles unmasks one or more units in the system
//
// It takes two arguments:
// * list of unit files to mask (either just file names or full
// absolute paths if the unit files are residing outside
// the usual unit search paths)
// * runtime to specify whether the unit was enabled for runtime
// only (true, /run/systemd/..), or persistently (false, /etc/systemd/..)
func (c *Conn) UnmaskUnitFiles(files []string, runtime bool) ([]UnmaskUnitFileChange, error) {
result := make([][]interface{}, 0)
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.UnmaskUnitFiles", 0, files, runtime).Store(&result)
if err != nil {
return nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
changes := make([]UnmaskUnitFileChange, len(result))
changesInterface := make([]interface{}, len(changes))
for i := range changes {
changesInterface[i] = &changes[i]
}
err = dbus.Store(resultInterface, changesInterface...)
if err != nil {
return nil, err
}
return changes, nil
}
type UnmaskUnitFileChange struct {
Type string // Type of the change (one of symlink or unlink)
Filename string // File name of the symlink
Destination string // Destination of the symlink
}
// Reload instructs systemd to scan for and reload unit files. This is
// equivalent to a 'systemctl daemon-reload'.
func (c *Conn) Reload() error {
return c.sysobj.Call("org.freedesktop.systemd1.Manager.Reload", 0).Store()
}
func unitPath(name string) dbus.ObjectPath {
return dbus.ObjectPath("/org/freedesktop/systemd1/unit/" + PathBusEscape(name))
}
// unitName returns the unescaped base element of the supplied escaped path
func unitName(dpath dbus.ObjectPath) string {
return pathBusUnescape(path.Base(string(dpath)))
}

1541
vendor/github.com/coreos/go-systemd/dbus/methods_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

237
vendor/github.com/coreos/go-systemd/dbus/properties.go generated vendored Normal file
View File

@ -0,0 +1,237 @@
// 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 dbus
import (
"github.com/godbus/dbus/v5"
)
// From the systemd docs:
//
// The properties array of StartTransientUnit() may take many of the settings
// that may also be configured in unit files. Not all parameters are currently
// accepted though, but we plan to cover more properties with future release.
// Currently you may set the Description, Slice and all dependency types of
// units, as well as RemainAfterExit, ExecStart for service units,
// TimeoutStopUSec and PIDs for scope units, and CPUAccounting, CPUShares,
// BlockIOAccounting, BlockIOWeight, BlockIOReadBandwidth,
// BlockIOWriteBandwidth, BlockIODeviceWeight, MemoryAccounting, MemoryLimit,
// DevicePolicy, DeviceAllow for services/scopes/slices. These fields map
// directly to their counterparts in unit files and as normal D-Bus object
// properties. The exception here is the PIDs field of scope units which is
// used for construction of the scope only and specifies the initial PIDs to
// add to the scope object.
type Property struct {
Name string
Value dbus.Variant
}
type PropertyCollection struct {
Name string
Properties []Property
}
type execStart struct {
Path string // the binary path to execute
Args []string // an array with all arguments to pass to the executed command, starting with argument 0
UncleanIsFailure bool // a boolean whether it should be considered a failure if the process exits uncleanly
}
// PropExecStart sets the ExecStart service property. The first argument is a
// slice with the binary path to execute followed by the arguments to pass to
// the executed command. See
// http://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart=
func PropExecStart(command []string, uncleanIsFailure bool) Property {
execStarts := []execStart{
{
Path: command[0],
Args: command,
UncleanIsFailure: uncleanIsFailure,
},
}
return Property{
Name: "ExecStart",
Value: dbus.MakeVariant(execStarts),
}
}
// PropRemainAfterExit sets the RemainAfterExit service property. See
// http://www.freedesktop.org/software/systemd/man/systemd.service.html#RemainAfterExit=
func PropRemainAfterExit(b bool) Property {
return Property{
Name: "RemainAfterExit",
Value: dbus.MakeVariant(b),
}
}
// PropType sets the Type service property. See
// http://www.freedesktop.org/software/systemd/man/systemd.service.html#Type=
func PropType(t string) Property {
return Property{
Name: "Type",
Value: dbus.MakeVariant(t),
}
}
// PropDescription sets the Description unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit#Description=
func PropDescription(desc string) Property {
return Property{
Name: "Description",
Value: dbus.MakeVariant(desc),
}
}
func propDependency(name string, units []string) Property {
return Property{
Name: name,
Value: dbus.MakeVariant(units),
}
}
// PropRequires sets the Requires unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requires=
func PropRequires(units ...string) Property {
return propDependency("Requires", units)
}
// PropRequiresOverridable sets the RequiresOverridable unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresOverridable=
func PropRequiresOverridable(units ...string) Property {
return propDependency("RequiresOverridable", units)
}
// PropRequisite sets the Requisite unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requisite=
func PropRequisite(units ...string) Property {
return propDependency("Requisite", units)
}
// PropRequisiteOverridable sets the RequisiteOverridable unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequisiteOverridable=
func PropRequisiteOverridable(units ...string) Property {
return propDependency("RequisiteOverridable", units)
}
// PropWants sets the Wants unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Wants=
func PropWants(units ...string) Property {
return propDependency("Wants", units)
}
// PropBindsTo sets the BindsTo unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#BindsTo=
func PropBindsTo(units ...string) Property {
return propDependency("BindsTo", units)
}
// PropRequiredBy sets the RequiredBy unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredBy=
func PropRequiredBy(units ...string) Property {
return propDependency("RequiredBy", units)
}
// PropRequiredByOverridable sets the RequiredByOverridable unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredByOverridable=
func PropRequiredByOverridable(units ...string) Property {
return propDependency("RequiredByOverridable", units)
}
// PropWantedBy sets the WantedBy unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#WantedBy=
func PropWantedBy(units ...string) Property {
return propDependency("WantedBy", units)
}
// PropBoundBy sets the BoundBy unit property. See
// http://www.freedesktop.org/software/systemd/main/systemd.unit.html#BoundBy=
func PropBoundBy(units ...string) Property {
return propDependency("BoundBy", units)
}
// PropConflicts sets the Conflicts unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Conflicts=
func PropConflicts(units ...string) Property {
return propDependency("Conflicts", units)
}
// PropConflictedBy sets the ConflictedBy unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#ConflictedBy=
func PropConflictedBy(units ...string) Property {
return propDependency("ConflictedBy", units)
}
// PropBefore sets the Before unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Before=
func PropBefore(units ...string) Property {
return propDependency("Before", units)
}
// PropAfter sets the After unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#After=
func PropAfter(units ...string) Property {
return propDependency("After", units)
}
// PropOnFailure sets the OnFailure unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#OnFailure=
func PropOnFailure(units ...string) Property {
return propDependency("OnFailure", units)
}
// PropTriggers sets the Triggers unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Triggers=
func PropTriggers(units ...string) Property {
return propDependency("Triggers", units)
}
// PropTriggeredBy sets the TriggeredBy unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#TriggeredBy=
func PropTriggeredBy(units ...string) Property {
return propDependency("TriggeredBy", units)
}
// PropPropagatesReloadTo sets the PropagatesReloadTo unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#PropagatesReloadTo=
func PropPropagatesReloadTo(units ...string) Property {
return propDependency("PropagatesReloadTo", units)
}
// PropRequiresMountsFor sets the RequiresMountsFor unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresMountsFor=
func PropRequiresMountsFor(units ...string) Property {
return propDependency("RequiresMountsFor", units)
}
// PropSlice sets the Slice unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.resource-control.html#Slice=
func PropSlice(slice string) Property {
return Property{
Name: "Slice",
Value: dbus.MakeVariant(slice),
}
}
// PropPids sets the PIDs field of scope units used in the initial construction
// of the scope only and specifies the initial PIDs to add to the scope object.
// See https://www.freedesktop.org/wiki/Software/systemd/ControlGroupInterface/#properties
func PropPids(pids ...uint32) Property {
return Property{
Name: "PIDs",
Value: dbus.MakeVariant(pids),
}
}

47
vendor/github.com/coreos/go-systemd/dbus/set.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
// 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 dbus
type set struct {
data map[string]bool
}
func (s *set) Add(value string) {
s.data[value] = true
}
func (s *set) Remove(value string) {
delete(s.data, value)
}
func (s *set) Contains(value string) (exists bool) {
_, exists = s.data[value]
return
}
func (s *set) Length() int {
return len(s.data)
}
func (s *set) Values() (values []string) {
for val := range s.data {
values = append(values, val)
}
return
}
func newSet() *set {
return &set{make(map[string]bool)}
}

53
vendor/github.com/coreos/go-systemd/dbus/set_test.go generated vendored Normal file
View File

@ -0,0 +1,53 @@
// 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 dbus
import (
"testing"
)
// TestBasicSetActions asserts that Add & Remove behavior is correct
func TestBasicSetActions(t *testing.T) {
s := newSet()
if s.Contains("foo") {
t.Fatal("set should not contain 'foo'")
}
s.Add("foo")
if !s.Contains("foo") {
t.Fatal("set should contain 'foo'")
}
v := s.Values()
if len(v) != 1 {
t.Fatal("set.Values did not report correct number of values")
}
if v[0] != "foo" {
t.Fatal("set.Values did not report value")
}
s.Remove("foo")
if s.Contains("foo") {
t.Fatal("set should not contain 'foo'")
}
v = s.Values()
if len(v) != 0 {
t.Fatal("set.Values did not report correct number of values")
}
}

View File

@ -0,0 +1,333 @@
// 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 dbus
import (
"errors"
"log"
"time"
"github.com/godbus/dbus/v5"
)
const (
cleanIgnoreInterval = int64(10 * time.Second)
ignoreInterval = int64(30 * time.Millisecond)
)
// Subscribe sets up this connection to subscribe to all systemd dbus events.
// This is required before calling SubscribeUnits. When the connection closes
// systemd will automatically stop sending signals so there is no need to
// explicitly call Unsubscribe().
func (c *Conn) Subscribe() error {
c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
"type='signal',interface='org.freedesktop.systemd1.Manager',member='UnitNew'")
c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
"type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'")
return c.sigobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store()
}
// Unsubscribe this connection from systemd dbus events.
func (c *Conn) Unsubscribe() error {
return c.sigobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store()
}
func (c *Conn) dispatch() {
ch := make(chan *dbus.Signal, signalBuffer)
c.sigconn.Signal(ch)
go func() {
for {
signal, ok := <-ch
if !ok {
return
}
if signal.Name == "org.freedesktop.systemd1.Manager.JobRemoved" {
c.jobComplete(signal)
}
if c.subStateSubscriber.updateCh == nil &&
c.propertiesSubscriber.updateCh == nil {
continue
}
var unitPath dbus.ObjectPath
switch signal.Name {
case "org.freedesktop.systemd1.Manager.JobRemoved":
unitName := signal.Body[2].(string)
c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath)
case "org.freedesktop.systemd1.Manager.UnitNew":
unitPath = signal.Body[1].(dbus.ObjectPath)
case "org.freedesktop.DBus.Properties.PropertiesChanged":
if signal.Body[0].(string) == "org.freedesktop.systemd1.Unit" {
unitPath = signal.Path
if len(signal.Body) >= 2 {
if changed, ok := signal.Body[1].(map[string]dbus.Variant); ok {
c.sendPropertiesUpdate(unitPath, changed)
}
}
}
}
if unitPath == dbus.ObjectPath("") {
continue
}
c.sendSubStateUpdate(unitPath)
}
}()
}
// SubscribeUnits returns two unbuffered channels which will receive all changed units every
// interval. Deleted units are sent as nil.
func (c *Conn) SubscribeUnits(interval time.Duration) (<-chan map[string]*UnitStatus, <-chan error) {
return c.SubscribeUnitsCustom(interval, 0, func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, nil)
}
// SubscribeUnitsCustom is like SubscribeUnits but lets you specify the buffer
// size of the channels, the comparison function for detecting changes and a filter
// function for cutting down on the noise that your channel receives.
func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func(string) bool) (<-chan map[string]*UnitStatus, <-chan error) {
old := make(map[string]*UnitStatus)
statusChan := make(chan map[string]*UnitStatus, buffer)
errChan := make(chan error, buffer)
go func() {
for {
timerChan := time.After(interval)
units, err := c.ListUnits()
if err == nil {
cur := make(map[string]*UnitStatus)
for i := range units {
if filterUnit != nil && filterUnit(units[i].Name) {
continue
}
cur[units[i].Name] = &units[i]
}
// add all new or changed units
changed := make(map[string]*UnitStatus)
for n, u := range cur {
if oldU, ok := old[n]; !ok || isChanged(oldU, u) {
changed[n] = u
}
delete(old, n)
}
// add all deleted units
for oldN := range old {
changed[oldN] = nil
}
old = cur
if len(changed) != 0 {
statusChan <- changed
}
} else {
errChan <- err
}
<-timerChan
}
}()
return statusChan, errChan
}
type SubStateUpdate struct {
UnitName string
SubState string
}
// SetSubStateSubscriber writes to updateCh when any unit's substate changes.
// Although this writes to updateCh on every state change, the reported state
// may be more recent than the change that generated it (due to an unavoidable
// race in the systemd dbus interface). That is, this method provides a good
// way to keep a current view of all units' states, but is not guaranteed to
// show every state transition they go through. Furthermore, state changes
// will only be written to the channel with non-blocking writes. If updateCh
// is full, it attempts to write an error to errCh; if errCh is full, the error
// passes silently.
func (c *Conn) SetSubStateSubscriber(updateCh chan<- *SubStateUpdate, errCh chan<- error) {
if c == nil {
msg := "nil receiver"
select {
case errCh <- errors.New(msg):
default:
log.Printf("full error channel while reporting: %s\n", msg)
}
return
}
c.subStateSubscriber.Lock()
defer c.subStateSubscriber.Unlock()
c.subStateSubscriber.updateCh = updateCh
c.subStateSubscriber.errCh = errCh
}
func (c *Conn) sendSubStateUpdate(unitPath dbus.ObjectPath) {
c.subStateSubscriber.Lock()
defer c.subStateSubscriber.Unlock()
if c.subStateSubscriber.updateCh == nil {
return
}
isIgnored := c.shouldIgnore(unitPath)
defer c.cleanIgnore()
if isIgnored {
return
}
info, err := c.GetUnitPathProperties(unitPath)
if err != nil {
select {
case c.subStateSubscriber.errCh <- err:
default:
log.Printf("full error channel while reporting: %s\n", err)
}
return
}
defer c.updateIgnore(unitPath, info)
name, ok := info["Id"].(string)
if !ok {
msg := "failed to cast info.Id"
select {
case c.subStateSubscriber.errCh <- errors.New(msg):
default:
log.Printf("full error channel while reporting: %s\n", err)
}
return
}
substate, ok := info["SubState"].(string)
if !ok {
msg := "failed to cast info.SubState"
select {
case c.subStateSubscriber.errCh <- errors.New(msg):
default:
log.Printf("full error channel while reporting: %s\n", msg)
}
return
}
update := &SubStateUpdate{name, substate}
select {
case c.subStateSubscriber.updateCh <- update:
default:
msg := "update channel is full"
select {
case c.subStateSubscriber.errCh <- errors.New(msg):
default:
log.Printf("full error channel while reporting: %s\n", msg)
}
return
}
}
// The ignore functions work around a wart in the systemd dbus interface.
// Requesting the properties of an unloaded unit will cause systemd to send a
// pair of UnitNew/UnitRemoved signals. Because we need to get a unit's
// properties on UnitNew (as that's the only indication of a new unit coming up
// for the first time), we would enter an infinite loop if we did not attempt
// to detect and ignore these spurious signals. The signal themselves are
// indistinguishable from relevant ones, so we (somewhat hackishly) ignore an
// unloaded unit's signals for a short time after requesting its properties.
// This means that we will miss e.g. a transient unit being restarted
// *immediately* upon failure and also a transient unit being started
// immediately after requesting its status (with systemctl status, for example,
// because this causes a UnitNew signal to be sent which then causes us to fetch
// the properties).
func (c *Conn) shouldIgnore(path dbus.ObjectPath) bool {
t, ok := c.subStateSubscriber.ignore[path]
return ok && t >= time.Now().UnixNano()
}
func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]interface{}) {
loadState, ok := info["LoadState"].(string)
if !ok {
return
}
// unit is unloaded - it will trigger bad systemd dbus behavior
if loadState == "not-found" {
c.subStateSubscriber.ignore[path] = time.Now().UnixNano() + ignoreInterval
}
}
// without this, ignore would grow unboundedly over time
func (c *Conn) cleanIgnore() {
now := time.Now().UnixNano()
if c.subStateSubscriber.cleanIgnore < now {
c.subStateSubscriber.cleanIgnore = now + cleanIgnoreInterval
for p, t := range c.subStateSubscriber.ignore {
if t < now {
delete(c.subStateSubscriber.ignore, p)
}
}
}
}
// PropertiesUpdate holds a map of a unit's changed properties
type PropertiesUpdate struct {
UnitName string
Changed map[string]dbus.Variant
}
// SetPropertiesSubscriber writes to updateCh when any unit's properties
// change. Every property change reported by systemd will be sent; that is, no
// transitions will be "missed" (as they might be with SetSubStateSubscriber).
// However, state changes will only be written to the channel with non-blocking
// writes. If updateCh is full, it attempts to write an error to errCh; if
// errCh is full, the error passes silently.
func (c *Conn) SetPropertiesSubscriber(updateCh chan<- *PropertiesUpdate, errCh chan<- error) {
c.propertiesSubscriber.Lock()
defer c.propertiesSubscriber.Unlock()
c.propertiesSubscriber.updateCh = updateCh
c.propertiesSubscriber.errCh = errCh
}
// we don't need to worry about shouldIgnore() here because
// sendPropertiesUpdate doesn't call GetProperties()
func (c *Conn) sendPropertiesUpdate(unitPath dbus.ObjectPath, changedProps map[string]dbus.Variant) {
c.propertiesSubscriber.Lock()
defer c.propertiesSubscriber.Unlock()
if c.propertiesSubscriber.updateCh == nil {
return
}
update := &PropertiesUpdate{unitName(unitPath), changedProps}
select {
case c.propertiesSubscriber.updateCh <- update:
default:
msg := "update channel is full"
select {
case c.propertiesSubscriber.errCh <- errors.New(msg):
default:
log.Printf("full error channel while reporting: %s\n", msg)
}
return
}
}

View File

@ -0,0 +1,57 @@
// 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 dbus
import (
"time"
)
// SubscriptionSet returns a subscription set which is like conn.Subscribe but
// can filter to only return events for a set of units.
type SubscriptionSet struct {
*set
conn *Conn
}
func (s *SubscriptionSet) filter(unit string) bool {
return !s.Contains(unit)
}
// Subscribe starts listening for dbus events for all of the units in the set.
// Returns channels identical to conn.SubscribeUnits.
func (s *SubscriptionSet) Subscribe() (<-chan map[string]*UnitStatus, <-chan error) {
// TODO: Make fully evented by using systemd 209 with properties changed values
return s.conn.SubscribeUnitsCustom(time.Second, 0,
mismatchUnitStatus,
func(unit string) bool { return s.filter(unit) },
)
}
// NewSubscriptionSet returns a new subscription set.
func (conn *Conn) NewSubscriptionSet() *SubscriptionSet {
return &SubscriptionSet{newSet(), conn}
}
// mismatchUnitStatus returns true if the provided UnitStatus objects
// are not equivalent. false is returned if the objects are equivalent.
// Only the Name, Description and state-related fields are used in
// the comparison.
func mismatchUnitStatus(u1, u2 *UnitStatus) bool {
return u1.Name != u2.Name ||
u1.Description != u2.Description ||
u1.LoadState != u2.LoadState ||
u1.ActiveState != u2.ActiveState ||
u1.SubState != u2.SubState
}

View File

@ -0,0 +1,82 @@
// 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 dbus
import (
"testing"
"time"
)
// TestSubscribeUnit exercises the basics of subscription of a particular unit.
func TestSubscriptionSetUnit(t *testing.T) {
target := "subscribe-events-set.service"
conn, err := New()
if err != nil {
t.Fatal(err)
}
err = conn.Subscribe()
if err != nil {
t.Fatal(err)
}
subSet := conn.NewSubscriptionSet()
evChan, errChan := subSet.Subscribe()
subSet.Add(target)
setupUnit(target, conn, t)
linkUnit(target, conn, t)
reschan := make(chan string)
_, err = conn.StartUnit(target, "replace", reschan)
if err != nil {
t.Fatal(err)
}
job := <-reschan
if job != "done" {
t.Fatal("Couldn't start", target)
}
timeout := make(chan bool, 1)
go func() {
time.Sleep(3 * time.Second)
close(timeout)
}()
for {
select {
case changes := <-evChan:
tCh, ok := changes[target]
if !ok {
t.Fatal("Unexpected event:", changes)
}
if tCh.ActiveState == "active" && tCh.Name == target {
goto success
}
case err = <-errChan:
t.Fatal(err)
case <-timeout:
t.Fatal("Reached timeout")
}
}
success:
return
}

View File

@ -0,0 +1,191 @@
// 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 dbus
import (
"testing"
"time"
)
// TestSubscribe exercises the basics of subscription
func TestSubscribe(t *testing.T) {
conn, err := New()
if err != nil {
t.Fatal(err)
}
err = conn.Subscribe()
if err != nil {
t.Fatal(err)
}
err = conn.Unsubscribe()
if err != nil {
t.Fatal(err)
}
}
// TestSubscribeUnit exercises the basics of subscription of a particular unit.
func TestSubscribeUnit(t *testing.T) {
target := "subscribe-events.service"
conn, err := New()
if err != nil {
t.Fatal(err)
}
err = conn.Subscribe()
if err != nil {
t.Fatal(err)
}
err = conn.Unsubscribe()
if err != nil {
t.Fatal(err)
}
evChan, errChan := conn.SubscribeUnits(time.Second)
setupUnit(target, conn, t)
linkUnit(target, conn, t)
reschan := make(chan string)
_, err = conn.StartUnit(target, "replace", reschan)
if err != nil {
t.Fatal(err)
}
job := <-reschan
if job != "done" {
t.Fatal("Couldn't start", target)
}
for {
select {
case changes := <-evChan:
tCh, ok := changes[target]
// Just continue until we see our event.
if !ok {
continue
}
if tCh.ActiveState == "active" && tCh.Name == target {
goto success
}
case err = <-errChan:
t.Fatal(err)
case <-time.After(10 * time.Second):
t.Fatal("Reached timeout")
}
}
success:
return
}
// TestSubStateSubscription exercises the basics of sub-state event subscriptions
func TestSubStateSubscription(t *testing.T) {
target := "subscribe-events.service"
conn, err := New()
defer conn.Close()
if err != nil {
t.Fatal(err)
}
updateCh := make(chan *SubStateUpdate, 256)
errCh := make(chan error, 256)
conn.SetSubStateSubscriber(updateCh, errCh)
setupUnit(target, conn, t)
linkUnit(target, conn, t)
reschan := make(chan string)
_, err = conn.StartUnit(target, "replace", reschan)
if err != nil {
t.Fatal(err)
}
job := <-reschan
if job != "done" {
t.Fatal("Couldn't start", target)
}
for {
select {
case update := <-updateCh:
if update.UnitName == target && update.SubState == "running" {
return // success
}
case err := <-errCh:
t.Fatal(err)
case <-time.After(10 * time.Second):
t.Fatal("Reached timeout")
}
}
}
// TestPropertiesSubscription exercises the basics of property change event subscriptions
func TestPropertiesSubscription(t *testing.T) {
target := "subscribe-events.service"
conn, err := New()
defer conn.Close()
if err != nil {
t.Fatal(err)
}
err = conn.Subscribe()
if err != nil {
t.Fatal(err)
}
updateCh := make(chan *PropertiesUpdate, 256)
errCh := make(chan error, 256)
conn.SetPropertiesSubscriber(updateCh, errCh)
setupUnit(target, conn, t)
linkUnit(target, conn, t)
reschan := make(chan string)
_, err = conn.StartUnit(target, "replace", reschan)
if err != nil {
t.Fatal(err)
}
job := <-reschan
if job != "done" {
t.Fatal("Couldn't start", target)
}
for {
select {
case update := <-updateCh:
if update.UnitName == target {
subState, ok := update.Changed["SubState"].Value().(string)
if ok && subState == "running" {
return // success
}
}
case err := <-errCh:
t.Fatal(err)
case <-time.After(10 * time.Second):
t.Fatal("Reached timeout")
}
}
}

View File

@ -0,0 +1,60 @@
// 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.
// +build ignore
// Activation example used by the activation unit tests.
package main
import (
"fmt"
"os"
"github.com/coreos/go-systemd/v22/activation"
)
func fixListenPid() {
if os.Getenv("FIX_LISTEN_PID") != "" {
// HACK: real systemd would set LISTEN_PID before exec'ing but
// this is too difficult in golang for the purpose of a test.
// Do not do this in real code.
os.Setenv("LISTEN_PID", fmt.Sprintf("%d", os.Getpid()))
}
}
func main() {
fixListenPid()
files := activation.Files(false)
if len(files) == 0 {
panic("No files")
}
if os.Getenv("LISTEN_PID") == "" || os.Getenv("LISTEN_FDS") == "" || os.Getenv("LISTEN_FDNAMES") == "" {
panic("Should not unset envs")
}
files = activation.Files(true)
if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" || os.Getenv("LISTEN_FDNAMES") != "" {
panic("Can not unset envs")
}
// Write out the expected strings to the two pipes
files[0].Write([]byte("Hello world: " + files[0].Name()))
files[1].Write([]byte("Goodbye world: " + files[1].Name()))
return
}

View File

@ -0,0 +1,19 @@
## socket activated http server
This is a simple example of using socket activation with systemd to serve a
simple HTTP server on http://127.0.0.1:8076
To try it out `go get` the httpserver and run it under the systemd-activate helper
```bash
export GOPATH="$PWD"
go get github.com/coreos/go-systemd/examples/activation/httpserver
systemd-socket-activate -l 127.0.0.1:8076 ./bin/httpserver
```
Then curl the URL and you will notice that it starts up:
```
curl 127.0.0.1:8076
hello socket activated world!
```

View File

@ -0,0 +1,11 @@
[Unit]
Description=Hello World HTTP
Requires=network.target
After=multi-user.target
[Service]
Type=simple
ExecStart=/usr/local/bin/httpserver
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,5 @@
[Socket]
ListenStream=127.0.0.1:8076
[Install]
WantedBy=sockets.target

View File

@ -0,0 +1,42 @@
// 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.
// +build ignore
package main
import (
"io"
"net/http"
"github.com/coreos/go-systemd/v22/activation"
)
func HelloServer(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "hello socket activated world!\n")
}
func main() {
listeners, err := activation.Listeners()
if err != nil {
panic(err)
}
if len(listeners) != 1 {
panic("Unexpected number of socket activation fds")
}
http.HandleFunc("/", HelloServer)
http.Serve(listeners[0], nil)
}

View File

@ -0,0 +1,56 @@
// 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.
// +build ignore
// Activation example used by the activation unit tests.
package main
import (
"fmt"
"os"
"github.com/coreos/go-systemd/v22/activation"
)
func fixListenPid() {
if os.Getenv("FIX_LISTEN_PID") != "" {
// HACK: real systemd would set LISTEN_PID before exec'ing but
// this is too difficult in golang for the purpose of a test.
// Do not do this in real code.
os.Setenv("LISTEN_PID", fmt.Sprintf("%d", os.Getpid()))
}
}
func main() {
fixListenPid()
listenersWithNames, err := activation.ListenersWithNames()
if err != nil {
panic(err)
}
if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" || os.Getenv("LISTEN_FDNAMES") != "" {
panic("Can not unset envs")
}
c0, _ := listenersWithNames["fd1"][0].Accept()
c1, _ := listenersWithNames["fd2"][0].Accept()
// Write out the expected strings to the two pipes
c0.Write([]byte("Hello world: fd1"))
c1.Write([]byte("Goodbye world: fd2"))
return
}

View File

@ -0,0 +1,78 @@
// 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.
// +build ignore
// Activation example used by the activation unit tests.
package main
import (
"fmt"
"net"
"os"
"github.com/coreos/go-systemd/v22/activation"
)
func fixListenPid() {
if os.Getenv("FIX_LISTEN_PID") != "" {
// HACK: real systemd would set LISTEN_PID before exec'ing but
// this is too difficult in golang for the purpose of a test.
// Do not do this in real code.
os.Setenv("LISTEN_PID", fmt.Sprintf("%d", os.Getpid()))
}
}
func main() {
fixListenPid()
pc, err := activation.PacketConns()
if err != nil {
panic(err)
}
if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" || os.Getenv("LISTEN_FDNAMES") != "" {
panic("Can not unset envs")
}
udp1, ok := pc[0].(*net.UDPConn)
if !ok {
panic("packetConn 1 not UDP")
}
udp2, ok := pc[1].(*net.UDPConn)
if !ok {
panic("packetConn 2 not UDP")
}
_, addr1, err := udp1.ReadFromUDP(nil)
if err != nil {
panic(err)
}
_, addr2, err := udp2.ReadFromUDP(nil)
if err != nil {
panic(err)
}
// Write out the expected strings to the two pipes
_, err = udp1.WriteToUDP([]byte("Hello world"), addr1)
if err != nil {
panic(err)
}
_, err = udp2.WriteToUDP([]byte("Goodbye world"), addr2)
if err != nil {
panic(err)
}
return
}

View File

@ -0,0 +1,5 @@
[Unit]
Description=enable disable test
[Service]
ExecStart=/bin/sleep 400

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,5 @@
[Unit]
Description=mask unmask test
[Service]
ExecStart=/bin/sleep 400

View File

@ -0,0 +1,6 @@
[Unit]
Description=reload test
[Service]
ExecStart=/bin/bash -c "trap '' HUP; /bin/sleep 400"
ExecReload=-/bin/systemctl kill -s HUP reload.service

View File

@ -0,0 +1,6 @@
[Unit]
Description=starting a failed test
[Service]
ExecStartPre=/bin/false
ExecStart=/bin/sleep 400

View File

@ -0,0 +1,5 @@
[Unit]
Description=start stop test
[Service]
ExecStart=/bin/sleep 400

View File

@ -0,0 +1,5 @@
[Unit]
Description=start stop test
[Service]
ExecStart=/bin/sleep 400

View File

@ -0,0 +1,5 @@
[Unit]
Description=start stop test
[Service]
ExecStart=/bin/sleep 400

5
vendor/github.com/coreos/go-systemd/go.mod generated vendored Normal file
View File

@ -0,0 +1,5 @@
module github.com/coreos/go-systemd/v22
go 1.12
require github.com/godbus/dbus/v5 v5.0.3

2
vendor/github.com/coreos/go-systemd/go.sum generated vendored Normal file
View File

@ -0,0 +1,2 @@
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=

234
vendor/github.com/coreos/go-systemd/import1/dbus.go generated vendored Normal file
View File

@ -0,0 +1,234 @@
// Copyright 2019 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 import1 provides integration with the systemd-importd API. See https://www.freedesktop.org/wiki/Software/systemd/importd/
// Note: Requires systemd v231 or higher
package import1
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/godbus/dbus/v5"
)
const (
dbusInterface = "org.freedesktop.import1.Manager"
dbusPath = "/org/freedesktop/import1"
)
// Conn is a connection to systemds dbus endpoint.
type Conn struct {
conn *dbus.Conn
object dbus.BusObject
}
// Transfer is an object in dbus for an import, export or download operation.
type Transfer struct {
Id uint32 // The numeric transfer ID of the transfer object
Path dbus.ObjectPath // The dbus objectPath for the transfer
}
// TransferStatus is the status for an import, export or download operation.
type TransferStatus struct {
Id uint32 // The numeric transfer ID of the transfer object
Local string // The local container name of this transfer
Remote string // The remote source (in case of download: the URL, in case of import/export a string describing the file descriptor passed in)
Type string // The type of operation
Verify string // The selected verification setting, and is only defined for download operations
Progress float64 // The current progress of the transfer, as a value between 0.0 and 1.0
}
// New establishes a connection to the system bus and authenticates.
// Note: systemd-importd will be activated via D-Bus, we don't need to check service status.
func New() (*Conn, error) {
c := new(Conn)
if err := c.initConnection(); err != nil {
return nil, err
}
return c, nil
}
func (c *Conn) initConnection() error {
var err error
c.conn, err = dbus.SystemBusPrivate()
if err != nil {
return err
}
// Only use EXTERNAL method, and hardcode the uid (not username)
// to avoid a username lookup (which requires a dynamically linked
// libc)
methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))}
err = c.conn.Auth(methods)
if err != nil {
c.conn.Close()
return err
}
err = c.conn.Hello()
if err != nil {
c.conn.Close()
return err
}
c.object = c.conn.Object("org.freedesktop.import1", dbus.ObjectPath(dbusPath))
return nil
}
// getResult will return:
// - transfer object (*Transfer)
// - err (error)
func (c *Conn) getResult(method string, args ...interface{}) (*Transfer, error) {
result := c.object.Call(fmt.Sprintf("%s.%s", dbusInterface, method), 0, args...)
if result.Err != nil {
return nil, result.Err
}
if len(result.Body) < 2 {
return nil, fmt.Errorf("invalid number of result fields: %v", result.Body)
}
ok := false
transfer := &Transfer{}
transfer.Id, ok = result.Body[0].(uint32)
if !ok {
return nil, fmt.Errorf("unable to convert dbus response '%v' to uint32", result.Body[0])
}
transfer.Path, ok = result.Body[1].(dbus.ObjectPath)
if !ok {
return nil, fmt.Errorf("unable to convert dbus response '%v' to dbus.ObjectPath", result.Body[1])
}
return transfer, nil
}
// ImportTar imports a tar into systemd-importd.
func (c *Conn) ImportTar(
f *os.File, local_name string, force, read_only bool,
) (*Transfer, error) {
return c.getResult("ImportTar", dbus.UnixFD(f.Fd()), local_name, force, read_only)
}
// ImportRaw imports a raw image into systemd-importd.
func (c *Conn) ImportRaw(
f *os.File, local_name string, force, read_only bool,
) (*Transfer, error) {
return c.getResult("ImportRaw", dbus.UnixFD(f.Fd()), local_name, force, read_only)
}
// ExportTar exports a tar from systemd-importd.
func (c *Conn) ExportTar(
local_name string, f *os.File, format string,
) (*Transfer, error) {
return c.getResult("ExportTar", local_name, dbus.UnixFD(f.Fd()), format)
}
// ExportRaw exports a raw image from systemd-importd.
func (c *Conn) ExportRaw(
local_name string, f *os.File, format string,
) (*Transfer, error) {
return c.getResult("ExportRaw", local_name, dbus.UnixFD(f.Fd()), format)
}
// PullTar pulls a tar into systemd-importd.
func (c *Conn) PullTar(
url, local_name, verify_mode string, force bool,
) (*Transfer, error) {
return c.getResult("PullTar", url, local_name, verify_mode, force)
}
// PullRaw pulls a raw image into systemd-importd.
func (c *Conn) PullRaw(
url, local_name, verify_mode string, force bool,
) (*Transfer, error) {
return c.getResult("PullRaw", url, local_name, verify_mode, force)
}
// ListTransfers will list ongoing import, export or download operations.
func (c *Conn) ListTransfers() ([]TransferStatus, error) {
result := make([][]interface{}, 0)
if err := c.object.Call(dbusInterface+".ListTransfers", 0).Store(&result); err != nil {
return nil, err
}
transfers := make([]TransferStatus, 0)
for _, v := range result {
transfer, err := transferFromInterfaces(v)
if err != nil {
return nil, err
}
transfers = append(transfers, *transfer)
}
return transfers, nil
}
// CancelTransfer will cancel an ongoing import, export or download operations.
func (c *Conn) CancelTransfer(transfer_id uint32) error {
return c.object.Call(dbusInterface+".CancelTransfer", 0, transfer_id).Err
}
func transferFromInterfaces(transfer []interface{}) (*TransferStatus, error) {
// Verify may be not defined in response.
if len(transfer) < 5 {
return nil, fmt.Errorf("invalid number of transfer fields: %d", len(transfer))
}
ok := false
ret := &TransferStatus{}
ret.Id, ok = transfer[0].(uint32)
if !ok {
return nil, fmt.Errorf("failed to typecast transfer field 0 to uint32")
}
ret.Local, ok = transfer[1].(string)
if !ok {
return nil, fmt.Errorf("failed to typecast transfer field 1 to string")
}
ret.Remote, ok = transfer[2].(string)
if !ok {
return nil, fmt.Errorf("failed to typecast transfer field 2 to string")
}
ret.Type, ok = transfer[3].(string)
if !ok {
return nil, fmt.Errorf("failed to typecast transfer field 3 to string")
}
// Verify is only defined for download operations.
// If operation is not pull, we should ignore Verify field.
if !strings.HasPrefix(ret.Type, "pull-") {
ret.Progress, ok = transfer[4].(float64)
if !ok {
return nil, fmt.Errorf("failed to typecast transfer field 4 to float64")
}
return ret, nil
}
ret.Verify, ok = transfer[4].(string)
if !ok {
return nil, fmt.Errorf("failed to typecast transfer field 4 to string")
}
ret.Progress, ok = transfer[5].(float64)
if !ok {
return nil, fmt.Errorf("failed to typecast transfer field 5 to float64")
}
return ret, nil
}

View File

@ -0,0 +1,175 @@
// Copyright 2019 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 import1
import (
"log"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
)
const (
importPrefix = "importd-test-"
)
func TestImportTar(t *testing.T) {
conn, err := New()
if err != nil {
t.Fatal(err)
}
f, err := os.Open(findFixture("image.tar.xz", t))
if err != nil {
t.Fatal(err)
}
_, err = conn.ImportTar(f, importPrefix+"ImportTar", true, true)
if err != nil {
t.Fatal(err)
}
}
func TestImportRaw(t *testing.T) {
conn, err := New()
if err != nil {
t.Fatal(err)
}
f, err := os.Open(findFixture("image.raw.xz", t))
if err != nil {
t.Fatal(err)
}
_, err = conn.ImportRaw(f, importPrefix+"ImportRaw", true, true)
if err != nil {
t.Fatal(err)
}
}
func TestExportTar(t *testing.T) {
conn, err := New()
if err != nil {
t.Fatal(err)
}
err = os.MkdirAll("tmp/", os.ModeDir|0755)
if err != nil {
t.Fatal(err)
}
f, err := os.Create("tmp/image-export.tar.xz")
if err != nil {
t.Fatal(err)
}
_, err = conn.ExportTar(importPrefix+"ImportTar", f, "xz")
if err != nil {
t.Fatal(err)
}
}
func TestExportRaw(t *testing.T) {
conn, err := New()
if err != nil {
t.Fatal(err)
}
err = os.MkdirAll("tmp/", os.ModeDir|0755)
if err != nil {
t.Fatal(err)
}
f, err := os.Create("tmp/image-export.raw.xz")
if err != nil {
t.Fatal(err)
}
_, err = conn.ExportRaw(importPrefix+"ImportRaw", f, "xz")
if err != nil {
t.Fatal(err)
}
}
func TestPullTar(t *testing.T) {
conn, err := New()
if err != nil {
t.Fatal(err)
}
_, err = conn.PullTar("http://127.0.0.1:8080/image.tar.xz", importPrefix+"PullTar", "no", true)
if err != nil {
t.Fatal(err)
}
}
func TestPullRaw(t *testing.T) {
conn, err := New()
if err != nil {
t.Fatal(err)
}
_, err = conn.PullRaw("http://127.0.0.1:8080/image.raw.xz", importPrefix+"PullRaw", "no", true)
if err != nil {
t.Fatal(err)
}
}
func TestListAndCancelTransfers(t *testing.T) {
conn, err := New()
if err != nil {
t.Fatal(err)
}
go func() {
_, _ = conn.PullTar("http://127.0.0.1:8080/image.tar.xz", importPrefix+"ListAndCancelTransfers", "no", true)
_, _ = conn.PullTar("http://127.0.0.1:8080/image.raw.xz", importPrefix+"ListAndCancelTransfers", "no", true)
}()
transfers, err := conn.ListTransfers()
if err != nil {
t.Error(err)
}
if len(transfers) < 1 {
t.Error("transfers length is not correct")
}
for _, v := range transfers {
err = conn.CancelTransfer(v.Id)
if err != nil {
// Let's just ignore the transfer id not found error.
if strings.Contains(err.Error(), "No transfer by id") {
continue
}
t.Error(err)
}
}
}
func findFixture(target string, t *testing.T) string {
abs, err := filepath.Abs("../fixtures/" + target)
if err != nil {
t.Fatal(err)
}
return abs
}
func init() {
go func() {
err := http.ListenAndServe(":8080", http.FileServer(http.Dir("../fixtures")))
if err != nil {
log.Fatal(err)
}
}()
}

View File

@ -0,0 +1,82 @@
// Copyright 2016 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 dlopen provides some convenience functions to dlopen a library and
// get its symbols.
package dlopen
// #cgo LDFLAGS: -ldl
// #include <stdlib.h>
// #include <dlfcn.h>
import "C"
import (
"errors"
"fmt"
"unsafe"
)
var ErrSoNotFound = errors.New("unable to open a handle to the library")
// LibHandle represents an open handle to a library (.so)
type LibHandle struct {
Handle unsafe.Pointer
Libname string
}
// GetHandle tries to get a handle to a library (.so), attempting to access it
// by the names specified in libs and returning the first that is successfully
// opened. Callers are responsible for closing the handler. If no library can
// be successfully opened, an error is returned.
func GetHandle(libs []string) (*LibHandle, error) {
for _, name := range libs {
libname := C.CString(name)
defer C.free(unsafe.Pointer(libname))
handle := C.dlopen(libname, C.RTLD_LAZY)
if handle != nil {
h := &LibHandle{
Handle: handle,
Libname: name,
}
return h, nil
}
}
return nil, ErrSoNotFound
}
// GetSymbolPointer takes a symbol name and returns a pointer to the symbol.
func (l *LibHandle) GetSymbolPointer(symbol string) (unsafe.Pointer, error) {
sym := C.CString(symbol)
defer C.free(unsafe.Pointer(sym))
C.dlerror()
p := C.dlsym(l.Handle, sym)
e := C.dlerror()
if e != nil {
return nil, fmt.Errorf("error resolving symbol %q: %v", symbol, errors.New(C.GoString(e)))
}
return p, nil
}
// Close closes a LibHandle.
func (l *LibHandle) Close() error {
C.dlerror()
C.dlclose(l.Handle)
e := C.dlerror()
if e != nil {
return fmt.Errorf("error closing %v: %v", l.Libname, errors.New(C.GoString(e)))
}
return nil
}

View File

@ -0,0 +1,63 @@
// 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 dlopen
import (
"fmt"
"testing"
)
func checkFailure(shouldSucceed bool, err error) (rErr error) {
switch {
case err != nil && shouldSucceed:
rErr = fmt.Errorf("expected test to succeed, failed unexpectedly: %v", err)
case err == nil && !shouldSucceed:
rErr = fmt.Errorf("expected test to fail, succeeded unexpectedly")
}
return
}
func TestDlopen(t *testing.T) {
tests := []struct {
libs []string
shouldSucceed bool
}{
{
libs: []string{
"libc.so.6",
"libc.so",
},
shouldSucceed: true,
},
{
libs: []string{
"libstrange.so",
},
shouldSucceed: false,
},
}
for i, tt := range tests {
expLen := 4
len, err := strlen(tt.libs, "test")
if checkFailure(tt.shouldSucceed, err) != nil {
t.Errorf("case %d: %v", i, err)
}
if tt.shouldSucceed && len != expLen {
t.Errorf("case %d: expected length %d, got %d", i, expLen, len)
}
}
}

225
vendor/github.com/coreos/go-systemd/journal/journal.go generated vendored Normal file
View File

@ -0,0 +1,225 @@
// 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 journal provides write bindings to the local systemd journal.
// It is implemented in pure Go and connects to the journal directly over its
// unix socket.
//
// To read from the journal, see the "sdjournal" package, which wraps the
// sd-journal a C API.
//
// http://www.freedesktop.org/software/systemd/man/systemd-journald.service.html
package journal
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall"
"unsafe"
)
// Priority of a journal message
type Priority int
const (
PriEmerg Priority = iota
PriAlert
PriCrit
PriErr
PriWarning
PriNotice
PriInfo
PriDebug
)
var (
// This can be overridden at build-time:
// https://github.com/golang/go/wiki/GcToolchainTricks#including-build-information-in-the-executable
journalSocket = "/run/systemd/journal/socket"
// unixConnPtr atomically holds the local unconnected Unix-domain socket.
// Concrete safe pointer type: *net.UnixConn
unixConnPtr unsafe.Pointer
// onceConn ensures that unixConnPtr is initialized exactly once.
onceConn sync.Once
)
func init() {
onceConn.Do(initConn)
}
// Enabled checks whether the local systemd journal is available for logging.
func Enabled() bool {
onceConn.Do(initConn)
if (*net.UnixConn)(atomic.LoadPointer(&unixConnPtr)) == nil {
return false
}
if _, err := net.Dial("unixgram", journalSocket); err != nil {
return false
}
return true
}
// Send a message to the local systemd journal. vars is a map of journald
// fields to values. Fields must be composed of uppercase letters, numbers,
// and underscores, but must not start with an underscore. Within these
// restrictions, any arbitrary field name may be used. Some names have special
// significance: see the journalctl documentation
// (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html)
// for more details. vars may be nil.
func Send(message string, priority Priority, vars map[string]string) error {
conn := (*net.UnixConn)(atomic.LoadPointer(&unixConnPtr))
if conn == nil {
return errors.New("could not initialize socket to journald")
}
socketAddr := &net.UnixAddr{
Name: journalSocket,
Net: "unixgram",
}
data := new(bytes.Buffer)
appendVariable(data, "PRIORITY", strconv.Itoa(int(priority)))
appendVariable(data, "MESSAGE", message)
for k, v := range vars {
appendVariable(data, k, v)
}
_, _, err := conn.WriteMsgUnix(data.Bytes(), nil, socketAddr)
if err == nil {
return nil
}
if !isSocketSpaceError(err) {
return err
}
// Large log entry, send it via tempfile and ancillary-fd.
file, err := tempFd()
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, data)
if err != nil {
return err
}
rights := syscall.UnixRights(int(file.Fd()))
_, _, err = conn.WriteMsgUnix([]byte{}, rights, socketAddr)
if err != nil {
return err
}
return nil
}
// Print prints a message to the local systemd journal using Send().
func Print(priority Priority, format string, a ...interface{}) error {
return Send(fmt.Sprintf(format, a...), priority, nil)
}
func appendVariable(w io.Writer, name, value string) {
if err := validVarName(name); err != nil {
fmt.Fprintf(os.Stderr, "variable name %s contains invalid character, ignoring\n", name)
}
if strings.ContainsRune(value, '\n') {
/* When the value contains a newline, we write:
* - the variable name, followed by a newline
* - the size (in 64bit little endian format)
* - the data, followed by a newline
*/
fmt.Fprintln(w, name)
binary.Write(w, binary.LittleEndian, uint64(len(value)))
fmt.Fprintln(w, value)
} else {
/* just write the variable and value all on one line */
fmt.Fprintf(w, "%s=%s\n", name, value)
}
}
// validVarName validates a variable name to make sure journald will accept it.
// The variable name must be in uppercase and consist only of characters,
// numbers and underscores, and may not begin with an underscore:
// https://www.freedesktop.org/software/systemd/man/sd_journal_print.html
func validVarName(name string) error {
if name == "" {
return errors.New("Empty variable name")
} else if name[0] == '_' {
return errors.New("Variable name begins with an underscore")
}
for _, c := range name {
if !(('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_') {
return errors.New("Variable name contains invalid characters")
}
}
return nil
}
// isSocketSpaceError checks whether the error is signaling
// an "overlarge message" condition.
func isSocketSpaceError(err error) bool {
opErr, ok := err.(*net.OpError)
if !ok || opErr == nil {
return false
}
sysErr, ok := opErr.Err.(*os.SyscallError)
if !ok || sysErr == nil {
return false
}
return sysErr.Err == syscall.EMSGSIZE || sysErr.Err == syscall.ENOBUFS
}
// tempFd creates a temporary, unlinked file under `/dev/shm`.
func tempFd() (*os.File, error) {
file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX")
if err != nil {
return nil, err
}
err = syscall.Unlink(file.Name())
if err != nil {
return nil, err
}
return file, nil
}
// initConn initializes the global `unixConnPtr` socket.
// It is meant to be called exactly once, at program startup.
func initConn() {
autobind, err := net.ResolveUnixAddr("unixgram", "")
if err != nil {
return
}
sock, err := net.ListenUnixgram("unixgram", autobind)
if err != nil {
return
}
atomic.StorePointer(&unixConnPtr, unsafe.Pointer(sock))
}

View File

@ -0,0 +1,121 @@
// Copyright 2018 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 journal
import (
"fmt"
"io/ioutil"
"runtime"
"strconv"
"strings"
"testing"
)
func TestJournalEnabled(t *testing.T) {
enabled := Enabled()
if !enabled {
t.Fatalf("journald socket not detected")
}
}
func TestValidVarName(t *testing.T) {
validTestCases := []string{
"TEST",
"TE_ST",
"TEST_",
"0TEST0",
}
invalidTestCases := []string{
"test",
"_TEST",
"",
}
for _, tt := range validTestCases {
if err := validVarName(tt); err != nil {
t.Fatalf("\"%s\" should be a valid variable", tt)
}
}
for _, tt := range invalidTestCases {
if err := validVarName(tt); err == nil {
t.Fatalf("\"%s\" should be an invalid variable", tt)
}
}
}
func TestJournalSend(t *testing.T) {
if !Enabled() {
t.Skip("systemd journal not available locally")
}
// an always-too-big value (hopefully)
hugeValue := 1234567890
// a value slightly larger than default limit,
// see `SO_SNDBUF` in socket(7)
largeValue := hugeValue
if wmem, err := ioutil.ReadFile("/proc/sys/net/core/wmem_default"); err == nil {
wmemStr := strings.TrimSpace(string(wmem))
if v, err := strconv.Atoi(wmemStr); err == nil {
largeValue = v + 1
}
}
// small messages should go over normal data,
// larger ones over temporary file with fd in ancillary data
testValues := []struct {
label string
len int
}{
{
"empty message",
0,
},
{
"small message",
5,
},
{
"large message",
largeValue,
},
{
"huge message",
hugeValue,
},
}
// This is memory intensive, so we manually trigger GC before and after each test.
for i, tt := range testValues {
t.Logf("journal send test #%v - %s (len=%d)", i, tt.label, tt.len)
runtime.GC()
err := SendAlloc(i, tt.label, tt.len)
if err != nil {
t.Fatalf("#%v: failed sending %s: %s", i, tt.label, err)
}
runtime.GC()
}
}
func SendAlloc(run int, label string, len int) error {
largeVars := map[string]string{
"KEY": string(make([]byte, len)),
}
msg := fmt.Sprintf("go-systemd test #%v - %s", run, label)
return Send(msg, PriCrit, largeVars)
}

260
vendor/github.com/coreos/go-systemd/login1/dbus.go generated vendored Normal file
View File

@ -0,0 +1,260 @@
// 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 login1 provides integration with the systemd logind API. See http://www.freedesktop.org/wiki/Software/systemd/logind/
package login1
import (
"fmt"
"os"
"strconv"
"github.com/godbus/dbus/v5"
)
const (
dbusInterface = "org.freedesktop.login1.Manager"
dbusPath = "/org/freedesktop/login1"
)
// Conn is a connection to systemds dbus endpoint.
type Conn struct {
conn *dbus.Conn
object dbus.BusObject
}
// New establishes a connection to the system bus and authenticates.
func New() (*Conn, error) {
c := new(Conn)
if err := c.initConnection(); err != nil {
return nil, err
}
return c, nil
}
// Close closes the dbus connection
func (c *Conn) Close() {
if c == nil {
return
}
if c.conn != nil {
c.conn.Close()
}
}
func (c *Conn) initConnection() error {
var err error
c.conn, err = dbus.SystemBusPrivate()
if err != nil {
return err
}
// Only use EXTERNAL method, and hardcode the uid (not username)
// to avoid a username lookup (which requires a dynamically linked
// libc)
methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))}
err = c.conn.Auth(methods)
if err != nil {
c.conn.Close()
return err
}
err = c.conn.Hello()
if err != nil {
c.conn.Close()
return err
}
c.object = c.conn.Object("org.freedesktop.login1", dbus.ObjectPath(dbusPath))
return nil
}
// Session object definition.
type Session struct {
ID string
UID uint32
User string
Seat string
Path dbus.ObjectPath
}
// User object definition.
type User struct {
UID uint32
Name string
Path dbus.ObjectPath
}
func (s Session) toInterface() []interface{} {
return []interface{}{s.ID, s.UID, s.User, s.Seat, s.Path}
}
func sessionFromInterfaces(session []interface{}) (*Session, error) {
if len(session) < 5 {
return nil, fmt.Errorf("invalid number of session fields: %d", len(session))
}
id, ok := session[0].(string)
if !ok {
return nil, fmt.Errorf("failed to typecast session field 0 to string")
}
uid, ok := session[1].(uint32)
if !ok {
return nil, fmt.Errorf("failed to typecast session field 1 to uint32")
}
user, ok := session[2].(string)
if !ok {
return nil, fmt.Errorf("failed to typecast session field 2 to string")
}
seat, ok := session[3].(string)
if !ok {
return nil, fmt.Errorf("failed to typecast session field 2 to string")
}
path, ok := session[4].(dbus.ObjectPath)
if !ok {
return nil, fmt.Errorf("failed to typecast session field 4 to ObjectPath")
}
ret := Session{ID: id, UID: uid, User: user, Seat: seat, Path: path}
return &ret, nil
}
func userFromInterfaces(user []interface{}) (*User, error) {
if len(user) < 3 {
return nil, fmt.Errorf("invalid number of user fields: %d", len(user))
}
uid, ok := user[0].(uint32)
if !ok {
return nil, fmt.Errorf("failed to typecast user field 0 to uint32")
}
name, ok := user[1].(string)
if !ok {
return nil, fmt.Errorf("failed to typecast session field 1 to string")
}
path, ok := user[2].(dbus.ObjectPath)
if !ok {
return nil, fmt.Errorf("failed to typecast user field 2 to ObjectPath")
}
ret := User{UID: uid, Name: name, Path: path}
return &ret, nil
}
// GetSession may be used to get the session object path for the session with the specified ID.
func (c *Conn) GetSession(id string) (dbus.ObjectPath, error) {
var out interface{}
if err := c.object.Call(dbusInterface+".GetSession", 0, id).Store(&out); err != nil {
return "", err
}
ret, ok := out.(dbus.ObjectPath)
if !ok {
return "", fmt.Errorf("failed to typecast session to ObjectPath")
}
return ret, nil
}
// ListSessions returns an array with all current sessions.
func (c *Conn) ListSessions() ([]Session, error) {
out := [][]interface{}{}
if err := c.object.Call(dbusInterface+".ListSessions", 0).Store(&out); err != nil {
return nil, err
}
ret := []Session{}
for _, el := range out {
session, err := sessionFromInterfaces(el)
if err != nil {
return nil, err
}
ret = append(ret, *session)
}
return ret, nil
}
// ListUsers returns an array with all currently logged in users.
func (c *Conn) ListUsers() ([]User, error) {
out := [][]interface{}{}
if err := c.object.Call(dbusInterface+".ListUsers", 0).Store(&out); err != nil {
return nil, err
}
ret := []User{}
for _, el := range out {
user, err := userFromInterfaces(el)
if err != nil {
return nil, err
}
ret = append(ret, *user)
}
return ret, nil
}
// LockSession asks the session with the specified ID to activate the screen lock.
func (c *Conn) LockSession(id string) {
c.object.Call(dbusInterface+".LockSession", 0, id)
}
// LockSessions asks all sessions to activate the screen locks. This may be used to lock any access to the machine in one action.
func (c *Conn) LockSessions() {
c.object.Call(dbusInterface+".LockSessions", 0)
}
// TerminateSession forcibly terminate one specific session.
func (c *Conn) TerminateSession(id string) {
c.object.Call(dbusInterface+".TerminateSession", 0, id)
}
// TerminateUser forcibly terminates all processes of a user.
func (c *Conn) TerminateUser(uid uint32) {
c.object.Call(dbusInterface+".TerminateUser", 0, uid)
}
// Reboot asks logind for a reboot optionally asking for auth.
func (c *Conn) Reboot(askForAuth bool) {
c.object.Call(dbusInterface+".Reboot", 0, askForAuth)
}
// Inhibit takes inhibition lock in logind.
func (c *Conn) Inhibit(what, who, why, mode string) (*os.File, error) {
var fd dbus.UnixFD
err := c.object.Call(dbusInterface+".Inhibit", 0, what, who, why, mode).Store(&fd)
if err != nil {
return nil, err
}
return os.NewFile(uintptr(fd), "inhibit"), nil
}
// Subscribe to signals on the logind dbus
func (c *Conn) Subscribe(members ...string) chan *dbus.Signal {
for _, member := range members {
c.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
fmt.Sprintf("type='signal',interface='org.freedesktop.login1.Manager',member='%s'", member))
}
ch := make(chan *dbus.Signal, 10)
c.conn.Signal(ch)
return ch
}
// PowerOff asks logind for a power off optionally asking for auth.
func (c *Conn) PowerOff(askForAuth bool) {
c.object.Call(dbusInterface+".PowerOff", 0, askForAuth)
}

View File

@ -0,0 +1,89 @@
// 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 login1
import (
"fmt"
"os/user"
"regexp"
"testing"
)
// TestNew ensures that New() works without errors.
func TestNew(t *testing.T) {
_, err := New()
if err != nil {
t.Fatal(err)
}
}
func TestListSessions(t *testing.T) {
c, err := New()
if err != nil {
t.Fatal(err)
}
sessions, err := c.ListSessions()
if err != nil {
t.Fatal(err)
}
if len(sessions) > 0 {
for _, s := range sessions {
lookup, err := user.Lookup(s.User)
if err != nil {
t.Fatal(err)
}
if fmt.Sprint(s.UID) != lookup.Uid {
t.Fatalf("expected uid '%d' but got '%s'", s.UID, lookup.Uid)
}
validPath := regexp.MustCompile(`/org/freedesktop/login1/session/_[0-9]+`)
if !validPath.MatchString(fmt.Sprint(s.Path)) {
t.Fatalf("invalid session path: %s", s.Path)
}
}
}
}
func TestListUsers(t *testing.T) {
c, err := New()
if err != nil {
t.Fatal(err)
}
users, err := c.ListUsers()
if err != nil {
t.Fatal(err)
}
if len(users) > 0 {
for _, u := range users {
lookup, err := user.Lookup(u.Name)
if err != nil {
t.Fatal(err)
}
if fmt.Sprint(u.UID) != lookup.Uid {
t.Fatalf("expected uid '%d' but got '%s'", u.UID, lookup.Uid)
}
validPath := regexp.MustCompile(`/org/freedesktop/login1/user/_[0-9]+`)
if !validPath.MatchString(fmt.Sprint(u.Path)) {
t.Fatalf("invalid user path: %s", u.Path)
}
}
}
}

282
vendor/github.com/coreos/go-systemd/machine1/dbus.go generated vendored Normal file
View File

@ -0,0 +1,282 @@
/*
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.
*/
// Integration with the systemd machined API. See http://www.freedesktop.org/wiki/Software/systemd/machined/
package machine1
import (
"fmt"
"os"
"strconv"
"syscall"
"github.com/godbus/dbus/v5"
sd_dbus "github.com/coreos/go-systemd/v22/dbus"
)
const (
dbusInterface = "org.freedesktop.machine1.Manager"
dbusPath = "/org/freedesktop/machine1"
)
// Conn is a connection to systemds dbus endpoint.
type Conn struct {
conn *dbus.Conn
object dbus.BusObject
}
// MachineStatus is a set of necessary info for each machine
type MachineStatus struct {
Name string // The primary machine name as string
Class string // The machine class as string
Service string // The machine service as string
JobPath dbus.ObjectPath // The job object path
}
// ImageStatus is a set of necessary info for each machine image
type ImageStatus struct {
Name string // The primary image name as string
ImageType string // The image type as string
Readonly bool // whether it's readonly or not
CreateTime uint64 // time when it's created
ModifyTime uint64 // time when it's modified
DiskUsage uint64 // used disk space
JobPath dbus.ObjectPath // The job object path
}
// New() establishes a connection to the system bus and authenticates.
func New() (*Conn, error) {
c := new(Conn)
if err := c.initConnection(); err != nil {
return nil, err
}
return c, nil
}
func (c *Conn) initConnection() error {
var err error
c.conn, err = dbus.SystemBusPrivate()
if err != nil {
return err
}
// Only use EXTERNAL method, and hardcode the uid (not username)
// to avoid a username lookup (which requires a dynamically linked
// libc)
methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))}
err = c.conn.Auth(methods)
if err != nil {
c.conn.Close()
return err
}
err = c.conn.Hello()
if err != nil {
c.conn.Close()
return err
}
c.object = c.conn.Object("org.freedesktop.machine1", dbus.ObjectPath(dbusPath))
return nil
}
func (c *Conn) getPath(method string, args ...interface{}) (dbus.ObjectPath, error) {
result := c.object.Call(fmt.Sprintf("%s.%s", dbusInterface, method), 0, args...)
if result.Err != nil {
return "", result.Err
}
path, typeErr := result.Body[0].(dbus.ObjectPath)
if !typeErr {
return "", fmt.Errorf("unable to convert dbus response '%v' to dbus.ObjectPath", result.Body[0])
}
return path, nil
}
// CreateMachine creates a new virtual machine or container with systemd-machined, generating a scope unit for it
func (c *Conn) CreateMachine(name string, id []byte, service string, class string, pid int, root_directory string, scope_properties []sd_dbus.Property) error {
return c.object.Call(dbusInterface+".CreateMachine", 0, name, id, service, class, uint32(pid), root_directory, scope_properties).Err
}
// CreateMachineWithNetwork creates the container with its network config with systemd-machined
func (c *Conn) CreateMachineWithNetwork(name string, id []byte, service string, class string, pid int, root_directory string, ifindices []int, scope_properties []sd_dbus.Property) error {
return c.object.Call(dbusInterface+".CreateMachineWithNetwork", 0, name, id, service, class, uint32(pid), root_directory, ifindices, scope_properties).Err
}
// GetMachine gets a specific container with systemd-machined
func (c *Conn) GetMachine(name string) (dbus.ObjectPath, error) {
return c.getPath("GetMachine", name)
}
// GetImage gets a specific image with systemd-machined
func (c *Conn) GetImage(name string) (dbus.ObjectPath, error) {
return c.getPath("GetImage", name)
}
// GetMachineByPID gets a machine specified by a PID from systemd-machined
func (c *Conn) GetMachineByPID(pid uint) (dbus.ObjectPath, error) {
return c.getPath("GetMachineByPID", pid)
}
// GetMachineAddresses gets a list of IP addresses
func (c *Conn) GetMachineAddresses(name string) (dbus.ObjectPath, error) {
return c.getPath("GetMachineAddresses", name)
}
// DescribeMachine gets the properties of a machine
func (c *Conn) DescribeMachine(name string) (machineProps map[string]interface{}, err error) {
var dbusProps map[string]dbus.Variant
path, pathErr := c.GetMachine(name)
if pathErr != nil {
return nil, pathErr
}
obj := c.conn.Object("org.freedesktop.machine1", path)
err = obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, "").Store(&dbusProps)
if err != nil {
return nil, err
}
machineProps = make(map[string]interface{}, len(dbusProps))
for key, val := range dbusProps {
machineProps[key] = val.Value()
}
return
}
// KillMachine sends a signal to a machine
func (c *Conn) KillMachine(name, who string, sig syscall.Signal) error {
return c.object.Call(dbusInterface+".KillMachine", 0, name, who, sig).Err
}
// TerminateMachine causes systemd-machined to terminate a machine, killing its processes
func (c *Conn) TerminateMachine(name string) error {
return c.object.Call(dbusInterface+".TerminateMachine", 0, name).Err
}
// RegisterMachine registers the container with the systemd-machined
func (c *Conn) RegisterMachine(name string, id []byte, service string, class string, pid int, root_directory string) error {
return c.object.Call(dbusInterface+".RegisterMachine", 0, name, id, service, class, uint32(pid), root_directory).Err
}
// RegisterMachineWithNetwork registers the container with its network with systemd-machined
func (c *Conn) RegisterMachineWithNetwork(name string, id []byte, service string, class string, pid int, root_directory string, ifindices []int) error {
return c.object.Call(dbusInterface+".RegisterMachineWithNetwork", 0, name, id, service, class, uint32(pid), root_directory, ifindices).Err
}
func machineFromInterfaces(machine []interface{}) (*MachineStatus, error) {
if len(machine) < 4 {
return nil, fmt.Errorf("invalid number of machine fields: %d", len(machine))
}
name, ok := machine[0].(string)
if !ok {
return nil, fmt.Errorf("failed to typecast machine field 0 to string")
}
class, ok := machine[1].(string)
if !ok {
return nil, fmt.Errorf("failed to typecast class field 1 to string")
}
service, ok := machine[2].(string)
if !ok {
return nil, fmt.Errorf("failed to typecast service field 2 to string")
}
jobpath, ok := machine[3].(dbus.ObjectPath)
if !ok {
return nil, fmt.Errorf("failed to typecast jobpath field 3 to ObjectPath")
}
ret := MachineStatus{Name: name, Class: class, Service: service, JobPath: jobpath}
return &ret, nil
}
// ListMachines returns an array of all currently running machines.
func (c *Conn) ListMachines() ([]MachineStatus, error) {
result := make([][]interface{}, 0)
if err := c.object.Call(dbusInterface+".ListMachines", 0).Store(&result); err != nil {
return nil, err
}
machs := []MachineStatus{}
for _, i := range result {
machine, err := machineFromInterfaces(i)
if err != nil {
return nil, err
}
machs = append(machs, *machine)
}
return machs, nil
}
func imageFromInterfaces(image []interface{}) (*ImageStatus, error) {
if len(image) < 7 {
return nil, fmt.Errorf("invalid number of image fields: %d", len(image))
}
name, ok := image[0].(string)
if !ok {
return nil, fmt.Errorf("failed to typecast image field 0 to string")
}
imagetype, ok := image[1].(string)
if !ok {
return nil, fmt.Errorf("failed to typecast imagetype field 1 to string")
}
readonly, ok := image[2].(bool)
if !ok {
return nil, fmt.Errorf("failed to typecast readonly field 2 to bool")
}
createtime, ok := image[3].(uint64)
if !ok {
return nil, fmt.Errorf("failed to typecast createtime field 3 to uint64")
}
modifytime, ok := image[4].(uint64)
if !ok {
return nil, fmt.Errorf("failed to typecast modifytime field 4 to uint64")
}
diskusage, ok := image[5].(uint64)
if !ok {
return nil, fmt.Errorf("failed to typecast diskusage field 5 to uint64")
}
jobpath, ok := image[6].(dbus.ObjectPath)
if !ok {
return nil, fmt.Errorf("failed to typecast jobpath field 6 to ObjectPath")
}
ret := ImageStatus{Name: name, ImageType: imagetype, Readonly: readonly, CreateTime: createtime, ModifyTime: modifytime, DiskUsage: diskusage, JobPath: jobpath}
return &ret, nil
}
// ListImages returns an array of all currently available images.
func (c *Conn) ListImages() ([]ImageStatus, error) {
result := make([][]interface{}, 0)
if err := c.object.Call(dbusInterface+".ListImages", 0).Store(&result); err != nil {
return nil, err
}
images := []ImageStatus{}
for _, i := range result {
image, err := imageFromInterfaces(i)
if err != nil {
return nil, err
}
images = append(images, *image)
}
return images, nil
}

View File

@ -0,0 +1,184 @@
/*
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 machine1
import (
"fmt"
"math/rand"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
sd_dbus "github.com/coreos/go-systemd/v22/dbus"
"github.com/godbus/dbus/v5"
)
const (
machinePrefix = "machined-test-"
)
func mustCreateTestProcess(machineName string) (pid int) {
testServiceName := machineName + ".service"
systemdRun, err := exec.LookPath("systemd-run")
if err != nil {
panic(err.Error())
}
sleep, err := exec.LookPath("sleep")
if err != nil {
panic(err.Error())
}
cmd := exec.Command(systemdRun, "--unit="+testServiceName, sleep, "5000")
out, err := cmd.CombinedOutput()
if err != nil {
panic(fmt.Errorf("systemd-run failed: %q", out))
}
dbusConn, err := sd_dbus.New()
if err != nil {
panic(err.Error())
}
defer dbusConn.Close()
mainPIDProperty, err := dbusConn.GetServiceProperty(testServiceName, "MainPID")
if err != nil {
panic(err.Error())
}
mainPID := mainPIDProperty.Value.Value().(uint32)
return int(mainPID)
}
func TestMachine(t *testing.T) {
machineNames := []string{
machinePrefix + "register-" + generateRandomLabel(8),
machinePrefix + "register-with-network-" + generateRandomLabel(8),
machinePrefix + "create-" + generateRandomLabel(8),
machinePrefix + "create-with-network-" + generateRandomLabel(8),
}
leaders := []int{
mustCreateTestProcess(machineNames[0]),
mustCreateTestProcess(machineNames[1]),
mustCreateTestProcess(machineNames[2]),
mustCreateTestProcess(machineNames[3]),
}
conn, newErr := New()
if newErr != nil {
t.Fatal(newErr)
}
regErr := conn.RegisterMachine(machineNames[0], nil, "go-systemd", "container", leaders[0], "")
if regErr != nil {
t.Fatal(regErr)
}
regWithNetworkErr := conn.RegisterMachineWithNetwork(machineNames[1], nil, "go-systemd", "container", leaders[1], "", nil)
if regWithNetworkErr != nil {
t.Fatal(regWithNetworkErr)
}
createErr := conn.CreateMachine(machineNames[2], nil, "go-systemd", "container", leaders[2], "", nil)
if createErr != nil {
t.Fatal(createErr)
}
createWithNetworkErr := conn.CreateMachineWithNetwork(machineNames[3], nil, "go-systemd", "container", leaders[3], "", nil, nil)
if createWithNetworkErr != nil {
t.Fatal(createWithNetworkErr)
}
machines := make([]dbus.ObjectPath, 0)
for _, v := range machineNames {
machine, getErr := conn.GetMachine(v)
if getErr != nil {
t.Fatal(getErr)
}
if machine != "" {
machines = append(machines, machine)
}
}
if len(machines) != 4 {
t.Fatalf("did not find all machine nameds %s", machineNames)
}
listMachines, getErr := conn.ListMachines()
if getErr != nil {
t.Fatal(getErr)
}
// listMachines includes also `.host`, so by default the length should be greater than 2
if len(listMachines) <= 4 {
t.Fatalf("did not find any machine")
}
for _, v := range machineNames {
tErr := conn.TerminateMachine(v)
if tErr != nil {
t.Fatal(tErr)
}
var machine dbus.ObjectPath
for i := 1; i <= 10; i++ {
machine, getErr = conn.GetMachine(v)
if len(machine) == 0 && getErr != nil {
break
}
time.Sleep(1 * time.Second)
}
if len(machine) != 0 {
t.Fatalf("unexpectedly found machine named %s", v)
} else if getErr == nil {
t.Fatal("expected error but got nil")
}
}
}
func generateRandomLabel(n int) string {
letters := []rune("abcdefghijklmnopqrstuvwxyz")
s := make([]rune, n)
rand.Seed(time.Now().UnixNano())
for i := range s {
s[i] = letters[rand.Intn(len(letters))]
}
return string(s)
}
func TestImages(t *testing.T) {
imageName := machinePrefix + generateRandomLabel(8)
imagePath := filepath.Join("/var/lib/machines", imageName)
if _, err := os.Create(imagePath); err != nil {
t.Fatal(err)
}
defer os.Remove(imagePath)
if err := os.Truncate(imagePath, 500*1024*1024); err != nil {
t.Fatal(err)
}
conn, newErr := New()
if newErr != nil {
t.Fatal(newErr)
}
listImages, listErr := conn.ListImages()
if listErr != nil {
t.Fatal(listErr)
}
if len(listImages) < 1 {
t.Fatalf("did not find any image")
}
}

View File

@ -0,0 +1,23 @@
#!/bin/bash
echo ""
echo "Public IP: "`wget -O - http://checkip.amazonaws.com/ 2>/dev/null || true`
echo "Systemd version: "`systemctl --version || true`
echo "Linux version: "`uname -srm || true`
echo "Distribution version: "`(. /etc/os-release ; echo $PRETTY_NAME) || true`
echo ""
echo "git commit: "`git describe --abbrev=40 HEAD || true`
echo ""
# unsquashfs is in /usr/sbin
export PATH=/usr/lib64/ccache:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/home/fedora/.local/bin:/home/fedora/bin
# As a convenience, set up a self-contained GOPATH if none set
if [ ! -h gopath/src/github.com/coreos/go-systemd ]; then
mkdir -p gopath/src/github.com/coreos
ln -s ../../../.. gopath/src/github.com/coreos/go-systemd || exit 255
fi
export GOPATH=${PWD}/gopath
go get -u github.com/godbus/dbus
sudo -E ./test

109
vendor/github.com/coreos/go-systemd/scripts/travis/pr-test.sh generated vendored Executable file
View File

@ -0,0 +1,109 @@
#!/usr/bin/env bash
set -e
set -o pipefail
PROJ="go-systemd"
ORG_PATH="github.com/coreos"
REPO_PATH="${ORG_PATH}/${PROJ}"
PACKAGES="activation daemon dbus journal login1 machine1 sdjournal unit util import1"
EXAMPLES="activation listen udpconn"
function build_source {
go build ./...
}
function build_tests {
rm -rf ./test_bins ; mkdir -p ./test_bins
for pkg in ${PACKAGES}; do
echo " - ${pkg}"
go test -c -o ./test_bins/${pkg}.test ./${pkg}
done
for ex in ${EXAMPLES}; do
echo " - examples/${ex}"
go build -o ./test_bins/${ex}.example ./examples/activation/${ex}.go
done
}
function run_tests {
pushd test_bins
sudo -v
for pkg in ${PACKAGES}; do
echo " - ${pkg}"
sudo -E ./${pkg}.test -test.v
done
popd
sudo rm -rf ./test_bins
}
function go_fmt {
for pkg in ${PACKAGES}; do
echo " - ${pkg}"
fmtRes=$(gofmt -l "./${pkg}")
if [ -n "${fmtRes}" ]; then
echo -e "gofmt checking failed:\n${fmtRes}"
exit 255
fi
done
}
function go_vet {
for pkg in ${PACKAGES}; do
echo " - ${pkg}"
vetRes=$(go vet "./${pkg}")
if [ -n "${vetRes}" ]; then
echo -e "govet checking failed:\n${vetRes}"
exit 254
fi
done
}
function license_check {
licRes=$(for file in $(find . -type f -iname '*.go' ! -path './vendor/*'); do
head -n3 "${file}" | grep -Eq "(Copyright|generated|GENERATED)" || echo -e " ${file}"
done;)
if [ -n "${licRes}" ]; then
echo -e "license header checking failed:\n${licRes}"
exit 253
fi
}
export GO15VENDOREXPERIMENT=1
subcommand="$1"
case "$subcommand" in
"build_source" )
echo "Building source..."
build_source
;;
"build_tests" )
echo "Building tests..."
build_tests
;;
"run_tests" )
echo "Running tests..."
run_tests
;;
"go_fmt" )
echo "Checking gofmt..."
go_fmt
;;
"go_vet" )
echo "Checking govet..."
go_vet
;;
"license_check" )
echo "Checking licenses..."
license_check
;;
* )
echo "Error: unrecognized subcommand."
exit 1
;;
esac

View File

@ -0,0 +1,66 @@
// Copyright 2015 RedHat, Inc.
// 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 sdjournal
import (
"github.com/coreos/go-systemd/v22/internal/dlopen"
"sync"
"unsafe"
)
var (
// lazy initialized
libsystemdHandle *dlopen.LibHandle
libsystemdMutex = &sync.Mutex{}
libsystemdFunctions = map[string]unsafe.Pointer{}
libsystemdNames = []string{
// systemd < 209
"libsystemd-journal.so.0",
"libsystemd-journal.so",
// systemd >= 209 merged libsystemd-journal into libsystemd proper
"libsystemd.so.0",
"libsystemd.so",
}
)
func getFunction(name string) (unsafe.Pointer, error) {
libsystemdMutex.Lock()
defer libsystemdMutex.Unlock()
if libsystemdHandle == nil {
h, err := dlopen.GetHandle(libsystemdNames)
if err != nil {
return nil, err
}
libsystemdHandle = h
}
f, ok := libsystemdFunctions[name]
if !ok {
var err error
f, err = libsystemdHandle.GetSymbolPointer(name)
if err != nil {
return nil, err
}
libsystemdFunctions[name] = f
}
return f, nil
}

View File

@ -0,0 +1,36 @@
// Copyright 2015 RedHat, Inc.
// 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 sdjournal
import "testing"
func TestGetFunction(t *testing.T) {
f, err := getFunction("sd_journal_open")
if err != nil {
t.Errorf("Error getting an existing function: %s", err)
}
if f == nil {
t.Error("Got nil function pointer")
}
_, err = getFunction("non_existent_function")
if err == nil {
t.Error("Expected to get an error, got nil")
}
}

1120
vendor/github.com/coreos/go-systemd/sdjournal/journal.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

508
vendor/github.com/coreos/go-systemd/sdjournal/journal_test.go generated vendored Executable file
View File

@ -0,0 +1,508 @@
// Copyright 2015 RedHat, Inc.
// 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 sdjournal
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"math/rand"
"os"
"strings"
"testing"
"time"
"github.com/coreos/go-systemd/v22/journal"
)
func TestJournalFollow(t *testing.T) {
r, err := NewJournalReader(JournalReaderConfig{
Since: time.Duration(-15) * time.Second,
Matches: []Match{
{
Field: SD_JOURNAL_FIELD_SYSTEMD_UNIT,
Value: "NetworkManager.service",
},
},
})
if err != nil {
t.Fatalf("Error opening journal: %s", err)
}
if r == nil {
t.Fatal("Got a nil reader")
}
defer r.Close()
// start writing some test entries
done := make(chan struct{}, 1)
errCh := make(chan error, 1)
defer close(done)
go func() {
for {
select {
case <-done:
return
default:
if perr := journal.Print(journal.PriInfo, "test message %s", time.Now()); err != nil {
errCh <- perr
return
}
time.Sleep(time.Second)
}
}
}()
// and follow the reader synchronously
timeout := time.Duration(5) * time.Second
if err = r.Follow(time.After(timeout), os.Stdout); err != ErrExpired {
t.Fatalf("Error during follow: %s", err)
}
select {
case err := <-errCh:
t.Fatalf("Error writing to journal: %s", err)
default:
}
}
func TestJournalWait(t *testing.T) {
id := time.Now().String()
j, err := NewJournal()
if err != nil {
t.Fatalf("Error opening journal: %s", err)
}
if err := j.AddMatch("TEST=TestJournalWait " + id); err != nil {
t.Fatalf("Error adding match: %s", err)
}
if err := j.SeekTail(); err != nil {
t.Fatalf("Error seeking to tail: %s", err)
}
if _, err := j.Next(); err != nil {
t.Fatalf("Error retrieving next entry: %s", err)
}
var t1, t2 time.Time
for ret := -1; ret != SD_JOURNAL_NOP; {
// Wait() might return for reasons other than timeout.
// For example the first call initializes stuff and returns immediately.
t1 = time.Now()
ret = j.Wait(time.Millisecond * 300)
t2 = time.Now()
}
duration := t2.Sub(t1)
if duration > time.Millisecond*325 || duration < time.Millisecond*300 {
t.Errorf("Wait did not wait 300ms. Actually waited %s", duration.String())
}
journal.Send("test message", journal.PriInfo, map[string]string{"TEST": "TestJournalWait " + id})
for ret := -1; ret != SD_JOURNAL_APPEND; {
t1 = time.Now()
ret = j.Wait(time.Millisecond * 300)
t2 = time.Now()
}
duration = t2.Sub(t1)
if duration >= time.Millisecond*300 {
t.Errorf("Wait took longer than 300ms. Actual duration %s", duration.String())
}
}
func TestJournalGetUsage(t *testing.T) {
j, err := NewJournal()
if err != nil {
t.Fatalf("Error opening journal: %s", err)
}
if j == nil {
t.Fatal("Got a nil journal")
}
defer j.Close()
_, err = j.GetUsage()
if err != nil {
t.Fatalf("Error getting journal size: %s", err)
}
}
func TestJournalCursorGetSeekAndTest(t *testing.T) {
j, err := NewJournal()
if err != nil {
t.Fatalf("Error opening journal: %s", err)
}
if j == nil {
t.Fatal("Got a nil journal")
}
defer j.Close()
err = journal.Print(journal.PriInfo, "test message for cursor %s", time.Now())
if err != nil {
t.Fatalf("Error writing to journal: %s", err)
}
if err = waitAndNext(j); err != nil {
t.Fatalf(err.Error())
}
c, err := j.GetCursor()
if err != nil {
t.Fatalf("Error getting cursor from journal: %s", err)
}
err = j.SeekCursor(c)
if err != nil {
t.Fatalf("Error seeking cursor to journal: %s", err)
}
if err = waitAndNext(j); err != nil {
t.Fatalf(err.Error())
}
err = j.TestCursor(c)
if err != nil {
t.Fatalf("Error testing cursor to journal: %s", err)
}
err = journal.Print(journal.PriInfo, "second message %s", time.Now())
if err != nil {
t.Fatalf("Error writing to journal: %s", err)
}
if err = waitAndNext(j); err != nil {
t.Fatalf(err.Error())
}
err = j.TestCursor(c)
if err != ErrNoTestCursor {
t.Fatalf("Error, TestCursor should fail because current cursor has moved from the previous obtained cursor")
}
}
func TestNewJournalFromDir(t *testing.T) {
// test for error handling
dir := "/ClearlyNonExistingPath/"
j, err := NewJournalFromDir(dir)
if err == nil {
defer j.Close()
t.Fatalf("Error expected when opening dummy path (%s)", dir)
}
// test for main code path
dir, err = ioutil.TempDir("", "go-systemd-test")
if err != nil {
t.Fatalf("Error creating tempdir: %s", err)
}
defer os.RemoveAll(dir)
j, err = NewJournalFromDir(dir)
if err != nil {
t.Fatalf("Error opening journal: %s", err)
}
if j == nil {
t.Fatal("Got a nil journal")
}
j.Close()
}
func setupJournalRoundtrip() (*Journal, map[string]string, error) {
j, err := NewJournal()
if err != nil {
return nil, nil, fmt.Errorf("Error opening journal: %s", err)
}
if j == nil {
return nil, nil, fmt.Errorf("Got a nil journal")
}
j.FlushMatches()
matchField := "TESTJOURNALENTRY"
matchValue := fmt.Sprintf("%d", time.Now().UnixNano())
m := Match{Field: matchField, Value: matchValue}
err = j.AddMatch(m.String())
if err != nil {
return nil, nil, fmt.Errorf("Error adding matches to journal: %s", err)
}
msg := fmt.Sprintf("test journal get entry message %s", time.Now())
data := map[string]string{matchField: matchValue}
err = journal.Send(msg, journal.PriInfo, data)
if err != nil {
return nil, nil, fmt.Errorf("Error writing to journal: %s", err)
}
time.Sleep(time.Duration(1) * time.Second)
n, err := j.Next()
if err != nil {
return nil, nil, fmt.Errorf("Error reading from journal: %s", err)
}
if n == 0 {
return nil, nil, fmt.Errorf("Error reading from journal: %s", io.EOF)
}
data["MESSAGE"] = msg
return j, data, nil
}
func TestJournalGetData(t *testing.T) {
j, wantEntry, err := setupJournalRoundtrip()
if err != nil {
t.Fatal(err.Error())
}
defer j.Close()
for k, v := range wantEntry {
data := fmt.Sprintf("%s=%s", k, v)
dataStr, err := j.GetData(k)
if err != nil {
t.Fatalf("GetData() error: %v", err)
}
if dataStr != data {
t.Fatalf("Invalid data for \"%s\": got %s, want %s", k, dataStr, data)
}
dataBytes, err := j.GetDataBytes(k)
if err != nil {
t.Fatalf("GetDataBytes() error: %v", err)
}
if string(dataBytes) != data {
t.Fatalf("Invalid data bytes for \"%s\": got %s, want %s", k, string(dataBytes), data)
}
valStr, err := j.GetDataValue(k)
if err != nil {
t.Fatalf("GetDataValue() error: %v", err)
}
if valStr != v {
t.Fatalf("Invalid data value for \"%s\": got %s, want %s", k, valStr, v)
}
valBytes, err := j.GetDataValueBytes(k)
if err != nil {
t.Fatalf("GetDataValueBytes() error: %v", err)
}
if string(valBytes) != v {
t.Fatalf("Invalid data value bytes for \"%s\": got %s, want %s", k, string(valBytes), v)
}
}
}
func TestJournalGetEntry(t *testing.T) {
j, wantEntry, err := setupJournalRoundtrip()
if err != nil {
t.Fatal(err.Error())
}
defer j.Close()
entry, err := j.GetEntry()
if err != nil {
t.Fatalf("Error getting the entry to journal: %s", err)
}
for k, wantV := range wantEntry {
gotV := entry.Fields[k]
if gotV != wantV {
t.Fatalf("Bad result for entry.Fields[\"%s\"]: got %s, want %s", k, gotV, wantV)
}
}
}
// Check for incorrect read into small buffers,
// see https://github.com/coreos/go-systemd/issues/172
func TestJournalReaderSmallReadBuffer(t *testing.T) {
// Write a long entry ...
delim := "%%%%%%"
longEntry := strings.Repeat("a", 256)
matchField := "TESTJOURNALREADERSMALLBUF"
matchValue := fmt.Sprintf("%d", time.Now().UnixNano())
r, err := NewJournalReader(JournalReaderConfig{
Since: time.Duration(-15) * time.Second,
Matches: []Match{
{
Field: matchField,
Value: matchValue,
},
},
})
if err != nil {
t.Fatalf("Error opening journal: %s", err)
}
if r == nil {
t.Fatal("Got a nil reader")
}
defer r.Close()
want := fmt.Sprintf("%slongentry %s%s", delim, longEntry, delim)
err = journal.Send(want, journal.PriInfo, map[string]string{matchField: matchValue})
if err != nil {
t.Fatal("Error writing to journal", err)
}
time.Sleep(time.Second)
// ... and try to read it back piece by piece via a small buffer
finalBuff := new(bytes.Buffer)
var e error
for c := -1; c != 0 && e == nil; {
smallBuf := make([]byte, 5)
c, e = r.Read(smallBuf)
if c > len(smallBuf) {
t.Fatalf("Got unexpected read length: %d vs %d", c, len(smallBuf))
}
_, _ = finalBuff.Write(smallBuf)
}
b := finalBuff.String()
got := strings.Split(b, delim)
if len(got) != 3 {
t.Fatalf("Got unexpected entry %s", b)
}
if got[1] != strings.Trim(want, delim) {
t.Fatalf("Got unexpected message %s", got[1])
}
}
func TestJournalGetUniqueValues(t *testing.T) {
j, err := NewJournal()
if err != nil {
t.Fatal(err)
}
defer j.Close()
uniqueString := generateRandomField(20)
testEntries := []string{"A", "B", "C", "D"}
for _, v := range testEntries {
err = journal.Send("TEST: "+uniqueString, journal.PriInfo, map[string]string{uniqueString: v})
if err != nil {
t.Fatal(err)
}
}
// TODO: add proper `waitOnMatch` function which should wait for journal entry with filter to commit.
time.Sleep(time.Millisecond * 500)
values, err := j.GetUniqueValues(uniqueString)
if err != nil {
t.Fatal(err)
}
if len(values) != len(testEntries) {
t.Fatalf("Expect %d entries. Got %d", len(testEntries), len(values))
}
if !contains(values, "A") || !contains(values, "B") || !contains(values, "C") || !contains(values, "D") {
t.Fatalf("Expect 4 values for %s field: A,B,C,D. Got %s", uniqueString, values)
}
}
func TestJournalGetCatalog(t *testing.T) {
want := []string{
"Subject: ",
"Defined-By: systemd",
"Support: ",
}
j, err := NewJournal()
if err != nil {
t.Fatalf("Error opening journal: %s", err)
}
if j == nil {
t.Fatal("Got a nil journal")
}
defer j.Close()
if err = j.SeekHead(); err != nil {
t.Fatalf("Seek to head failed: %s", err)
}
matchField := SD_JOURNAL_FIELD_SYSTEMD_UNIT
m := Match{Field: matchField, Value: "systemd-journald.service"}
if err = j.AddMatch(m.String()); err != nil {
t.Fatalf("Error adding matches to journal: %s", err)
}
if err = waitAndNext(j); err != nil {
t.Fatalf(err.Error())
}
catalog, err := j.GetCatalog()
if err != nil {
t.Fatalf("Failed to retrieve catalog entry: %s", err)
}
for _, w := range want {
if !strings.Contains(catalog, w) {
t.Fatalf("Failed to find \"%s\" in \n%s", w, catalog)
}
}
}
func contains(s []string, v string) bool {
for _, entry := range s {
if entry == v {
return true
}
}
return false
}
func generateRandomField(n int) string {
letters := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
s := make([]rune, n)
rand.Seed(time.Now().UnixNano())
for i := range s {
s[i] = letters[rand.Intn(len(letters))]
}
return string(s)
}
func waitAndNext(j *Journal) error {
r := j.Wait(time.Duration(1) * time.Second)
if r < 0 {
return errors.New("Error waiting to journal")
}
n, err := j.Next()
if err != nil {
return fmt.Errorf("Error reading to journal: %s", err)
}
if n == 0 {
return fmt.Errorf("Error reading to journal: %s", io.EOF)
}
return nil
}

272
vendor/github.com/coreos/go-systemd/sdjournal/read.go generated vendored Normal file
View File

@ -0,0 +1,272 @@
// Copyright 2015 RedHat, Inc.
// 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 sdjournal
import (
"errors"
"fmt"
"io"
"log"
"strings"
"sync"
"time"
)
var (
// ErrExpired gets returned when the Follow function runs into the
// specified timeout.
ErrExpired = errors.New("Timeout expired")
)
// JournalReaderConfig represents options to drive the behavior of a JournalReader.
type JournalReaderConfig struct {
// The Since, NumFromTail and Cursor options are mutually exclusive and
// determine where the reading begins within the journal. The order in which
// options are written is exactly the order of precedence.
Since time.Duration // start relative to a Duration from now
NumFromTail uint64 // start relative to the tail
Cursor string // start relative to the cursor
// Show only journal entries whose fields match the supplied values. If
// the array is empty, entries will not be filtered.
Matches []Match
// If not empty, the journal instance will point to a journal residing
// in this directory. The supplied path may be relative or absolute.
Path string
// If not nil, Formatter will be used to translate the resulting entries
// into strings. If not set, the default format (timestamp and message field)
// will be used. If Formatter returns an error, Read will stop and return the error.
Formatter func(entry *JournalEntry) (string, error)
}
// JournalReader is an io.ReadCloser which provides a simple interface for iterating through the
// systemd journal. A JournalReader is not safe for concurrent use by multiple goroutines.
type JournalReader struct {
journal *Journal
msgReader *strings.Reader
formatter func(entry *JournalEntry) (string, error)
}
// NewJournalReader creates a new JournalReader with configuration options that are similar to the
// systemd journalctl tool's iteration and filtering features.
func NewJournalReader(config JournalReaderConfig) (*JournalReader, error) {
// use simpleMessageFormatter as default formatter.
if config.Formatter == nil {
config.Formatter = simpleMessageFormatter
}
r := &JournalReader{
formatter: config.Formatter,
}
// Open the journal
var err error
if config.Path != "" {
r.journal, err = NewJournalFromDir(config.Path)
} else {
r.journal, err = NewJournal()
}
if err != nil {
return nil, err
}
// Add any supplied matches
for _, m := range config.Matches {
if err = r.journal.AddMatch(m.String()); err != nil {
return nil, err
}
}
// Set the start position based on options
if config.Since != 0 {
// Start based on a relative time
start := time.Now().Add(config.Since)
if err := r.journal.SeekRealtimeUsec(uint64(start.UnixNano() / 1000)); err != nil {
return nil, err
}
} else if config.NumFromTail != 0 {
// Start based on a number of lines before the tail
if err := r.journal.SeekTail(); err != nil {
return nil, err
}
// Move the read pointer into position near the tail. Go one further than
// the option so that the initial cursor advancement positions us at the
// correct starting point.
skip, err := r.journal.PreviousSkip(config.NumFromTail + 1)
if err != nil {
return nil, err
}
// If we skipped fewer lines than expected, we have reached journal start.
// Thus, we seek to head so that next invocation can read the first line.
if skip != config.NumFromTail+1 {
if err := r.journal.SeekHead(); err != nil {
return nil, err
}
}
} else if config.Cursor != "" {
// Start based on a custom cursor
if err := r.journal.SeekCursor(config.Cursor); err != nil {
return nil, err
}
}
return r, nil
}
// Read reads entries from the journal. Read follows the Reader interface so
// it must be able to read a specific amount of bytes. Journald on the other
// hand only allows us to read full entries of arbitrary size (without byte
// granularity). JournalReader is therefore internally buffering entries that
// don't fit in the read buffer. Callers should keep calling until 0 and/or an
// error is returned.
func (r *JournalReader) Read(b []byte) (int, error) {
if r.msgReader == nil {
// Advance the journal cursor. It has to be called at least one time
// before reading
c, err := r.journal.Next()
// An unexpected error
if err != nil {
return 0, err
}
// EOF detection
if c == 0 {
return 0, io.EOF
}
entry, err := r.journal.GetEntry()
if err != nil {
return 0, err
}
// Build a message
msg, err := r.formatter(entry)
if err != nil {
return 0, err
}
r.msgReader = strings.NewReader(msg)
}
// Copy and return the message
sz, err := r.msgReader.Read(b)
if err == io.EOF {
// The current entry has been fully read. Don't propagate this
// EOF, so the next entry can be read at the next Read()
// iteration.
r.msgReader = nil
return sz, nil
}
if err != nil {
return sz, err
}
if r.msgReader.Len() == 0 {
r.msgReader = nil
}
return sz, nil
}
// Close closes the JournalReader's handle to the journal.
func (r *JournalReader) Close() error {
return r.journal.Close()
}
// Rewind attempts to rewind the JournalReader to the first entry.
func (r *JournalReader) Rewind() error {
r.msgReader = nil
return r.journal.SeekHead()
}
// Follow synchronously follows the JournalReader, writing each new journal entry to writer. The
// follow will continue until a single time.Time is received on the until channel.
func (r *JournalReader) Follow(until <-chan time.Time, writer io.Writer) error {
// Process journal entries and events. Entries are flushed until the tail or
// timeout is reached, and then we wait for new events or the timeout.
var msg = make([]byte, 64*1<<(10))
var waitCh = make(chan int, 1)
var waitGroup sync.WaitGroup
defer waitGroup.Wait()
process:
for {
c, err := r.Read(msg)
if err != nil && err != io.EOF {
return err
}
select {
case <-until:
return ErrExpired
default:
}
if c > 0 {
if _, err = writer.Write(msg[:c]); err != nil {
return err
}
continue process
}
// We're at the tail, so wait for new events or time out.
// Holds journal events to process. Tightly bounded for now unless there's a
// reason to unblock the journal watch routine more quickly.
for {
waitGroup.Add(1)
go func() {
status := r.journal.Wait(100 * time.Millisecond)
waitCh <- status
waitGroup.Done()
}()
select {
case <-until:
return ErrExpired
case e := <-waitCh:
switch e {
case SD_JOURNAL_NOP:
// the journal did not change since the last invocation
case SD_JOURNAL_APPEND, SD_JOURNAL_INVALIDATE:
continue process
default:
if e < 0 {
return fmt.Errorf("received error event: %d", e)
}
log.Printf("received unknown event: %d\n", e)
}
}
}
}
}
// simpleMessageFormatter is the default formatter.
// It returns a string representing the current journal entry in a simple format which
// includes the entry timestamp and MESSAGE field.
func simpleMessageFormatter(entry *JournalEntry) (string, error) {
msg, ok := entry.Fields["MESSAGE"]
if !ok {
return "", fmt.Errorf("no MESSAGE field present in journal entry")
}
usec := entry.RealtimeTimestamp
timestamp := time.Unix(0, int64(usec)*int64(time.Microsecond))
return fmt.Sprintf("%s %s\n", timestamp, msg), nil
}

87
vendor/github.com/coreos/go-systemd/test generated vendored Executable file
View File

@ -0,0 +1,87 @@
#!/bin/bash -e
#
# Run all tests
# ./test
# ./test -v
#
# Run tests for one package
# PKG=./foo ./test
# PKG=bar ./test
#
# Invoke ./cover for HTML output
COVER=${COVER:-"-cover"}
PROJ="go-systemd"
ORG_PATH="github.com/coreos"
REPO_PATH="${ORG_PATH}/${PROJ}"
# As a convenience, set up a self-contained GOPATH if none set
if [ -z "$GOPATH" ]; then
if [ ! -h gopath/src/${REPO_PATH} ]; then
mkdir -p gopath/src/${ORG_PATH}
ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255
fi
export GOPATH=${PWD}/gopath
go get -u github.com/godbus/dbus
fi
TESTABLE="activation daemon journal login1 unit"
FORMATTABLE="$TESTABLE sdjournal dbus machine1"
if [ -e "/run/systemd/system/" ]; then
# if we're on a systemd-system, we can test sdjournal
TESTABLE="${TESTABLE} sdjournal"
if [ "$EUID" == "0" ]; then
# testing actual systemd/machined behaviour requires root
TESTABLE="${TESTABLE} dbus machine1"
fi
fi
# user has not provided PKG override
if [ -z "$PKG" ]; then
TEST=$TESTABLE
FMT=$FORMATTABLE
# user has provided PKG override
else
# strip out slashes and dots from PKG=./foo/
TEST=${PKG//\//}
TEST=${TEST//./}
# only run gofmt on packages provided by user
FMT="$TEST"
fi
# split TEST into an array and prepend REPO_PATH to each local package
split=(${TEST// / })
TEST=${split[@]/#/${REPO_PATH}/}
echo "Running tests..."
go test -v ${COVER} $@ ${TEST}
echo "Checking gofmt..."
fmtRes=$(gofmt -l $FMT)
if [ -n "${fmtRes}" ]; then
echo -e "gofmt checking failed:\n${fmtRes}"
exit 255
fi
echo "Checking govet..."
vetRes=$(go vet $TEST)
if [ -n "${vetRes}" ]; then
echo -e "govet checking failed:\n${vetRes}"
exit 255
fi
echo "Checking for license header..."
licRes=$(for file in $(find . -type f -iname '*.go' ! -path './gopath/*'); do
head -n3 "${file}" | grep -Eq "(Copyright|generated|GENERATED)" || echo -e " ${file}"
done;)
if [ -n "${licRes}" ]; then
echo -e "license header checking failed:\n${licRes}"
exit 255
fi
echo "Success"

278
vendor/github.com/coreos/go-systemd/unit/deserialize.go generated vendored Normal file
View File

@ -0,0 +1,278 @@
// 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 unit
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"strings"
"unicode"
)
const (
// SYSTEMD_LINE_MAX mimics the maximum line length that systemd can use.
// On typical systemd platforms (i.e. modern Linux), this will most
// commonly be 2048, so let's use that as a sanity check.
// Technically, we should probably pull this at runtime:
// SYSTEMD_LINE_MAX = int(C.sysconf(C.__SC_LINE_MAX))
// but this would introduce an (unfortunate) dependency on cgo
SYSTEMD_LINE_MAX = 2048
// SYSTEMD_NEWLINE defines characters that systemd considers indicators
// for a newline.
SYSTEMD_NEWLINE = "\r\n"
)
var (
// ErrLineTooLong gets returned when a line is too long for systemd to handle.
ErrLineTooLong = fmt.Errorf("line too long (max %d bytes)", SYSTEMD_LINE_MAX)
)
// Deserialize parses a systemd unit file into a list of UnitOption objects.
func Deserialize(f io.Reader) (opts []*UnitOption, err error) {
lexer, optchan, errchan := newLexer(f)
go lexer.lex()
for opt := range optchan {
opts = append(opts, &(*opt))
}
err = <-errchan
return opts, err
}
func newLexer(f io.Reader) (*lexer, <-chan *UnitOption, <-chan error) {
optchan := make(chan *UnitOption)
errchan := make(chan error, 1)
buf := bufio.NewReader(f)
return &lexer{buf, optchan, errchan, ""}, optchan, errchan
}
type lexer struct {
buf *bufio.Reader
optchan chan *UnitOption
errchan chan error
section string
}
func (l *lexer) lex() {
defer func() {
close(l.optchan)
close(l.errchan)
}()
next := l.lexNextSection
for next != nil {
if l.buf.Buffered() >= SYSTEMD_LINE_MAX {
// systemd truncates lines longer than LINE_MAX
// https://bugs.freedesktop.org/show_bug.cgi?id=85308
// Rather than allowing this to pass silently, let's
// explicitly gate people from encountering this
line, err := l.buf.Peek(SYSTEMD_LINE_MAX)
if err != nil {
l.errchan <- err
return
}
if !bytes.ContainsAny(line, SYSTEMD_NEWLINE) {
l.errchan <- ErrLineTooLong
return
}
}
var err error
next, err = next()
if err != nil {
l.errchan <- err
return
}
}
}
type lexStep func() (lexStep, error)
func (l *lexer) lexSectionName() (lexStep, error) {
sec, err := l.buf.ReadBytes(']')
if err != nil {
return nil, errors.New("unable to find end of section")
}
return l.lexSectionSuffixFunc(string(sec[:len(sec)-1])), nil
}
func (l *lexer) lexSectionSuffixFunc(section string) lexStep {
return func() (lexStep, error) {
garbage, _, err := l.toEOL()
if err != nil {
return nil, err
}
garbage = bytes.TrimSpace(garbage)
if len(garbage) > 0 {
return nil, fmt.Errorf("found garbage after section name %s: %v", l.section, garbage)
}
return l.lexNextSectionOrOptionFunc(section), nil
}
}
func (l *lexer) ignoreLineFunc(next lexStep) lexStep {
return func() (lexStep, error) {
for {
line, _, err := l.toEOL()
if err != nil {
return nil, err
}
line = bytes.TrimSuffix(line, []byte{' '})
// lack of continuation means this line has been exhausted
if !bytes.HasSuffix(line, []byte{'\\'}) {
break
}
}
// reached end of buffer, safe to exit
return next, nil
}
}
func (l *lexer) lexNextSection() (lexStep, error) {
r, _, err := l.buf.ReadRune()
if err != nil {
if err == io.EOF {
err = nil
}
return nil, err
}
if r == '[' {
return l.lexSectionName, nil
} else if isComment(r) {
return l.ignoreLineFunc(l.lexNextSection), nil
}
return l.lexNextSection, nil
}
func (l *lexer) lexNextSectionOrOptionFunc(section string) lexStep {
return func() (lexStep, error) {
r, _, err := l.buf.ReadRune()
if err != nil {
if err == io.EOF {
err = nil
}
return nil, err
}
if unicode.IsSpace(r) {
return l.lexNextSectionOrOptionFunc(section), nil
} else if r == '[' {
return l.lexSectionName, nil
} else if isComment(r) {
return l.ignoreLineFunc(l.lexNextSectionOrOptionFunc(section)), nil
}
l.buf.UnreadRune()
return l.lexOptionNameFunc(section), nil
}
}
func (l *lexer) lexOptionNameFunc(section string) lexStep {
return func() (lexStep, error) {
var partial bytes.Buffer
for {
r, _, err := l.buf.ReadRune()
if err != nil {
return nil, err
}
if r == '\n' || r == '\r' {
return nil, errors.New("unexpected newline encountered while parsing option name")
}
if r == '=' {
break
}
partial.WriteRune(r)
}
name := strings.TrimSpace(partial.String())
return l.lexOptionValueFunc(section, name, bytes.Buffer{}), nil
}
}
func (l *lexer) lexOptionValueFunc(section, name string, partial bytes.Buffer) lexStep {
return func() (lexStep, error) {
for {
line, eof, err := l.toEOL()
if err != nil {
return nil, err
}
if len(bytes.TrimSpace(line)) == 0 {
break
}
partial.Write(line)
// lack of continuation means this value has been exhausted
idx := bytes.LastIndex(line, []byte{'\\'})
if idx == -1 || idx != (len(line)-1) {
break
}
if !eof {
partial.WriteRune('\n')
}
return l.lexOptionValueFunc(section, name, partial), nil
}
val := partial.String()
if strings.HasSuffix(val, "\n") {
// A newline was added to the end, so the file didn't end with a backslash.
// => Keep the newline
val = strings.TrimSpace(val) + "\n"
} else {
val = strings.TrimSpace(val)
}
l.optchan <- &UnitOption{Section: section, Name: name, Value: val}
return l.lexNextSectionOrOptionFunc(section), nil
}
}
// toEOL reads until the end-of-line or end-of-file.
// Returns (data, EOFfound, error)
func (l *lexer) toEOL() ([]byte, bool, error) {
line, err := l.buf.ReadBytes('\n')
// ignore EOF here since it's roughly equivalent to EOL
if err != nil && err != io.EOF {
return nil, false, err
}
line = bytes.TrimSuffix(line, []byte{'\r'})
line = bytes.TrimSuffix(line, []byte{'\n'})
return line, err == io.EOF, nil
}
func isComment(r rune) bool {
return r == '#' || r == ';'
}

View File

@ -0,0 +1,381 @@
// 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 unit
import (
"bytes"
"fmt"
"reflect"
"testing"
)
func TestDeserialize(t *testing.T) {
tests := []struct {
input []byte
output []*UnitOption
}{
// multiple options underneath a section
{
[]byte(`[Unit]
Description=Foo
Description=Bar
Requires=baz.service
After=baz.service
`),
[]*UnitOption{
&UnitOption{"Unit", "Description", "Foo"},
&UnitOption{"Unit", "Description", "Bar"},
&UnitOption{"Unit", "Requires", "baz.service"},
&UnitOption{"Unit", "After", "baz.service"},
},
},
// multiple sections
{
[]byte(`[Unit]
Description=Foo
[Service]
ExecStart=/usr/bin/sleep infinity
[X-Third-Party]
Pants=on
`),
[]*UnitOption{
&UnitOption{"Unit", "Description", "Foo"},
&UnitOption{"Service", "ExecStart", "/usr/bin/sleep infinity"},
&UnitOption{"X-Third-Party", "Pants", "on"},
},
},
// multiple sections with no options
{
[]byte(`[Unit]
[Service]
[X-Third-Party]
`),
[]*UnitOption{},
},
// multiple values not special-cased
{
[]byte(`[Service]
Environment= "FOO=BAR" "BAZ=QUX"
`),
[]*UnitOption{
&UnitOption{"Service", "Environment", "\"FOO=BAR\" \"BAZ=QUX\""},
},
},
// line continuations unmodified
{
[]byte(`[Unit]
Description= Unnecessarily wrapped \
words here
`),
[]*UnitOption{
&UnitOption{"Unit", "Description", `Unnecessarily wrapped \
words here`},
},
},
// comments ignored
{
[]byte(`; comment alpha
# comment bravo
[Unit]
; comment charlie
# comment delta
#Description=Foo
Description=Bar
; comment echo
# comment foxtrot
`),
[]*UnitOption{
&UnitOption{"Unit", "Description", "Bar"},
},
},
// apparent comment lines inside of line continuations not ignored
{
[]byte(`[Unit]
Description=Bar\
# comment alpha
Description=Bar\
# comment bravo \
Baz
`),
[]*UnitOption{
&UnitOption{"Unit", "Description", "Bar\\\n# comment alpha"},
&UnitOption{"Unit", "Description", "Bar\\\n# comment bravo \\\nBaz"},
},
},
// options outside of sections are ignored
{
[]byte(`Description=Foo
[Unit]
Description=Bar
`),
[]*UnitOption{
&UnitOption{"Unit", "Description", "Bar"},
},
},
// garbage outside of sections are ignored
{
[]byte(`<<<<<<<<
[Unit]
Description=Bar
`),
[]*UnitOption{
&UnitOption{"Unit", "Description", "Bar"},
},
},
// garbage used as unit option
{
[]byte(`[Unit]
<<<<<<<<=Bar
`),
[]*UnitOption{
&UnitOption{"Unit", "<<<<<<<<", "Bar"},
},
},
// option name with spaces are valid
{
[]byte(`[Unit]
Some Thing = Bar
`),
[]*UnitOption{
&UnitOption{"Unit", "Some Thing", "Bar"},
},
},
// lack of trailing newline doesn't cause problem for non-continued file
{
[]byte(`[Unit]
Description=Bar`),
[]*UnitOption{
&UnitOption{"Unit", "Description", "Bar"},
},
},
// unit file with continuation but no following line is ok, too
{
[]byte(`[Unit]
Description=Bar \`),
[]*UnitOption{
&UnitOption{"Unit", "Description", "Bar \\"},
},
},
// Assert utf8 characters are preserved
{
[]byte(`[©]
µ=ÇôrèÕ$`),
[]*UnitOption{
&UnitOption{"©", "µ☃", "ÇôrèÕ$"},
},
},
// whitespace removed around option name
{
[]byte(`[Unit]
Description =words here
`),
[]*UnitOption{
&UnitOption{"Unit", "Description", "words here"},
},
},
// whitespace around option value stripped
{
[]byte(`[Unit]
Description= words here `),
[]*UnitOption{
&UnitOption{"Unit", "Description", "words here"},
},
},
// whitespace around option value stripped, regardless of continuation
{
[]byte(`[Unit]
Description= words here \
`),
[]*UnitOption{
&UnitOption{"Unit", "Description", "words here \\\n"},
},
},
// backslash not considered continuation if followed by text
{
[]byte(`[Service]
ExecStart=/bin/bash -c "while true; do echo \"ping\"; sleep 1; done"
`),
[]*UnitOption{
&UnitOption{"Service", "ExecStart", `/bin/bash -c "while true; do echo \"ping\"; sleep 1; done"`},
},
},
// backslash not considered continuation if followed by whitespace, but still trimmed
{
[]byte(`[Service]
ExecStart=/bin/bash echo poof \ `),
[]*UnitOption{
&UnitOption{"Service", "ExecStart", `/bin/bash echo poof \`},
},
},
// a long unit file line that's just equal to the maximum permitted length
{
[]byte(`[Service]
ExecStart=/bin/bash -c "echo ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................."`),
[]*UnitOption{
&UnitOption{"Service", "ExecStart", `/bin/bash -c "echo ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................."`},
},
},
// the same, but with a trailing newline
{
[]byte(`[Service]
ExecStart=/bin/bash -c "echo ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................."
Option=value
`),
[]*UnitOption{
&UnitOption{"Service", "ExecStart", `/bin/bash -c "echo ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................."`},
&UnitOption{"Service", "Option", "value"},
},
},
}
assert := func(expect, output []*UnitOption) error {
if len(expect) != len(output) {
return fmt.Errorf("expected %d items, got %d", len(expect), len(output))
}
for i := range expect {
if !reflect.DeepEqual(expect[i], output[i]) {
return fmt.Errorf("item %d: expected %v, got %v", i, expect[i], output[i])
}
}
return nil
}
for i, tt := range tests {
output, err := Deserialize(bytes.NewReader(tt.input))
if err != nil {
t.Errorf("case %d: unexpected error parsing unit: %v", i, err)
continue
}
err = assert(tt.output, output)
if err != nil {
t.Errorf("case %d: %v", i, err)
t.Log("Expected options:")
logUnitOptionSlice(t, tt.output)
t.Log("Actual options:")
logUnitOptionSlice(t, output)
}
}
}
func TestDeserializeFail(t *testing.T) {
tests := [][]byte{
// malformed section header
[]byte(`[Unit
Description=Foo
`),
// garbage following section header
[]byte(`[Unit] pants
Description=Foo
`),
// option without value
[]byte(`[Unit]
Description
`),
// garbage inside of section
[]byte(`[Unit]
<<<<<<
Description=Foo
`),
}
for i, tt := range tests {
output, err := Deserialize(bytes.NewReader(tt))
if err == nil {
t.Errorf("case %d: unexpected nil error", i)
t.Log("Output:")
logUnitOptionSlice(t, output)
}
}
}
func logUnitOptionSlice(t *testing.T, opts []*UnitOption) {
for idx, opt := range opts {
t.Logf("%d: %v", idx, opt)
}
}
func TestDeserializeLineTooLong(t *testing.T) {
tests := [][]byte{
// section header that's far too long
[]byte(`[Seeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeervice]
`),
// sane-looking unit file with a line just greater than the maximum allowed (currently, 2048)
[]byte(`[Service]
ExecStart=/bin/bash -c "echo ..................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................."
`),
// sane-looking unit file with option value way too long
[]byte(`
# test unit file
[Service]
ExecStartPre=-/usr/bin/docker rm %p
ExecStartPre=-/usr/bin/docker pull busybox
ExecStart=/usr/bin/docker run --rm --name %p --net=host \
-e "test=1123t" \
-e "test=1123t" \
-e "fiz=1123t" \
-e "buz=1123t" \
-e "FOO=BARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBABARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARRBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBAR"BARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBABARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARRBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBARBAR" \
busybox sleep 10
ExecStop=-/usr/bin/docker kill %p
SyslogIdentifier=busybox
Restart=always
RestartSec=10s
`),
// single arbitrary line that's way too long
[]byte(`arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 character arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 character arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 character arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 character arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters arbitrary and extraordinarily long line that is far greater than 2048 characters`),
// sane-looking unit file with option name way too long
[]byte(`[Service]
ExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStartExecStart=/bin/true
`),
}
for i, tt := range tests {
output, err := Deserialize(bytes.NewReader(tt))
if err != ErrLineTooLong {
t.Errorf("case %d: unexpected err: %v", i, err)
t.Log("Output:")
logUnitOptionSlice(t, output)
}
}
}

View File

@ -0,0 +1,88 @@
// 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 unit
import (
"bytes"
"io/ioutil"
"testing"
)
func TestDeserializeAndReserialize(t *testing.T) {
tests := []struct {
in string
wout string
}{
{
`[Service]
ExecStart=/bin/bash -c "while true; do echo \"ping\"; sleep 1; done"
`,
`[Service]
ExecStart=/bin/bash -c "while true; do echo \"ping\"; sleep 1; done"
`},
{
`[Unit]
Description= Unnecessarily wrapped \
words here`,
`[Unit]
Description=Unnecessarily wrapped \
words here
`,
},
{
`[Unit]
Description=Demo \
Requires=docker.service
`,
`[Unit]
Description=Demo \
Requires=docker.service
`,
},
{
`; comment alpha
# comment bravo
[Unit]
; comment charlie
# comment delta
#Description=Foo
Description=Bar
; comment echo
# comment foxtrot
`,
`[Unit]
Description=Bar
`},
}
for i, tt := range tests {
ds, err := Deserialize(bytes.NewBufferString(tt.in))
if err != nil {
t.Errorf("case %d: unexpected error parsing unit: %v", i, err)
continue
}
out, err := ioutil.ReadAll(Serialize(ds))
if err != nil {
t.Errorf("case %d: unexpected error serializing unit: %v", i, err)
continue
}
if g := string(out); g != tt.wout {
t.Errorf("case %d: incorrect output", i)
t.Logf("Expected:\n%#v", tt.wout)
t.Logf("Actual:\n%#v", g)
}
}
}

116
vendor/github.com/coreos/go-systemd/unit/escape.go generated vendored Normal file
View File

@ -0,0 +1,116 @@
// 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.
// Implements systemd-escape [--unescape] [--path]
package unit
import (
"fmt"
"strconv"
"strings"
)
const (
allowed = `:_.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`
)
// If isPath is true:
// We remove redundant '/'s, the leading '/', and trailing '/'.
// If the result is empty, a '/' is inserted.
//
// We always:
// Replace the following characters with `\x%x`:
// Leading `.`
// `-`, `\`, and anything not in this set: `:-_.\[0-9a-zA-Z]`
// Replace '/' with '-'.
func escape(unescaped string, isPath bool) string {
e := []byte{}
inSlashes := false
start := true
for i := 0; i < len(unescaped); i++ {
c := unescaped[i]
if isPath {
if c == '/' {
inSlashes = true
continue
} else if inSlashes {
inSlashes = false
if !start {
e = append(e, '-')
}
}
}
if c == '/' {
e = append(e, '-')
} else if start && c == '.' || strings.IndexByte(allowed, c) == -1 {
e = append(e, []byte(fmt.Sprintf(`\x%x`, c))...)
} else {
e = append(e, c)
}
start = false
}
if isPath && len(e) == 0 {
e = append(e, '-')
}
return string(e)
}
// If isPath is true:
// We always return a string beginning with '/'.
//
// We always:
// Replace '-' with '/'.
// Replace `\x%x` with the value represented in hex.
func unescape(escaped string, isPath bool) string {
u := []byte{}
for i := 0; i < len(escaped); i++ {
c := escaped[i]
if c == '-' {
c = '/'
} else if c == '\\' && len(escaped)-i >= 4 && escaped[i+1] == 'x' {
n, err := strconv.ParseInt(escaped[i+2:i+4], 16, 8)
if err == nil {
c = byte(n)
i += 3
}
}
u = append(u, c)
}
if isPath && (len(u) == 0 || u[0] != '/') {
u = append([]byte("/"), u...)
}
return string(u)
}
// UnitNameEscape escapes a string as `systemd-escape` would
func UnitNameEscape(unescaped string) string {
return escape(unescaped, false)
}
// UnitNameUnescape unescapes a string as `systemd-escape --unescape` would
func UnitNameUnescape(escaped string) string {
return unescape(escaped, false)
}
// UnitNamePathEscape escapes a string as `systemd-escape --path` would
func UnitNamePathEscape(unescaped string) string {
return escape(unescaped, true)
}
// UnitNamePathUnescape unescapes a string as `systemd-escape --path --unescape` would
func UnitNamePathUnescape(escaped string) string {
return unescape(escaped, true)
}

211
vendor/github.com/coreos/go-systemd/unit/escape_test.go generated vendored Normal file
View File

@ -0,0 +1,211 @@
// 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 unit
import (
"testing"
)
func TestUnitNameEscape(t *testing.T) {
tests := []struct {
in string
out string
isPath bool
}{
// turn empty string path into escaped /
{
in: "",
out: "-",
isPath: true,
},
// turn redundant ////s into single escaped /
{
in: "/////////",
out: "-",
isPath: true,
},
// remove all redundant ////s
{
in: "///foo////bar/////tail//////",
out: "foo-bar-tail",
isPath: true,
},
// leave empty string empty
{
in: "",
out: "",
isPath: false,
},
// escape leading dot
{
in: ".",
out: `\x2e`,
isPath: true,
},
// escape leading dot
{
in: "/.",
out: `\x2e`,
isPath: true,
},
// escape leading dot
{
in: "/////////.",
out: `\x2e`,
isPath: true,
},
// escape leading dot
{
in: "/////////.///////////////",
out: `\x2e`,
isPath: true,
},
// escape leading dot
{
in: ".....",
out: `\x2e....`,
isPath: true,
},
// escape leading dot
{
in: "/.foo/.bar",
out: `\x2efoo-.bar`,
isPath: true,
},
// escape leading dot
{
in: ".foo/.bar",
out: `\x2efoo-.bar`,
isPath: true,
},
// escape leading dot
{
in: ".foo/.bar",
out: `\x2efoo-.bar`,
isPath: false,
},
// escape disallowed
{
in: `///..\-!#??///`,
out: `---..\x5c\x2d\x21\x23\x3f\x3f---`,
isPath: false,
},
// escape disallowed
{
in: `///..\-!#??///`,
out: `\x2e.\x5c\x2d\x21\x23\x3f\x3f`,
isPath: true,
},
// escape real-world example
{
in: `user-cloudinit@/var/lib/coreos/vagrant/vagrantfile-user-data.service`,
out: `user\x2dcloudinit\x40-var-lib-coreos-vagrant-vagrantfile\x2duser\x2ddata.service`,
isPath: false,
},
}
for i, tt := range tests {
var s string
if tt.isPath {
s = UnitNamePathEscape(tt.in)
} else {
s = UnitNameEscape(tt.in)
}
if s != tt.out {
t.Errorf("case %d: failed escaping %v isPath: %v - expected %v, got %v", i, tt.in, tt.isPath, tt.out, s)
}
}
}
func TestUnitNameUnescape(t *testing.T) {
tests := []struct {
in string
out string
isPath bool
}{
// turn empty string path into /
{
in: "",
out: "/",
isPath: true,
},
// leave empty string empty
{
in: "",
out: "",
isPath: false,
},
// turn ////s into
{
in: "---------",
out: "/////////",
isPath: true,
},
// unescape hex
{
in: `---..\x5c\x2d\x21\x23\x3f\x3f---`,
out: `///..\-!#??///`,
isPath: false,
},
// unescape hex
{
in: `\x2e.\x5c\x2d\x21\x23\x3f\x3f`,
out: `/..\-!#??`,
isPath: true,
},
// unescape hex, retain invalids
{
in: `\x2e.\x5c\x2d\xaZ\x.o\x21\x23\x3f\x3f`,
out: `/..\-\xaZ\x.o!#??`,
isPath: true,
},
// unescape hex, retain invalids, partial tail
{
in: `\x2e.\x5c\x\x2d\xaZ\x.o\x21\x23\x3f\x3f\x3`,
out: `/..\\x-\xaZ\x.o!#??\x3`,
isPath: true,
},
// unescape hex, retain invalids, partial tail
{
in: `\x2e.\x5c\x\x2d\xaZ\x.o\x21\x23\x3f\x3f\x`,
out: `/..\\x-\xaZ\x.o!#??\x`,
isPath: true,
},
// unescape hex, retain invalids, partial tail
{
in: `\x2e.\x5c\x\x2d\xaZ\x.o\x21\x23\x3f\x3f\`,
out: `/..\\x-\xaZ\x.o!#??\`,
isPath: true,
},
// unescape real-world example
{
in: `user\x2dcloudinit\x40-var-lib-coreos-vagrant-vagrantfile\x2duser\x2ddata.service`,
out: `user-cloudinit@/var/lib/coreos/vagrant/vagrantfile-user-data.service`,
isPath: false,
},
}
for i, tt := range tests {
var s string
if tt.isPath {
s = UnitNamePathUnescape(tt.in)
} else {
s = UnitNameUnescape(tt.in)
}
if s != tt.out {
t.Errorf("case %d: failed unescaping %v isPath: %v - expected %v, got %v", i, tt.in, tt.isPath, tt.out, s)
}
}
}

59
vendor/github.com/coreos/go-systemd/unit/option.go generated vendored Normal file
View File

@ -0,0 +1,59 @@
// 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 unit
import (
"fmt"
)
// UnitOption represents an option in a systemd unit file.
type UnitOption struct {
Section string
Name string
Value string
}
// NewUnitOption returns a new UnitOption instance with pre-set values.
func NewUnitOption(section, name, value string) *UnitOption {
return &UnitOption{Section: section, Name: name, Value: value}
}
func (uo *UnitOption) String() string {
return fmt.Sprintf("{Section: %q, Name: %q, Value: %q}", uo.Section, uo.Name, uo.Value)
}
// Match compares two UnitOptions and returns true if they are identical.
func (uo *UnitOption) Match(other *UnitOption) bool {
return uo.Section == other.Section &&
uo.Name == other.Name &&
uo.Value == other.Value
}
// AllMatch compares two slices of UnitOptions and returns true if they are
// identical.
func AllMatch(u1 []*UnitOption, u2 []*UnitOption) bool {
length := len(u1)
if length != len(u2) {
return false
}
for i := 0; i < length; i++ {
if !u1[i].Match(u2[i]) {
return false
}
}
return true
}

214
vendor/github.com/coreos/go-systemd/unit/option_test.go generated vendored Normal file
View File

@ -0,0 +1,214 @@
// 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 unit
import (
"testing"
)
func TestAllMatch(t *testing.T) {
tests := []struct {
u1 []*UnitOption
u2 []*UnitOption
match bool
}{
// empty lists match
{
u1: []*UnitOption{},
u2: []*UnitOption{},
match: true,
},
// simple match of a single option
{
u1: []*UnitOption{
{Section: "Unit", Name: "Description", Value: "FOO"},
},
u2: []*UnitOption{
{Section: "Unit", Name: "Description", Value: "FOO"},
},
match: true,
},
// single option mismatched
{
u1: []*UnitOption{
{Section: "Unit", Name: "Description", Value: "FOO"},
},
u2: []*UnitOption{
{Section: "Unit", Name: "Description", Value: "BAR"},
},
match: false,
},
// multiple options match
{
u1: []*UnitOption{
{Section: "Unit", Name: "Description", Value: "FOO"},
{Section: "Unit", Name: "BindsTo", Value: "bar.service"},
{Section: "Service", Name: "ExecStart", Value: "/bin/true"},
},
u2: []*UnitOption{
{Section: "Unit", Name: "Description", Value: "FOO"},
{Section: "Unit", Name: "BindsTo", Value: "bar.service"},
{Section: "Service", Name: "ExecStart", Value: "/bin/true"},
},
match: true,
},
// mismatch length
{
u1: []*UnitOption{
{Section: "Unit", Name: "Description", Value: "FOO"},
{Section: "Unit", Name: "BindsTo", Value: "bar.service"},
},
u2: []*UnitOption{
{Section: "Unit", Name: "Description", Value: "FOO"},
{Section: "Unit", Name: "BindsTo", Value: "bar.service"},
{Section: "Service", Name: "ExecStart", Value: "/bin/true"},
},
match: false,
},
// multiple options misordered
{
u1: []*UnitOption{
{Section: "Unit", Name: "Description", Value: "FOO"},
{Section: "Service", Name: "ExecStart", Value: "/bin/true"},
},
u2: []*UnitOption{
{Section: "Service", Name: "ExecStart", Value: "/bin/true"},
{Section: "Unit", Name: "Description", Value: "FOO"},
},
match: false,
},
// interleaved sections mismatch
{
u1: []*UnitOption{
{Section: "Unit", Name: "Description", Value: "FOO"},
{Section: "Unit", Name: "BindsTo", Value: "bar.service"},
{Section: "Service", Name: "ExecStart", Value: "/bin/true"},
{Section: "Service", Name: "ExecStop", Value: "/bin/true"},
},
u2: []*UnitOption{
{Section: "Unit", Name: "Description", Value: "FOO"},
{Section: "Service", Name: "ExecStart", Value: "/bin/true"},
{Section: "Unit", Name: "BindsTo", Value: "bar.service"},
{Section: "Service", Name: "ExecStop", Value: "/bin/true"},
},
match: false,
},
}
for i, tt := range tests {
match := AllMatch(tt.u1, tt.u2)
if match != tt.match {
t.Errorf("case %d: failed comparing u1 to u2 - expected match=%t, got %t", i, tt.match, match)
}
match = AllMatch(tt.u2, tt.u1)
if match != tt.match {
t.Errorf("case %d: failed comparing u2 to u1 - expected match=%t, got %t", i, tt.match, match)
}
}
}
func TestMatch(t *testing.T) {
tests := []struct {
o1 *UnitOption
o2 *UnitOption
match bool
}{
// empty options match
{
o1: &UnitOption{},
o2: &UnitOption{},
match: true,
},
// all fields match
{
o1: &UnitOption{
Section: "Unit",
Name: "Description",
Value: "FOO",
},
o2: &UnitOption{
Section: "Unit",
Name: "Description",
Value: "FOO",
},
match: true,
},
// Section mismatch
{
o1: &UnitOption{
Section: "Unit",
Name: "Description",
Value: "FOO",
},
o2: &UnitOption{
Section: "X-Other",
Name: "Description",
Value: "FOO",
},
match: false,
},
// Name mismatch
{
o1: &UnitOption{
Section: "Unit",
Name: "Description",
Value: "FOO",
},
o2: &UnitOption{
Section: "Unit",
Name: "BindsTo",
Value: "FOO",
},
match: false,
},
// Value mismatch
{
o1: &UnitOption{
Section: "Unit",
Name: "Description",
Value: "FOO",
},
o2: &UnitOption{
Section: "Unit",
Name: "Description",
Value: "BAR",
},
match: false,
},
}
for i, tt := range tests {
match := tt.o1.Match(tt.o2)
if match != tt.match {
t.Errorf("case %d: failed comparing o1 to o2 - expected match=%t, got %t", i, tt.match, match)
}
match = tt.o2.Match(tt.o1)
if match != tt.match {
t.Errorf("case %d: failed comparing o2 to o1 - expected match=%t, got %t", i, tt.match, match)
}
}
}

75
vendor/github.com/coreos/go-systemd/unit/serialize.go generated vendored Normal file
View File

@ -0,0 +1,75 @@
// 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 unit
import (
"bytes"
"io"
)
// Serialize encodes all of the given UnitOption objects into a
// unit file. When serialized the options are sorted in their
// supplied order but grouped by section.
func Serialize(opts []*UnitOption) io.Reader {
var buf bytes.Buffer
if len(opts) == 0 {
return &buf
}
// Index of sections -> ordered options
idx := map[string][]*UnitOption{}
// Separately preserve order in which sections were seen
sections := []string{}
for _, opt := range opts {
sec := opt.Section
if _, ok := idx[sec]; !ok {
sections = append(sections, sec)
}
idx[sec] = append(idx[sec], opt)
}
for i, sect := range sections {
writeSectionHeader(&buf, sect)
writeNewline(&buf)
opts := idx[sect]
for _, opt := range opts {
writeOption(&buf, opt)
writeNewline(&buf)
}
if i < len(sections)-1 {
writeNewline(&buf)
}
}
return &buf
}
func writeNewline(buf *bytes.Buffer) {
buf.WriteRune('\n')
}
func writeSectionHeader(buf *bytes.Buffer, section string) {
buf.WriteRune('[')
buf.WriteString(section)
buf.WriteRune(']')
}
func writeOption(buf *bytes.Buffer, opt *UnitOption) {
buf.WriteString(opt.Name)
buf.WriteRune('=')
buf.WriteString(opt.Value)
}

View File

@ -0,0 +1,170 @@
// 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 unit
import (
"io/ioutil"
"testing"
)
func TestSerialize(t *testing.T) {
tests := []struct {
input []*UnitOption
output string
}{
// no options results in empty file
{
[]*UnitOption{},
``,
},
// options with same section share the header
{
[]*UnitOption{
&UnitOption{"Unit", "Description", "Foo"},
&UnitOption{"Unit", "BindsTo", "bar.service"},
},
`[Unit]
Description=Foo
BindsTo=bar.service
`,
},
// options with same name are not combined
{
[]*UnitOption{
&UnitOption{"Unit", "Description", "Foo"},
&UnitOption{"Unit", "Description", "Bar"},
},
`[Unit]
Description=Foo
Description=Bar
`,
},
// multiple options printed under different section headers
{
[]*UnitOption{
&UnitOption{"Unit", "Description", "Foo"},
&UnitOption{"Service", "ExecStart", "/usr/bin/sleep infinity"},
},
`[Unit]
Description=Foo
[Service]
ExecStart=/usr/bin/sleep infinity
`,
},
// options are grouped into sections
{
[]*UnitOption{
&UnitOption{"Unit", "Description", "Foo"},
&UnitOption{"Service", "ExecStart", "/usr/bin/sleep infinity"},
&UnitOption{"Unit", "BindsTo", "bar.service"},
},
`[Unit]
Description=Foo
BindsTo=bar.service
[Service]
ExecStart=/usr/bin/sleep infinity
`,
},
// options are ordered within groups, and sections are ordered in the order in which they were first seen
{
[]*UnitOption{
&UnitOption{"Unit", "Description", "Foo"},
&UnitOption{"Service", "ExecStart", "/usr/bin/sleep infinity"},
&UnitOption{"Unit", "BindsTo", "bar.service"},
&UnitOption{"X-Foo", "Bar", "baz"},
&UnitOption{"Service", "ExecStop", "/usr/bin/sleep 1"},
&UnitOption{"Unit", "Documentation", "https://foo.com"},
},
`[Unit]
Description=Foo
BindsTo=bar.service
Documentation=https://foo.com
[Service]
ExecStart=/usr/bin/sleep infinity
ExecStop=/usr/bin/sleep 1
[X-Foo]
Bar=baz
`,
},
// utf8 characters are not a problem
{
[]*UnitOption{
&UnitOption{"©", "µ☃", "ÇôrèÕ$"},
},
`[©]
µ=ÇôrèÕ$
`,
},
// no verification is done on section names
{
[]*UnitOption{
&UnitOption{"Un\nit", "Description", "Foo"},
},
`[Un
it]
Description=Foo
`,
},
// no verification is done on option names
{
[]*UnitOption{
&UnitOption{"Unit", "Desc\nription", "Foo"},
},
`[Unit]
Desc
ription=Foo
`,
},
// no verification is done on option values
{
[]*UnitOption{
&UnitOption{"Unit", "Description", "Fo\no"},
},
`[Unit]
Description=Fo
o
`,
},
}
for i, tt := range tests {
outReader := Serialize(tt.input)
outBytes, err := ioutil.ReadAll(outReader)
if err != nil {
t.Errorf("case %d: encountered error while reading output: %v", i, err)
continue
}
output := string(outBytes)
if tt.output != output {
t.Errorf("case %d: incorrect output", i)
t.Logf("Expected:\n%s", tt.output)
t.Logf("Actual:\n%s", output)
}
}
}

90
vendor/github.com/coreos/go-systemd/util/util.go generated vendored Normal file
View File

@ -0,0 +1,90 @@
// 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 util contains utility functions related to systemd that applications
// can use to check things like whether systemd is running. Note that some of
// these functions attempt to manually load systemd libraries at runtime rather
// than linking against them.
package util
import (
"fmt"
"io/ioutil"
"os"
"strings"
)
var (
ErrNoCGO = fmt.Errorf("go-systemd built with CGO disabled")
)
// GetRunningSlice attempts to retrieve the name of the systemd slice in which
// the current process is running.
// This function is a wrapper around the libsystemd C library; if it cannot be
// opened, an error is returned.
func GetRunningSlice() (string, error) {
return getRunningSlice()
}
// RunningFromSystemService tries to detect whether the current process has
// been invoked from a system service. The condition for this is whether the
// process is _not_ a user process. User processes are those running in session
// scopes or under per-user `systemd --user` instances.
//
// To avoid false positives on systems without `pam_systemd` (which is
// responsible for creating user sessions), this function also uses a heuristic
// to detect whether it's being invoked from a session leader process. This is
// the case if the current process is executed directly from a service file
// (e.g. with `ExecStart=/this/cmd`). Note that this heuristic will fail if the
// command is instead launched in a subshell or similar so that it is not
// session leader (e.g. `ExecStart=/bin/bash -c "/this/cmd"`)
//
// This function is a wrapper around the libsystemd C library; if this is
// unable to successfully open a handle to the library for any reason (e.g. it
// cannot be found), an error will be returned.
func RunningFromSystemService() (bool, error) {
return runningFromSystemService()
}
// CurrentUnitName attempts to retrieve the name of the systemd system unit
// from which the calling process has been invoked. It wraps the systemd
// `sd_pid_get_unit` call, with the same caveat: for processes not part of a
// systemd system unit, this function will return an error.
func CurrentUnitName() (string, error) {
return currentUnitName()
}
// IsRunningSystemd checks whether the host was booted with systemd as its init
// system. This functions similarly to systemd's `sd_booted(3)`: internally, it
// checks whether /run/systemd/system/ exists and is a directory.
// http://www.freedesktop.org/software/systemd/man/sd_booted.html
func IsRunningSystemd() bool {
fi, err := os.Lstat("/run/systemd/system")
if err != nil {
return false
}
return fi.IsDir()
}
// GetMachineID returns a host's 128-bit machine ID as a string. This functions
// similarly to systemd's `sd_id128_get_machine`: internally, it simply reads
// the contents of /etc/machine-id
// http://www.freedesktop.org/software/systemd/man/sd_id128_get_machine.html
func GetMachineID() (string, error) {
machineID, err := ioutil.ReadFile("/etc/machine-id")
if err != nil {
return "", fmt.Errorf("failed to read /etc/machine-id: %v", err)
}
return strings.TrimSpace(string(machineID)), nil
}

175
vendor/github.com/coreos/go-systemd/util/util_cgo.go generated vendored Normal file
View File

@ -0,0 +1,175 @@
// Copyright 2016 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.
// +build cgo
package util
// #include <stdlib.h>
// #include <sys/types.h>
// #include <unistd.h>
//
// int
// my_sd_pid_get_owner_uid(void *f, pid_t pid, uid_t *uid)
// {
// int (*sd_pid_get_owner_uid)(pid_t, uid_t *);
//
// sd_pid_get_owner_uid = (int (*)(pid_t, uid_t *))f;
// return sd_pid_get_owner_uid(pid, uid);
// }
//
// int
// my_sd_pid_get_unit(void *f, pid_t pid, char **unit)
// {
// int (*sd_pid_get_unit)(pid_t, char **);
//
// sd_pid_get_unit = (int (*)(pid_t, char **))f;
// return sd_pid_get_unit(pid, unit);
// }
//
// int
// my_sd_pid_get_slice(void *f, pid_t pid, char **slice)
// {
// int (*sd_pid_get_slice)(pid_t, char **);
//
// sd_pid_get_slice = (int (*)(pid_t, char **))f;
// return sd_pid_get_slice(pid, slice);
// }
//
// int
// am_session_leader()
// {
// return (getsid(0) == getpid());
// }
import "C"
import (
"fmt"
"syscall"
"unsafe"
"github.com/coreos/go-systemd/v22/internal/dlopen"
)
var libsystemdNames = []string{
// systemd < 209
"libsystemd-login.so.0",
"libsystemd-login.so",
// systemd >= 209 merged libsystemd-login into libsystemd proper
"libsystemd.so.0",
"libsystemd.so",
}
func getRunningSlice() (slice string, err error) {
var h *dlopen.LibHandle
h, err = dlopen.GetHandle(libsystemdNames)
if err != nil {
return
}
defer func() {
if err1 := h.Close(); err1 != nil {
err = err1
}
}()
sd_pid_get_slice, err := h.GetSymbolPointer("sd_pid_get_slice")
if err != nil {
return
}
var s string
sl := C.CString(s)
defer C.free(unsafe.Pointer(sl))
ret := C.my_sd_pid_get_slice(sd_pid_get_slice, 0, &sl)
if ret < 0 {
err = fmt.Errorf("error calling sd_pid_get_slice: %v", syscall.Errno(-ret))
return
}
return C.GoString(sl), nil
}
func runningFromSystemService() (ret bool, err error) {
var h *dlopen.LibHandle
h, err = dlopen.GetHandle(libsystemdNames)
if err != nil {
return
}
defer func() {
if err1 := h.Close(); err1 != nil {
err = err1
}
}()
sd_pid_get_owner_uid, err := h.GetSymbolPointer("sd_pid_get_owner_uid")
if err != nil {
return
}
var uid C.uid_t
errno := C.my_sd_pid_get_owner_uid(sd_pid_get_owner_uid, 0, &uid)
serrno := syscall.Errno(-errno)
// when we're running from a unit file, sd_pid_get_owner_uid returns
// ENOENT (systemd <220), ENXIO (systemd 220-223), or ENODATA
// (systemd >=234)
switch {
case errno >= 0:
ret = false
case serrno == syscall.ENOENT, serrno == syscall.ENXIO, serrno == syscall.ENODATA:
// Since the implementation of sessions in systemd relies on
// the `pam_systemd` module, using the sd_pid_get_owner_uid
// heuristic alone can result in false positives if that module
// (or PAM itself) is not present or properly configured on the
// system. As such, we also check if we're the session leader,
// which should be the case if we're invoked from a unit file,
// but not if e.g. we're invoked from the command line from a
// user's login session
ret = C.am_session_leader() == 1
default:
err = fmt.Errorf("error calling sd_pid_get_owner_uid: %v", syscall.Errno(-errno))
}
return
}
func currentUnitName() (unit string, err error) {
var h *dlopen.LibHandle
h, err = dlopen.GetHandle(libsystemdNames)
if err != nil {
return
}
defer func() {
if err1 := h.Close(); err1 != nil {
err = err1
}
}()
sd_pid_get_unit, err := h.GetSymbolPointer("sd_pid_get_unit")
if err != nil {
return
}
var s string
u := C.CString(s)
defer C.free(unsafe.Pointer(u))
ret := C.my_sd_pid_get_unit(sd_pid_get_unit, 0, &u)
if ret < 0 {
err = fmt.Errorf("error calling sd_pid_get_unit: %v", syscall.Errno(-ret))
return
}
unit = C.GoString(u)
return
}

23
vendor/github.com/coreos/go-systemd/util/util_stub.go generated vendored Normal file
View File

@ -0,0 +1,23 @@
// Copyright 2016 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.
// +build !cgo
package util
func getRunningSlice() (string, error) { return "", ErrNoCGO }
func runningFromSystemService() (bool, error) { return false, ErrNoCGO }
func currentUnitName() (string, error) { return "", ErrNoCGO }

79
vendor/github.com/coreos/go-systemd/util/util_test.go generated vendored Normal file
View File

@ -0,0 +1,79 @@
// Copyright 2016 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 util
import (
"testing"
)
func testIsRunningSystemd(t *testing.T) {
if !IsRunningSystemd() {
t.Skip("Not running on a systemd host")
}
}
func TestRunningFromSystemService(t *testing.T) {
testIsRunningSystemd(t)
t.Parallel()
// tests shouldn't be running as a service
s, err := RunningFromSystemService()
if err != nil {
t.Error(err.Error())
} else if s {
t.Errorf("tests aren't expected to run as a service")
}
}
func TestCurrentUnitName(t *testing.T) {
testIsRunningSystemd(t)
fromService, err := RunningFromSystemService()
if err != nil || !fromService {
t.Skip("Not running from a systemd service")
}
s, err := CurrentUnitName()
if err != nil {
t.Error(err.Error())
}
if s == "" {
t.Error("CurrentUnitName returned a empty string")
}
}
func TestGetMachineID(t *testing.T) {
testIsRunningSystemd(t)
id, err := GetMachineID()
if err != nil {
t.Error(err.Error())
}
if id == "" {
t.Error("GetMachineID returned a empty string")
}
}
func TestGetRunningSlice(t *testing.T) {
testIsRunningSystemd(t)
s, err := getRunningSlice()
if err != nil {
t.Error(err.Error())
}
if s == "" {
t.Error("getRunningSlice returned a empty string")
}
}

View File

@ -15,7 +15,7 @@
<div class="form-group has-feedback">
{{/*<i class="glyphicon glyphicon-search form-control-feedback" style="line-height: 32px;"></i>*/}}
<div class="input-group input-group-sm">
<input class="form-control input-sm" name="document_name" type="text" onkeydown="KeyDown('#search_btn')" placeholder="输入文档名搜索..." style="width: 280px">
<input class="form-control input-sm" name="keyword" type="text" onkeydown="KeyDown('#search_btn')" placeholder="请输入关键字搜索..." style="width: 280px">
<span class="input-group-btn">
<button class="btn btn-primary" type="button" name="search" id="search_btn" onclick="mainSearch(this.form, '/main/search')"><i class="fa fa-search"></i></button>
</span>
@ -75,8 +75,8 @@
}
}
function mainSearch(element, url) {
var searchText = $(element).find("input[name='document_name']").val();
url = url + "?document_name="+searchText;
var searchText = $(element).find("input[name='keyword']").val();
url = url + "?keyword="+searchText;
layer.open({
type: 2,
skin: Layers.skin,

View File

@ -3,9 +3,18 @@
<div class="panel-body">
<div class="row">
<form action="/main/search" method="get">
<div class="col-md-8">
<div class="col-md-3">
<div class="input-group">
<input class="form-control" name="document_name" type="text" value="{{.document_name}}" placeholder="输入文档名搜索...">
<span class="input-group-addon"> 搜索方式</span>
<select class="form-control" name="search_type">
<option value="title" {{if eq $.search_type "title"}} selected="selected" {{end}}>文档标题</option>
<option value="content" {{if eq $.search_type "content"}} selected="selected" {{end}}>文档内容</option>
</select>
</div>
</div>
<div class="col-md-9">
<div class="input-group">
<input class="form-control" name="keyword" type="text" value="{{.keyword}}" placeholder="请输入搜索关键字...">
<span class="input-group-btn">
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-search"></i></button>
</span>
@ -16,7 +25,7 @@
<hr>
{{if eq .count 0}}
<div class="alert alert-info alert-dismissible fade in" role="alert" style="margin-bottom: 0">
<p><i class="glyphicon glyphicon-volume-up"></i> 没有找到搜索结果</p>
<p><i class="glyphicon glyphicon-volume-up"></i> 抱歉暂时没有搜索到您想要的结果切换搜索方式试试看</p>
</div>
{{else}}
<div class="row">
@ -35,18 +44,27 @@
</div>
<span class="text text-font-12 document-user-time"><i class="fa fa-calendar"></i>
创建于 {{dateFormat $document.create_time "Y-m-d H:i:s"}}修改于{{dateFormat $document.update_time "Y-m-d H:i:s"}}
</span>
</span>
</li>
<hr>
<p class="search-content">{{$document.search_content}}</p>
{{end}}
</ul>
</div>
</div>
<div class="center">
{{template "paginator/default.html" .}}
{{/* {{template "paginator/default.html" .}}*/}}
</div>
{{end}}
</div>
</div>
</div>
</div>
<script type="text/javascript" src="/static/plugins/mark/mark.min.js"></script>
<script type="text/javascript">
var keyword = {{.keyword}}
if (keyword) {
var searchContent = document.querySelectorAll('.search-content');
var instance = new Mark(searchContent);
instance.mark(keyword);
}
</script>