add feature full text search
This commit is contained in:
parent
1877c8dec2
commit
3c9c94a730
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
|
@ -89,4 +89,10 @@ file="{"level":7, "filename":"logs/mm-wiki.log"}"
|
|||
# ----------------------------------------------------------------------
|
||||
[document]
|
||||
# 文档的根目录,最好写绝对路径
|
||||
root_dir="./data"
|
||||
root_dir="./data"
|
||||
|
||||
# 搜索配置
|
||||
# ----------------------------------------------------------------------
|
||||
[search]
|
||||
# 搜索索引更新间隔时间(单位秒),默认为 30;如果文档数过多,建议增加时长
|
||||
interval_time=30
|
|
@ -89,4 +89,10 @@ file="{"level":7, "filename":"#log.filename#"}"
|
|||
# ----------------------------------------------------------------------
|
||||
[document]
|
||||
# 文档的根目录,最好写绝对路径
|
||||
root_dir="#document_dir#"
|
||||
root_dir="#document_dir#"
|
||||
|
||||
# 搜索配置
|
||||
# ----------------------------------------------------------------------
|
||||
[search]
|
||||
# 搜索索引更新间隔时间(单位秒),默认为 30;如果文档数过多,建议增加时长
|
||||
interval_time=30
|
|
@ -0,0 +1,3 @@
|
|||
dictionary.txt 词典拷贝自 [github.com/fxsjy/jieba](https://github.com/fxsjy/jieba)
|
||||
|
||||
stop_tokens.txt 停用词列表来自网络
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,10 @@
|
|||
package global
|
||||
|
||||
import (
|
||||
"github.com/go-ego/riot"
|
||||
)
|
||||
|
||||
// 文档搜索
|
||||
var (
|
||||
DocSearcher = riot.Engine{}
|
||||
)
|
11
go.mod
11
go.mod
|
@ -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
218
go.sum
|
@ -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=
|
||||
|
|
|
@ -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);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -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`
|
|
@ -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.
|
|
@ -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.
|
|
@ -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')
|
||||
}
|
||||
}
|
|
@ -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.
|
|
@ -0,0 +1,5 @@
|
|||
CoreOS Project
|
||||
Copyright 2018 CoreOS, Inc
|
||||
|
||||
This product includes software developed at CoreOS, Inc.
|
||||
(http://www.coreos.com/).
|
|
@ -0,0 +1,71 @@
|
|||
# go-systemd
|
||||
|
||||
[](https://travis-ci.org/coreos/go-systemd)
|
||||
[](http://godoc.org/github.com/coreos/go-systemd)
|
||||

|
||||
|
||||
|
||||
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).
|
|
@ -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}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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
|
||||
}
|
69
vendor/github.com/coreos/go-systemd/activation/packetconns_test.go
generated
vendored
Normal file
69
vendor/github.com/coreos/go-systemd/activation/packetconns_test.go
generated
vendored
Normal 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")
|
||||
}
|
|
@ -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 employer’s
|
||||
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>.
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"))
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)))
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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),
|
||||
}
|
||||
}
|
|
@ -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)}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
60
vendor/github.com/coreos/go-systemd/examples/activation/activation.go
generated
vendored
Normal file
60
vendor/github.com/coreos/go-systemd/examples/activation/activation.go
generated
vendored
Normal 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
|
||||
}
|
19
vendor/github.com/coreos/go-systemd/examples/activation/httpserver/README.md
generated
vendored
Normal file
19
vendor/github.com/coreos/go-systemd/examples/activation/httpserver/README.md
generated
vendored
Normal 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!
|
||||
```
|
11
vendor/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service
generated
vendored
Normal file
11
vendor/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service
generated
vendored
Normal 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
|
5
vendor/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket
generated
vendored
Normal file
5
vendor/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
[Socket]
|
||||
ListenStream=127.0.0.1:8076
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
42
vendor/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go
generated
vendored
Normal file
42
vendor/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go
generated
vendored
Normal 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)
|
||||
}
|
|
@ -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
|
||||
}
|
78
vendor/github.com/coreos/go-systemd/examples/activation/udpconn.go
generated
vendored
Normal file
78
vendor/github.com/coreos/go-systemd/examples/activation/udpconn.go
generated
vendored
Normal 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
|
||||
}
|
5
vendor/github.com/coreos/go-systemd/fixtures/enable-disable.service
generated
vendored
Normal file
5
vendor/github.com/coreos/go-systemd/fixtures/enable-disable.service
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
[Unit]
|
||||
Description=enable disable test
|
||||
|
||||
[Service]
|
||||
ExecStart=/bin/sleep 400
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,5 @@
|
|||
[Unit]
|
||||
Description=mask unmask test
|
||||
|
||||
[Service]
|
||||
ExecStart=/bin/sleep 400
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
[Unit]
|
||||
Description=starting a failed test
|
||||
|
||||
[Service]
|
||||
ExecStartPre=/bin/false
|
||||
ExecStart=/bin/sleep 400
|
|
@ -0,0 +1,5 @@
|
|||
[Unit]
|
||||
Description=start stop test
|
||||
|
||||
[Service]
|
||||
ExecStart=/bin/sleep 400
|
5
vendor/github.com/coreos/go-systemd/fixtures/subscribe-events-set.service
generated
vendored
Normal file
5
vendor/github.com/coreos/go-systemd/fixtures/subscribe-events-set.service
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
[Unit]
|
||||
Description=start stop test
|
||||
|
||||
[Service]
|
||||
ExecStart=/bin/sleep 400
|
5
vendor/github.com/coreos/go-systemd/fixtures/subscribe-events.service
generated
vendored
Normal file
5
vendor/github.com/coreos/go-systemd/fixtures/subscribe-events.service
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
[Unit]
|
||||
Description=start stop test
|
||||
|
||||
[Service]
|
||||
ExecStart=/bin/sleep 400
|
|
@ -0,0 +1,5 @@
|
|||
module github.com/coreos/go-systemd/v22
|
||||
|
||||
go 1.12
|
||||
|
||||
require github.com/godbus/dbus/v5 v5.0.3
|
|
@ -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=
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}()
|
||||
}
|
|
@ -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
|
||||
}
|
63
vendor/github.com/coreos/go-systemd/internal/dlopen/dlopen_test.go
generated
vendored
Normal file
63
vendor/github.com/coreos/go-systemd/internal/dlopen/dlopen_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
23
vendor/github.com/coreos/go-systemd/scripts/jenkins/periodic-go-systemd-builder.sh
generated
vendored
Executable file
23
vendor/github.com/coreos/go-systemd/scripts/jenkins/periodic-go-systemd-builder.sh
generated
vendored
Executable 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
109
vendor/github.com/coreos/go-systemd/scripts/travis/pr-test.sh
generated
vendored
Executable 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
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
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
508
vendor/github.com/coreos/go-systemd/sdjournal/journal_test.go
generated
vendored
Executable 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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"
|
|
@ -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 == ';'
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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 }
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue