vendor: Update fuze, cloudinit, and go-systemd

This commit is contained in:
Dalton Hubble
2016-09-07 10:48:27 -07:00
parent 549727aae9
commit 099f3dbf2d
63 changed files with 2470 additions and 749 deletions

11
glide.lock generated
View File

@@ -1,5 +1,5 @@
hash: ed75f60308590c51616b57d4b2ecac260fe8da6fe54dc6d436dccc148d0d90c5
updated: 2016-09-06T16:41:57.122230713-07:00
hash: 1f2a602a6a995b4f8eb93ad0fa3ef5734b57078366e5969237d117d02146db88
updated: 2016-09-07T11:42:59.832174164-07:00
imports:
- name: github.com/alecthomas/units
version: 2efee857e7cfd4f3d0138cc3cbb1b4966962b93a
@@ -8,12 +8,11 @@ imports:
subpackages:
- pkg/errorutil
- name: github.com/coreos/coreos-cloudinit
version: b3f805dee6a4aa5ed298a1f370284df470eecf43
version: 4c333e657bfbaa8f6594298b48324f45e6bf5961
subpackages:
- Godeps/_workspace/src/github.com/coreos/yaml
- config
- name: github.com/coreos/fuze
version: 60c987a0aba4976ac6cbc9350671c2fedc431e8b
version: 7df4f06041d9daba45e4c68221b9b04203dff1d8
subpackages:
- config
- name: github.com/coreos/go-semver
@@ -21,7 +20,7 @@ imports:
subpackages:
- semver
- name: github.com/coreos/go-systemd
version: 7b2428fec40033549c68f54e26e89e7ca9a9ce31
version: 43e4800a6165b4e02bb2a36673c54b230d6f7b26
subpackages:
- journal
- name: github.com/coreos/ignition

View File

@@ -1,28 +1,23 @@
package: github.com/coreos/coreos-baremetal
import:
- package: github.com/alecthomas/units
version: 2efee857e7cfd4f3d0138cc3cbb1b4966962b93a
- package: github.com/camlistore/camlistore
version: 9106ce829629773474c689b34aacd7d3aaa99426
- package: github.com/coreos/coreos-cloudinit
version: b3f805dee6a4aa5ed298a1f370284df470eecf43
- package: github.com/golang/protobuf
version: 7cc19b78d562895b13596ddce7aafb59dd789318
subpackages:
- Godeps/_workspace/src/github.com/coreos/yaml
- config
- package: github.com/coreos/go-semver
version: 294930c1e79c64e7dbe360054274fdad492c8cf5
- proto
- package: google.golang.org/grpc
version: 83e628beaa8cab332f304ad93dff7abc46722386
subpackages:
- semver
- package: github.com/coreos/go-systemd
version: 7b2428fec40033549c68f54e26e89e7ca9a9ce31
subpackages:
- journal
- codes
- package: github.com/coreos/ignition
version: b6850837b3b9bd17b673e58b5c406b5e4192ddca
subpackages:
- config
- package: github.com/coreos/fuze
version: 60c987a0aba4976ac6cbc9350671c2fedc431e8b
version: 7df4f06041d9daba45e4c68221b9b04203dff1d8
subpackages:
- config
- package: github.com/coreos/coreos-cloudinit
version: v1.11.0
subpackages:
- config
- package: github.com/coreos/pkg
@@ -33,32 +28,8 @@ import:
version: v0.10.0
subpackages:
- hooks/test
- package: github.com/davecgh/go-spew
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
subpackages:
- spew
- package: github.com/golang/protobuf
version: 7cc19b78d562895b13596ddce7aafb59dd789318
subpackages:
- proto
- package: github.com/pmezard/go-difflib
version: 792786c7400a136282c1664665ae0a8db921c6c2
subpackages:
- difflib
- package: github.com/spf13/cobra
version: 65a708cee0a4424f4e353d031ce440643e312f92
- package: github.com/spf13/pflag
version: 7f60f83a2c81bc3c3c0d5297f61ddfa68da9d3b7
- package: github.com/stretchr/testify
version: 1f4a1643a57e798696635ea4c126e9127adb7d3c
subpackages:
- assert
- package: github.com/vincent-petithory/dataurl
version: 9a301d65acbb728fcc3ace14f45f511a4cfeea9c
- package: go4.org
version: 03efcb870d84809319ea509714dd6d19a1498483
subpackages:
- errorutil
- package: golang.org/x/crypto
version: 5dc8cb4b8a8eb076cbb5a06bc3b8682c15bdbbd3
subpackages:
@@ -71,11 +42,39 @@ import:
- http2
- internal/timeseries
- trace
- package: google.golang.org/grpc
version: 83e628beaa8cab332f304ad93dff7abc46722386
- package: github.com/stretchr/testify
version: 1f4a1643a57e798696635ea4c126e9127adb7d3c
subpackages:
- codes
- assert
- package: gopkg.in/yaml.v2
version: f7716cbe52baa25d2e9b0d0da546fcf909fc16b4
- package: github.com/coreos/yaml
version: 6b16a5714269b2f70720a45406b1babd947a17ef
- package: github.com/alecthomas/units
version: 2efee857e7cfd4f3d0138cc3cbb1b4966962b93a
- package: github.com/camlistore/camlistore
version: 9106ce829629773474c689b34aacd7d3aaa99426
- package: github.com/coreos/go-semver
version: 294930c1e79c64e7dbe360054274fdad492c8cf5
subpackages:
- semver
- package: github.com/coreos/go-systemd
version: v12
subpackages:
- journal
- package: github.com/davecgh/go-spew
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
subpackages:
- spew
- package: github.com/pmezard/go-difflib
version: 792786c7400a136282c1664665ae0a8db921c6c2
subpackages:
- difflib
- package: github.com/spf13/pflag
version: 7f60f83a2c81bc3c3c0d5297f61ddfa68da9d3b7
- package: github.com/vincent-petithory/dataurl
version: 9a301d65acbb728fcc3ace14f45f511a4cfeea9c
- package: go4.org
version: 03efcb870d84809319ea509714dd6d19a1498483
subpackages:
- errorutil

View File

@@ -1,12 +1,9 @@
language: go
sudo: false
matrix:
include:
- go: 1.4
install:
- go get golang.org/x/tools/cmd/cover
- go get golang.org/x/tools/cmd/vet
- go: 1.5
env: GO15VENDOREXPERIMENT=1
- go: 1.6
script:
- ./test

View File

@@ -223,6 +223,9 @@ List of locksmith configuration parameters:
- **etcd_cafile**: Path to CA file used for TLS communication with etcd
- **etcd_certfile**: Path to certificate file used for TLS communication with etcd
- **etcd_keyfile**: Path to private key file used for TLS communication with etcd
- **group**: Name of the reboot group in which this instance belongs
- **window_start**: Start time of the reboot window
- **window_length**: Duration of the reboot window
For the complete list of locksmith configuration parameters, see the [locksmith documentation][locksmith-readme].

View File

@@ -1,46 +0,0 @@
{
"ImportPath": "github.com/coreos/coreos-cloudinit",
"GoVersion": "go1.3.3",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/cloudsigma/cepgo",
"Rev": "1bfc4895bf5c4d3b599f3f6ee142299488c8739b"
},
{
"ImportPath": "github.com/coreos/go-systemd/dbus",
"Rev": "4fbc5060a317b142e6c7bfbedb65596d5f0ab99b"
},
{
"ImportPath": "github.com/coreos/yaml",
"Rev": "6b16a5714269b2f70720a45406b1babd947a17ef"
},
{
"ImportPath": "github.com/dotcloud/docker/pkg/netlink",
"Comment": "v0.11.1-359-g55d41c3e21e1",
"Rev": "55d41c3e21e1593b944c06196ffb2ac57ab7f653"
},
{
"ImportPath": "github.com/guelfey/go.dbus",
"Rev": "f6a3a2366cc39b8479cadc499d3c735fb10fbdda"
},
{
"ImportPath": "github.com/tarm/goserial",
"Rev": "cdabc8d44e8e84f58f18074ae44337e1f2f375b9"
},
{
"ImportPath": "github.com/sigma/vmw-guestinfo",
"Rev": "95dd4126d6e8b4ef1970b3f3fe2e8cdd470d2903"
},
{
"ImportPath": "github.com/sigma/vmw-ovflib",
"Rev": "56b4f44581cac03d17d8270158bdfd0942ffe790"
},
{
"ImportPath": "github.com/sigma/bdoor",
"Rev": "babf2a4017b020d4ce04e8167076186e82645dd1"
}
]
}

View File

@@ -1,5 +0,0 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

View File

@@ -68,6 +68,22 @@ func NewCloudConfig(contents string) (*CloudConfig, error) {
return &cfg, err
}
// Decode decodes the content of cloud config. Currently only WriteFiles section
// supports several types of encoding and all of them are supported. After
// decode operation, Encoding type is unset.
func (cc *CloudConfig) Decode() error {
for i, file := range cc.WriteFiles {
content, err := DecodeContent(file.Content, file.Encoding)
if err != nil {
return err
}
cc.WriteFiles[i].Content = string(content)
cc.WriteFiles[i].Encoding = ""
}
return nil
}
func (cc CloudConfig) String() string {
bytes, err := yaml.Marshal(cc)
if err != nil {

View File

@@ -15,6 +15,7 @@
package config
import (
"fmt"
"reflect"
"regexp"
"strings"
@@ -30,11 +31,11 @@ func TestNewCloudConfig(t *testing.T) {
{},
{
contents: "#cloud-config\nwrite_files:\n - path: underscore",
config: CloudConfig{WriteFiles: []File{File{Path: "underscore"}}},
config: CloudConfig{WriteFiles: []File{{Path: "underscore"}}},
},
{
contents: "#cloud-config\nwrite-files:\n - path: hyphen",
config: CloudConfig{WriteFiles: []File{File{Path: "hyphen"}}},
config: CloudConfig{WriteFiles: []File{{Path: "hyphen"}}},
},
{
contents: "#cloud-config\ncoreos:\n update:\n reboot-strategy: off",
@@ -46,19 +47,19 @@ func TestNewCloudConfig(t *testing.T) {
},
{
contents: "#cloud-config\nwrite_files:\n - permissions: 0744",
config: CloudConfig{WriteFiles: []File{File{RawFilePermissions: "0744"}}},
config: CloudConfig{WriteFiles: []File{{RawFilePermissions: "0744"}}},
},
{
contents: "#cloud-config\nwrite_files:\n - permissions: 744",
config: CloudConfig{WriteFiles: []File{File{RawFilePermissions: "744"}}},
config: CloudConfig{WriteFiles: []File{{RawFilePermissions: "744"}}},
},
{
contents: "#cloud-config\nwrite_files:\n - permissions: '0744'",
config: CloudConfig{WriteFiles: []File{File{RawFilePermissions: "0744"}}},
config: CloudConfig{WriteFiles: []File{{RawFilePermissions: "0744"}}},
},
{
contents: "#cloud-config\nwrite_files:\n - permissions: '744'",
config: CloudConfig{WriteFiles: []File{File{RawFilePermissions: "744"}}},
config: CloudConfig{WriteFiles: []File{{RawFilePermissions: "744"}}},
},
}
@@ -73,6 +74,50 @@ func TestNewCloudConfig(t *testing.T) {
}
}
func TestNewCloudConfigDecode(t *testing.T) {
// //all of these decode to "bar"
contentTests := map[string]string{
"base64": "YmFy",
"b64": "YmFy",
// theoretically gz+gzip are supported but they break yaml
// "gz": "\x1f\x8b\x08\x08w\x14\x87T\x02\xffok\x00KJ,\x02\x00\xaa\x8c\xffv\x03\x00\x00\x00",
// "gzip": "\x1f\x8b\x08\x08w\x14\x87T\x02\xffok\x00KJ,\x02\x00\xaa\x8c\xffv\x03\x00\x00\x00",
"gz+base64": "H4sIABMVh1QAA0tKLAIAqoz/dgMAAAA=",
"gzip+base64": "H4sIABMVh1QAA0tKLAIAqoz/dgMAAAA=",
"gz+b64": "H4sIABMVh1QAA0tKLAIAqoz/dgMAAAA=",
"gzip+b64": "H4sIABMVh1QAA0tKLAIAqoz/dgMAAAA=",
}
type testCase struct {
contents string
config CloudConfig
}
var decodingTests []testCase
for name, content := range contentTests {
decodingTests = append(decodingTests, testCase{
contents: fmt.Sprintf("#cloud-config\nwrite_files:\n - encoding: %q\n content: |\n %s", name, content),
config: CloudConfig{WriteFiles: []File{{Content: "bar"}}},
})
}
for i, tt := range decodingTests {
config, err := NewCloudConfig(tt.contents)
if err != nil {
t.Errorf("bad error (test case #%d): want %v, got %s", i, nil, err)
}
if err := config.Decode(); err != nil {
t.Errorf("bad error (test case #%d): want %v, got %s", i, nil, err)
}
if !reflect.DeepEqual(&tt.config, config) {
t.Errorf("bad config (test case #%d): want %#v, got %#v", i, tt.config, config)
}
}
}
func TestIsZero(t *testing.T) {
tests := []struct {
c interface{}

View File

@@ -27,6 +27,7 @@ type Etcd2 struct {
DiscoverySRV string `yaml:"discovery_srv" env:"ETCD_DISCOVERY_SRV"`
DiscoveryProxy string `yaml:"discovery_proxy" env:"ETCD_DISCOVERY_PROXY"`
ElectionTimeout int `yaml:"election_timeout" env:"ETCD_ELECTION_TIMEOUT"`
EnablePprof bool `yaml:"enable_pprof" env:"ETCD_ENABLE_PPROF"`
ForceNewCluster bool `yaml:"force_new_cluster" env:"ETCD_FORCE_NEW_CLUSTER"`
HeartbeatInterval int `yaml:"heartbeat_interval" env:"ETCD_HEARTBEAT_INTERVAL"`
InitialAdvertisePeerURLs string `yaml:"initial_advertise_peer_urls" env:"ETCD_INITIAL_ADVERTISE_PEER_URLS"`
@@ -52,6 +53,7 @@ type Etcd2 struct {
ProxyRefreshInterval int `yaml:"proxy_refresh_interval" env:"ETCD_PROXY_REFRESH_INTERVAL"`
ProxyWriteTimeout int `yaml:"proxy_write_timeout" env:"ETCD_PROXY_WRITE_TIMEOUT"`
SnapshotCount int `yaml:"snapshot_count" env:"ETCD_SNAPSHOT_COUNT"`
StrictReconfigCheck bool `yaml:"strict_reconfig_check" env:"ETCD_STRICT_RECONFIG_CHECK"`
TrustedCAFile string `yaml:"trusted_ca_file" env:"ETCD_TRUSTED_CA_FILE"`
WalDir string `yaml:"wal_dir" env:"ETCD_WAL_DIR"`
}

View File

@@ -20,7 +20,10 @@ import (
func IsIgnitionConfig(userdata string) bool {
var cfg struct {
Version *int `json:"ignitionVersion" yaml:"ignition_version"`
Version *int `json:"ignitionVersion"`
Ignition struct {
Version *string `json:"version"`
} `json:"ignition"`
}
return (json.Unmarshal([]byte(userdata), &cfg) == nil && cfg.Version != nil)
return (json.Unmarshal([]byte(userdata), &cfg) == nil && (cfg.Version != nil || cfg.Ignition.Version != nil))
}

View File

@@ -33,18 +33,18 @@ func TestChild(t *testing.T) {
{
parent: node{
children: []node{
node{name: "c1"},
node{name: "c2"},
node{name: "c3"},
{name: "c1"},
{name: "c2"},
{name: "c3"},
},
},
},
{
parent: node{
children: []node{
node{name: "c1"},
node{name: "c2"},
node{name: "c3"},
{name: "c1"},
{name: "c2"},
{name: "c3"},
},
},
name: "c2",
@@ -76,8 +76,8 @@ func TestHumanType(t *testing.T) {
node: node{
Value: reflect.ValueOf([]int{1, 2}),
children: []node{
node{Value: reflect.ValueOf(1)},
node{Value: reflect.ValueOf(2)},
{Value: reflect.ValueOf(1)},
{Value: reflect.ValueOf(2)},
}},
humanType: "[]int",
},
@@ -108,7 +108,7 @@ func TestToNode(t *testing.T) {
}{},
node: node{
children: []node{
node{
{
name: "a",
field: reflect.TypeOf(struct {
A int `yaml:"a"`
@@ -123,7 +123,7 @@ func TestToNode(t *testing.T) {
}{},
node: node{
children: []node{
node{
{
name: "a",
field: reflect.TypeOf(struct {
A []int `yaml:"a"`
@@ -141,11 +141,11 @@ func TestToNode(t *testing.T) {
context: NewContext([]byte("a:\n b: 2")),
node: node{
children: []node{
node{
{
line: 1,
name: "a",
children: []node{
node{name: "b", line: 2},
{name: "b", line: 2},
},
},
},
@@ -159,10 +159,10 @@ func TestToNode(t *testing.T) {
}{},
node: node{
children: []node{
node{
{
name: "a",
children: []node{
node{
{
name: "b",
field: reflect.TypeOf(struct {
Jon bool `yaml:"b"`

View File

@@ -77,9 +77,9 @@ func TestReport(t *testing.T) {
{(*Report).Info, 10, "test info 10"},
},
[]Entry{
Entry{entryWarning, "test warning 1", 1},
Entry{entryError, "test error 2", 2},
Entry{entryInfo, "test info 10", 10},
{entryWarning, "test warning 1", 1},
{entryError, "test error 2", 2},
{entryInfo, "test info 10", 10},
},
},
}

View File

@@ -46,7 +46,7 @@ func Validate(userdataBytes []byte) (Report, error) {
return validateCloudConfig(userdataBytes, Rules)
default:
return Report{entries: []Entry{
Entry{kind: entryError, message: `must be "#cloud-config" or begin with "#!"`, line: 1},
{kind: entryError, message: `must be "#cloud-config" or begin with "#!"`, line: 1},
}}, nil
}
}

View File

@@ -34,6 +34,7 @@ import (
"github.com/coreos/coreos-cloudinit/datasource/metadata/cloudsigma"
"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
"github.com/coreos/coreos-cloudinit/datasource/metadata/ec2"
"github.com/coreos/coreos-cloudinit/datasource/metadata/gce"
"github.com/coreos/coreos-cloudinit/datasource/metadata/packet"
"github.com/coreos/coreos-cloudinit/datasource/proc_cmdline"
"github.com/coreos/coreos-cloudinit/datasource/url"
@@ -61,6 +62,7 @@ var (
waagent string
metadataService bool
ec2MetadataService string
gceMetadataService string
cloudSigmaMetadataService bool
digitalOceanMetadataService string
packetMetadataService string
@@ -86,6 +88,7 @@ func init() {
flag.StringVar(&flags.sources.waagent, "from-waagent", "", "Read data from provided waagent directory")
flag.BoolVar(&flags.sources.metadataService, "from-metadata-service", false, "[DEPRECATED - Use -from-ec2-metadata] Download data from metadata service")
flag.StringVar(&flags.sources.ec2MetadataService, "from-ec2-metadata", "", "Download EC2 data from the provided url")
flag.StringVar(&flags.sources.gceMetadataService, "from-gce-metadata", "", "Download GCE data from the provided url")
flag.BoolVar(&flags.sources.cloudSigmaMetadataService, "from-cloudsigma-metadata", false, "Download data from CloudSigma server context")
flag.StringVar(&flags.sources.digitalOceanMetadataService, "from-digitalocean-metadata", "", "Download DigitalOcean data from the provided url")
flag.StringVar(&flags.sources.packetMetadataService, "from-packet-metadata", "", "Download Packet data from metadata service")
@@ -104,28 +107,31 @@ type oemConfig map[string]string
var (
oemConfigs = map[string]oemConfig{
"digitalocean": oemConfig{
"digitalocean": {
"from-digitalocean-metadata": "http://169.254.169.254/",
"convert-netconf": "digitalocean",
},
"ec2-compat": oemConfig{
"ec2-compat": {
"from-ec2-metadata": "http://169.254.169.254/",
"from-configdrive": "/media/configdrive",
},
"rackspace-onmetal": oemConfig{
"gce": {
"from-gce-metadata": "http://metadata.google.internal/",
},
"rackspace-onmetal": {
"from-configdrive": "/media/configdrive",
"convert-netconf": "debian",
},
"azure": oemConfig{
"azure": {
"from-waagent": "/var/lib/waagent",
},
"cloudsigma": oemConfig{
"cloudsigma": {
"from-cloudsigma-metadata": "true",
},
"packet": oemConfig{
"packet": {
"from-packet-metadata": "https://metadata.packet.net/",
},
"vmware": oemConfig{
"vmware": {
"from-vmware-guestinfo": "true",
"convert-netconf": "vmware",
},
@@ -174,7 +180,7 @@ func main() {
dss := getDatasources()
if len(dss) == 0 {
fmt.Println("Provide at least one of --from-file, --from-configdrive, --from-ec2-metadata, --from-cloudsigma-metadata, --from-packet-metadata, --from-digitalocean-metadata, --from-vmware-guestinfo, --from-waagent, --from-url or --from-proc-cmdline")
fmt.Println("Provide at least one of --from-file, --from-configdrive, --from-ec2-metadata, --from-gce-metadata, --from-cloudsigma-metadata, --from-packet-metadata, --from-digitalocean-metadata, --from-vmware-guestinfo, --from-waagent, --from-url or --from-proc-cmdline")
os.Exit(2)
}
@@ -322,6 +328,9 @@ func getDatasources() []datasource.Datasource {
if flags.sources.ec2MetadataService != "" {
dss = append(dss, ec2.NewDatasource(flags.sources.ec2MetadataService))
}
if flags.sources.gceMetadataService != "" {
dss = append(dss, gce.NewDatasource(flags.sources.gceMetadataService))
}
if flags.sources.cloudSigmaMetadataService {
dss = append(dss, cloudsigma.NewServerContextService())
}

View File

@@ -66,7 +66,7 @@ type metadataService struct {
}
func NewDatasource(root string) *metadataService {
return &metadataService{MetadataService: metadata.NewDatasource(root, apiVersion, userdataUrl, metadataPath)}
return &metadataService{MetadataService: metadata.NewDatasource(root, apiVersion, userdataUrl, metadataPath, nil)}
}
func (ms *metadataService) FetchMetadata() (metadata datasource.Metadata, err error) {

View File

@@ -93,7 +93,7 @@ func TestFetchMetadata(t *testing.T) {
NetworkConfig: Metadata{
Interfaces: Interfaces{
Public: []Interface{
Interface{
{
IPv4: &Address{
IPAddress: "192.168.1.2",
Netmask: "255.255.255.0",

View File

@@ -39,7 +39,7 @@ type metadataService struct {
}
func NewDatasource(root string) *metadataService {
return &metadataService{metadata.NewDatasource(root, apiVersion, userdataPath, metadataPath)}
return &metadataService{metadata.NewDatasource(root, apiVersion, userdataPath, metadataPath, nil)}
}
func (ms metadataService) FetchMetadata() (datasource.Metadata, error) {

View File

@@ -0,0 +1,89 @@
// 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 gce
import (
"fmt"
"net"
"net/http"
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/datasource/metadata"
)
const (
apiVersion = "computeMetadata/v1/"
metadataPath = apiVersion + "instance/"
userdataPath = apiVersion + "instance/attributes/user-data"
)
type metadataService struct {
metadata.MetadataService
}
func NewDatasource(root string) *metadataService {
return &metadataService{metadata.NewDatasource(root, apiVersion, userdataPath, metadataPath, http.Header{"Metadata-Flavor": {"Google"}})}
}
func (ms metadataService) FetchMetadata() (datasource.Metadata, error) {
public, err := ms.fetchIP("network-interfaces/0/access-configs/0/external-ip")
if err != nil {
return datasource.Metadata{}, err
}
local, err := ms.fetchIP("network-interfaces/0/ip")
if err != nil {
return datasource.Metadata{}, err
}
hostname, err := ms.fetchString("hostname")
if err != nil {
return datasource.Metadata{}, err
}
return datasource.Metadata{
PublicIPv4: public,
PrivateIPv4: local,
Hostname: hostname,
}, nil
}
func (ms metadataService) Type() string {
return "gce-metadata-service"
}
func (ms metadataService) fetchString(key string) (string, error) {
data, err := ms.FetchData(ms.MetadataUrl() + key)
if err != nil {
return "", err
}
return string(data), nil
}
func (ms metadataService) fetchIP(key string) (net.IP, error) {
str, err := ms.fetchString(key)
if err != nil {
return nil, err
}
if str == "" {
return nil, nil
}
if ip := net.ParseIP(str); ip != nil {
return ip, nil
} else {
return nil, fmt.Errorf("couldn't parse %q as IP address", str)
}
}

View File

@@ -0,0 +1,99 @@
// 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 gce
import (
"fmt"
"net"
"reflect"
"testing"
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/datasource/metadata"
"github.com/coreos/coreos-cloudinit/datasource/metadata/test"
"github.com/coreos/coreos-cloudinit/pkg"
)
func TestType(t *testing.T) {
want := "gce-metadata-service"
if kind := (metadataService{}).Type(); kind != want {
t.Fatalf("bad type: want %q, got %q", want, kind)
}
}
func TestFetchMetadata(t *testing.T) {
for _, tt := range []struct {
root string
metadataPath string
resources map[string]string
expect datasource.Metadata
clientErr error
expectErr error
}{
{
root: "/",
metadataPath: "computeMetadata/v1/instance/",
resources: map[string]string{},
},
{
root: "/",
metadataPath: "computeMetadata/v1/instance/",
resources: map[string]string{
"/computeMetadata/v1/instance/hostname": "host",
},
expect: datasource.Metadata{
Hostname: "host",
},
},
{
root: "/",
metadataPath: "computeMetadata/v1/instance/",
resources: map[string]string{
"/computeMetadata/v1/instance/hostname": "host",
"/computeMetadata/v1/instance/network-interfaces/0/ip": "1.2.3.4",
"/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip": "5.6.7.8",
},
expect: datasource.Metadata{
Hostname: "host",
PrivateIPv4: net.ParseIP("1.2.3.4"),
PublicIPv4: net.ParseIP("5.6.7.8"),
},
},
{
clientErr: pkg.ErrTimeout{Err: fmt.Errorf("test error")},
expectErr: pkg.ErrTimeout{Err: fmt.Errorf("test error")},
},
} {
service := &metadataService{metadata.MetadataService{
Root: tt.root,
Client: &test.HttpClient{Resources: tt.resources, Err: tt.clientErr},
MetadataPath: tt.metadataPath,
}}
metadata, err := service.FetchMetadata()
if Error(err) != Error(tt.expectErr) {
t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err)
}
if !reflect.DeepEqual(tt.expect, metadata) {
t.Fatalf("bad fetch (%q): want %#v, got %#v", tt.resources, tt.expect, metadata)
}
}
}
func Error(err error) string {
if err != nil {
return err.Error()
}
return ""
}

View File

@@ -15,6 +15,7 @@
package metadata
import (
"net/http"
"strings"
"github.com/coreos/coreos-cloudinit/pkg"
@@ -28,11 +29,11 @@ type MetadataService struct {
MetadataPath string
}
func NewDatasource(root, apiVersion, userdataPath, metadataPath string) MetadataService {
func NewDatasource(root, apiVersion, userdataPath, metadataPath string, header http.Header) MetadataService {
if !strings.HasSuffix(root, "/") {
root += "/"
}
return MetadataService{root, pkg.NewHttpClient(), apiVersion, userdataPath, metadataPath}
return MetadataService{root, pkg.NewHttpClientHeader(header), apiVersion, userdataPath, metadataPath}
}
func (ms MetadataService) IsAvailable() bool {

View File

@@ -170,7 +170,7 @@ func TestNewDatasource(t *testing.T) {
expectRoot: "http://169.254.169.254/",
},
} {
service := NewDatasource(tt.root, "", "", "")
service := NewDatasource(tt.root, "", "", "", nil)
if service.Root != tt.expectRoot {
t.Fatalf("bad root (%q): want %q, got %q", tt.root, tt.expectRoot, service.Root)
}

View File

@@ -62,7 +62,7 @@ type metadataService struct {
}
func NewDatasource(root string) *metadataService {
return &metadataService{MetadataService: metadata.NewDatasource(root, apiVersion, userdataUrl, metadataPath)}
return &metadataService{MetadataService: metadata.NewDatasource(root, apiVersion, userdataUrl, metadataPath, nil)}
}
func (ms *metadataService) FetchMetadata() (metadata datasource.Metadata, err error) {

View File

@@ -70,26 +70,26 @@ func TestNewMockFilesystem(t *testing.T) {
filesystem: MockFilesystem{},
},
{
files: []File{File{Path: "file"}},
files: []File{{Path: "file"}},
filesystem: MockFilesystem{
"file": File{Path: "file"},
},
},
{
files: []File{File{Path: "/file"}},
files: []File{{Path: "/file"}},
filesystem: MockFilesystem{
"/file": File{Path: "/file"},
},
},
{
files: []File{File{Path: "/dir/file"}},
files: []File{{Path: "/dir/file"}},
filesystem: MockFilesystem{
"/dir": File{Path: "/dir", Directory: true},
"/dir/file": File{Path: "/dir/file"},
},
},
{
files: []File{File{Path: "/dir/dir/file"}},
files: []File{{Path: "/dir/dir/file"}},
filesystem: MockFilesystem{
"/dir": File{Path: "/dir", Directory: true},
"/dir/dir": File{Path: "/dir/dir", Directory: true},
@@ -97,7 +97,7 @@ func TestNewMockFilesystem(t *testing.T) {
},
},
{
files: []File{File{Path: "/dir/dir/dir", Directory: true}},
files: []File{{Path: "/dir/dir/dir", Directory: true}},
filesystem: MockFilesystem{
"/dir": File{Path: "/dir", Directory: true},
"/dir/dir": File{Path: "/dir/dir", Directory: true},

View File

@@ -16,18 +16,10 @@ package vmware
import (
"fmt"
"io/ioutil"
"log"
"net"
"os"
"github.com/coreos/coreos-cloudinit/config"
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/pkg"
"github.com/sigma/vmw-guestinfo/rpcvmx"
"github.com/sigma/vmw-guestinfo/vmcheck"
"github.com/sigma/vmw-ovflib"
)
type readConfigFunction func(key string) (string, error)
@@ -39,65 +31,6 @@ type vmware struct {
urlDownload urlDownloadFunction
}
type ovfWrapper struct {
env *ovf.OvfEnvironment
}
func (ovf ovfWrapper) readConfig(key string) (string, error) {
return ovf.env.Properties["guestinfo."+key], nil
}
func NewDatasource(fileName string) *vmware {
getOvfReadConfig := func(ovfEnv []byte) readConfigFunction {
env := &ovf.OvfEnvironment{}
if len(ovfEnv) != 0 {
env = ovf.ReadEnvironment(ovfEnv)
}
wrapper := ovfWrapper{env}
return wrapper.readConfig
}
// read from provided ovf environment document (typically /media/ovfenv/ovf-env.xml)
if fileName != "" {
log.Printf("Using OVF environment from %s\n", fileName)
ovfEnv, err := ioutil.ReadFile(fileName)
if err != nil {
ovfEnv = make([]byte, 0)
}
return &vmware{
ovfFileName: fileName,
readConfig: getOvfReadConfig(ovfEnv),
urlDownload: urlDownload,
}
}
// try to read ovf environment from VMware tools
data, err := readConfig("ovfenv")
if err == nil && data != "" {
log.Printf("Using OVF environment from guestinfo\n")
return &vmware{
readConfig: getOvfReadConfig([]byte(data)),
urlDownload: urlDownload,
}
}
// if everything fails, fallback to directly reading variables from the backdoor
log.Printf("Using guestinfo variables\n")
return &vmware{
readConfig: readConfig,
urlDownload: urlDownload,
}
}
func (v vmware) IsAvailable() bool {
if v.ovfFileName != "" {
_, err := os.Stat(v.ovfFileName)
return !os.IsNotExist(err)
}
return vmcheck.IsVirtualWorld()
}
func (v vmware) AvailabilityChanges() bool {
return false
}
@@ -218,18 +151,3 @@ func (v vmware) FetchUserdata() ([]byte, error) {
func (v vmware) Type() string {
return "vmware"
}
func urlDownload(url string) ([]byte, error) {
client := pkg.NewHttpClient()
return client.GetRetry(url)
}
func readConfig(key string) (string, error) {
data, err := rpcvmx.NewConfig().String(key, "")
if err == nil {
log.Printf("Read from %q: %q\n", key, data)
} else {
log.Printf("Failed to read from %q: %v\n", key, err)
}
return data, err
}

View File

@@ -0,0 +1,101 @@
// 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 vmware
import (
"io/ioutil"
"log"
"os"
"github.com/coreos/coreos-cloudinit/pkg"
"github.com/sigma/vmw-guestinfo/rpcvmx"
"github.com/sigma/vmw-guestinfo/vmcheck"
"github.com/sigma/vmw-ovflib"
)
type ovfWrapper struct {
env *ovf.OvfEnvironment
}
func (ovf ovfWrapper) readConfig(key string) (string, error) {
return ovf.env.Properties["guestinfo."+key], nil
}
func NewDatasource(fileName string) *vmware {
// read from provided ovf environment document (typically /media/ovfenv/ovf-env.xml)
if fileName != "" {
log.Printf("Using OVF environment from %s\n", fileName)
ovfEnv, err := ioutil.ReadFile(fileName)
if err != nil {
ovfEnv = make([]byte, 0)
}
return &vmware{
ovfFileName: fileName,
readConfig: getOvfReadConfig(ovfEnv),
urlDownload: urlDownload,
}
}
// try to read ovf environment from VMware tools
data, err := readConfig("ovfenv")
if err == nil && data != "" {
log.Printf("Using OVF environment from guestinfo\n")
return &vmware{
readConfig: getOvfReadConfig([]byte(data)),
urlDownload: urlDownload,
}
}
// if everything fails, fallback to directly reading variables from the backdoor
log.Printf("Using guestinfo variables\n")
return &vmware{
readConfig: readConfig,
urlDownload: urlDownload,
}
}
func (v vmware) IsAvailable() bool {
if v.ovfFileName != "" {
_, err := os.Stat(v.ovfFileName)
return !os.IsNotExist(err)
}
return vmcheck.IsVirtualWorld()
}
func readConfig(key string) (string, error) {
data, err := rpcvmx.NewConfig().String(key, "")
if err == nil {
log.Printf("Read from %q: %q\n", key, data)
} else {
log.Printf("Failed to read from %q: %v\n", key, err)
}
return data, err
}
func getOvfReadConfig(ovfEnv []byte) readConfigFunction {
env := &ovf.OvfEnvironment{}
if len(ovfEnv) != 0 {
env = ovf.ReadEnvironment(ovfEnv)
}
wrapper := ovfWrapper{env}
return wrapper.readConfig
}
func urlDownload(url string) ([]byte, error) {
client := pkg.NewHttpClient()
return client.GetRetry(url)
}

View File

@@ -0,0 +1,25 @@
// 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 !amd64
package vmware
func NewDatasource(fileName string) *vmware {
return &vmware{}
}
func (v vmware) IsAvailable() bool {
return false
}

View File

@@ -123,9 +123,9 @@ func TestCreateNetworkingUnits(t *testing.T) {
network.InterfaceGenerator(mockInterface{filename: "test3", network: "test network"}),
},
[]system.Unit{
system.Unit{Unit: config.Unit{Name: "test1.netdev", Runtime: true, Content: "test netdev"}},
system.Unit{Unit: config.Unit{Name: "test2.link", Runtime: true, Content: "test link"}},
system.Unit{Unit: config.Unit{Name: "test3.network", Runtime: true, Content: "test network"}},
{Unit: config.Unit{Name: "test1.netdev", Runtime: true, Content: "test netdev"}},
{Unit: config.Unit{Name: "test2.link", Runtime: true, Content: "test link"}},
{Unit: config.Unit{Name: "test3.network", Runtime: true, Content: "test network"}},
},
},
{
@@ -133,9 +133,9 @@ func TestCreateNetworkingUnits(t *testing.T) {
network.InterfaceGenerator(mockInterface{filename: "test", netdev: "test netdev", link: "test link", network: "test network"}),
},
[]system.Unit{
system.Unit{Unit: config.Unit{Name: "test.netdev", Runtime: true, Content: "test netdev"}},
system.Unit{Unit: config.Unit{Name: "test.link", Runtime: true, Content: "test link"}},
system.Unit{Unit: config.Unit{Name: "test.network", Runtime: true, Content: "test network"}},
{Unit: config.Unit{Name: "test.netdev", Runtime: true, Content: "test netdev"}},
{Unit: config.Unit{Name: "test.link", Runtime: true, Content: "test link"}},
{Unit: config.Unit{Name: "test.network", Runtime: true, Content: "test network"}},
},
},
} {
@@ -154,7 +154,7 @@ func TestProcessUnits(t *testing.T) {
}{
{
units: []system.Unit{
system.Unit{Unit: config.Unit{
{Unit: config.Unit{
Name: "foo",
Mask: true,
}},
@@ -165,16 +165,16 @@ func TestProcessUnits(t *testing.T) {
},
{
units: []system.Unit{
system.Unit{Unit: config.Unit{
{Unit: config.Unit{
Name: "baz.service",
Content: "[Service]\nExecStart=/bin/baz",
Command: "start",
}},
system.Unit{Unit: config.Unit{
{Unit: config.Unit{
Name: "foo.network",
Content: "[Network]\nFoo=true",
}},
system.Unit{Unit: config.Unit{
{Unit: config.Unit{
Name: "bar.network",
Content: "[Network]\nBar=true",
}},
@@ -182,15 +182,15 @@ func TestProcessUnits(t *testing.T) {
result: TestUnitManager{
placed: []string{"baz.service", "foo.network", "bar.network"},
commands: []UnitAction{
UnitAction{"systemd-networkd.service", "restart"},
UnitAction{"baz.service", "start"},
{"systemd-networkd.service", "restart"},
{"baz.service", "start"},
},
reload: true,
},
},
{
units: []system.Unit{
system.Unit{Unit: config.Unit{
{Unit: config.Unit{
Name: "baz.service",
Content: "[Service]\nExecStart=/bin/true",
}},
@@ -202,7 +202,7 @@ func TestProcessUnits(t *testing.T) {
},
{
units: []system.Unit{
system.Unit{Unit: config.Unit{
{Unit: config.Unit{
Name: "locksmithd.service",
Runtime: true,
}},
@@ -213,7 +213,7 @@ func TestProcessUnits(t *testing.T) {
},
{
units: []system.Unit{
system.Unit{Unit: config.Unit{
{Unit: config.Unit{
Name: "woof",
Enable: true,
}},
@@ -224,7 +224,7 @@ func TestProcessUnits(t *testing.T) {
},
{
units: []system.Unit{
system.Unit{Unit: config.Unit{
{Unit: config.Unit{
Name: "hi.service",
Runtime: true,
Content: "[Service]\nExecStart=/bin/echo hi",
@@ -248,7 +248,7 @@ func TestProcessUnits(t *testing.T) {
},
{
units: []system.Unit{
system.Unit{Unit: config.Unit{
{Unit: config.Unit{
DropIns: []config.UnitDropIn{
{
Name: "lo.conf",
@@ -261,7 +261,7 @@ func TestProcessUnits(t *testing.T) {
},
{
units: []system.Unit{
system.Unit{Unit: config.Unit{
{Unit: config.Unit{
Name: "hi.service",
DropIns: []config.UnitDropIn{
{
@@ -274,7 +274,7 @@ func TestProcessUnits(t *testing.T) {
},
{
units: []system.Unit{
system.Unit{Unit: config.Unit{
{Unit: config.Unit{
Name: "hi.service",
DropIns: []config.UnitDropIn{
{

View File

@@ -36,7 +36,16 @@ func ParseUserData(contents string) (interface{}, error) {
return config.NewScript(contents)
case config.IsCloudConfig(contents):
log.Printf("Parsing user-data as cloud-config")
return config.NewCloudConfig(contents)
cc, err := config.NewCloudConfig(contents)
if err != nil {
return nil, err
}
if err := cc.Decode(); err != nil {
return nil, err
}
return cc, nil
case config.IsIgnitionConfig(contents):
return nil, ErrIgnitionConfig
default:

View File

@@ -138,7 +138,7 @@ func TestParseInterface(t *testing.T) {
iface: &logicalInterface{
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
config: configMethodStatic{
addresses: []net.IPNet{net.IPNet{
addresses: []net.IPNet{{
IP: net.ParseIP("1.2.3.4"),
Mask: net.IPMask(net.ParseIP("255.255.0.0")),
}},
@@ -174,12 +174,12 @@ func TestParseInterface(t *testing.T) {
iface: &logicalInterface{
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
config: configMethodStatic{
addresses: []net.IPNet{net.IPNet{
addresses: []net.IPNet{{
IP: net.ParseIP("1.2.3.4"),
Mask: net.IPMask(net.ParseIP("255.255.0.0")),
}},
nameservers: []net.IP{},
routes: []route{route{
routes: []route{{
net.IPNet{IP: net.IPv4zero, Mask: net.IPMask(net.IPv4zero)},
net.ParseIP("5.6.7.8"),
}},
@@ -210,7 +210,7 @@ func TestParseInterface(t *testing.T) {
iface: &logicalInterface{
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
config: configMethodStatic{
addresses: []net.IPNet{net.IPNet{
addresses: []net.IPNet{{
IP: net.ParseIP("fe00::"),
Mask: net.IPMask(net.ParseIP("ffff::")),
}},
@@ -246,12 +246,12 @@ func TestParseInterface(t *testing.T) {
iface: &logicalInterface{
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
config: configMethodStatic{
addresses: []net.IPNet{net.IPNet{
addresses: []net.IPNet{{
IP: net.ParseIP("fe00::"),
Mask: net.IPMask(net.ParseIP("ffff::")),
}},
nameservers: []net.IP{},
routes: []route{route{
routes: []route{{
net.IPNet{IP: net.IPv6zero, Mask: net.IPMask(net.IPv6zero)},
net.ParseIP("fe00:1234::"),
}},
@@ -446,7 +446,7 @@ func TestProcessDigitalOceanNetconf(t *testing.T) {
cfg: digitalocean.Metadata{
Interfaces: digitalocean.Interfaces{
Public: []digitalocean.Interface{
digitalocean.Interface{
{
IPv4: &digitalocean.Address{
IPAddress: "bad",
},

View File

@@ -96,7 +96,7 @@ func TestInterfaceGenerators(t *testing.T) {
config: configMethodStatic{
addresses: []net.IPNet{{IP: []byte{192, 168, 1, 100}, Mask: []byte{255, 255, 255, 0}}},
nameservers: []net.IP{[]byte{8, 8, 8, 8}},
routes: []route{route{destination: net.IPNet{IP: []byte{0, 0, 0, 0}, Mask: []byte{0, 0, 0, 0}}, gateway: []byte{1, 2, 3, 4}}},
routes: []route{{destination: net.IPNet{IP: []byte{0, 0, 0, 0}, Mask: []byte{0, 0, 0, 0}}, gateway: []byte{1, 2, 3, 4}}},
},
}},
},
@@ -152,7 +152,7 @@ func TestModprobeParams(t *testing.T) {
func TestBuildInterfacesLo(t *testing.T) {
stanzas := []*stanzaInterface{
&stanzaInterface{
{
name: "lo",
kind: interfacePhysical,
auto: false,
@@ -174,7 +174,7 @@ func TestBuildInterfacesBlindBond(t *testing.T) {
auto: false,
configMethod: configMethodManual{},
options: map[string][]string{
"bond-slaves": []string{"eth0"},
"bond-slaves": {"eth0"},
},
},
}
@@ -211,8 +211,8 @@ func TestBuildInterfacesBlindVLAN(t *testing.T) {
auto: false,
configMethod: configMethodManual{},
options: map[string][]string{
"id": []string{"0"},
"raw_device": []string{"eth0"},
"id": {"0"},
"raw_device": {"eth0"},
},
},
}
@@ -243,51 +243,51 @@ func TestBuildInterfacesBlindVLAN(t *testing.T) {
func TestBuildInterfaces(t *testing.T) {
stanzas := []*stanzaInterface{
&stanzaInterface{
{
name: "eth0",
kind: interfacePhysical,
auto: false,
configMethod: configMethodManual{},
options: map[string][]string{},
},
&stanzaInterface{
{
name: "bond0",
kind: interfaceBond,
auto: false,
configMethod: configMethodManual{},
options: map[string][]string{
"bond-slaves": []string{"eth0"},
"bond-mode": []string{"4"},
"bond-miimon": []string{"100"},
"bond-slaves": {"eth0"},
"bond-mode": {"4"},
"bond-miimon": {"100"},
},
},
&stanzaInterface{
{
name: "bond1",
kind: interfaceBond,
auto: false,
configMethod: configMethodManual{},
options: map[string][]string{
"bond-slaves": []string{"bond0"},
"bond-slaves": {"bond0"},
},
},
&stanzaInterface{
{
name: "vlan0",
kind: interfaceVLAN,
auto: false,
configMethod: configMethodManual{},
options: map[string][]string{
"id": []string{"0"},
"raw_device": []string{"eth0"},
"id": {"0"},
"raw_device": {"eth0"},
},
},
&stanzaInterface{
{
name: "vlan1",
kind: interfaceVLAN,
auto: false,
configMethod: configMethodManual{},
options: map[string][]string{
"id": []string{"1"},
"raw_device": []string{"bond0"},
"id": {"1"},
"raw_device": {"bond0"},
},
},
}

View File

@@ -151,7 +151,7 @@ func TestParseBondStanzaNoSlaves(t *testing.T) {
func TestParseBondStanza(t *testing.T) {
conf := configMethodManual{}
options := map[string][]string{
"bond-slaves": []string{"1", "2"},
"bond-slaves": {"1", "2"},
}
bond, err := parseBondStanza("test", conf, nil, options)
if err != nil {
@@ -171,8 +171,8 @@ func TestParseBondStanza(t *testing.T) {
func TestParsePhysicalStanza(t *testing.T) {
conf := configMethodManual{}
options := map[string][]string{
"a": []string{"1", "2"},
"b": []string{"1"},
"a": {"1", "2"},
"b": {"1"},
}
physical, err := parsePhysicalStanza("test", conf, nil, options)
if err != nil {

View File

@@ -82,11 +82,11 @@ func TestProcessVMwareNetconf(t *testing.T) {
hwaddr: mustParseMac(net.ParseMAC("00:11:22:33:44:55")),
config: configMethodStatic{
hwaddress: mustParseMac(net.ParseMAC("00:11:22:33:44:55")),
addresses: []net.IPNet{net.IPNet{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}},
addresses: []net.IPNet{{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}},
// I realize how upset you must be that I am shoving an IPMask into an IP. This is because net.IPv4zero is
// actually a magic IPv6 address which ruins our equality check. What's that? Just use IP::Equal()? I'd rather
// DeepEqual just handle that for me, but until Go gets operator overloading, we are stuck with this.
routes: []route{route{
routes: []route{{
destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)},
gateway: net.ParseIP("10.0.0.1")},
},
@@ -116,10 +116,10 @@ func TestProcessVMwareNetconf(t *testing.T) {
config: configMethodStatic{
hwaddress: mustParseMac(net.ParseMAC("00:11:22:33:44:55")),
addresses: []net.IPNet{
net.IPNet{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)},
net.IPNet{IP: net.ParseIP("10.0.0.101"), Mask: net.CIDRMask(24, net.IPv4len*8)},
{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)},
{IP: net.ParseIP("10.0.0.101"), Mask: net.CIDRMask(24, net.IPv4len*8)},
},
routes: []route{route{
routes: []route{{
destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)},
gateway: net.ParseIP("10.0.0.1")},
},
@@ -129,8 +129,8 @@ func TestProcessVMwareNetconf(t *testing.T) {
&physicalInterface{logicalInterface{
name: "eth0",
config: configMethodStatic{
addresses: []net.IPNet{net.IPNet{IP: net.ParseIP("10.0.1.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}},
routes: []route{route{
addresses: []net.IPNet{{IP: net.ParseIP("10.0.1.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}},
routes: []route{{
destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)},
gateway: net.ParseIP("10.0.1.1")},
},

View File

@@ -62,8 +62,8 @@ type HttpClient struct {
// Maximum number of connection retries. Defaults to 15
MaxRetries int
// Whether or not to skip TLS verification. Defaults to false
SkipTLS bool
// Headers to add to the request.
Header http.Header
client *http.Client
}
@@ -74,11 +74,15 @@ type Getter interface {
}
func NewHttpClient() *HttpClient {
return NewHttpClientHeader(nil)
}
func NewHttpClientHeader(header http.Header) *HttpClient {
hc := &HttpClient{
InitialBackoff: 50 * time.Millisecond,
MaxBackoff: time.Second * 5,
MaxRetries: 15,
SkipTLS: false,
Header: header,
client: &http.Client{
Timeout: 10 * time.Second,
},
@@ -139,7 +143,13 @@ func (h *HttpClient) GetRetry(rawurl string) ([]byte, error) {
}
func (h *HttpClient) Get(dataURL string) ([]byte, error) {
if resp, err := h.client.Get(dataURL); err == nil {
request, err := http.NewRequest("GET", dataURL, nil)
if err != nil {
return nil, err
}
request.Header = h.Header
if resp, err := h.client.Do(request); err == nil {
defer resp.Body.Close()
switch resp.StatusCode / 100 {
case HTTP_2xx:

View File

@@ -106,7 +106,7 @@ func WriteEnvFile(ef *EnvFile, root string) error {
// keys returns the keys of a map in sorted order
func keys(m map[string]string) (s []string) {
for k, _ := range m {
for k := range m {
s = append(s, k)
}
sort.Strings(s)

View File

@@ -45,17 +45,16 @@ func (f *File) Permissions() (os.FileMode, error) {
return os.FileMode(perm), nil
}
// WriteFile writes given endecoded file to the filesystem
func WriteFile(f *File, root string) (string, error) {
if f.Encoding != "" {
return "", fmt.Errorf("Unable to write file with encoding %s", f.Encoding)
}
fullpath := path.Join(root, f.Path)
dir := path.Dir(fullpath)
log.Printf("Writing file to %q", fullpath)
content, err := config.DecodeContent(f.Content, f.Encoding)
if err != nil {
return "", fmt.Errorf("Unable to decode %s (%v)", f.Path, err)
}
if err := EnsureDirectoryExists(dir); err != nil {
return "", err
}
@@ -71,7 +70,7 @@ func WriteFile(f *File, root string) (string, error) {
return "", err
}
if err := ioutil.WriteFile(tmp.Name(), content, perm); err != nil {
if err := ioutil.WriteFile(tmp.Name(), []byte(f.Content), perm); err != nil {
return "", err
}

View File

@@ -147,62 +147,6 @@ func TestWriteFilePermissions(t *testing.T) {
}
}
func TestWriteFileEncodedContent(t *testing.T) {
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
if err != nil {
t.Fatalf("Unable to create tempdir: %v", err)
}
defer os.RemoveAll(dir)
//all of these decode to "bar"
content_tests := map[string]string{
"base64": "YmFy",
"b64": "YmFy",
"gz": "\x1f\x8b\x08\x08w\x14\x87T\x02\xffok\x00KJ,\x02\x00\xaa\x8c\xffv\x03\x00\x00\x00",
"gzip": "\x1f\x8b\x08\x08w\x14\x87T\x02\xffok\x00KJ,\x02\x00\xaa\x8c\xffv\x03\x00\x00\x00",
"gz+base64": "H4sIABMVh1QAA0tKLAIAqoz/dgMAAAA=",
"gzip+base64": "H4sIABMVh1QAA0tKLAIAqoz/dgMAAAA=",
"gz+b64": "H4sIABMVh1QAA0tKLAIAqoz/dgMAAAA=",
"gzip+b64": "H4sIABMVh1QAA0tKLAIAqoz/dgMAAAA=",
}
for encoding, content := range content_tests {
fullPath := path.Join(dir, encoding)
wf := File{config.File{
Path: encoding,
Encoding: encoding,
Content: content,
RawFilePermissions: "0644",
}}
path, err := WriteFile(&wf, dir)
if err != nil {
t.Fatalf("Processing of WriteFile failed: %v", err)
} else if path != fullPath {
t.Fatalf("WriteFile returned bad path: want %s, got %s", fullPath, path)
}
fi, err := os.Stat(fullPath)
if err != nil {
t.Fatalf("Unable to stat file: %v", err)
}
if fi.Mode() != os.FileMode(0644) {
t.Errorf("File has incorrect mode: %v", fi.Mode())
}
contents, err := ioutil.ReadFile(fullPath)
if err != nil {
t.Fatalf("Unable to read expected file: %v", err)
}
if string(contents) != "bar" {
t.Fatalf("File has incorrect contents: '%s'", contents)
}
}
}
func TestWriteFileInvalidEncodedContent(t *testing.T) {
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
if err != nil {

View File

@@ -2,42 +2,26 @@
source ./build
SRC="
config
config/validate
datasource
datasource/configdrive
datasource/file
datasource/metadata
datasource/metadata/cloudsigma
datasource/metadata/digitalocean
datasource/metadata/ec2
datasource/proc_cmdline
datasource/test
datasource/url
datasource/vmware
datasource/waagent
initialize
network
pkg
system
.
"
SRC=$(find . -name '*.go' \
-not -path "./vendor/*")
PKG=$(cd gopath/src/${REPO_PATH}; go list ./... | \
grep --invert-match vendor)
echo "Checking gofix..."
go tool fix -diff $SRC
echo "Checking gofmt..."
gofmt -d -e $SRC
# split SRC into an array and prepend REPO_PATH to each local package for go vet
split_vet=(${SRC// / })
VET_TEST="${REPO_PATH} ${split_vet[@]/#/${REPO_PATH}/}"
res=$(gofmt -d -e -s $SRC)
echo "${res}"
if [ -n "${res}" ]; then
exit 1
fi
echo "Checking govet..."
go vet $VET_TEST
go vet $PKG
echo "Running tests..."
go test -timeout 60s -cover $@ ${VET_TEST} --race
go test -timeout 60s -cover $@ ${PKG} --race
echo "Success"

View File

@@ -4,8 +4,6 @@ Requires=system-config.target
After=system-config.target
# Watch for configs at a couple common paths
Requires=user-configdrive.path
After=user-configdrive.path
Requires=user-cloudinit@var-lib-coreos\x2dinstall-user_data.path
After=user-cloudinit@var-lib-coreos\x2dinstall-user_data.path

View File

@@ -1,10 +0,0 @@
[Unit]
Description=Watch for a cloud-config at /media/configdrive
# Note: This unit is essentially just here as a fall-back mechanism to
# trigger cloudinit if it isn't triggered explicitly by other means
# such as by a Wants= in the mount unit. This ensures we handle the
# case where /media/configdrive is provided to a CoreOS container.
[Path]
DirectoryNotEmpty=/media/configdrive

View File

@@ -0,0 +1,11 @@
# If you manipulate the contents of vendor/, amend this accordingly.
# pkg version
github.com/cloudsigma/cepgo 1bfc4895bf5c4d3b599f3f6ee142299488c8739b
github.com/coreos/go-systemd/dbus 4fbc5060a317b142e6c7bfbedb65596d5f0ab99b
github.com/coreos/yaml 6b16a5714269b2f70720a45406b1babd947a17ef
github.com/dotcloud/docker/pkg/netlink 55d41c3e21e1593b944c06196ffb2ac57ab7f653
github.com/guelfey/go.dbus f6a3a2366cc39b8479cadc499d3c735fb10fbdda
github.com/tarm/goserial cdabc8d44e8e84f58f18074ae44337e1f2f375b9
github.com/sigma/vmw-guestinfo 95dd4126d6e8b4ef1970b3f3fe2e8cdd470d2903
github.com/sigma/vmw-ovflib 56b4f44581cac03d17d8270158bdfd0942ffe790
github.com/sigma/bdoor babf2a4017b020d4ce04e8167076186e82645dd1

View File

@@ -47,7 +47,7 @@ The Fuze configuration is a YAML document conforming to the following specificat
* **_inline_** (string): the contents of the file.
* **_remote_** (object): options related to the fetching of remote file contents.
* **_compression_** (string): the type of compression used on the contents (null or gzip)
* **_source_** (string): the URL of the file contents. Supported schemes are http and [data][rfc2397]. Note: When using http, it is advisable to use the verification option to ensure the contents haven't been modified.
* **_url_** (string): the URL of the file contents. Supported schemes are http and [data][rfc2397]. Note: When using http, it is advisable to use the verification option to ensure the contents haven't been modified.
* **_verification_** (object): options related to the verification of the file contents.
* **_hash_** (object): the hash of the config
* **_function_** (string): the function used to hash the config. Supported functions are sha512.

View File

@@ -1,8 +1,27 @@
language: go
go: 1.4
sudo: required
services:
- docker
env:
global:
- GOPATH=/opt
- BUILD_DIR=/opt/src/github.com/coreos/go-systemd
matrix:
- DOCKER_BASE=ubuntu:16.04
- DOCKER_BASE=debian:stretch
before_install:
- 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 build-essential git golang dbus libsystemd-dev libpam-systemd && go get github.com/coreos/pkg/dlopen && go get github.com/godbus/dbus"
- docker commit `cat /tmp/cidfile` go-systemd/container-tests
- rm -f /tmp/cidfile
install:
- go get github.com/godbus/dbus
- docker run -d --cidfile=/tmp/cidfile --privileged -e GOPATH=${GOPATH} -v ${PWD}:${BUILD_DIR} go-systemd/container-tests /bin/systemd --system
script:
- ./test
- docker exec `cat /tmp/cidfile` /bin/bash -c "cd ${BUILD_DIR} && ./test"
after_script:
- docker kill `cat /tmp/cidfile`

View File

@@ -48,8 +48,6 @@ func TLSListeners(unsetEnv bool, tlsConfig *tls.Config) ([]net.Listener, error)
}
if tlsConfig != nil && err == nil {
tlsConfig.NextProtos = []string{"http/1.1"}
for i, l := range listeners {
// Activate TLS only for TCP sockets
if l.Addr().Network() == "tcp" {

View File

@@ -2,30 +2,37 @@
package daemon
import (
"errors"
"net"
"os"
)
var SdNotifyNoSocket = errors.New("No socket")
// SdNotify sends a message to the init daemon. It is common to ignore the error.
func SdNotify(state string) error {
// 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(state string) (sent bool, err error) {
socketAddr := &net.UnixAddr{
Name: os.Getenv("NOTIFY_SOCKET"),
Net: "unixgram",
}
// NOTIFY_SOCKET not set
if socketAddr.Name == "" {
return SdNotifyNoSocket
return false, nil
}
conn, err := net.DialUnix(socketAddr.Net, nil, socketAddr)
// Error connecting to NOTIFY_SOCKET
if err != nil {
return err
return false, err
}
defer conn.Close()
_, err = conn.Write([]byte(state))
return err
// Error sending the message
if err != nil {
return false, err
}
return true, nil
}

View File

@@ -0,0 +1,75 @@
// 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 (
"io/ioutil"
"net"
"os"
"testing"
)
// TestSdNotify
func TestSdNotify(t *testing.T) {
notificationSupportedDataSent := "Notification supported, data sent"
notificationSupportedFailure := "Notification supported, but failure happened"
notificationNotSupported := "Notification not supported"
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)
}
// (true, nil) - notification supported, data has been sent
e = os.Setenv("NOTIFY_SOCKET", notifySocket)
if e != nil {
panic(e)
}
sent, err := SdNotify(notificationSupportedDataSent)
if !sent || err != nil {
t.Errorf("TEST: %s FAILED", notificationSupportedDataSent)
}
// (false, err) - notification supported, but failure happened
e = os.Setenv("NOTIFY_SOCKET", testDir+"/not-exist.sock")
if e != nil {
panic(e)
}
sent, err = SdNotify(notificationSupportedFailure)
if sent && err == nil {
t.Errorf("TEST: %s FAILED", notificationSupportedFailure)
}
// (false, nil) - notification not supported
e = os.Unsetenv("NOTIFY_SOCKET")
if e != nil {
panic(e)
}
sent, err = SdNotify(notificationNotSupported)
if sent || err != nil {
t.Errorf("TEST: %s FAILED", notificationNotSupported)
}
}

View File

@@ -86,7 +86,7 @@ type Conn struct {
// New establishes a connection to the system bus and authenticates.
// Callers should call Close() when done with the connection.
func New() (*Conn, error) {
return newConnection(func() (*dbus.Conn, error) {
return NewConnection(func() (*dbus.Conn, error) {
return dbusAuthHelloConnection(dbus.SystemBusPrivate)
})
}
@@ -95,7 +95,7 @@ func New() (*Conn, error) {
// 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 NewConnection(func() (*dbus.Conn, error) {
return dbusAuthHelloConnection(dbus.SessionBusPrivate)
})
}
@@ -104,7 +104,7 @@ func NewUserConnection() (*Conn, error) {
// 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) {
return NewConnection(func() (*dbus.Conn, error) {
// We skip Hello when talking directly to systemd.
return dbusAuthConnection(func() (*dbus.Conn, error) {
return dbus.Dial("unix:path=/run/systemd/private")
@@ -118,13 +118,18 @@ func (c *Conn) Close() {
c.sigconn.Close()
}
func newConnection(createBus func() (*dbus.Conn, error)) (*Conn, error) {
sysconn, err := createBus()
// 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 := createBus()
sigconn, err := dialBus()
if err != nil {
sysconn.Close()
return nil, err

View File

@@ -199,6 +199,11 @@ func (c *Conn) GetUnitProperty(unit string, propertyName string) (*Property, err
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
@@ -234,12 +239,11 @@ type UnitStatus struct {
JobPath dbus.ObjectPath // The job object path
}
// 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.
func (c *Conn) ListUnits() ([]UnitStatus, error) {
type storeFunc func(retvalues ...interface{}) error
func (c *Conn) listUnitsInternal(f storeFunc) ([]UnitStatus, error) {
result := make([][]interface{}, 0)
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnits", 0).Store(&result)
err := f(&result)
if err != nil {
return nil, err
}
@@ -263,15 +267,43 @@ func (c *Conn) ListUnits() ([]UnitStatus, error) {
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.
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.
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
}
// ListUnitFiles returns an array of all available units on disk.
func (c *Conn) ListUnitFiles() ([]UnitFile, error) {
func (c *Conn) listUnitFilesInternal(f storeFunc) ([]UnitFile, error) {
result := make([][]interface{}, 0)
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitFiles", 0).Store(&result)
err := f(&result)
if err != nil {
return nil, err
}
@@ -295,6 +327,16 @@ func (c *Conn) ListUnitFiles() ([]UnitFile, error) {
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
@@ -431,6 +473,87 @@ type DisableUnitFileChange struct {
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 {

View File

@@ -18,6 +18,8 @@ import (
"fmt"
"math/rand"
"os"
"os/exec"
"path"
"path/filepath"
"reflect"
"testing"
@@ -70,6 +72,24 @@ func linkUnit(target string, conn *Conn, t *testing.T) {
}
}
func getUnitStatus(units []UnitStatus, name string) *UnitStatus {
for _, u := range units {
if u.Name == name {
return &u
}
}
return nil
}
func getUnitFile(units []UnitFile, name string) *UnitFile {
for _, u := range units {
if path.Base(u.Path) == name {
return &u
}
}
return nil
}
// Ensure that basic unit starting and stopping works.
func TestStartStopUnit(t *testing.T) {
target := "start-stop.service"
@@ -92,18 +112,11 @@ func TestStartStopUnit(t *testing.T) {
units, err := conn.ListUnits()
var unit *UnitStatus
for _, u := range units {
if u.Name == target {
unit = &u
}
}
unit := getUnitStatus(units, target)
if unit == nil {
t.Fatalf("Test unit not found in list")
}
if unit.ActiveState != "active" {
} else if unit.ActiveState != "active" {
t.Fatalf("Test unit not active")
}
@@ -118,18 +131,169 @@ func TestStartStopUnit(t *testing.T) {
units, err = conn.ListUnits()
unit = nil
for _, u := range units {
if u.Name == target {
unit = &u
}
}
unit = getUnitStatus(units, target)
if unit != nil {
t.Fatalf("Test unit found in list, should be stopped")
}
}
// Ensure that ListUnitsByNames works.
func TestListUnitsByNames(t *testing.T) {
target1 := "systemd-journald.service"
target2 := "unexisting.service"
conn := setupConn(t)
units, err := conn.ListUnitsByNames([]string{target1, target2})
if err != nil {
t.Skip(err)
}
unit := getUnitStatus(units, target1)
if unit == nil {
t.Fatalf("%s unit not found in list", target1)
} else if unit.ActiveState != "active" {
t.Fatalf("%s unit should be active but it is %s", target1, unit.ActiveState)
}
unit = getUnitStatus(units, target2)
if unit == nil {
t.Fatalf("Unexisting test unit not found in list")
} else if unit.ActiveState != "inactive" {
t.Fatalf("Test unit should be inactive")
}
}
// Ensure that ListUnitsByPatterns works.
func TestListUnitsByPatterns(t *testing.T) {
target1 := "systemd-journald.service"
target2 := "unexisting.service"
conn := setupConn(t)
units, err := conn.ListUnitsByPatterns([]string{}, []string{"systemd-journald*", target2})
if err != nil {
t.Skip(err)
}
unit := getUnitStatus(units, target1)
if unit == nil {
t.Fatalf("%s unit not found in list", target1)
} else if unit.ActiveState != "active" {
t.Fatalf("Test unit should be active")
}
unit = getUnitStatus(units, target2)
if unit != nil {
t.Fatalf("Unexisting test unit found in list")
}
}
// Ensure that ListUnitsFiltered works.
func TestListUnitsFiltered(t *testing.T) {
target := "systemd-journald.service"
conn := setupConn(t)
units, err := conn.ListUnitsFiltered([]string{"active"})
if err != nil {
t.Fatal(err)
}
unit := getUnitStatus(units, target)
if unit == nil {
t.Fatalf("%s unit not found in list", target)
} else if unit.ActiveState != "active" {
t.Fatalf("Test unit should be active")
}
units, err = conn.ListUnitsFiltered([]string{"inactive"})
if err != nil {
t.Fatal(err)
}
unit = getUnitStatus(units, target)
if unit != nil {
t.Fatalf("Inactive unit should not be found in list")
}
}
// Ensure that ListUnitFilesByPatterns works.
func TestListUnitFilesByPatterns(t *testing.T) {
target1 := "systemd-journald.service"
target2 := "exit.target"
conn := setupConn(t)
units, err := conn.ListUnitFilesByPatterns([]string{"static"}, []string{"systemd-journald*", target2})
if err != nil {
t.Skip(err)
}
unit := getUnitFile(units, target1)
if unit == nil {
t.Fatalf("%s unit not found in list", target1)
} else if unit.Type != "static" {
t.Fatalf("Test unit file should be static")
}
units, err = conn.ListUnitFilesByPatterns([]string{"disabled"}, []string{"systemd-journald*", target2})
if err != nil {
t.Fatal(err)
}
unit = getUnitFile(units, target2)
if unit == nil {
t.Fatalf("%s unit not found in list", target2)
} else if unit.Type != "disabled" {
t.Fatalf("%s unit file should be disabled", target2)
}
}
func TestListUnitFiles(t *testing.T) {
target1 := "systemd-journald.service"
target2 := "exit.target"
conn := setupConn(t)
units, err := conn.ListUnitFiles()
if err != nil {
t.Fatal(err)
}
unit := getUnitFile(units, target1)
if unit == nil {
t.Fatalf("%s unit not found in list", target1)
} else if unit.Type != "static" {
t.Fatalf("Test unit file should be static")
}
unit = getUnitFile(units, target2)
if unit == nil {
t.Fatalf("%s unit not found in list", target2)
} else if unit.Type != "disabled" {
t.Fatalf("%s unit file should be disabled", target2)
}
}
// Enables a unit and then immediately tears it down
func TestEnableDisableUnit(t *testing.T) {
target := "enable-disable.service"
@@ -146,7 +310,7 @@ func TestEnableDisableUnit(t *testing.T) {
}
if install != false {
t.Fatal("Install was true")
t.Log("Install was true")
}
if len(changes) < 1 {
@@ -158,7 +322,7 @@ func TestEnableDisableUnit(t *testing.T) {
}
// 2. Disable the unit
dChanges, err := conn.DisableUnitFiles([]string{abs}, true)
dChanges, err := conn.DisableUnitFiles([]string{target}, true)
if err != nil {
t.Fatal(err)
}
@@ -186,27 +350,19 @@ func TestGetUnitProperties(t *testing.T) {
t.Fatal(err)
}
names := info["Wants"].([]string)
desc, _ := info["Description"].(string)
if len(names) < 1 {
t.Fatal("/ is unwanted")
}
if names[0] != "system.slice" {
t.Fatal("unexpected wants for /")
}
prop, err := conn.GetUnitProperty(unit, "Wants")
prop, err := conn.GetUnitProperty(unit, "Description")
if err != nil {
t.Fatal(err)
}
if prop.Name != "Wants" {
if prop.Name != "Description" {
t.Fatal("unexpected property name")
}
val := prop.Value.Value().([]string)
if !reflect.DeepEqual(val, names) {
val := prop.Value.Value().(string)
if !reflect.DeepEqual(val, desc) {
t.Fatal("unexpected property value")
}
}
@@ -230,13 +386,34 @@ func TestGetUnitPropertiesRejectsInvalidName(t *testing.T) {
}
}
// TestSetUnitProperties changes a cgroup setting on the `tmp.mount`
// TestGetServiceProperty reads the `systemd-udevd.service` which should exist
// on all systemd systems and ensures that one of its property is valid.
func TestGetServiceProperty(t *testing.T) {
conn := setupConn(t)
service := "systemd-udevd.service"
prop, err := conn.GetServiceProperty(service, "Type")
if err != nil {
t.Fatal(err)
}
if prop.Name != "Type" {
t.Fatal("unexpected property name")
}
if _, ok := prop.Value.Value().(string); !ok {
t.Fatal("invalid property value")
}
}
// TestSetUnitProperties changes a cgroup setting on the `-.mount`
// which should exist on all systemd systems and ensures that the
// property was set.
func TestSetUnitProperties(t *testing.T) {
conn := setupConn(t)
unit := "tmp.mount"
unit := "-.mount"
if err := conn.SetUnitProperties(unit, true, Property{"CPUShares", dbus.MakeVariant(uint64(1023))}); err != nil {
t.Fatal(err)
@@ -247,7 +424,7 @@ func TestSetUnitProperties(t *testing.T) {
t.Fatal(err)
}
value := info["CPUShares"].(uint64)
value, _ := info["CPUShares"].(uint64)
if value != 1023 {
t.Fatal("CPUShares of unit is not 1023:", value)
}
@@ -276,18 +453,11 @@ func TestStartStopTransientUnit(t *testing.T) {
units, err := conn.ListUnits()
var unit *UnitStatus
for _, u := range units {
if u.Name == target {
unit = &u
}
}
unit := getUnitStatus(units, target)
if unit == nil {
t.Fatalf("Test unit not found in list")
}
if unit.ActiveState != "active" {
} else if unit.ActiveState != "active" {
t.Fatalf("Test unit not active")
}
@@ -302,18 +472,57 @@ func TestStartStopTransientUnit(t *testing.T) {
units, err = conn.ListUnits()
unit = nil
for _, u := range units {
if u.Name == target {
unit = &u
}
}
unit = getUnitStatus(units, target)
if unit != nil {
t.Fatalf("Test unit found in list, should be stopped")
}
}
// Ensure that putting running programs into scopes works
func TestStartStopTransientScope(t *testing.T) {
conn := setupConn(t)
cmd := exec.Command("/bin/sleep", "400")
err := cmd.Start()
if err != nil {
t.Fatal(err)
}
defer cmd.Process.Kill()
props := []Property{
PropPids(uint32(cmd.Process.Pid)),
}
target := fmt.Sprintf("testing-transient-%d.scope", cmd.Process.Pid)
// Start the unit
reschan := make(chan string)
_, err = conn.StartTransientUnit(target, "replace", props, reschan)
if err != nil {
t.Fatal(err)
}
job := <-reschan
if job != "done" {
t.Fatal("Job is not done:", job)
}
units, err := conn.ListUnits()
unit := getUnitStatus(units, target)
if unit == nil {
t.Fatalf("Test unit not found in list")
} else if unit.ActiveState != "active" {
t.Fatalf("Test unit not active")
}
// maybe check if pid is really a member of the just created scope
// systemd uses the following api which does not use dbus, but directly
// accesses procfs for cgroup information.
// int sd_pid_get_unit(pid_t pid, char **session)
}
func TestConnJobListener(t *testing.T) {
target := "start-stop.service"
conn := setupConn(t)
@@ -343,3 +552,56 @@ func TestConnJobListener(t *testing.T) {
t.Fatal("JobListener jobs leaked")
}
}
// Enables a unit and then masks/unmasks it
func TestMaskUnmask(t *testing.T) {
target := "mask-unmask.service"
conn := setupConn(t)
setupUnit(target, conn, t)
abs := findFixture(target, t)
runPath := filepath.Join("/run/systemd/system/", target)
// 1. Enable the unit
install, changes, err := conn.EnableUnitFiles([]string{abs}, true, true)
if err != nil {
t.Fatal(err)
}
if install != false {
t.Log("Install was true")
}
if len(changes) < 1 {
t.Fatalf("Expected one change, got %v", changes)
}
if changes[0].Filename != runPath {
t.Fatal("Unexpected target filename")
}
// 2. Mask the unit
mChanges, err := conn.MaskUnitFiles([]string{target}, true, true)
if err != nil {
t.Fatal(err)
}
if mChanges[0].Filename != runPath {
t.Fatalf("Change should include correct filename, %+v", mChanges[0])
}
if mChanges[0].Destination != "" {
t.Fatalf("Change destination should be empty, %+v", mChanges[0])
}
// 3. Unmask the unit
uChanges, err := conn.UnmaskUnitFiles([]string{target}, true)
if err != nil {
t.Fatal(err)
}
if uChanges[0].Filename != runPath {
t.Fatalf("Change should include correct filename, %+v", uChanges[0])
}
if uChanges[0].Destination != "" {
t.Fatalf("Change destination should be empty, %+v", uChanges[0])
}
}

View File

@@ -216,3 +216,13 @@ func PropSlice(slice string) Property {
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),
}
}

View File

@@ -12,6 +12,8 @@
// 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

View File

@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build ignore
package main
import (

View File

@@ -12,6 +12,8 @@
// 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

View File

@@ -12,6 +12,8 @@
// 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

View File

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

View File

@@ -0,0 +1,66 @@
// Copyright 2015 RedHat, Inc.
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sdjournal
import (
"github.com/coreos/pkg/dlopen"
"sync"
"unsafe"
)
var (
// lazy initialized
libsystemdHandle *dlopen.LibHandle
libsystemdMutex = &sync.Mutex{}
libsystemdFunctions = map[string]unsafe.Pointer{}
libsystemdNames = []string{
// systemd < 209
"libsystemd-journal.so.0",
"libsystemd-journal.so",
// systemd >= 209 merged libsystemd-journal into libsystemd proper
"libsystemd.so.0",
"libsystemd.so",
}
)
func getFunction(name string) (unsafe.Pointer, error) {
libsystemdMutex.Lock()
defer libsystemdMutex.Unlock()
if libsystemdHandle == nil {
h, err := dlopen.GetHandle(libsystemdNames)
if err != nil {
return nil, err
}
libsystemdHandle = h
}
f, ok := libsystemdFunctions[name]
if !ok {
var err error
f, err = libsystemdHandle.GetSymbolPointer(name)
if err != nil {
return nil, err
}
libsystemdFunctions[name] = f
}
return f, nil
}

View File

@@ -0,0 +1,36 @@
// Copyright 2015 RedHat, Inc.
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sdjournal
import "testing"
func TestGetFunction(t *testing.T) {
f, err := getFunction("sd_journal_open")
if err != nil {
t.Errorf("Error getting an existing function: %s", err)
}
if f == nil {
t.Error("Got nil function pointer")
}
_, err = getFunction("non_existent_function")
if err == nil {
t.Error("Expected to get an error, got nil")
}
}

View File

@@ -24,18 +24,244 @@
// [1] http://www.freedesktop.org/software/systemd/man/sd-journal.html
package sdjournal
/*
#cgo pkg-config: libsystemd
#include <systemd/sd-journal.h>
#include <stdlib.h>
#include <syslog.h>
*/
// #include <systemd/sd-journal.h>
// #include <systemd/sd-id128.h>
// #include <stdlib.h>
// #include <syslog.h>
//
// int
// my_sd_journal_open(void *f, sd_journal **ret, int flags)
// {
// int (*sd_journal_open)(sd_journal **, int);
//
// sd_journal_open = f;
// return sd_journal_open(ret, flags);
// }
//
// int
// my_sd_journal_open_directory(void *f, sd_journal **ret, const char *path, int flags)
// {
// int (*sd_journal_open_directory)(sd_journal **, const char *, int);
//
// sd_journal_open_directory = f;
// return sd_journal_open_directory(ret, path, flags);
// }
//
// void
// my_sd_journal_close(void *f, sd_journal *j)
// {
// int (*sd_journal_close)(sd_journal *);
//
// sd_journal_close = f;
// sd_journal_close(j);
// }
//
// int
// my_sd_journal_get_usage(void *f, sd_journal *j, uint64_t *bytes)
// {
// int (*sd_journal_get_usage)(sd_journal *, uint64_t *);
//
// sd_journal_get_usage = f;
// return sd_journal_get_usage(j, bytes);
// }
//
// int
// my_sd_journal_add_match(void *f, sd_journal *j, const void *data, size_t size)
// {
// int (*sd_journal_add_match)(sd_journal *, const void *, size_t);
//
// sd_journal_add_match = f;
// return sd_journal_add_match(j, data, size);
// }
//
// int
// my_sd_journal_add_disjunction(void *f, sd_journal *j)
// {
// int (*sd_journal_add_disjunction)(sd_journal *);
//
// sd_journal_add_disjunction = f;
// return sd_journal_add_disjunction(j);
// }
//
// int
// my_sd_journal_add_conjunction(void *f, sd_journal *j)
// {
// int (*sd_journal_add_conjunction)(sd_journal *);
//
// sd_journal_add_conjunction = f;
// return sd_journal_add_conjunction(j);
// }
//
// void
// my_sd_journal_flush_matches(void *f, sd_journal *j)
// {
// int (*sd_journal_flush_matches)(sd_journal *);
//
// sd_journal_flush_matches = f;
// sd_journal_flush_matches(j);
// }
//
// int
// my_sd_journal_next(void *f, sd_journal *j)
// {
// int (*sd_journal_next)(sd_journal *);
//
// sd_journal_next = f;
// return sd_journal_next(j);
// }
//
// int
// my_sd_journal_next_skip(void *f, sd_journal *j, uint64_t skip)
// {
// int (*sd_journal_next_skip)(sd_journal *, uint64_t);
//
// sd_journal_next_skip = f;
// return sd_journal_next_skip(j, skip);
// }
//
// int
// my_sd_journal_previous(void *f, sd_journal *j)
// {
// int (*sd_journal_previous)(sd_journal *);
//
// sd_journal_previous = f;
// return sd_journal_previous(j);
// }
//
// int
// my_sd_journal_previous_skip(void *f, sd_journal *j, uint64_t skip)
// {
// int (*sd_journal_previous_skip)(sd_journal *, uint64_t);
//
// sd_journal_previous_skip = f;
// return sd_journal_previous_skip(j, skip);
// }
//
// int
// my_sd_journal_get_data(void *f, sd_journal *j, const char *field, const void **data, size_t *length)
// {
// int (*sd_journal_get_data)(sd_journal *, const char *, const void **, size_t *);
//
// sd_journal_get_data = f;
// return sd_journal_get_data(j, field, data, length);
// }
//
// int
// my_sd_journal_set_data_threshold(void *f, sd_journal *j, size_t sz)
// {
// int (*sd_journal_set_data_threshold)(sd_journal *, size_t);
//
// sd_journal_set_data_threshold = f;
// return sd_journal_set_data_threshold(j, sz);
// }
//
// int
// my_sd_journal_get_cursor(void *f, sd_journal *j, char **cursor)
// {
// int (*sd_journal_get_cursor)(sd_journal *, char **);
//
// sd_journal_get_cursor = f;
// return sd_journal_get_cursor(j, cursor);
// }
//
// int
// my_sd_journal_test_cursor(void *f, sd_journal *j, const char *cursor)
// {
// int (*sd_journal_test_cursor)(sd_journal *, const char *);
//
// sd_journal_test_cursor = f;
// return sd_journal_test_cursor(j, cursor);
// }
//
// int
// my_sd_journal_get_realtime_usec(void *f, sd_journal *j, uint64_t *usec)
// {
// int (*sd_journal_get_realtime_usec)(sd_journal *, uint64_t *);
//
// sd_journal_get_realtime_usec = f;
// return sd_journal_get_realtime_usec(j, usec);
// }
//
// int
// my_sd_journal_get_monotonic_usec(void *f, sd_journal *j, uint64_t *usec, sd_id128_t *boot_id)
// {
// int (*sd_journal_get_monotonic_usec)(sd_journal *, uint64_t *, sd_id128_t *);
//
// sd_journal_get_monotonic_usec = f;
// return sd_journal_get_monotonic_usec(j, usec, boot_id);
// }
//
// int
// my_sd_journal_seek_head(void *f, sd_journal *j)
// {
// int (*sd_journal_seek_head)(sd_journal *);
//
// sd_journal_seek_head = f;
// return sd_journal_seek_head(j);
// }
//
// int
// my_sd_journal_seek_tail(void *f, sd_journal *j)
// {
// int (*sd_journal_seek_tail)(sd_journal *);
//
// sd_journal_seek_tail = f;
// return sd_journal_seek_tail(j);
// }
//
//
// int
// my_sd_journal_seek_cursor(void *f, sd_journal *j, const char *cursor)
// {
// int (*sd_journal_seek_cursor)(sd_journal *, const char *);
//
// sd_journal_seek_cursor = f;
// return sd_journal_seek_cursor(j, cursor);
// }
//
// int
// my_sd_journal_seek_realtime_usec(void *f, sd_journal *j, uint64_t usec)
// {
// int (*sd_journal_seek_realtime_usec)(sd_journal *, uint64_t);
//
// sd_journal_seek_realtime_usec = f;
// return sd_journal_seek_realtime_usec(j, usec);
// }
//
// int
// my_sd_journal_wait(void *f, sd_journal *j, uint64_t timeout_usec)
// {
// int (*sd_journal_wait)(sd_journal *, uint64_t);
//
// sd_journal_wait = f;
// return sd_journal_wait(j, timeout_usec);
// }
//
// void
// my_sd_journal_restart_data(void *f, sd_journal *j)
// {
// void (*sd_journal_restart_data)(sd_journal *);
//
// sd_journal_restart_data = f;
// sd_journal_restart_data(j);
// }
//
// int
// my_sd_journal_enumerate_data(void *f, sd_journal *j, const void **data, size_t *length)
// {
// int (*sd_journal_enumerate_data)(sd_journal *, const void **, size_t *);
//
// sd_journal_enumerate_data = f;
// return sd_journal_enumerate_data(j, data, length);
// }
//
import "C"
import (
"bytes"
"fmt"
"path/filepath"
"strings"
"sync"
"syscall"
"time"
"unsafe"
)
@@ -43,13 +269,45 @@ import (
// Journal entry field strings which correspond to:
// http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
const (
SD_JOURNAL_FIELD_SYSTEMD_UNIT = "_SYSTEMD_UNIT"
SD_JOURNAL_FIELD_MESSAGE = "MESSAGE"
SD_JOURNAL_FIELD_PID = "_PID"
SD_JOURNAL_FIELD_UID = "_UID"
SD_JOURNAL_FIELD_GID = "_GID"
SD_JOURNAL_FIELD_HOSTNAME = "_HOSTNAME"
SD_JOURNAL_FIELD_MACHINE_ID = "_MACHINE_ID"
// User Journal Fields
SD_JOURNAL_FIELD_MESSAGE = "MESSAGE"
SD_JOURNAL_FIELD_MESSAGE_ID = "MESSAGE_ID"
SD_JOURNAL_FIELD_PRIORITY = "PRIORITY"
SD_JOURNAL_FIELD_CODE_FILE = "CODE_FILE"
SD_JOURNAL_FIELD_CODE_LINE = "CODE_LINE"
SD_JOURNAL_FIELD_CODE_FUNC = "CODE_FUNC"
SD_JOURNAL_FIELD_ERRNO = "ERRNO"
SD_JOURNAL_FIELD_SYSLOG_FACILITY = "SYSLOG_FACILITY"
SD_JOURNAL_FIELD_SYSLOG_IDENTIFIER = "SYSLOG_IDENTIFIER"
SD_JOURNAL_FIELD_SYSLOG_PID = "SYSLOG_PID"
// Trusted Journal Fields
SD_JOURNAL_FIELD_PID = "_PID"
SD_JOURNAL_FIELD_UID = "_UID"
SD_JOURNAL_FIELD_GID = "_GID"
SD_JOURNAL_FIELD_COMM = "_COMM"
SD_JOURNAL_FIELD_EXE = "_EXE"
SD_JOURNAL_FIELD_CMDLINE = "_CMDLINE"
SD_JOURNAL_FIELD_CAP_EFFECTIVE = "_CAP_EFFECTIVE"
SD_JOURNAL_FIELD_AUDIT_SESSION = "_AUDIT_SESSION"
SD_JOURNAL_FIELD_AUDIT_LOGINUID = "_AUDIT_LOGINUID"
SD_JOURNAL_FIELD_SYSTEMD_CGROUP = "_SYSTEMD_CGROUP"
SD_JOURNAL_FIELD_SYSTEMD_SESSION = "_SYSTEMD_SESSION"
SD_JOURNAL_FIELD_SYSTEMD_UNIT = "_SYSTEMD_UNIT"
SD_JOURNAL_FIELD_SYSTEMD_USER_UNIT = "_SYSTEMD_USER_UNIT"
SD_JOURNAL_FIELD_SYSTEMD_OWNER_UID = "_SYSTEMD_OWNER_UID"
SD_JOURNAL_FIELD_SYSTEMD_SLICE = "_SYSTEMD_SLICE"
SD_JOURNAL_FIELD_SELINUX_CONTEXT = "_SELINUX_CONTEXT"
SD_JOURNAL_FIELD_SOURCE_REALTIME_TIMESTAMP = "_SOURCE_REALTIME_TIMESTAMP"
SD_JOURNAL_FIELD_BOOT_ID = "_BOOT_ID"
SD_JOURNAL_FIELD_MACHINE_ID = "_MACHINE_ID"
SD_JOURNAL_FIELD_HOSTNAME = "_HOSTNAME"
SD_JOURNAL_FIELD_TRANSPORT = "_TRANSPORT"
// Address Fields
SD_JOURNAL_FIELD_CURSOR = "__CURSOR"
SD_JOURNAL_FIELD_REALTIME_TIMESTAMP = "__REALTIME_TIMESTAMP"
SD_JOURNAL_FIELD_MONOTONIC_TIMESTAMP = "__MONOTONIC_TIMESTAMP"
)
// Journal event constants
@@ -73,6 +331,14 @@ type Journal struct {
mu sync.Mutex
}
// JournalEntry represents all fields of a journal entry plus address fields.
type JournalEntry struct {
Fields map[string]string
Cursor string
RealtimeTimestamp uint64
MonotonicTimestamp uint64
}
// Match is a convenience wrapper to describe filters supplied to AddMatch.
type Match struct {
Field string
@@ -85,12 +351,18 @@ func (m *Match) String() string {
}
// NewJournal returns a new Journal instance pointing to the local journal
func NewJournal() (*Journal, error) {
j := &Journal{}
r := C.sd_journal_open(&j.cjournal, C.SD_JOURNAL_LOCAL_ONLY)
func NewJournal() (j *Journal, err error) {
j = &Journal{}
sd_journal_open, err := getFunction("sd_journal_open")
if err != nil {
return nil, err
}
r := C.my_sd_journal_open(sd_journal_open, &j.cjournal, C.SD_JOURNAL_LOCAL_ONLY)
if r < 0 {
return nil, fmt.Errorf("failed to open journal: %d", r)
return nil, fmt.Errorf("failed to open journal: %d", syscall.Errno(-r))
}
return j, nil
@@ -99,8 +371,10 @@ func NewJournal() (*Journal, error) {
// NewJournalFromDir returns a new Journal instance pointing to a journal residing
// in a given directory. The supplied path may be relative or absolute; if
// relative, it will be converted to an absolute path before being opened.
func NewJournalFromDir(path string) (*Journal, error) {
path, err := filepath.Abs(path)
func NewJournalFromDir(path string) (j *Journal, err error) {
j = &Journal{}
sd_journal_open_directory, err := getFunction("sd_journal_open_directory")
if err != nil {
return nil, err
}
@@ -108,10 +382,9 @@ func NewJournalFromDir(path string) (*Journal, error) {
p := C.CString(path)
defer C.free(unsafe.Pointer(p))
j := &Journal{}
r := C.sd_journal_open_directory(&j.cjournal, p, 0)
r := C.my_sd_journal_open_directory(sd_journal_open_directory, &j.cjournal, p, 0)
if r < 0 {
return nil, fmt.Errorf("failed to open journal in directory %q: %d", path, r)
return nil, fmt.Errorf("failed to open journal in directory %q: %d", path, syscall.Errno(-r))
}
return j, nil
@@ -119,8 +392,13 @@ func NewJournalFromDir(path string) (*Journal, error) {
// Close closes a journal opened with NewJournal.
func (j *Journal) Close() error {
sd_journal_close, err := getFunction("sd_journal_close")
if err != nil {
return err
}
j.mu.Lock()
C.sd_journal_close(j.cjournal)
C.my_sd_journal_close(sd_journal_close, j.cjournal)
j.mu.Unlock()
return nil
@@ -128,15 +406,20 @@ func (j *Journal) Close() error {
// AddMatch adds a match by which to filter the entries of the journal.
func (j *Journal) AddMatch(match string) error {
sd_journal_add_match, err := getFunction("sd_journal_add_match")
if err != nil {
return err
}
m := C.CString(match)
defer C.free(unsafe.Pointer(m))
j.mu.Lock()
r := C.sd_journal_add_match(j.cjournal, unsafe.Pointer(m), C.size_t(len(match)))
r := C.my_sd_journal_add_match(sd_journal_add_match, j.cjournal, unsafe.Pointer(m), C.size_t(len(match)))
j.mu.Unlock()
if r < 0 {
return fmt.Errorf("failed to add match: %d", r)
return fmt.Errorf("failed to add match: %d", syscall.Errno(-r))
}
return nil
@@ -144,12 +427,17 @@ func (j *Journal) AddMatch(match string) error {
// AddDisjunction inserts a logical OR in the match list.
func (j *Journal) AddDisjunction() error {
sd_journal_add_disjunction, err := getFunction("sd_journal_add_disjunction")
if err != nil {
return err
}
j.mu.Lock()
r := C.sd_journal_add_disjunction(j.cjournal)
r := C.my_sd_journal_add_disjunction(sd_journal_add_disjunction, j.cjournal)
j.mu.Unlock()
if r < 0 {
return fmt.Errorf("failed to add a disjunction in the match list: %d", r)
return fmt.Errorf("failed to add a disjunction in the match list: %d", syscall.Errno(-r))
}
return nil
@@ -157,12 +445,17 @@ func (j *Journal) AddDisjunction() error {
// AddConjunction inserts a logical AND in the match list.
func (j *Journal) AddConjunction() error {
sd_journal_add_conjunction, err := getFunction("sd_journal_add_conjunction")
if err != nil {
return err
}
j.mu.Lock()
r := C.sd_journal_add_conjunction(j.cjournal)
r := C.my_sd_journal_add_conjunction(sd_journal_add_conjunction, j.cjournal)
j.mu.Unlock()
if r < 0 {
return fmt.Errorf("failed to add a conjunction in the match list: %d", r)
return fmt.Errorf("failed to add a conjunction in the match list: %d", syscall.Errno(-r))
}
return nil
@@ -170,19 +463,29 @@ func (j *Journal) AddConjunction() error {
// FlushMatches flushes all matches, disjunctions and conjunctions.
func (j *Journal) FlushMatches() {
sd_journal_flush_matches, err := getFunction("sd_journal_flush_matches")
if err != nil {
return
}
j.mu.Lock()
C.sd_journal_flush_matches(j.cjournal)
C.my_sd_journal_flush_matches(sd_journal_flush_matches, j.cjournal)
j.mu.Unlock()
}
// Next advances the read pointer into the journal by one entry.
func (j *Journal) Next() (int, error) {
sd_journal_next, err := getFunction("sd_journal_next")
if err != nil {
return -1, err
}
j.mu.Lock()
r := C.sd_journal_next(j.cjournal)
r := C.my_sd_journal_next(sd_journal_next, j.cjournal)
j.mu.Unlock()
if r < 0 {
return int(r), fmt.Errorf("failed to iterate journal: %d", r)
return int(r), fmt.Errorf("failed to iterate journal: %d", syscall.Errno(-r))
}
return int(r), nil
@@ -191,12 +494,17 @@ func (j *Journal) Next() (int, error) {
// NextSkip advances the read pointer by multiple entries at once,
// as specified by the skip parameter.
func (j *Journal) NextSkip(skip uint64) (uint64, error) {
sd_journal_next_skip, err := getFunction("sd_journal_next_skip")
if err != nil {
return 0, err
}
j.mu.Lock()
r := C.sd_journal_next_skip(j.cjournal, C.uint64_t(skip))
r := C.my_sd_journal_next_skip(sd_journal_next_skip, j.cjournal, C.uint64_t(skip))
j.mu.Unlock()
if r < 0 {
return uint64(r), fmt.Errorf("failed to iterate journal: %d", r)
return uint64(r), fmt.Errorf("failed to iterate journal: %d", syscall.Errno(-r))
}
return uint64(r), nil
@@ -204,12 +512,17 @@ func (j *Journal) NextSkip(skip uint64) (uint64, error) {
// Previous sets the read pointer into the journal back by one entry.
func (j *Journal) Previous() (uint64, error) {
sd_journal_previous, err := getFunction("sd_journal_previous")
if err != nil {
return 0, err
}
j.mu.Lock()
r := C.sd_journal_previous(j.cjournal)
r := C.my_sd_journal_previous(sd_journal_previous, j.cjournal)
j.mu.Unlock()
if r < 0 {
return uint64(r), fmt.Errorf("failed to iterate journal: %d", r)
return uint64(r), fmt.Errorf("failed to iterate journal: %d", syscall.Errno(-r))
}
return uint64(r), nil
@@ -218,20 +531,28 @@ func (j *Journal) Previous() (uint64, error) {
// PreviousSkip sets back the read pointer by multiple entries at once,
// as specified by the skip parameter.
func (j *Journal) PreviousSkip(skip uint64) (uint64, error) {
sd_journal_previous_skip, err := getFunction("sd_journal_previous_skip")
if err != nil {
return 0, err
}
j.mu.Lock()
r := C.sd_journal_previous_skip(j.cjournal, C.uint64_t(skip))
r := C.my_sd_journal_previous_skip(sd_journal_previous_skip, j.cjournal, C.uint64_t(skip))
j.mu.Unlock()
if r < 0 {
return uint64(r), fmt.Errorf("failed to iterate journal: %d", r)
return uint64(r), fmt.Errorf("failed to iterate journal: %d", syscall.Errno(-r))
}
return uint64(r), nil
}
// GetData gets the data object associated with a specific field from the
// current journal entry.
func (j *Journal) GetData(field string) (string, error) {
func (j *Journal) getData(field string) (unsafe.Pointer, C.int, error) {
sd_journal_get_data, err := getFunction("sd_journal_get_data")
if err != nil {
return nil, 0, err
}
f := C.CString(field)
defer C.free(unsafe.Pointer(f))
@@ -239,16 +560,25 @@ func (j *Journal) GetData(field string) (string, error) {
var l C.size_t
j.mu.Lock()
r := C.sd_journal_get_data(j.cjournal, f, &d, &l)
r := C.my_sd_journal_get_data(sd_journal_get_data, j.cjournal, f, &d, &l)
j.mu.Unlock()
if r < 0 {
return "", fmt.Errorf("failed to read message: %d", r)
return nil, 0, fmt.Errorf("failed to read message: %d", syscall.Errno(-r))
}
msg := C.GoStringN((*C.char)(d), C.int(l))
return d, C.int(l), nil
}
return msg, nil
// GetData gets the data object associated with a specific field from the
// current journal entry.
func (j *Journal) GetData(field string) (string, error) {
d, l, err := j.getData(field)
if err != nil {
return "", err
}
return C.GoStringN((*C.char)(d), l), nil
}
// GetDataValue gets the data object associated with a specific field from the
@@ -258,20 +588,138 @@ func (j *Journal) GetDataValue(field string) (string, error) {
if err != nil {
return "", err
}
return strings.SplitN(val, "=", 2)[1], nil
}
// GetDataBytes gets the data object associated with a specific field from the
// current journal entry.
func (j *Journal) GetDataBytes(field string) ([]byte, error) {
d, l, err := j.getData(field)
if err != nil {
return nil, err
}
return C.GoBytes(d, l), nil
}
// GetDataValueBytes gets the data object associated with a specific field from the
// current journal entry, returning only the value of the object.
func (j *Journal) GetDataValueBytes(field string) ([]byte, error) {
val, err := j.GetDataBytes(field)
if err != nil {
return nil, err
}
return bytes.SplitN(val, []byte("="), 2)[1], nil
}
// GetEntry returns a full representation of a journal entry with
// all key-value pairs of data as well as address fields (cursor, realtime
// timestamp and monotonic timestamp)
func (j *Journal) GetEntry() (*JournalEntry, error) {
sd_journal_get_realtime_usec, err := getFunction("sd_journal_get_realtime_usec")
if err != nil {
return nil, err
}
sd_journal_get_monotonic_usec, err := getFunction("sd_journal_get_monotonic_usec")
if err != nil {
return nil, err
}
sd_journal_get_cursor, err := getFunction("sd_journal_get_cursor")
if err != nil {
return nil, err
}
sd_journal_restart_data, err := getFunction("sd_journal_restart_data")
if err != nil {
return nil, err
}
sd_journal_enumerate_data, err := getFunction("sd_journal_enumerate_data")
if err != nil {
return nil, err
}
j.mu.Lock()
defer j.mu.Unlock()
var r C.int
entry := &JournalEntry{Fields: make(map[string]string)}
var realtimeUsec C.uint64_t
r = C.my_sd_journal_get_realtime_usec(sd_journal_get_realtime_usec, j.cjournal, &realtimeUsec)
if r < 0 {
return nil, fmt.Errorf("failed to get realtime timestamp: %d", syscall.Errno(-r))
}
entry.RealtimeTimestamp = uint64(realtimeUsec)
var monotonicUsec C.uint64_t
var boot_id C.sd_id128_t
r = C.my_sd_journal_get_monotonic_usec(sd_journal_get_monotonic_usec, j.cjournal, &monotonicUsec, &boot_id)
if r < 0 {
return nil, fmt.Errorf("failed to get monotonic timestamp: %d", syscall.Errno(-r))
}
entry.MonotonicTimestamp = uint64(monotonicUsec)
var c *C.char
// since the pointer is mutated by sd_journal_get_cursor, need to wait
// until after the call to free the memory
r = C.my_sd_journal_get_cursor(sd_journal_get_cursor, j.cjournal, &c)
defer C.free(unsafe.Pointer(c))
if r < 0 {
return nil, fmt.Errorf("failed to get cursor: %d", syscall.Errno(-r))
}
entry.Cursor = C.GoString(c)
// Implements the JOURNAL_FOREACH_DATA_RETVAL macro from journal-internal.h
var d unsafe.Pointer
var l C.size_t
C.my_sd_journal_restart_data(sd_journal_restart_data, j.cjournal)
for {
r = C.my_sd_journal_enumerate_data(sd_journal_enumerate_data, j.cjournal, &d, &l)
if r == 0 {
break
}
if r < 0 {
return nil, fmt.Errorf("failed to read message field: %d", syscall.Errno(-r))
}
msg := C.GoStringN((*C.char)(d), C.int(l))
kv := strings.SplitN(msg, "=", 2)
if len(kv) < 2 {
return nil, fmt.Errorf("failed to parse field")
}
entry.Fields[kv[0]] = kv[1]
}
return entry, nil
}
// SetDataThresold sets the data field size threshold for data returned by
// GetData. To retrieve the complete data fields this threshold should be
// turned off by setting it to 0, so that the library always returns the
// complete data objects.
func (j *Journal) SetDataThreshold(threshold uint64) error {
sd_journal_set_data_threshold, err := getFunction("sd_journal_set_data_threshold")
if err != nil {
return err
}
j.mu.Lock()
r := C.sd_journal_set_data_threshold(j.cjournal, C.size_t(threshold))
r := C.my_sd_journal_set_data_threshold(sd_journal_set_data_threshold, j.cjournal, C.size_t(threshold))
j.mu.Unlock()
if r < 0 {
return fmt.Errorf("failed to set data threshold: %d", r)
return fmt.Errorf("failed to set data threshold: %d", syscall.Errno(-r))
}
return nil
@@ -282,26 +730,123 @@ func (j *Journal) SetDataThreshold(threshold uint64) error {
func (j *Journal) GetRealtimeUsec() (uint64, error) {
var usec C.uint64_t
sd_journal_get_realtime_usec, err := getFunction("sd_journal_get_realtime_usec")
if err != nil {
return 0, err
}
j.mu.Lock()
r := C.sd_journal_get_realtime_usec(j.cjournal, &usec)
r := C.my_sd_journal_get_realtime_usec(sd_journal_get_realtime_usec, j.cjournal, &usec)
j.mu.Unlock()
if r < 0 {
return 0, fmt.Errorf("error getting timestamp for entry: %d", r)
return 0, fmt.Errorf("failed to get realtime timestamp: %d", syscall.Errno(-r))
}
return uint64(usec), nil
}
// SeekTail may be used to seek to the end of the journal, i.e. the most recent
// available entry.
func (j *Journal) SeekTail() error {
// GetMonotonicUsec gets the monotonic timestamp of the current journal entry.
func (j *Journal) GetMonotonicUsec() (uint64, error) {
var usec C.uint64_t
var boot_id C.sd_id128_t
sd_journal_get_monotonic_usec, err := getFunction("sd_journal_get_monotonic_usec")
if err != nil {
return 0, err
}
j.mu.Lock()
r := C.sd_journal_seek_tail(j.cjournal)
r := C.my_sd_journal_get_monotonic_usec(sd_journal_get_monotonic_usec, j.cjournal, &usec, &boot_id)
j.mu.Unlock()
if r < 0 {
return fmt.Errorf("failed to seek to tail of journal: %d", r)
return 0, fmt.Errorf("failed to get monotonic timestamp: %d", syscall.Errno(-r))
}
return uint64(usec), nil
}
// GetCursor gets the cursor of the current journal entry.
func (j *Journal) GetCursor() (string, error) {
sd_journal_get_cursor, err := getFunction("sd_journal_get_cursor")
if err != nil {
return "", err
}
var d *C.char
// since the pointer is mutated by sd_journal_get_cursor, need to wait
// until after the call to free the memory
j.mu.Lock()
r := C.my_sd_journal_get_cursor(sd_journal_get_cursor, j.cjournal, &d)
j.mu.Unlock()
defer C.free(unsafe.Pointer(d))
if r < 0 {
return "", fmt.Errorf("failed to get cursor: %d", syscall.Errno(-r))
}
cursor := C.GoString(d)
return cursor, nil
}
// TestCursor checks whether the current position in the journal matches the
// specified cursor
func (j *Journal) TestCursor(cursor string) error {
sd_journal_test_cursor, err := getFunction("sd_journal_test_cursor")
if err != nil {
return err
}
c := C.CString(cursor)
defer C.free(unsafe.Pointer(c))
j.mu.Lock()
r := C.my_sd_journal_test_cursor(sd_journal_test_cursor, j.cjournal, c)
j.mu.Unlock()
if r < 0 {
return fmt.Errorf("failed to test to cursor %q: %d", cursor, syscall.Errno(-r))
}
return nil
}
// SeekHead seeks to the beginning of the journal, i.e. the oldest available
// entry.
func (j *Journal) SeekHead() error {
sd_journal_seek_head, err := getFunction("sd_journal_seek_head")
if err != nil {
return err
}
j.mu.Lock()
r := C.my_sd_journal_seek_head(sd_journal_seek_head, j.cjournal)
j.mu.Unlock()
if r < 0 {
return fmt.Errorf("failed to seek to head of journal: %d", syscall.Errno(-r))
}
return nil
}
// SeekTail may be used to seek to the end of the journal, i.e. the most recent
// available entry.
func (j *Journal) SeekTail() error {
sd_journal_seek_tail, err := getFunction("sd_journal_seek_tail")
if err != nil {
return err
}
j.mu.Lock()
r := C.my_sd_journal_seek_tail(sd_journal_seek_tail, j.cjournal)
j.mu.Unlock()
if r < 0 {
return fmt.Errorf("failed to seek to tail of journal: %d", syscall.Errno(-r))
}
return nil
@@ -310,12 +855,38 @@ func (j *Journal) SeekTail() error {
// SeekRealtimeUsec seeks to the entry with the specified realtime (wallclock)
// timestamp, i.e. CLOCK_REALTIME.
func (j *Journal) SeekRealtimeUsec(usec uint64) error {
sd_journal_seek_realtime_usec, err := getFunction("sd_journal_seek_realtime_usec")
if err != nil {
return err
}
j.mu.Lock()
r := C.sd_journal_seek_realtime_usec(j.cjournal, C.uint64_t(usec))
r := C.my_sd_journal_seek_realtime_usec(sd_journal_seek_realtime_usec, j.cjournal, C.uint64_t(usec))
j.mu.Unlock()
if r < 0 {
return fmt.Errorf("failed to seek to %d: %d", usec, r)
return fmt.Errorf("failed to seek to %d: %d", usec, syscall.Errno(-r))
}
return nil
}
// SeekCursor seeks to a concrete journal cursor.
func (j *Journal) SeekCursor(cursor string) error {
sd_journal_seek_cursor, err := getFunction("sd_journal_seek_cursor")
if err != nil {
return err
}
c := C.CString(cursor)
defer C.free(unsafe.Pointer(c))
j.mu.Lock()
r := C.my_sd_journal_seek_cursor(sd_journal_seek_cursor, j.cjournal, c)
j.mu.Unlock()
if r < 0 {
return fmt.Errorf("failed to seek to cursor %q: %d", cursor, syscall.Errno(-r))
}
return nil
@@ -327,6 +898,12 @@ func (j *Journal) SeekRealtimeUsec(usec uint64) error {
// wait indefinitely for a journal change.
func (j *Journal) Wait(timeout time.Duration) int {
var to uint64
sd_journal_wait, err := getFunction("sd_journal_wait")
if err != nil {
return -1
}
if timeout == IndefiniteWait {
// sd_journal_wait(3) calls for a (uint64_t) -1 to be passed to signify
// indefinite wait, but using a -1 overflows our C.uint64_t, so we use an
@@ -336,7 +913,7 @@ func (j *Journal) Wait(timeout time.Duration) int {
to = uint64(time.Now().Add(timeout).Unix() / 1000)
}
j.mu.Lock()
r := C.sd_journal_wait(j.cjournal, C.uint64_t(to))
r := C.my_sd_journal_wait(sd_journal_wait, j.cjournal, C.uint64_t(to))
j.mu.Unlock()
return int(r)
@@ -345,12 +922,18 @@ func (j *Journal) Wait(timeout time.Duration) int {
// GetUsage returns the journal disk space usage, in bytes.
func (j *Journal) GetUsage() (uint64, error) {
var out C.uint64_t
sd_journal_get_usage, err := getFunction("sd_journal_get_usage")
if err != nil {
return 0, err
}
j.mu.Lock()
r := C.sd_journal_get_usage(j.cjournal, &out)
r := C.my_sd_journal_get_usage(sd_journal_get_usage, j.cjournal, &out)
j.mu.Unlock()
if r < 0 {
return 0, fmt.Errorf("failed to get journal disk space usage: %d", r)
return 0, fmt.Errorf("failed to get journal disk space usage: %d", syscall.Errno(-r))
}
return uint64(out), nil

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

@@ -16,7 +16,13 @@
package sdjournal
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"testing"
"time"
@@ -53,7 +59,7 @@ func TestJournalFollow(t *testing.T) {
case <-done:
return
default:
if err = journal.Print(journal.PriInfo, "test message %s", time.Now()); err != nil {
if err := journal.Print(journal.PriInfo, "test message %s", time.Now()); err != nil {
t.Fatalf("Error writing to journal: %s", err)
}
@@ -88,3 +94,252 @@ func TestJournalGetUsage(t *testing.T) {
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()
waitAndNext := func(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
}
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)
}
}
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])
}
}

View File

@@ -20,6 +20,7 @@ import (
"fmt"
"io"
"log"
"strings"
"time"
)
@@ -29,10 +30,12 @@ var (
// JournalReaderConfig represents options to drive the behavior of a JournalReader.
type JournalReaderConfig struct {
// The Since and NumFromTail options are mutually exclusive and determine
// where the reading begins within the journal.
// 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.
@@ -44,9 +47,10 @@ type JournalReaderConfig struct {
}
// JournalReader is an io.ReadCloser which provides a simple interface for iterating through the
// systemd journal.
// systemd journal. A JournalReader is not safe for concurrent use by multiple goroutines.
type JournalReader struct {
journal *Journal
journal *Journal
msgReader *strings.Reader
}
// NewJournalReader creates a new JournalReader with configuration options that are similar to the
@@ -86,7 +90,20 @@ func NewJournalReader(config JournalReaderConfig) (*JournalReader, error) {
// 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.
if _, err := r.journal.PreviousSkip(config.NumFromTail + 1); err != nil {
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
}
}
@@ -94,41 +111,73 @@ func NewJournalReader(config JournalReaderConfig) (*JournalReader, error) {
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) {
var err error
var c int
// Advance the journal cursor
c, err = r.journal.Next()
if r.msgReader == nil {
var c int
// An unexpected error
if err != nil {
return 0, err
}
// Advance the journal cursor. It has to be called at least one time
// before reading
c, err = r.journal.Next()
// EOF detection
if c == 0 {
return 0, io.EOF
}
// An unexpected error
if err != nil {
return 0, err
}
// Build a message
var msg string
msg, err = r.buildMessage()
// EOF detection
if c == 0 {
return 0, io.EOF
}
if err != nil {
return 0, err
// Build a message
var msg string
msg, err = r.buildMessage()
if err != nil {
return 0, err
}
r.msgReader = strings.NewReader(msg)
}
// Copy and return the message
copy(b, []byte(msg))
var sz int
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 len(msg), 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) (err error) {
@@ -148,7 +197,9 @@ process:
return ErrExpired
default:
if c > 0 {
writer.Write(msg[:c])
if _, err = writer.Write(msg[:c]); err != nil {
break process
}
continue process
}
}

View File

@@ -24,9 +24,10 @@ if [ -z "$GOPATH" ]; then
fi
export GOPATH=${PWD}/gopath
go get -u github.com/godbus/dbus
go get -u github.com/coreos/pkg/dlopen
fi
TESTABLE="activation journal login1 machine1 unit"
TESTABLE="activation daemon journal login1 machine1 unit"
FORMATTABLE="$TESTABLE sdjournal dbus"
if [ -e "/run/systemd/system/" ]; then
TESTABLE="${TESTABLE} sdjournal"
@@ -57,7 +58,7 @@ split=(${TEST// / })
TEST=${split[@]/#/${REPO_PATH}/}
echo "Running tests..."
go test ${COVER} $@ ${TEST}
go test -v ${COVER} $@ ${TEST}
echo "Checking gofmt..."
fmtRes=$(gofmt -l $FMT)

View File

@@ -18,133 +18,23 @@
// than linking against them.
package util
// #cgo LDFLAGS: -ldl
// #include <stdlib.h>
// #include <dlfcn.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 (
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
"syscall"
"unsafe"
)
var ErrSoNotFound = errors.New("unable to open a handle to libsystemd")
// libHandle represents an open handle to the systemd C library
type libHandle struct {
handle unsafe.Pointer
libname string
}
func (h *libHandle) Close() error {
if r := C.dlclose(h.handle); r != 0 {
return fmt.Errorf("error closing %v: %d", h.libname, r)
}
return nil
}
// getHandle tries to get a handle to a systemd library (.so), attempting to
// access it by several different names 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() (*libHandle, error) {
for _, name := range []string{
// systemd < 209
"libsystemd-login.so",
"libsystemd-login.so.0",
// systemd >= 209 merged libsystemd-login into libsystemd proper
"libsystemd.so",
"libsystemd.so.0",
} {
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
}
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() (slice string, err error) {
var h *libHandle
h, err = getHandle()
if err != nil {
return
}
defer func() {
if err1 := h.Close(); err1 != nil {
err = err1
}
}()
sym := C.CString("sd_pid_get_slice")
defer C.free(unsafe.Pointer(sym))
sd_pid_get_slice := C.dlsym(h.handle, sym)
if sd_pid_get_slice == nil {
err = fmt.Errorf("error resolving sd_pid_get_slice function")
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 GetRunningSlice() (string, error) {
return getRunningSlice()
}
// RunningFromSystemService tries to detect whether the current process has
@@ -162,87 +52,17 @@ func GetRunningSlice() (slice string, err error) {
//
// 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 errr will be returned
func RunningFromSystemService() (ret bool, err error) {
var h *libHandle
h, err = getHandle()
if err != nil {
return
}
defer func() {
if err1 := h.Close(); err1 != nil {
err = err1
}
}()
sym := C.CString("sd_pid_get_owner_uid")
defer C.free(unsafe.Pointer(sym))
sd_pid_get_owner_uid := C.dlsym(h.handle, sym)
if sd_pid_get_owner_uid == nil {
err = fmt.Errorf("error resolving sd_pid_get_owner_uid function")
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) or ENXIO (systemd >=220)
switch {
case errno >= 0:
ret = false
case serrno == syscall.ENOENT, serrno == syscall.ENXIO:
// 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
// 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() (unit string, err error) {
var h *libHandle
h, err = getHandle()
if err != nil {
return
}
defer func() {
if err1 := h.Close(); err1 != nil {
err = err1
}
}()
sym := C.CString("sd_pid_get_unit")
defer C.free(unsafe.Pointer(sym))
sd_pid_get_unit := C.dlsym(h.handle, sym)
if sd_pid_get_unit == nil {
err = fmt.Errorf("error resolving sd_pid_get_unit function")
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
func CurrentUnitName() (string, error) {
return currentUnitName()
}
// IsRunningSystemd checks whether the host was booted with systemd as its init

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

@@ -0,0 +1,174 @@
// 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/pkg/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) or ENXIO (systemd >=220)
switch {
case errno >= 0:
ret = false
case serrno == syscall.ENOENT, serrno == syscall.ENXIO:
// Since the implementation of sessions in systemd relies on
// the `pam_systemd` module, using the sd_pid_get_owner_uid
// heuristic alone can result in false positives if that module
// (or PAM itself) is not present or properly configured on the
// system. As such, we also check if we're the session leader,
// which should be the case if we're invoked from a unit file,
// but not if e.g. we're invoked from the command line from a
// user's login session
ret = C.am_session_leader() == 1
default:
err = fmt.Errorf("error calling sd_pid_get_owner_uid: %v", syscall.Errno(-errno))
}
return
}
func currentUnitName() (unit string, err error) {
var h *dlopen.LibHandle
h, err = dlopen.GetHandle(libsystemdNames)
if err != nil {
return
}
defer func() {
if err1 := h.Close(); err1 != nil {
err = err1
}
}()
sd_pid_get_unit, err := h.GetSymbolPointer("sd_pid_get_unit")
if err != nil {
return
}
var s string
u := C.CString(s)
defer C.free(unsafe.Pointer(u))
ret := C.my_sd_pid_get_unit(sd_pid_get_unit, 0, &u)
if ret < 0 {
err = fmt.Errorf("error calling sd_pid_get_unit: %v", syscall.Errno(-ret))
return
}
unit = C.GoString(u)
return
}

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

@@ -0,0 +1,23 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !cgo
package util
func getRunningSlice() (string, error) { return "", ErrNoCGO }
func runningFromSystemService() (bool, error) { return false, ErrNoCGO }
func currentUnitName() (string, error) { return "", ErrNoCGO }