mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-01-06 16:06:51 +00:00
Merge pull request #84718 from yastij/remove-validators
remove system validators package from kubeadm and use k8s.io/system-validators
This commit is contained in:
@@ -23,7 +23,6 @@ go_library(
|
||||
"//cmd/kubeadm/app/images:go_default_library",
|
||||
"//cmd/kubeadm/app/util/initsystem:go_default_library",
|
||||
"//cmd/kubeadm/app/util/runtime:go_default_library",
|
||||
"//cmd/kubeadm/app/util/system:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/version:go_default_library",
|
||||
@@ -31,6 +30,7 @@ go_library(
|
||||
"//vendor/github.com/PuerkitoBio/purell:go_default_library",
|
||||
"//vendor/github.com/pkg/errors:go_default_library",
|
||||
"//vendor/k8s.io/klog:go_default_library",
|
||||
"//vendor/k8s.io/system-validators/validators:go_default_library",
|
||||
"//vendor/k8s.io/utils/exec:go_default_library",
|
||||
"//vendor/k8s.io/utils/net:go_default_library",
|
||||
] + select({
|
||||
|
||||
@@ -46,7 +46,7 @@ import (
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/images"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/initsystem"
|
||||
utilruntime "k8s.io/kubernetes/cmd/kubeadm/app/util/runtime"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/system"
|
||||
system "k8s.io/system-validators/validators"
|
||||
utilsexec "k8s.io/utils/exec"
|
||||
utilsnet "k8s.io/utils/net"
|
||||
)
|
||||
|
||||
@@ -91,7 +91,6 @@ filegroup(
|
||||
"//cmd/kubeadm/app/util/pubkeypin:all-srcs",
|
||||
"//cmd/kubeadm/app/util/runtime:all-srcs",
|
||||
"//cmd/kubeadm/app/util/staticpod:all-srcs",
|
||||
"//cmd/kubeadm/app/util/system:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"cgroup_validator.go",
|
||||
"docker_validator.go",
|
||||
"kernel_validator.go",
|
||||
"kernel_validator_helper.go",
|
||||
"os_validator.go",
|
||||
"package_validator.go",
|
||||
"report.go",
|
||||
"types.go",
|
||||
"types_unix.go",
|
||||
"types_windows.go",
|
||||
"validators.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/system",
|
||||
deps = [
|
||||
"//vendor/github.com/blang/semver:go_default_library",
|
||||
"//vendor/github.com/pkg/errors:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"cgroup_validator_test.go",
|
||||
"docker_validator_test.go",
|
||||
"kernel_validator_test.go",
|
||||
"os_validator_test.go",
|
||||
"package_validator_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
tags = ["e2e"],
|
||||
deps = [
|
||||
"//vendor/github.com/pkg/errors:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var _ Validator = &CgroupsValidator{}
|
||||
|
||||
// CgroupsValidator validates cgroup configuration.
|
||||
type CgroupsValidator struct {
|
||||
Reporter Reporter
|
||||
}
|
||||
|
||||
// Name is part of the system.Validator interface.
|
||||
func (c *CgroupsValidator) Name() string {
|
||||
return "cgroups"
|
||||
}
|
||||
|
||||
const (
|
||||
cgroupsConfigPrefix = "CGROUPS_"
|
||||
)
|
||||
|
||||
// Validate is part of the system.Validator interface.
|
||||
func (c *CgroupsValidator) Validate(spec SysSpec) ([]error, []error) {
|
||||
subsystems, err := c.getCgroupSubsystems()
|
||||
if err != nil {
|
||||
return nil, []error{errors.Wrap(err, "failed to get cgroup subsystems")}
|
||||
}
|
||||
if err = c.validateCgroupSubsystems(spec.Cgroups, subsystems); err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *CgroupsValidator) validateCgroupSubsystems(cgroupSpec, subsystems []string) error {
|
||||
missing := []string{}
|
||||
for _, cgroup := range cgroupSpec {
|
||||
found := false
|
||||
for _, subsystem := range subsystems {
|
||||
if cgroup == subsystem {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
item := cgroupsConfigPrefix + strings.ToUpper(cgroup)
|
||||
if found {
|
||||
c.Reporter.Report(item, "enabled", good)
|
||||
} else {
|
||||
c.Reporter.Report(item, "missing", bad)
|
||||
missing = append(missing, cgroup)
|
||||
}
|
||||
}
|
||||
if len(missing) > 0 {
|
||||
return errors.Errorf("missing cgroups: %s", strings.Join(missing, " "))
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (c *CgroupsValidator) getCgroupSubsystems() ([]string, error) {
|
||||
f, err := os.Open("/proc/cgroups")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
subsystems := []string{}
|
||||
s := bufio.NewScanner(f)
|
||||
for s.Scan() {
|
||||
if err := s.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
text := s.Text()
|
||||
if text[0] != '#' {
|
||||
parts := strings.Fields(text)
|
||||
if len(parts) >= 4 && parts[3] != "0" {
|
||||
subsystems = append(subsystems, parts[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
return subsystems, nil
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidateCgroupSubsystem(t *testing.T) {
|
||||
v := &CgroupsValidator{
|
||||
Reporter: DefaultReporter,
|
||||
}
|
||||
cgroupSpec := []string{"system1", "system2"}
|
||||
for desc, test := range map[string]struct {
|
||||
subsystems []string
|
||||
err bool
|
||||
}{
|
||||
"missing cgroup subsystem should report error": {
|
||||
subsystems: []string{"system1"},
|
||||
err: true,
|
||||
},
|
||||
"extra cgroup subsystems should not report error": {
|
||||
subsystems: []string{"system1", "system2", "system3"},
|
||||
err: false,
|
||||
},
|
||||
"subsystems the same with spec should not report error": {
|
||||
subsystems: []string{"system1", "system2"},
|
||||
err: false,
|
||||
},
|
||||
} {
|
||||
t.Run(desc, func(t *testing.T) {
|
||||
err := v.validateCgroupSubsystems(cgroupSpec, test.subsystems)
|
||||
if !test.err {
|
||||
assert.Nil(t, err, "%q: Expect error not to occur with cgroup", desc)
|
||||
} else {
|
||||
assert.NotNil(t, err, "%q: Expect error to occur with docker info", desc)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var _ Validator = &DockerValidator{}
|
||||
|
||||
// DockerValidator validates docker configuration.
|
||||
type DockerValidator struct {
|
||||
Reporter Reporter
|
||||
}
|
||||
|
||||
// dockerInfo holds a local subset of the Info struct from
|
||||
// github.com/docker/docker/api/types.
|
||||
// The JSON output from 'docker info' should map to this struct.
|
||||
type dockerInfo struct {
|
||||
Driver string `json:"Driver"`
|
||||
ServerVersion string `json:"ServerVersion"`
|
||||
}
|
||||
|
||||
// Name is part of the system.Validator interface.
|
||||
func (d *DockerValidator) Name() string {
|
||||
return "docker"
|
||||
}
|
||||
|
||||
const (
|
||||
dockerConfigPrefix = "DOCKER_"
|
||||
latestValidatedDockerVersion = "19.03"
|
||||
)
|
||||
|
||||
// Validate is part of the system.Validator interface.
|
||||
// TODO(random-liu): Add more validating items.
|
||||
func (d *DockerValidator) Validate(spec SysSpec) ([]error, []error) {
|
||||
if spec.RuntimeSpec.DockerSpec == nil {
|
||||
// If DockerSpec is not specified, assume current runtime is not
|
||||
// docker, skip the docker configuration validation.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Run 'docker info' with a JSON output and unmarshal it into a dockerInfo object
|
||||
info := dockerInfo{}
|
||||
out, err := exec.Command("docker", "info", "--format", "{{json .}}").CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, []error{errors.Errorf(`failed executing "docker info --format '{{json .}}'"\noutput: %s\nerror: %v`, string(out), err)}
|
||||
}
|
||||
if err := d.unmarshalDockerInfo(out, &info); err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
|
||||
// validate the resulted docker info object against the spec
|
||||
return d.validateDockerInfo(spec.RuntimeSpec.DockerSpec, info)
|
||||
}
|
||||
|
||||
func (d *DockerValidator) unmarshalDockerInfo(b []byte, info *dockerInfo) error {
|
||||
if err := json.Unmarshal(b, &info); err != nil {
|
||||
return errors.Wrapf(err, "could not unmarshal the JSON output of 'docker info':\n%s\n", b)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DockerValidator) validateDockerInfo(spec *DockerSpec, info dockerInfo) ([]error, []error) {
|
||||
// Validate docker version.
|
||||
matched := false
|
||||
for _, v := range spec.Version {
|
||||
r := regexp.MustCompile(v)
|
||||
if r.MatchString(info.ServerVersion) {
|
||||
d.Reporter.Report(dockerConfigPrefix+"VERSION", info.ServerVersion, good)
|
||||
matched = true
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
// If it's of the new Docker version scheme but didn't match above, it
|
||||
// must be a newer version than the most recently validated one.
|
||||
ver := `\d{2}\.\d+\.\d+(?:-[a-z]{2})?`
|
||||
r := regexp.MustCompile(ver)
|
||||
if r.MatchString(info.ServerVersion) {
|
||||
d.Reporter.Report(dockerConfigPrefix+"VERSION", info.ServerVersion, good)
|
||||
w := errors.Errorf(
|
||||
"this Docker version is not on the list of validated versions: %s. Latest validated version: %s",
|
||||
info.ServerVersion,
|
||||
latestValidatedDockerVersion,
|
||||
)
|
||||
return []error{w}, nil
|
||||
}
|
||||
d.Reporter.Report(dockerConfigPrefix+"VERSION", info.ServerVersion, bad)
|
||||
return nil, []error{errors.Errorf("unsupported docker version: %s", info.ServerVersion)}
|
||||
}
|
||||
// Validate graph driver.
|
||||
item := dockerConfigPrefix + "GRAPH_DRIVER"
|
||||
for _, gd := range spec.GraphDriver {
|
||||
if info.Driver == gd {
|
||||
d.Reporter.Report(item, info.Driver, good)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
d.Reporter.Report(item, info.Driver, bad)
|
||||
return nil, []error{errors.Errorf("unsupported graph driver: %s", info.Driver)}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidateDockerInfo(t *testing.T) {
|
||||
v := &DockerValidator{
|
||||
Reporter: DefaultReporter,
|
||||
}
|
||||
spec := &DockerSpec{
|
||||
Version: []string{`1\.13\..*`, `17\.0[3,6,9]\..*`, `18\.0[6,9]\..*`, `19\.03\..*`},
|
||||
GraphDriver: []string{"driver_1", "driver_2"},
|
||||
}
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
info dockerInfo
|
||||
err bool
|
||||
warn bool
|
||||
}{
|
||||
{
|
||||
name: "unsupported Docker version 1.12.1",
|
||||
info: dockerInfo{Driver: "driver_2", ServerVersion: "1.12.1"},
|
||||
err: true,
|
||||
warn: false,
|
||||
},
|
||||
{
|
||||
name: "unsupported driver",
|
||||
info: dockerInfo{Driver: "bad_driver", ServerVersion: "1.13.1"},
|
||||
err: true,
|
||||
warn: false,
|
||||
},
|
||||
{
|
||||
name: "valid Docker version 1.13.1",
|
||||
info: dockerInfo{Driver: "driver_1", ServerVersion: "1.13.1"},
|
||||
err: false,
|
||||
warn: false,
|
||||
},
|
||||
{
|
||||
name: "valid Docker version 17.03.0-ce",
|
||||
info: dockerInfo{Driver: "driver_2", ServerVersion: "17.03.0-ce"},
|
||||
err: false,
|
||||
warn: false,
|
||||
},
|
||||
{
|
||||
name: "valid Docker version 17.06.0-ce",
|
||||
info: dockerInfo{Driver: "driver_2", ServerVersion: "17.06.0-ce"},
|
||||
err: false,
|
||||
warn: false,
|
||||
},
|
||||
{
|
||||
name: "valid Docker version 17.09.0-ce",
|
||||
info: dockerInfo{Driver: "driver_2", ServerVersion: "17.09.0-ce"},
|
||||
err: false,
|
||||
warn: false,
|
||||
},
|
||||
{
|
||||
name: "valid Docker version 18.06.0-ce",
|
||||
info: dockerInfo{Driver: "driver_2", ServerVersion: "18.06.0-ce"},
|
||||
err: false,
|
||||
warn: false,
|
||||
},
|
||||
{
|
||||
name: "valid Docker version 18.09.1-ce",
|
||||
info: dockerInfo{Driver: "driver_2", ServerVersion: "18.09.1-ce"},
|
||||
err: false,
|
||||
warn: false,
|
||||
},
|
||||
{
|
||||
name: "valid Docker version 19.03.1-ce",
|
||||
info: dockerInfo{Driver: "driver_2", ServerVersion: "19.03.1-ce"},
|
||||
err: false,
|
||||
warn: false,
|
||||
},
|
||||
{
|
||||
name: "Docker version 19.06.0 is not in the list of validated versions",
|
||||
info: dockerInfo{Driver: "driver_2", ServerVersion: "19.06.0"},
|
||||
err: false,
|
||||
warn: true,
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
warn, err := v.validateDockerInfo(spec, test.info)
|
||||
if !test.err {
|
||||
assert.Nil(t, err, "Expect error not to occur with docker info %+v", test.info)
|
||||
} else {
|
||||
assert.NotNil(t, err, "Expect error to occur with docker info %+v", test.info)
|
||||
}
|
||||
if !test.warn {
|
||||
assert.Nil(t, warn, "Expect error not to occur with docker info %+v", test.info)
|
||||
} else {
|
||||
assert.NotNil(t, warn, "Expect error to occur with docker info %+v", test.info)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalDockerInfo(t *testing.T) {
|
||||
v := &DockerValidator{}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
input string
|
||||
expectedInfo dockerInfo
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
name: "valid: expected dockerInfo is valid",
|
||||
input: `{ "Driver":"foo", "ServerVersion":"bar" }`,
|
||||
expectedInfo: dockerInfo{Driver: "foo", ServerVersion: "bar"},
|
||||
},
|
||||
{
|
||||
name: "invalid: the JSON input is not valid",
|
||||
input: `{ "Driver":"foo"`,
|
||||
expectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var err error
|
||||
info := dockerInfo{}
|
||||
if err = v.unmarshalDockerInfo([]byte(tc.input), &info); (err != nil) != tc.expectedError {
|
||||
t.Fatalf("failed unmarshaling; expected error: %v, got: %v, error: %v", tc.expectedError, (err != nil), err)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(tc.expectedInfo, info) {
|
||||
t.Fatalf("dockerInfo do not match, expected: %#v, got: %#v", tc.expectedInfo, info)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var _ Validator = &KernelValidator{}
|
||||
|
||||
// KernelValidator validates kernel. Currently only validate kernel version
|
||||
// and kernel configuration.
|
||||
type KernelValidator struct {
|
||||
kernelRelease string
|
||||
Reporter Reporter
|
||||
}
|
||||
|
||||
// Name is part of the system.Validator interface.
|
||||
func (k *KernelValidator) Name() string {
|
||||
return "kernel"
|
||||
}
|
||||
|
||||
// kConfigOption is the possible kernel config option.
|
||||
type kConfigOption string
|
||||
|
||||
const (
|
||||
builtIn kConfigOption = "y"
|
||||
asModule kConfigOption = "m"
|
||||
leftOut kConfigOption = "n"
|
||||
|
||||
// validKConfigRegex is the regex matching kernel configuration line.
|
||||
validKConfigRegex = "^CONFIG_[A-Z0-9_]+=[myn]"
|
||||
// kernelConfigPrefix is the prefix of kernel configuration.
|
||||
kernelConfigPrefix = "CONFIG_"
|
||||
)
|
||||
|
||||
// Validate is part of the system.Validator interface.
|
||||
func (k *KernelValidator) Validate(spec SysSpec) ([]error, []error) {
|
||||
helper := KernelValidatorHelperImpl{}
|
||||
release, err := helper.GetKernelReleaseVersion()
|
||||
if err != nil {
|
||||
return nil, []error{errors.Wrap(err, "failed to get kernel release")}
|
||||
}
|
||||
k.kernelRelease = release
|
||||
var errs []error
|
||||
if err = k.validateKernelVersion(spec.KernelSpec); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
// only validate kernel config when necessary (currently no kernel config for windows)
|
||||
if len(spec.KernelSpec.Required) > 0 || len(spec.KernelSpec.Forbidden) > 0 || len(spec.KernelSpec.Optional) > 0 {
|
||||
if err = k.validateKernelConfig(spec.KernelSpec); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
// validateKernelVersion validates the kernel version.
|
||||
func (k *KernelValidator) validateKernelVersion(kSpec KernelSpec) error {
|
||||
versionRegexps := kSpec.Versions
|
||||
for _, versionRegexp := range versionRegexps {
|
||||
r := regexp.MustCompile(versionRegexp)
|
||||
if r.MatchString(k.kernelRelease) {
|
||||
k.Reporter.Report("KERNEL_VERSION", k.kernelRelease, good)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
k.Reporter.Report("KERNEL_VERSION", k.kernelRelease, bad)
|
||||
return errors.Errorf("unsupported kernel release: %s", k.kernelRelease)
|
||||
}
|
||||
|
||||
// validateKernelConfig validates the kernel configurations.
|
||||
func (k *KernelValidator) validateKernelConfig(kSpec KernelSpec) error {
|
||||
allConfig, err := k.getKernelConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse kernel config")
|
||||
}
|
||||
return k.validateCachedKernelConfig(allConfig, kSpec)
|
||||
}
|
||||
|
||||
// validateCachedKernelConfig validates the kernel confgiurations cached in internal data type.
|
||||
func (k *KernelValidator) validateCachedKernelConfig(allConfig map[string]kConfigOption, kSpec KernelSpec) error {
|
||||
badConfigs := []string{}
|
||||
// reportAndRecord is a helper function to record bad config when
|
||||
// report.
|
||||
reportAndRecord := func(name, msg, desc string, result ValidationResultType) {
|
||||
if result == bad {
|
||||
badConfigs = append(badConfigs, name)
|
||||
}
|
||||
// report description when the config is bad or warn.
|
||||
if result != good && desc != "" {
|
||||
msg = msg + " - " + desc
|
||||
}
|
||||
k.Reporter.Report(name, msg, result)
|
||||
}
|
||||
const (
|
||||
required = iota
|
||||
optional
|
||||
forbidden
|
||||
)
|
||||
validateOpt := func(config KernelConfig, expect int) {
|
||||
var found, missing ValidationResultType
|
||||
switch expect {
|
||||
case required:
|
||||
found, missing = good, bad
|
||||
case optional:
|
||||
found, missing = good, warn
|
||||
case forbidden:
|
||||
found, missing = bad, good
|
||||
}
|
||||
var name string
|
||||
var opt kConfigOption
|
||||
var ok bool
|
||||
for _, name = range append([]string{config.Name}, config.Aliases...) {
|
||||
name = kernelConfigPrefix + name
|
||||
if opt, ok = allConfig[name]; ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
reportAndRecord(name, "not set", config.Description, missing)
|
||||
return
|
||||
}
|
||||
switch opt {
|
||||
case builtIn:
|
||||
reportAndRecord(name, "enabled", config.Description, found)
|
||||
case asModule:
|
||||
reportAndRecord(name, "enabled (as module)", config.Description, found)
|
||||
case leftOut:
|
||||
reportAndRecord(name, "disabled", config.Description, missing)
|
||||
default:
|
||||
reportAndRecord(name, fmt.Sprintf("unknown option: %s", opt), config.Description, missing)
|
||||
}
|
||||
}
|
||||
for _, config := range kSpec.Required {
|
||||
validateOpt(config, required)
|
||||
}
|
||||
for _, config := range kSpec.Optional {
|
||||
validateOpt(config, optional)
|
||||
}
|
||||
for _, config := range kSpec.Forbidden {
|
||||
validateOpt(config, forbidden)
|
||||
}
|
||||
if len(badConfigs) > 0 {
|
||||
return errors.Errorf("unexpected kernel config: %s", strings.Join(badConfigs, " "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getKernelConfigReader search kernel config file in a predefined list. Once the kernel config
|
||||
// file is found it will read the configurations into a byte buffer and return. If the kernel
|
||||
// config file is not found, it will try to load kernel config module and retry again.
|
||||
func (k *KernelValidator) getKernelConfigReader() (io.Reader, error) {
|
||||
possibePaths := []string{
|
||||
"/proc/config.gz",
|
||||
"/boot/config-" + k.kernelRelease,
|
||||
"/usr/src/linux-" + k.kernelRelease + "/.config",
|
||||
"/usr/src/linux/.config",
|
||||
"/usr/lib/modules/" + k.kernelRelease + "/config",
|
||||
"/usr/lib/ostree-boot/config-" + k.kernelRelease,
|
||||
"/usr/lib/kernel/config-" + k.kernelRelease,
|
||||
"/usr/src/linux-headers-" + k.kernelRelease + "/.config",
|
||||
"/lib/modules/" + k.kernelRelease + "/build/.config",
|
||||
}
|
||||
configsModule := "configs"
|
||||
modprobeCmd := "modprobe"
|
||||
// loadModule indicates whether we've tried to load kernel config module ourselves.
|
||||
loadModule := false
|
||||
for {
|
||||
for _, path := range possibePaths {
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// Buffer the whole file, so that we can close the file and unload
|
||||
// kernel config module in this function.
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var r io.Reader
|
||||
r = bytes.NewReader(b)
|
||||
// This is a gzip file (config.gz), unzip it.
|
||||
if filepath.Ext(path) == ".gz" {
|
||||
r, err = gzip.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
// If we've tried to load kernel config module, break and return error.
|
||||
if loadModule {
|
||||
break
|
||||
}
|
||||
// If the kernel config file is not found, try to load the kernel
|
||||
// config module and check again.
|
||||
output, err := exec.Command(modprobeCmd, configsModule).CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to load kernel module: %q, output: %q, err",
|
||||
configsModule, output)
|
||||
}
|
||||
// Unload the kernel config module to make sure the validation have no side effect.
|
||||
defer exec.Command(modprobeCmd, "-r", configsModule).Run()
|
||||
loadModule = true
|
||||
}
|
||||
return nil, errors.Errorf("no config path in %v is available", possibePaths)
|
||||
}
|
||||
|
||||
// getKernelConfig gets kernel config from kernel config file and convert kernel config to internal type.
|
||||
func (k *KernelValidator) getKernelConfig() (map[string]kConfigOption, error) {
|
||||
r, err := k.getKernelConfigReader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return k.parseKernelConfig(r)
|
||||
}
|
||||
|
||||
// parseKernelConfig converts kernel config to internal type.
|
||||
func (k *KernelValidator) parseKernelConfig(r io.Reader) (map[string]kConfigOption, error) {
|
||||
config := map[string]kConfigOption{}
|
||||
regex := regexp.MustCompile(validKConfigRegex)
|
||||
s := bufio.NewScanner(r)
|
||||
for s.Scan() {
|
||||
if err := s.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
line := strings.TrimSpace(s.Text())
|
||||
if !regex.MatchString(line) {
|
||||
continue
|
||||
}
|
||||
fields := strings.Split(line, "=")
|
||||
config[fields[0]] = kConfigOption(fields[1][0])
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package system
|
||||
|
||||
// KernelValidatorHelper is an interface intended to help with os specific kernel validation
|
||||
type KernelValidatorHelper interface {
|
||||
// GetKernelReleaseVersion gets the current kernel release version of the system
|
||||
GetKernelReleaseVersion() (string, error)
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidateKernelVersion(t *testing.T) {
|
||||
v := &KernelValidator{
|
||||
Reporter: DefaultReporter,
|
||||
}
|
||||
// Currently, testRegex is align with DefaultSysSpec.KernelVersion, but in the future
|
||||
// they may be different.
|
||||
// This is fine, because the test mainly tests the kernel version validation logic,
|
||||
// not the DefaultSysSpec. The DefaultSysSpec should be tested with node e2e.
|
||||
testRegex := []string{`^3\.[1-9][0-9].*$`, `^([4-9]|[1-9][0-9]+)\.([0-9]+)\.([0-9]+).*$`}
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
version string
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
name: "3.19.9-99-test first version regex matches",
|
||||
version: "3.19.9-99-test",
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
name: "4.4.14+ one of version regexes matches",
|
||||
version: "4.4.14+",
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
name: "2.0.0 no version regex matches",
|
||||
version: "2.0.0",
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "5.0.0 one of version regexes matches",
|
||||
version: "5.0.0",
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
name: "10.21.1 one of version regexes matches",
|
||||
version: "10.21.1",
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
name: "99.12.12 one of version regexes matches",
|
||||
version: "99.12.12",
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
name: "3.9.0 no version regex matches",
|
||||
version: "3.9.0",
|
||||
err: true,
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
v.kernelRelease = test.version
|
||||
err := v.validateKernelVersion(KernelSpec{Versions: testRegex})
|
||||
if !test.err {
|
||||
assert.Nil(t, err, "Expect error not to occur with kernel version %q", test.version)
|
||||
} else {
|
||||
assert.NotNil(t, err, "Expect error to occur with kenrel version %q", test.version)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCachedKernelConfig(t *testing.T) {
|
||||
v := &KernelValidator{
|
||||
Reporter: DefaultReporter,
|
||||
}
|
||||
testKernelSpec := KernelSpec{
|
||||
Required: []KernelConfig{{Name: "REQUIRED_1"}, {Name: "REQUIRED_2", Aliases: []string{"ALIASE_REQUIRED_2"}}},
|
||||
Optional: []KernelConfig{{Name: "OPTIONAL_1"}, {Name: "OPTIONAL_2"}},
|
||||
Forbidden: []KernelConfig{
|
||||
{Name: "FORBIDDEN_1", Description: "TEST FORBIDDEN"},
|
||||
{Name: "FORBIDDEN_2", Aliases: []string{"ALIASE_FORBIDDEN_2"}},
|
||||
},
|
||||
}
|
||||
for _, test := range []struct {
|
||||
desc string
|
||||
config map[string]kConfigOption
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
desc: "meet all required configurations should not report error",
|
||||
config: map[string]kConfigOption{
|
||||
"REQUIRED_1": builtIn,
|
||||
"REQUIRED_2": asModule,
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
desc: "one required configuration disabled should report error",
|
||||
config: map[string]kConfigOption{
|
||||
"REQUIRED_1": leftOut,
|
||||
"REQUIRED_2": builtIn,
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
desc: "one required configuration missing should report error",
|
||||
config: map[string]kConfigOption{
|
||||
"REQUIRED_1": builtIn,
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
desc: "alias of required configuration should not report error",
|
||||
config: map[string]kConfigOption{
|
||||
"REQUIRED_1": builtIn,
|
||||
"ALIASE_REQUIRED_2": asModule,
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
desc: "optional configuration set or not should not report error",
|
||||
config: map[string]kConfigOption{
|
||||
"REQUIRED_1": builtIn,
|
||||
"REQUIRED_2": asModule,
|
||||
"OPTIONAL_1": builtIn,
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
desc: "forbidden configuration disabled should not report error",
|
||||
config: map[string]kConfigOption{
|
||||
"REQUIRED_1": builtIn,
|
||||
"REQUIRED_2": asModule,
|
||||
"FORBIDDEN_1": leftOut,
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
desc: "forbidden configuration built-in should report error",
|
||||
config: map[string]kConfigOption{
|
||||
"REQUIRED_1": builtIn,
|
||||
"REQUIRED_2": asModule,
|
||||
"FORBIDDEN_1": builtIn,
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
desc: "forbidden configuration built as module should report error",
|
||||
config: map[string]kConfigOption{
|
||||
"REQUIRED_1": builtIn,
|
||||
"REQUIRED_2": asModule,
|
||||
"FORBIDDEN_1": asModule,
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
desc: "alias of forbidden configuration should report error",
|
||||
config: map[string]kConfigOption{
|
||||
"REQUIRED_1": builtIn,
|
||||
"REQUIRED_2": asModule,
|
||||
"ALIASE_FORBIDDEN_2": asModule,
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
} {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
// Add kernel config prefix.
|
||||
for k, v := range test.config {
|
||||
delete(test.config, k)
|
||||
test.config[kernelConfigPrefix+k] = v
|
||||
}
|
||||
err := v.validateCachedKernelConfig(test.config, testKernelSpec)
|
||||
if !test.err {
|
||||
assert.Nil(t, err, "Expect error not to occur with kernel config %q", test.config)
|
||||
} else {
|
||||
assert.NotNil(t, err, "Expect error to occur with kenrel config %q", test.config)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateParseKernelConfig(t *testing.T) {
|
||||
config := `CONFIG_1=y
|
||||
CONFIG_2=m
|
||||
CONFIG_3=n`
|
||||
expected := map[string]kConfigOption{
|
||||
"CONFIG_1": builtIn,
|
||||
"CONFIG_2": asModule,
|
||||
"CONFIG_3": leftOut,
|
||||
}
|
||||
v := &KernelValidator{
|
||||
Reporter: DefaultReporter,
|
||||
}
|
||||
got, err := v.parseKernelConfig(bytes.NewReader([]byte(config)))
|
||||
assert.Nil(t, err, "Expect error not to occur when parse kernel configuration %q", config)
|
||||
assert.Equal(t, expected, got)
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var _ Validator = &OSValidator{}
|
||||
|
||||
// OSValidator validates OS.
|
||||
type OSValidator struct {
|
||||
Reporter Reporter
|
||||
}
|
||||
|
||||
// Name is part of the system.Validator interface.
|
||||
func (o *OSValidator) Name() string {
|
||||
return "os"
|
||||
}
|
||||
|
||||
// Validate is part of the system.Validator interface.
|
||||
func (o *OSValidator) Validate(spec SysSpec) ([]error, []error) {
|
||||
os, err := exec.Command("uname").CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, []error{errors.Wrap(err, "failed to get os name")}
|
||||
}
|
||||
if err = o.validateOS(strings.TrimSpace(string(os)), spec.OS); err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (o *OSValidator) validateOS(os, specOS string) error {
|
||||
if os != specOS {
|
||||
o.Reporter.Report("OS", os, bad)
|
||||
return errors.Errorf("unsupported operating system: %s", os)
|
||||
}
|
||||
o.Reporter.Report("OS", os, good)
|
||||
return nil
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidateOS(t *testing.T) {
|
||||
v := &OSValidator{
|
||||
Reporter: DefaultReporter,
|
||||
}
|
||||
specOS := "Linux"
|
||||
for _, test := range []struct {
|
||||
os string
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
os: "Linux",
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
os: "Windows",
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
os: "Darwin",
|
||||
err: true,
|
||||
},
|
||||
} {
|
||||
t.Run(test.os, func(t *testing.T) {
|
||||
err := v.validateOS(test.os, specOS)
|
||||
if !test.err {
|
||||
assert.Nil(t, err, "Expect error not to occur with os %q", test.os)
|
||||
} else {
|
||||
assert.NotNil(t, err, "Expect error to occur with os %q", test.os)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,321 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// semVerDotsCount is the number of dots in a valid semantic version.
|
||||
const semVerDotsCount int = 2
|
||||
|
||||
// packageManager is an interface that abstracts the basic operations of a
|
||||
// package manager.
|
||||
type packageManager interface {
|
||||
// getPackageVersion returns the version of the package given the
|
||||
// packageName, or an error if no such package exists.
|
||||
getPackageVersion(packageName string) (string, error)
|
||||
}
|
||||
|
||||
// newPackageManager returns the package manager on the running machine, and an
|
||||
// error if no package managers is available.
|
||||
func newPackageManager() (packageManager, error) {
|
||||
if m, ok := newDPKG(); ok {
|
||||
return m, nil
|
||||
}
|
||||
return nil, errors.New("failed to find package manager")
|
||||
}
|
||||
|
||||
// dpkg implements packageManager. It uses "dpkg-query" to retrieve package
|
||||
// information.
|
||||
type dpkg struct{}
|
||||
|
||||
// newDPKG returns a Debian package manager. It returns (nil, false) if no such
|
||||
// package manager exists on the running machine.
|
||||
func newDPKG() (packageManager, bool) {
|
||||
_, err := exec.LookPath("dpkg-query")
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return dpkg{}, true
|
||||
}
|
||||
|
||||
// getPackageVersion returns the upstream package version for the package given
|
||||
// the packageName, and an error if no such package exists.
|
||||
func (dpkg) getPackageVersion(packageName string) (string, error) {
|
||||
output, err := exec.Command("dpkg-query", "--show", "--showformat='${Version}'", packageName).Output()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "dpkg-query failed")
|
||||
}
|
||||
version := extractUpstreamVersion(string(output))
|
||||
if version == "" {
|
||||
return "", errors.New("no version information")
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
// packageValidator implements the Validator interface. It validates packages
|
||||
// and their versions.
|
||||
type packageValidator struct {
|
||||
reporter Reporter
|
||||
kernelRelease string
|
||||
osDistro string
|
||||
}
|
||||
|
||||
// Name returns the name of the package validator.
|
||||
func (validator *packageValidator) Name() string {
|
||||
return "package"
|
||||
}
|
||||
|
||||
// Validate checks packages and their versions against the spec using the
|
||||
// package manager on the running machine, and returns an error on any
|
||||
// package/version mismatch.
|
||||
func (validator *packageValidator) Validate(spec SysSpec) ([]error, []error) {
|
||||
if len(spec.PackageSpecs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
if validator.kernelRelease, err = getKernelRelease(); err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
if validator.osDistro, err = getOSDistro(); err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
|
||||
manager, err := newPackageManager()
|
||||
if err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
specs := applyPackageSpecOverride(spec.PackageSpecs, spec.PackageSpecOverrides, validator.osDistro)
|
||||
return validator.validate(specs, manager)
|
||||
}
|
||||
|
||||
// Validate checks packages and their versions against the packageSpecs using
|
||||
// the packageManager, and returns an error on any package/version mismatch.
|
||||
func (validator *packageValidator) validate(packageSpecs []PackageSpec, manager packageManager) ([]error, []error) {
|
||||
var errs []error
|
||||
for _, spec := range packageSpecs {
|
||||
// Substitute variables in package name.
|
||||
packageName := resolvePackageName(spec.Name, validator.kernelRelease)
|
||||
|
||||
nameWithVerRange := fmt.Sprintf("%s (%s)", packageName, spec.VersionRange)
|
||||
|
||||
// Get the version of the package on the running machine.
|
||||
version, err := manager.getPackageVersion(packageName)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
validator.reporter.Report(nameWithVerRange, "not installed", bad)
|
||||
continue
|
||||
}
|
||||
|
||||
// Version requirement will not be enforced if version range is
|
||||
// not specified in the spec.
|
||||
if spec.VersionRange == "" {
|
||||
validator.reporter.Report(packageName, version, good)
|
||||
continue
|
||||
}
|
||||
|
||||
// Convert both the version range in the spec and the version returned
|
||||
// from package manager to semantic version format, and then check if
|
||||
// the version is in the range.
|
||||
sv, err := semver.Make(toSemVer(version))
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
validator.reporter.Report(nameWithVerRange, "internal error", bad)
|
||||
continue
|
||||
}
|
||||
versionRange := semver.MustParseRange(toSemVerRange(spec.VersionRange))
|
||||
if versionRange(sv) {
|
||||
validator.reporter.Report(nameWithVerRange, version, good)
|
||||
} else {
|
||||
errs = append(errs, errors.Errorf("package \"%s %s\" does not meet the spec \"%s (%s)\"", packageName, sv, packageName, spec.VersionRange))
|
||||
validator.reporter.Report(nameWithVerRange, version, bad)
|
||||
}
|
||||
}
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
// getKernelRelease returns the kernel release of the local machine.
|
||||
func getKernelRelease() (string, error) {
|
||||
output, err := exec.Command("uname", "-r").Output()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to get kernel release")
|
||||
}
|
||||
return strings.TrimSpace(string(output)), nil
|
||||
}
|
||||
|
||||
// getOSDistro returns the OS distro of the local machine.
|
||||
func getOSDistro() (string, error) {
|
||||
f := "/etc/lsb-release"
|
||||
b, err := ioutil.ReadFile(f)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to read %q", f)
|
||||
}
|
||||
content := string(b)
|
||||
switch {
|
||||
case strings.Contains(content, "Ubuntu"):
|
||||
return "ubuntu", nil
|
||||
case strings.Contains(content, "Chrome OS"):
|
||||
return "cos", nil
|
||||
case strings.Contains(content, "CoreOS"):
|
||||
return "coreos", nil
|
||||
default:
|
||||
return "", errors.Errorf("failed to get OS distro: %s", content)
|
||||
}
|
||||
}
|
||||
|
||||
// resolvePackageName substitutes the variables in the packageName with the
|
||||
// local information.
|
||||
// E.g., "linux-headers-${KERNEL_RELEASE}" -> "linux-headers-4.4.0-75-generic".
|
||||
func resolvePackageName(packageName string, kernelRelease string) string {
|
||||
packageName = strings.Replace(packageName, "${KERNEL_RELEASE}", kernelRelease, -1)
|
||||
return packageName
|
||||
}
|
||||
|
||||
// applyPackageSpecOverride applies the package spec overrides for the given
|
||||
// osDistro to the packageSpecs and returns the applied result.
|
||||
func applyPackageSpecOverride(packageSpecs []PackageSpec, overrides []PackageSpecOverride, osDistro string) []PackageSpec {
|
||||
var override *PackageSpecOverride
|
||||
for _, o := range overrides {
|
||||
if o.OSDistro == osDistro {
|
||||
override = &o
|
||||
break
|
||||
}
|
||||
}
|
||||
if override == nil {
|
||||
return packageSpecs
|
||||
}
|
||||
|
||||
// Remove packages in the spec that matches the overrides in
|
||||
// Subtractions.
|
||||
var out []PackageSpec
|
||||
subtractions := make(map[string]bool)
|
||||
for _, spec := range override.Subtractions {
|
||||
subtractions[spec.Name] = true
|
||||
}
|
||||
for _, spec := range packageSpecs {
|
||||
if _, ok := subtractions[spec.Name]; !ok {
|
||||
out = append(out, spec)
|
||||
}
|
||||
}
|
||||
|
||||
// Add packages in the spec that matches the overrides in Additions.
|
||||
return append(out, override.Additions...)
|
||||
}
|
||||
|
||||
// extractUpstreamVersion returns the upstream version of the given full
|
||||
// version in dpkg format. E.g., "1:1.0.6-2ubuntu2.1" -> "1.0.6".
|
||||
func extractUpstreamVersion(version string) string {
|
||||
// The full version is in the format of
|
||||
// "[epoch:]upstream_version[-debian_revision]". See
|
||||
// https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version.
|
||||
version = strings.Trim(version, " '")
|
||||
if i := strings.Index(version, ":"); i != -1 {
|
||||
version = version[i+1:]
|
||||
}
|
||||
if i := strings.Index(version, "-"); i != -1 {
|
||||
version = version[:i]
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
// toSemVerRange converts the input to a semantic version range.
|
||||
// E.g., ">=1.0" -> ">=1.0.x"
|
||||
// ">=1" -> ">=1.x"
|
||||
// ">=1 <=2.3" -> ">=1.x <=2.3.x"
|
||||
// ">1 || >3.1.0 !4.2" -> ">1.x || >3.1.0 !4.2.x"
|
||||
func toSemVerRange(input string) string {
|
||||
var output []string
|
||||
fields := strings.Fields(input)
|
||||
for _, f := range fields {
|
||||
numDots, hasDigits := 0, false
|
||||
for _, c := range f {
|
||||
switch {
|
||||
case c == '.':
|
||||
numDots++
|
||||
case c >= '0' && c <= '9':
|
||||
hasDigits = true
|
||||
}
|
||||
}
|
||||
if hasDigits && numDots < semVerDotsCount {
|
||||
f = strings.TrimRight(f, " ")
|
||||
f += ".x"
|
||||
}
|
||||
output = append(output, f)
|
||||
}
|
||||
return strings.Join(output, " ")
|
||||
}
|
||||
|
||||
// toSemVer converts the input to a semantic version, and an empty string on
|
||||
// error.
|
||||
func toSemVer(version string) string {
|
||||
// Remove the first non-digit and non-dot character as well as the ones
|
||||
// following it.
|
||||
// E.g., "1.8.19p1" -> "1.8.19".
|
||||
if i := strings.IndexFunc(version, func(c rune) bool {
|
||||
if (c < '0' || c > '9') && c != '.' {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}); i != -1 {
|
||||
version = version[:i]
|
||||
}
|
||||
|
||||
// Remove the trailing dots if there's any, and then returns an empty
|
||||
// string if nothing left.
|
||||
version = strings.TrimRight(version, ".")
|
||||
if version == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
numDots := strings.Count(version, ".")
|
||||
switch {
|
||||
case numDots < semVerDotsCount:
|
||||
// Add minor version and patch version.
|
||||
// E.g. "1.18" -> "1.18.0" and "481" -> "481.0.0".
|
||||
version += strings.Repeat(".0", semVerDotsCount-numDots)
|
||||
case numDots > semVerDotsCount:
|
||||
// Remove anything beyond the patch version
|
||||
// E.g. "2.0.10.4" -> "2.0.10".
|
||||
for numDots != semVerDotsCount {
|
||||
if i := strings.LastIndex(version, "."); i != -1 {
|
||||
version = version[:i]
|
||||
numDots--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove leading zeros in major/minor/patch version.
|
||||
// E.g., "2.02" -> "2.2"
|
||||
// "8.0.0095" -> "8.0.95"
|
||||
var subs []string
|
||||
for _, s := range strings.Split(version, ".") {
|
||||
s := strings.TrimLeft(s, "0")
|
||||
if s == "" {
|
||||
s = "0"
|
||||
}
|
||||
subs = append(subs, s)
|
||||
}
|
||||
return strings.Join(subs, ".")
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func TestExtractUpstreamVersion(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
input: "1.0.6",
|
||||
expected: "1.0.6",
|
||||
},
|
||||
{
|
||||
input: "1:1.0.6",
|
||||
expected: "1.0.6",
|
||||
},
|
||||
{
|
||||
input: "1.0.6-2ubuntu2.1",
|
||||
expected: "1.0.6",
|
||||
},
|
||||
{
|
||||
input: "1:1.0.6-2ubuntu2.1",
|
||||
expected: "1.0.6",
|
||||
},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("input:%s,expected:%s", test.input, test.expected), func(t *testing.T) {
|
||||
got := extractUpstreamVersion(test.input)
|
||||
if test.expected != got {
|
||||
t.Errorf("extractUpstreamVersion(%q) = %q, want %q", test.input, got, test.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToSemVer(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
input: "1.2.3",
|
||||
expected: "1.2.3",
|
||||
},
|
||||
{
|
||||
input: "1.8.19p1",
|
||||
expected: "1.8.19",
|
||||
},
|
||||
{
|
||||
input: "1.8.19.p1",
|
||||
expected: "1.8.19",
|
||||
},
|
||||
{
|
||||
input: "p1",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
input: "1.18",
|
||||
expected: "1.18.0",
|
||||
},
|
||||
{
|
||||
input: "481",
|
||||
expected: "481.0.0",
|
||||
},
|
||||
{
|
||||
input: "2.0.10.4",
|
||||
expected: "2.0.10",
|
||||
},
|
||||
{
|
||||
input: "03",
|
||||
expected: "3.0.0",
|
||||
},
|
||||
{
|
||||
input: "2.02",
|
||||
expected: "2.2.0",
|
||||
},
|
||||
{
|
||||
input: "8.0.0095",
|
||||
expected: "8.0.95",
|
||||
},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("input:%s,expected:%s", test.input, test.expected), func(t *testing.T) {
|
||||
got := toSemVer(test.input)
|
||||
if test.expected != got {
|
||||
t.Errorf("toSemVer(%q) = %q, want %q", test.input, got, test.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToSemVerRange(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
input: ">=1.0.0",
|
||||
expected: ">=1.0.0",
|
||||
},
|
||||
{
|
||||
input: ">=1.0",
|
||||
expected: ">=1.0.x",
|
||||
},
|
||||
{
|
||||
input: ">=1",
|
||||
expected: ">=1.x",
|
||||
},
|
||||
{
|
||||
input: ">=1 || !2.3",
|
||||
expected: ">=1.x || !2.3.x",
|
||||
},
|
||||
{
|
||||
input: ">1 || >3.1.0 !4.2",
|
||||
expected: ">1.x || >3.1.0 !4.2.x",
|
||||
},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("input:%s,expected:%s", test.input, test.expected), func(t *testing.T) {
|
||||
got := toSemVerRange(test.input)
|
||||
if test.expected != got {
|
||||
t.Errorf("toSemVerRange(%q) = %q, want %q", test.input, got, test.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// testPackageManager implements the packageManager interface.
|
||||
type testPackageManager struct {
|
||||
packageVersions map[string]string
|
||||
}
|
||||
|
||||
func (m testPackageManager) getPackageVersion(packageName string) (string, error) {
|
||||
if v, ok := m.packageVersions[packageName]; ok {
|
||||
return v, nil
|
||||
}
|
||||
return "", errors.Errorf("package %q does not exist", packageName)
|
||||
}
|
||||
|
||||
func TestValidatePackageVersion(t *testing.T) {
|
||||
testKernelRelease := "test-kernel-release"
|
||||
manager := testPackageManager{
|
||||
packageVersions: map[string]string{
|
||||
"foo": "1.0.0",
|
||||
"bar": "2.1.0",
|
||||
"bar-" + testKernelRelease: "3.0.0",
|
||||
},
|
||||
}
|
||||
v := &packageValidator{
|
||||
reporter: DefaultReporter,
|
||||
kernelRelease: testKernelRelease,
|
||||
}
|
||||
for _, test := range []struct {
|
||||
desc string
|
||||
specs []PackageSpec
|
||||
errs []error
|
||||
}{
|
||||
{
|
||||
desc: "all packages meet the spec",
|
||||
specs: []PackageSpec{
|
||||
{Name: "foo", VersionRange: ">=1.0"},
|
||||
{Name: "bar", VersionRange: ">=2.0 <= 3.0"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "package version does not meet the spec",
|
||||
specs: []PackageSpec{
|
||||
{Name: "foo", VersionRange: ">=1.0"},
|
||||
{Name: "bar", VersionRange: ">=3.0"},
|
||||
},
|
||||
errs: []error{
|
||||
errors.New("package \"bar 2.1.0\" does not meet the spec \"bar (>=3.0)\""),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "package not installed",
|
||||
specs: []PackageSpec{
|
||||
{Name: "baz"},
|
||||
},
|
||||
errs: []error{
|
||||
errors.New("package \"baz\" does not exist"),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "use variable in package name",
|
||||
specs: []PackageSpec{
|
||||
{Name: "bar-${KERNEL_RELEASE}", VersionRange: ">=3.0"},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
_, errs := v.validate(test.specs, manager)
|
||||
if len(test.errs) == 0 && len(errs) > 0 {
|
||||
t.Errorf("%s: v.validate(): err = %s", test.desc, errs)
|
||||
}
|
||||
if len(test.errs) > 0 {
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("%s: v.validate() is expected to fail.", test.desc)
|
||||
} else if len(errs) != len(test.errs) {
|
||||
t.Errorf("%s: v.validate(): errs = %q, want = %q", test.desc, errs, test.errs)
|
||||
} else {
|
||||
for i, v := range test.errs {
|
||||
if v.Error() != errs[i].Error() {
|
||||
t.Errorf("%s: v.validate(): errs = %q, want = %q", test.desc, errs, test.errs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyPackageOverride(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
overrides []PackageSpecOverride
|
||||
osDistro string
|
||||
specs []PackageSpec
|
||||
expected []PackageSpec
|
||||
}{
|
||||
{
|
||||
name: "foo>=1.0",
|
||||
specs: []PackageSpec{{Name: "foo", VersionRange: ">=1.0"}},
|
||||
expected: []PackageSpec{{Name: "foo", VersionRange: ">=1.0"}},
|
||||
},
|
||||
{
|
||||
name: "ubuntu:foo>=1.0/bar>=2.0",
|
||||
osDistro: "ubuntu",
|
||||
overrides: []PackageSpecOverride{
|
||||
{
|
||||
OSDistro: "ubuntu",
|
||||
Subtractions: []PackageSpec{{Name: "foo"}},
|
||||
Additions: []PackageSpec{{Name: "bar", VersionRange: ">=2.0"}},
|
||||
},
|
||||
},
|
||||
specs: []PackageSpec{{Name: "foo", VersionRange: ">=1.0"}},
|
||||
expected: []PackageSpec{{Name: "bar", VersionRange: ">=2.0"}},
|
||||
},
|
||||
{
|
||||
name: "ubuntu:foo>=1.0/debian:foo",
|
||||
osDistro: "ubuntu",
|
||||
overrides: []PackageSpecOverride{
|
||||
{
|
||||
OSDistro: "debian",
|
||||
Subtractions: []PackageSpec{{Name: "foo"}},
|
||||
},
|
||||
},
|
||||
specs: []PackageSpec{{Name: "foo", VersionRange: ">=1.0"}},
|
||||
expected: []PackageSpec{{Name: "foo", VersionRange: ">=1.0"}},
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got := applyPackageSpecOverride(test.specs, test.overrides, test.osDistro)
|
||||
if !reflect.DeepEqual(test.expected, got) {
|
||||
t.Errorf("applyPackageSpecOverride(%+v, %+v, %s) = %+v, want = %+v", test.specs, test.overrides, test.osDistro, got, test.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ValidationResultType is type of the validation result. Different validation results
|
||||
// corresponds to different colors.
|
||||
type ValidationResultType int32
|
||||
|
||||
const (
|
||||
good ValidationResultType = iota
|
||||
bad
|
||||
warn
|
||||
)
|
||||
|
||||
// color is the color of the message.
|
||||
type color int32
|
||||
|
||||
const (
|
||||
red color = 31
|
||||
green color = 32
|
||||
yellow color = 33
|
||||
white color = 37
|
||||
)
|
||||
|
||||
func colorize(s string, c color) string {
|
||||
return fmt.Sprintf("\033[0;%dm%s\033[0m", c, s)
|
||||
}
|
||||
|
||||
// StreamReporter is the default reporter for the system verification test.
|
||||
type StreamReporter struct {
|
||||
// The stream that this reporter is writing to
|
||||
WriteStream io.Writer
|
||||
}
|
||||
|
||||
// Report reports validation result in different color depending on the result type.
|
||||
func (dr *StreamReporter) Report(key, value string, resultType ValidationResultType) error {
|
||||
var c color
|
||||
switch resultType {
|
||||
case good:
|
||||
c = green
|
||||
case bad:
|
||||
c = red
|
||||
case warn:
|
||||
c = yellow
|
||||
default:
|
||||
c = white
|
||||
}
|
||||
if dr.WriteStream == nil {
|
||||
return errors.New("WriteStream has to be defined for this reporter")
|
||||
}
|
||||
|
||||
fmt.Fprintf(dr.WriteStream, "%s: %s\n", colorize(key, white), colorize(value, c))
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultReporter is the default Reporter
|
||||
var DefaultReporter = &StreamReporter{
|
||||
WriteStream: os.Stdout,
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package system
|
||||
|
||||
// KernelConfig defines one kernel configuration item.
|
||||
type KernelConfig struct {
|
||||
// Name is the general name of the kernel configuration. It is used to
|
||||
// match kernel configuration.
|
||||
Name string `json:"name,omitempty"`
|
||||
// TODO(yguo0905): Support the "or" operation, which will be the same
|
||||
// as the "aliases".
|
||||
//
|
||||
// Aliases are aliases of the kernel configuration. Some configuration
|
||||
// has different names in different kernel version. Names of different
|
||||
// versions will be treated as aliases.
|
||||
Aliases []string `json:"aliases,omitempty"`
|
||||
// Description is the description of the kernel configuration, for example:
|
||||
// * What is it used for?
|
||||
// * Why is it needed?
|
||||
// * Who needs it?
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
// KernelSpec defines the specification for the kernel. Currently, it contains
|
||||
// specification for:
|
||||
// * Kernel Version
|
||||
// * Kernel Configuration
|
||||
type KernelSpec struct {
|
||||
// Versions define supported kernel version. It is a group of regexps.
|
||||
Versions []string `json:"versions,omitempty"`
|
||||
// Required contains all kernel configurations required to be enabled
|
||||
// (built in or as module).
|
||||
Required []KernelConfig `json:"required,omitempty"`
|
||||
// Optional contains all kernel configurations are required for optional
|
||||
// features.
|
||||
Optional []KernelConfig `json:"optional,omitempty"`
|
||||
// Forbidden contains all kernel configurations which areforbidden (disabled
|
||||
// or not set)
|
||||
Forbidden []KernelConfig `json:"forbidden,omitempty"`
|
||||
}
|
||||
|
||||
// DockerSpec defines the requirement configuration for docker. Currently, it only
|
||||
// contains spec for graph driver.
|
||||
type DockerSpec struct {
|
||||
// Version is a group of regex matching supported docker versions.
|
||||
Version []string `json:"version,omitempty"`
|
||||
// GraphDriver is the graph drivers supported by kubelet.
|
||||
GraphDriver []string `json:"graphDriver,omitempty"`
|
||||
}
|
||||
|
||||
// RuntimeSpec is the abstract layer for different runtimes. Different runtimes
|
||||
// should put their spec inside the RuntimeSpec.
|
||||
type RuntimeSpec struct {
|
||||
*DockerSpec `json:",inline"`
|
||||
}
|
||||
|
||||
// PackageSpec defines the required packages and their versions.
|
||||
// PackageSpec is only supported on OS distro with Debian package manager.
|
||||
//
|
||||
// TODO(yguo0905): Support operator OR of multiple packages for the case where
|
||||
// either "foo (>=1.0)" or "bar (>=2.0)" is required.
|
||||
type PackageSpec struct {
|
||||
// Name is the name of the package to be checked.
|
||||
Name string `json:"name,omitempty"`
|
||||
// VersionRange represents a range of versions that the package must
|
||||
// satisfy. Note that the version requirement will not be enforced if
|
||||
// the version range is empty. For example,
|
||||
// - "" would match any versions but the package must be installed.
|
||||
// - ">=1" would match "1.0.0", "1.0.1", "1.1.0", and "2.0".
|
||||
// - ">1.0 <2.0" would match between both ranges, so "1.1.1" and "1.8.7"
|
||||
// but not "1.0.0" or "2.0.0".
|
||||
// - "<2.0.0 || >=3.0.0" would match "1.0.0" and "3.0.0" but not "2.0.0".
|
||||
VersionRange string `json:"versionRange,omitempty"`
|
||||
// Description explains the reason behind this package requirements.
|
||||
//
|
||||
// TODO(yguo0905): Print the description where necessary.
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
// PackageSpecOverride defines the overrides on the PackageSpec for an OS
|
||||
// distro.
|
||||
type PackageSpecOverride struct {
|
||||
// OSDistro identifies to which OS distro this override applies.
|
||||
// Must be "ubuntu", "cos" or "coreos".
|
||||
OSDistro string `json:"osDistro,omitempty"`
|
||||
// Subtractions is a list of package names that are excluded from the
|
||||
// package spec.
|
||||
Subtractions []PackageSpec `json:"subtractions,omitempty"`
|
||||
// Additions is a list of additional package requirements included the
|
||||
// package spec.
|
||||
Additions []PackageSpec `json:"additions,omitempty"`
|
||||
}
|
||||
|
||||
// SysSpec defines the requirement of supported system. Currently, it only contains
|
||||
// spec for OS, Kernel and Cgroups.
|
||||
type SysSpec struct {
|
||||
// OS is the operating system of the SysSpec.
|
||||
OS string `json:"os,omitempty"`
|
||||
// KernelConfig defines the spec for kernel.
|
||||
KernelSpec KernelSpec `json:"kernelSpec,omitempty"`
|
||||
// Cgroups is the required cgroups.
|
||||
Cgroups []string `json:"cgroups,omitempty"`
|
||||
// RuntimeSpec defines the spec for runtime.
|
||||
RuntimeSpec RuntimeSpec `json:"runtimeSpec,omitempty"`
|
||||
// PackageSpec defines the required packages and their versions.
|
||||
PackageSpecs []PackageSpec `json:"packageSpecs,omitempty"`
|
||||
// PackageSpec defines the overrides of the required packages and their
|
||||
// versions for an OS distro.
|
||||
PackageSpecOverrides []PackageSpecOverride `json:"packageSpecOverrides,omitempty"`
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DefaultSysSpec is the default SysSpec for Linux
|
||||
var DefaultSysSpec = SysSpec{
|
||||
OS: "Linux",
|
||||
KernelSpec: KernelSpec{
|
||||
Versions: []string{`^3\.[1-9][0-9].*$`, `^([4-9]|[1-9][0-9]+)\.([0-9]+)\.([0-9]+).*$`}, // Requires 3.10+, or newer
|
||||
// TODO(random-liu): Add more config
|
||||
// TODO(random-liu): Add description for each kernel configuration:
|
||||
Required: []KernelConfig{
|
||||
{Name: "NAMESPACES"},
|
||||
{Name: "NET_NS"},
|
||||
{Name: "PID_NS"},
|
||||
{Name: "IPC_NS"},
|
||||
{Name: "UTS_NS"},
|
||||
{Name: "CGROUPS"},
|
||||
{Name: "CGROUP_CPUACCT"},
|
||||
{Name: "CGROUP_DEVICE"},
|
||||
{Name: "CGROUP_FREEZER"},
|
||||
{Name: "CGROUP_SCHED"},
|
||||
{Name: "CPUSETS"},
|
||||
{Name: "MEMCG"},
|
||||
{Name: "INET"},
|
||||
{Name: "EXT4_FS"},
|
||||
{Name: "PROC_FS"},
|
||||
{Name: "NETFILTER_XT_TARGET_REDIRECT", Aliases: []string{"IP_NF_TARGET_REDIRECT"}},
|
||||
{Name: "NETFILTER_XT_MATCH_COMMENT"},
|
||||
},
|
||||
Optional: []KernelConfig{
|
||||
{Name: "OVERLAY_FS", Aliases: []string{"OVERLAYFS_FS"}, Description: "Required for overlayfs."},
|
||||
{Name: "AUFS_FS", Description: "Required for aufs."},
|
||||
{Name: "BLK_DEV_DM", Description: "Required for devicemapper."},
|
||||
},
|
||||
Forbidden: []KernelConfig{},
|
||||
},
|
||||
Cgroups: []string{"cpu", "cpuacct", "cpuset", "devices", "freezer", "memory"},
|
||||
RuntimeSpec: RuntimeSpec{
|
||||
DockerSpec: &DockerSpec{
|
||||
Version: []string{`1\.1[1-3]\..*`, `17\.0[3,6,9]\..*`, `18\.0[6,9]\..*`, `19\.03\..*`},
|
||||
GraphDriver: []string{"aufs", "overlay", "overlay2", "devicemapper", "zfs"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// KernelValidatorHelperImpl is the 'linux' implementation of KernelValidatorHelper
|
||||
type KernelValidatorHelperImpl struct{}
|
||||
|
||||
var _ KernelValidatorHelper = &KernelValidatorHelperImpl{}
|
||||
|
||||
// GetKernelReleaseVersion returns the kernel release version (ex. 4.4.0-96-generic) as a string
|
||||
func (o *KernelValidatorHelperImpl) GetKernelReleaseVersion() (string, error) {
|
||||
releaseVersion, err := exec.Command("uname", "-r").CombinedOutput()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(releaseVersion)), nil
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DefaultSysSpec is the default SysSpec for Windows
|
||||
var DefaultSysSpec = SysSpec{
|
||||
OS: "Microsoft Windows Server 2016",
|
||||
KernelSpec: KernelSpec{
|
||||
Versions: []string{`10\.0\.1439[3-9]`, `10\.0\.14[4-9][0-9]{2}`, `10\.0\.1[5-9][0-9]{3}`, `10\.0\.[2-9][0-9]{4}`, `10\.[1-9]+\.[0-9]+`}, //requires >= '10.0.14393'
|
||||
Required: []KernelConfig{},
|
||||
Optional: []KernelConfig{},
|
||||
Forbidden: []KernelConfig{},
|
||||
},
|
||||
Cgroups: []string{},
|
||||
RuntimeSpec: RuntimeSpec{
|
||||
DockerSpec: &DockerSpec{
|
||||
Version: []string{`18\.0[6,9]\..*`},
|
||||
GraphDriver: []string{"windowsfilter"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// KernelValidatorHelperImpl is the 'windows' implementation of KernelValidatorHelper
|
||||
type KernelValidatorHelperImpl struct{}
|
||||
|
||||
var _ KernelValidatorHelper = &KernelValidatorHelperImpl{}
|
||||
|
||||
// GetKernelReleaseVersion returns the windows release version (ex. 10.0.14393) as a string
|
||||
func (o *KernelValidatorHelperImpl) GetKernelReleaseVersion() (string, error) {
|
||||
args := []string{"(Get-CimInstance Win32_OperatingSystem).Version"}
|
||||
releaseVersion, err := exec.Command("powershell", args...).Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(releaseVersion)), nil
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Validator is the interface for all validators.
|
||||
type Validator interface {
|
||||
// Name is the name of the validator.
|
||||
Name() string
|
||||
// Validate is the validate function.
|
||||
Validate(SysSpec) ([]error, []error)
|
||||
}
|
||||
|
||||
// Reporter is the interface for the reporters for the validators.
|
||||
type Reporter interface {
|
||||
// Report reports the results of the system verification
|
||||
Report(string, string, ValidationResultType) error
|
||||
}
|
||||
|
||||
// Validate uses validators to validate the system and returns a warning or error.
|
||||
func Validate(spec SysSpec, validators []Validator) ([]error, []error) {
|
||||
var errs []error
|
||||
var warns []error
|
||||
|
||||
for _, v := range validators {
|
||||
fmt.Printf("Validating %s...\n", v.Name())
|
||||
warn, err := v.Validate(spec)
|
||||
if len(err) != 0 {
|
||||
errs = append(errs, err...)
|
||||
}
|
||||
if len(warn) != 0 {
|
||||
warns = append(warns, warn...)
|
||||
}
|
||||
}
|
||||
return warns, errs
|
||||
}
|
||||
|
||||
// ValidateSpec uses all default validators to validate the system and writes to stdout.
|
||||
func ValidateSpec(spec SysSpec, runtime string) ([]error, []error) {
|
||||
// OS-level validators.
|
||||
var osValidators = []Validator{
|
||||
&OSValidator{Reporter: DefaultReporter},
|
||||
&KernelValidator{Reporter: DefaultReporter},
|
||||
&CgroupsValidator{Reporter: DefaultReporter},
|
||||
&packageValidator{reporter: DefaultReporter},
|
||||
}
|
||||
// Docker-specific validators.
|
||||
var dockerValidators = []Validator{
|
||||
&DockerValidator{Reporter: DefaultReporter},
|
||||
}
|
||||
|
||||
validators := osValidators
|
||||
switch runtime {
|
||||
case "docker":
|
||||
validators = append(validators, dockerValidators...)
|
||||
}
|
||||
return Validate(spec, validators)
|
||||
}
|
||||
Reference in New Issue
Block a user