ext: namespace detector -> featurens
This commit is contained in:
parent
d9be34c3c4
commit
fb193e1fde
@ -26,6 +26,11 @@ import (
|
||||
"github.com/coreos/clair/config"
|
||||
|
||||
// Register extensions.
|
||||
_ "github.com/coreos/clair/ext/featurens/alpinerelease"
|
||||
_ "github.com/coreos/clair/ext/featurens/aptsources"
|
||||
_ "github.com/coreos/clair/ext/featurens/lsbrelease"
|
||||
_ "github.com/coreos/clair/ext/featurens/osrelease"
|
||||
_ "github.com/coreos/clair/ext/featurens/redhatrelease"
|
||||
_ "github.com/coreos/clair/ext/imagefmt/aci"
|
||||
_ "github.com/coreos/clair/ext/imagefmt/docker"
|
||||
_ "github.com/coreos/clair/ext/notification/webhook"
|
||||
@ -40,12 +45,6 @@ import (
|
||||
_ "github.com/coreos/clair/worker/detectors/feature/dpkg"
|
||||
_ "github.com/coreos/clair/worker/detectors/feature/rpm"
|
||||
|
||||
_ "github.com/coreos/clair/worker/detectors/namespace/alpinerelease"
|
||||
_ "github.com/coreos/clair/worker/detectors/namespace/aptsources"
|
||||
_ "github.com/coreos/clair/worker/detectors/namespace/lsbrelease"
|
||||
_ "github.com/coreos/clair/worker/detectors/namespace/osrelease"
|
||||
_ "github.com/coreos/clair/worker/detectors/namespace/redhatrelease"
|
||||
|
||||
_ "github.com/coreos/clair/database/pgsql"
|
||||
)
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2016 clair authors
|
||||
// Copyright 2017 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -12,6 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package alpinerelease implements a featurens.Detector for Alpine Linux based
|
||||
// container image layers.
|
||||
package alpinerelease
|
||||
|
||||
import (
|
||||
@ -21,8 +23,9 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/featurens"
|
||||
"github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||
"github.com/coreos/clair/worker/detectors"
|
||||
"github.com/coreos/clair/pkg/tarutil"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -33,15 +36,13 @@ const (
|
||||
var versionRegexp = regexp.MustCompile(`^(\d)+\.(\d)+\.(\d)+$`)
|
||||
|
||||
func init() {
|
||||
detectors.RegisterNamespaceDetector("alpine-release", &detector{})
|
||||
featurens.RegisterDetector("alpine-release", &detector{})
|
||||
}
|
||||
|
||||
// detector implements NamespaceDetector by reading the current version of
|
||||
// Alpine Linux from /etc/alpine-release.
|
||||
type detector struct{}
|
||||
|
||||
func (d *detector) Detect(data map[string][]byte) *database.Namespace {
|
||||
file, exists := data[alpineReleasePath]
|
||||
func (d detector) Detect(files tarutil.FilesMap) (*database.Namespace, error) {
|
||||
file, exists := files[alpineReleasePath]
|
||||
if exists {
|
||||
scanner := bufio.NewScanner(bytes.NewBuffer(file))
|
||||
for scanner.Scan() {
|
||||
@ -52,14 +53,14 @@ func (d *detector) Detect(data map[string][]byte) *database.Namespace {
|
||||
return &database.Namespace{
|
||||
Name: osName + ":" + "v" + versionNumbers[0] + "." + versionNumbers[1],
|
||||
VersionFormat: dpkg.ParserName,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d *detector) GetRequiredFiles() []string {
|
||||
func (d detector) RequiredFilenames() []string {
|
||||
return []string{alpineReleasePath}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2016 clair authors
|
||||
// Copyright 2017 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -18,34 +18,35 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/worker/detectors/namespace"
|
||||
"github.com/coreos/clair/ext/featurens"
|
||||
"github.com/coreos/clair/pkg/tarutil"
|
||||
)
|
||||
|
||||
func TestAlpineReleaseNamespaceDetection(t *testing.T) {
|
||||
testData := []namespace.TestData{
|
||||
func TestDetector(t *testing.T) {
|
||||
testData := []featurens.TestData{
|
||||
{
|
||||
ExpectedNamespace: &database.Namespace{Name: "alpine:v3.3"},
|
||||
Data: map[string][]byte{"etc/alpine-release": []byte(`3.3.4`)},
|
||||
Files: tarutil.FilesMap{"etc/alpine-release": []byte(`3.3.4`)},
|
||||
},
|
||||
{
|
||||
ExpectedNamespace: &database.Namespace{Name: "alpine:v3.4"},
|
||||
Data: map[string][]byte{"etc/alpine-release": []byte(`3.4.0`)},
|
||||
Files: tarutil.FilesMap{"etc/alpine-release": []byte(`3.4.0`)},
|
||||
},
|
||||
{
|
||||
ExpectedNamespace: &database.Namespace{Name: "alpine:v0.3"},
|
||||
Data: map[string][]byte{"etc/alpine-release": []byte(`0.3.4`)},
|
||||
Files: tarutil.FilesMap{"etc/alpine-release": []byte(`0.3.4`)},
|
||||
},
|
||||
{
|
||||
ExpectedNamespace: &database.Namespace{Name: "alpine:v0.3"},
|
||||
Data: map[string][]byte{"etc/alpine-release": []byte(`
|
||||
Files: tarutil.FilesMap{"etc/alpine-release": []byte(`
|
||||
0.3.4
|
||||
`)},
|
||||
},
|
||||
{
|
||||
ExpectedNamespace: nil,
|
||||
Data: map[string][]byte{},
|
||||
Files: tarutil.FilesMap{},
|
||||
},
|
||||
}
|
||||
|
||||
namespace.TestDetector(t, &detector{}, testData)
|
||||
featurens.TestDetector(t, &detector{}, testData)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2017 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -12,6 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package aptsources implements a featurens.Detector for apt based container
|
||||
// image layers.
|
||||
//
|
||||
// This detector is necessary to determine the precise Debian version when it
|
||||
// is an unstable version for instance.
|
||||
package aptsources
|
||||
|
||||
import (
|
||||
@ -19,25 +24,21 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/featurens"
|
||||
"github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||
"github.com/coreos/clair/worker/detectors"
|
||||
"github.com/coreos/clair/pkg/tarutil"
|
||||
)
|
||||
|
||||
// AptSourcesNamespaceDetector implements NamespaceDetector and detects the Namespace from the
|
||||
// /etc/apt/sources.list file.
|
||||
//
|
||||
// This detector is necessary to determine precise Debian version when it is
|
||||
// an unstable version for instance.
|
||||
type AptSourcesNamespaceDetector struct{}
|
||||
type detector struct{}
|
||||
|
||||
func init() {
|
||||
detectors.RegisterNamespaceDetector("apt-sources", &AptSourcesNamespaceDetector{})
|
||||
featurens.RegisterDetector("apt-sources", &detector{})
|
||||
}
|
||||
|
||||
func (detector *AptSourcesNamespaceDetector) Detect(data map[string][]byte) *database.Namespace {
|
||||
f, hasFile := data["etc/apt/sources.list"]
|
||||
func (d detector) Detect(files tarutil.FilesMap) (*database.Namespace, error) {
|
||||
f, hasFile := files["etc/apt/sources.list"]
|
||||
if !hasFile {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var OS, version string
|
||||
@ -79,11 +80,11 @@ func (detector *AptSourcesNamespaceDetector) Detect(data map[string][]byte) *dat
|
||||
return &database.Namespace{
|
||||
Name: OS + ":" + version,
|
||||
VersionFormat: dpkg.ParserName,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (detector *AptSourcesNamespaceDetector) GetRequiredFiles() []string {
|
||||
func (d detector) RequiredFilenames() []string {
|
||||
return []string{"etc/apt/sources.list"}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2016 clair authors
|
||||
// Copyright 2017 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -18,14 +18,15 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/worker/detectors/namespace"
|
||||
"github.com/coreos/clair/ext/featurens"
|
||||
"github.com/coreos/clair/pkg/tarutil"
|
||||
)
|
||||
|
||||
func TestAptSourcesNamespaceDetector(t *testing.T) {
|
||||
testData := []namespace.TestData{
|
||||
func TestDetector(t *testing.T) {
|
||||
testData := []featurens.TestData{
|
||||
{
|
||||
ExpectedNamespace: &database.Namespace{Name: "debian:unstable"},
|
||||
Data: map[string][]byte{
|
||||
Files: tarutil.FilesMap{
|
||||
"etc/os-release": []byte(
|
||||
`PRETTY_NAME="Debian GNU/Linux stretch/sid"
|
||||
NAME="Debian GNU/Linux"
|
||||
@ -38,5 +39,5 @@ BUG_REPORT_URL="https://bugs.debian.org/"`),
|
||||
},
|
||||
}
|
||||
|
||||
namespace.TestDetector(t, &AptSourcesNamespaceDetector{}, testData)
|
||||
featurens.TestDetector(t, &detector{}, testData)
|
||||
}
|
124
ext/featurens/driver.go
Normal file
124
ext/featurens/driver.go
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright 2017 clair 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 featurens exposes functions to dynamically register methods for
|
||||
// determining a namespace for features present in an image layer.
|
||||
package featurens
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/pkg/tarutil"
|
||||
)
|
||||
|
||||
var (
|
||||
log = capnslog.NewPackageLogger("github.com/coreos/clair", "ext/featurens")
|
||||
|
||||
detectorsM sync.RWMutex
|
||||
detectors = make(map[string]Detector)
|
||||
)
|
||||
|
||||
// Detector represents an ability to detect a namespace used for organizing
|
||||
// features present in an image layer.
|
||||
type Detector interface {
|
||||
// Detect attempts to determine a Namespace from a FilesMap of an image
|
||||
// layer.
|
||||
Detect(tarutil.FilesMap) (*database.Namespace, error)
|
||||
|
||||
// RequireFilenames returns the list of files required to be in the FilesMap
|
||||
// provided to the Detect method.
|
||||
// TODO(jzelinskie): strip "/" prefix
|
||||
RequiredFilenames() []string
|
||||
}
|
||||
|
||||
// RegisterDetector makes a detector available by the provided name.
|
||||
//
|
||||
// If called twice with the same name, the name is blank, or if the provided
|
||||
// Detector is nil, this function panics.
|
||||
func RegisterDetector(name string, d Detector) {
|
||||
if name == "" {
|
||||
panic("namespace: could not register a Detector with an empty name")
|
||||
}
|
||||
if d == nil {
|
||||
panic("namespace: could not register a nil Detector")
|
||||
}
|
||||
|
||||
detectorsM.Lock()
|
||||
defer detectorsM.Unlock()
|
||||
|
||||
if _, dup := detectors[name]; dup {
|
||||
panic("namespace: RegisterDetector called twice for " + name)
|
||||
}
|
||||
|
||||
detectors[name] = d
|
||||
}
|
||||
|
||||
// Detect iterators through all registered Detectors and returns the first
|
||||
// non-nil detected namespace.
|
||||
func Detect(files tarutil.FilesMap) (*database.Namespace, error) {
|
||||
detectorsM.RLock()
|
||||
defer detectorsM.RUnlock()
|
||||
|
||||
for name, detector := range detectors {
|
||||
namespace, err := detector.Detect(files)
|
||||
if err != nil {
|
||||
log.Warningf("failed while attempting to detect namespace %s: %s", name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if namespace != nil {
|
||||
log.Debugf("detected namespace %s: %#v", name, namespace)
|
||||
return namespace, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// RequiredFilenames returns the total list of files required for all
|
||||
// registered Detectors.
|
||||
func RequiredFilenames() (files []string) {
|
||||
for _, detector := range detectors {
|
||||
files = append(files, detector.RequiredFilenames()...)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TestData represents the data used to test an implementation of
|
||||
// NameSpaceDetector.
|
||||
type TestData struct {
|
||||
Files tarutil.FilesMap
|
||||
ExpectedNamespace *database.Namespace
|
||||
}
|
||||
|
||||
// TestDetector runs a Detector on each provided instance of TestData and
|
||||
// asserts the output to be equal to the expected output.
|
||||
func TestDetector(t *testing.T, d Detector, testData []TestData) {
|
||||
for _, td := range testData {
|
||||
namespace, err := d.Detect(td.Files)
|
||||
assert.Nil(t, err)
|
||||
|
||||
if namespace == nil {
|
||||
assert.Equal(t, td.ExpectedNamespace, namespace)
|
||||
} else {
|
||||
assert.Equal(t, td.ExpectedNamespace.Name, namespace.Name)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2017 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -12,6 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package lsbrelease implements a featurens.Detector for container image
|
||||
// layers containing an lsb-release file.
|
||||
//
|
||||
// This detector is necessary for detecting Ubuntu Precise.
|
||||
package lsbrelease
|
||||
|
||||
import (
|
||||
@ -20,9 +24,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/featurens"
|
||||
"github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||
"github.com/coreos/clair/ext/versionfmt/rpm"
|
||||
"github.com/coreos/clair/worker/detectors"
|
||||
"github.com/coreos/clair/pkg/tarutil"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -30,20 +35,16 @@ var (
|
||||
lsbReleaseVersionRegexp = regexp.MustCompile(`^DISTRIB_RELEASE=(.*)`)
|
||||
)
|
||||
|
||||
// LsbReleaseNamespaceDetector implements NamespaceDetector and detects the
|
||||
// Namespace from the /etc/lsb-release file.
|
||||
//
|
||||
// This detector is necessary for Ubuntu Precise.
|
||||
type LsbReleaseNamespaceDetector struct{}
|
||||
type detector struct{}
|
||||
|
||||
func init() {
|
||||
detectors.RegisterNamespaceDetector("lsb-release", &LsbReleaseNamespaceDetector{})
|
||||
featurens.RegisterDetector("lsb-release", &detector{})
|
||||
}
|
||||
|
||||
func (detector *LsbReleaseNamespaceDetector) Detect(data map[string][]byte) *database.Namespace {
|
||||
f, hasFile := data["etc/lsb-release"]
|
||||
func (d detector) Detect(files tarutil.FilesMap) (*database.Namespace, error) {
|
||||
f, hasFile := files["etc/lsb-release"]
|
||||
if !hasFile {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var OS, version string
|
||||
@ -79,19 +80,19 @@ func (detector *LsbReleaseNamespaceDetector) Detect(data map[string][]byte) *dat
|
||||
case "centos", "rhel", "fedora", "amzn", "ol", "oracle":
|
||||
versionFormat = rpm.ParserName
|
||||
default:
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if OS != "" && version != "" {
|
||||
return &database.Namespace{
|
||||
Name: OS + ":" + version,
|
||||
VersionFormat: versionFormat,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
return nil
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetRequiredFiles returns the list of files that are required for Detect()
|
||||
func (detector *LsbReleaseNamespaceDetector) GetRequiredFiles() []string {
|
||||
func (d *detector) RequiredFilenames() []string {
|
||||
return []string{"etc/lsb-release"}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2016 clair authors
|
||||
// Copyright 2017 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -18,14 +18,15 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/worker/detectors/namespace"
|
||||
"github.com/coreos/clair/ext/featurens"
|
||||
"github.com/coreos/clair/pkg/tarutil"
|
||||
)
|
||||
|
||||
func TestLsbReleaseNamespaceDetector(t *testing.T) {
|
||||
testData := []namespace.TestData{
|
||||
func TestDetector(t *testing.T) {
|
||||
testData := []featurens.TestData{
|
||||
{
|
||||
ExpectedNamespace: &database.Namespace{Name: "ubuntu:12.04"},
|
||||
Data: map[string][]byte{
|
||||
Files: tarutil.FilesMap{
|
||||
"etc/lsb-release": []byte(
|
||||
`DISTRIB_ID=Ubuntu
|
||||
DISTRIB_RELEASE=12.04
|
||||
@ -35,7 +36,7 @@ DISTRIB_DESCRIPTION="Ubuntu 12.04 LTS"`),
|
||||
},
|
||||
{ // We don't care about the minor version of Debian
|
||||
ExpectedNamespace: &database.Namespace{Name: "debian:7"},
|
||||
Data: map[string][]byte{
|
||||
Files: tarutil.FilesMap{
|
||||
"etc/lsb-release": []byte(
|
||||
`DISTRIB_ID=Debian
|
||||
DISTRIB_RELEASE=7.1
|
||||
@ -45,5 +46,5 @@ DISTRIB_DESCRIPTION="Debian 7.1"`),
|
||||
},
|
||||
}
|
||||
|
||||
namespace.TestDetector(t, &LsbReleaseNamespaceDetector{}, testData)
|
||||
featurens.TestDetector(t, &detector{}, testData)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2017 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -12,6 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package osrelease implements a featurens.Detector for container image
|
||||
// layers containing an os-release file.
|
||||
//
|
||||
// This detector is typically useful for detecting Debian or Ubuntu.
|
||||
package osrelease
|
||||
|
||||
import (
|
||||
@ -20,40 +24,41 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/featurens"
|
||||
"github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||
"github.com/coreos/clair/ext/versionfmt/rpm"
|
||||
"github.com/coreos/clair/worker/detectors"
|
||||
"github.com/coreos/clair/pkg/tarutil"
|
||||
)
|
||||
|
||||
var (
|
||||
//log = capnslog.NewPackageLogger("github.com/coreos/clair", "worker/detectors/namespace/osrelease")
|
||||
|
||||
osReleaseOSRegexp = regexp.MustCompile(`^ID=(.*)`)
|
||||
osReleaseVersionRegexp = regexp.MustCompile(`^VERSION_ID=(.*)`)
|
||||
|
||||
// blacklistFilenames are files that should exclude this detector.
|
||||
blacklistFilenames = []string{
|
||||
"etc/oracle-release",
|
||||
"etc/redhat-release",
|
||||
"usr/lib/centos-release",
|
||||
}
|
||||
)
|
||||
|
||||
// OsReleaseNamespaceDetector implements NamespaceDetector and detects the OS from the
|
||||
// /etc/os-release and usr/lib/os-release files.
|
||||
type OsReleaseNamespaceDetector struct{}
|
||||
type detector struct{}
|
||||
|
||||
func init() {
|
||||
detectors.RegisterNamespaceDetector("os-release", &OsReleaseNamespaceDetector{})
|
||||
featurens.RegisterDetector("os-release", &detector{})
|
||||
}
|
||||
|
||||
// Detect tries to detect OS/Version using "/etc/os-release" and "/usr/lib/os-release"
|
||||
// Typically for Debian / Ubuntu
|
||||
// /etc/debian_version can't be used, it does not make any difference between testing and unstable, it returns stretch/sid
|
||||
func (detector *OsReleaseNamespaceDetector) Detect(data map[string][]byte) *database.Namespace {
|
||||
func (d detector) Detect(files tarutil.FilesMap) (*database.Namespace, error) {
|
||||
var OS, version string
|
||||
|
||||
for _, filePath := range detector.getExcludeFiles() {
|
||||
if _, hasFile := data[filePath]; hasFile {
|
||||
return nil
|
||||
for _, filePath := range blacklistFilenames {
|
||||
if _, hasFile := files[filePath]; hasFile {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, filePath := range detector.GetRequiredFiles() {
|
||||
f, hasFile := data[filePath]
|
||||
for _, filePath := range d.RequiredFilenames() {
|
||||
f, hasFile := files[filePath]
|
||||
if !hasFile {
|
||||
continue
|
||||
}
|
||||
@ -82,24 +87,18 @@ func (detector *OsReleaseNamespaceDetector) Detect(data map[string][]byte) *data
|
||||
case "centos", "rhel", "fedora", "amzn", "ol", "oracle":
|
||||
versionFormat = rpm.ParserName
|
||||
default:
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if OS != "" && version != "" {
|
||||
return &database.Namespace{
|
||||
Name: OS + ":" + version,
|
||||
VersionFormat: versionFormat,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetRequiredFiles returns the list of files that are required for Detect()
|
||||
func (detector *OsReleaseNamespaceDetector) GetRequiredFiles() []string {
|
||||
func (d detector) RequiredFilenames() []string {
|
||||
return []string{"etc/os-release", "usr/lib/os-release"}
|
||||
}
|
||||
|
||||
// getExcludeFiles returns the list of files that are ought to exclude this detector from Detect()
|
||||
func (detector *OsReleaseNamespaceDetector) getExcludeFiles() []string {
|
||||
return []string{"etc/oracle-release", "etc/redhat-release", "usr/lib/centos-release"}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2016 clair authors
|
||||
// Copyright 2017 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -18,14 +18,15 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/worker/detectors/namespace"
|
||||
"github.com/coreos/clair/ext/featurens"
|
||||
"github.com/coreos/clair/pkg/tarutil"
|
||||
)
|
||||
|
||||
func TestOsReleaseNamespaceDetector(t *testing.T) {
|
||||
testData := []namespace.TestData{
|
||||
func TestDetector(t *testing.T) {
|
||||
testData := []featurens.TestData{
|
||||
{
|
||||
ExpectedNamespace: &database.Namespace{Name: "debian:8"},
|
||||
Data: map[string][]byte{
|
||||
Files: tarutil.FilesMap{
|
||||
"etc/os-release": []byte(
|
||||
`PRETTY_NAME="Debian GNU/Linux 8 (jessie)"
|
||||
NAME="Debian GNU/Linux"
|
||||
@ -39,7 +40,7 @@ BUG_REPORT_URL="https://bugs.debian.org/"`),
|
||||
},
|
||||
{
|
||||
ExpectedNamespace: &database.Namespace{Name: "ubuntu:15.10"},
|
||||
Data: map[string][]byte{
|
||||
Files: tarutil.FilesMap{
|
||||
"etc/os-release": []byte(
|
||||
`NAME="Ubuntu"
|
||||
VERSION="15.10 (Wily Werewolf)"
|
||||
@ -54,7 +55,7 @@ BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`),
|
||||
},
|
||||
{ // Doesn't have quotes around VERSION_ID
|
||||
ExpectedNamespace: &database.Namespace{Name: "fedora:20"},
|
||||
Data: map[string][]byte{
|
||||
Files: tarutil.FilesMap{
|
||||
"etc/os-release": []byte(
|
||||
`NAME=Fedora
|
||||
VERSION="20 (Heisenbug)"
|
||||
@ -73,5 +74,5 @@ REDHAT_SUPPORT_PRODUCT_VERSION=20`),
|
||||
},
|
||||
}
|
||||
|
||||
namespace.TestDetector(t, &OsReleaseNamespaceDetector{}, testData)
|
||||
featurens.TestDetector(t, &detector{}, testData)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2017 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -12,6 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package redhatrelease implements a featurens.Detector for container image
|
||||
// layers containing an redhat-release-like files.
|
||||
//
|
||||
// This detector is typically useful for detecting CentOS and Red-Hat like
|
||||
// systems.
|
||||
package redhatrelease
|
||||
|
||||
import (
|
||||
@ -19,77 +24,64 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/featurens"
|
||||
"github.com/coreos/clair/ext/versionfmt/rpm"
|
||||
"github.com/coreos/clair/worker/detectors"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
"github.com/coreos/clair/pkg/tarutil"
|
||||
)
|
||||
|
||||
var (
|
||||
log = capnslog.NewPackageLogger("github.com/coreos/clair", "worker/detectors/namespace/redhatrelease")
|
||||
|
||||
oracleReleaseRegexp = regexp.MustCompile(`(?P<os>[^\s]*) (Linux Server release) (?P<version>[\d]+)`)
|
||||
centosReleaseRegexp = regexp.MustCompile(`(?P<os>[^\s]*) (Linux release|release) (?P<version>[\d]+)`)
|
||||
redhatReleaseRegexp = regexp.MustCompile(`(?P<os>Red Hat Enterprise Linux) (Client release|Server release|Workstation release) (?P<version>[\d]+)`)
|
||||
)
|
||||
|
||||
// RedhatReleaseNamespaceDetector implements NamespaceDetector and detects the OS from the
|
||||
// /etc/oracle-release, /etc/centos-release, /etc/redhat-release and /etc/system-release files.
|
||||
//
|
||||
// Typically for CentOS and Red-Hat like systems
|
||||
// eg. CentOS release 5.11 (Final)
|
||||
// eg. CentOS release 6.6 (Final)
|
||||
// eg. CentOS Linux release 7.1.1503 (Core)
|
||||
// eg. Oracle Linux Server release 7.3
|
||||
// eg. Red Hat Enterprise Linux Server release 7.2 (Maipo)
|
||||
type RedhatReleaseNamespaceDetector struct{}
|
||||
type detector struct{}
|
||||
|
||||
func init() {
|
||||
detectors.RegisterNamespaceDetector("redhat-release", &RedhatReleaseNamespaceDetector{})
|
||||
featurens.RegisterDetector("redhat-release", &detector{})
|
||||
}
|
||||
|
||||
func (detector *RedhatReleaseNamespaceDetector) Detect(data map[string][]byte) *database.Namespace {
|
||||
for _, filePath := range detector.GetRequiredFiles() {
|
||||
f, hasFile := data[filePath]
|
||||
func (d detector) Detect(files tarutil.FilesMap) (*database.Namespace, error) {
|
||||
for _, filePath := range d.RequiredFilenames() {
|
||||
f, hasFile := files[filePath]
|
||||
if !hasFile {
|
||||
continue
|
||||
}
|
||||
|
||||
var r []string
|
||||
|
||||
// try for Oracle Linux
|
||||
// Attempt to match Oracle Linux.
|
||||
r = oracleReleaseRegexp.FindStringSubmatch(string(f))
|
||||
if len(r) == 4 {
|
||||
return &database.Namespace{
|
||||
Name: strings.ToLower(r[1]) + ":" + r[3],
|
||||
VersionFormat: rpm.ParserName,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// try for RHEL
|
||||
// Attempt to match RHEL.
|
||||
r = redhatReleaseRegexp.FindStringSubmatch(string(f))
|
||||
if len(r) == 4 {
|
||||
// TODO(vbatts) this is a hack until https://github.com/coreos/clair/pull/193
|
||||
// TODO(vbatts): this is a hack until https://github.com/coreos/clair/pull/193
|
||||
return &database.Namespace{
|
||||
Name: "centos" + ":" + r[3],
|
||||
VersionFormat: rpm.ParserName,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// then try centos first
|
||||
// Atempt to match CentOS.
|
||||
r = centosReleaseRegexp.FindStringSubmatch(string(f))
|
||||
if len(r) == 4 {
|
||||
return &database.Namespace{
|
||||
Name: strings.ToLower(r[1]) + ":" + r[3],
|
||||
VersionFormat: rpm.ParserName,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetRequiredFiles returns the list of files that are required for Detect()
|
||||
func (detector *RedhatReleaseNamespaceDetector) GetRequiredFiles() []string {
|
||||
func (d detector) RequiredFilenames() []string {
|
||||
return []string{"etc/oracle-release", "etc/centos-release", "etc/redhat-release", "etc/system-release"}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2016 clair authors
|
||||
// Copyright 2017 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -18,36 +18,37 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/worker/detectors/namespace"
|
||||
"github.com/coreos/clair/ext/featurens"
|
||||
"github.com/coreos/clair/pkg/tarutil"
|
||||
)
|
||||
|
||||
func TestRedhatReleaseNamespaceDetector(t *testing.T) {
|
||||
testData := []namespace.TestData{
|
||||
func TestDetector(t *testing.T) {
|
||||
testData := []featurens.TestData{
|
||||
{
|
||||
ExpectedNamespace: &database.Namespace{Name: "oracle:6"},
|
||||
Data: map[string][]byte{
|
||||
Files: tarutil.FilesMap{
|
||||
"etc/oracle-release": []byte(`Oracle Linux Server release 6.8`),
|
||||
},
|
||||
},
|
||||
{
|
||||
ExpectedNamespace: &database.Namespace{Name: "oracle:7"},
|
||||
Data: map[string][]byte{
|
||||
Files: tarutil.FilesMap{
|
||||
"etc/oracle-release": []byte(`Oracle Linux Server release 7.2`),
|
||||
},
|
||||
},
|
||||
{
|
||||
ExpectedNamespace: &database.Namespace{Name: "centos:6"},
|
||||
Data: map[string][]byte{
|
||||
Files: tarutil.FilesMap{
|
||||
"etc/centos-release": []byte(`CentOS release 6.6 (Final)`),
|
||||
},
|
||||
},
|
||||
{
|
||||
ExpectedNamespace: &database.Namespace{Name: "centos:7"},
|
||||
Data: map[string][]byte{
|
||||
Files: tarutil.FilesMap{
|
||||
"etc/system-release": []byte(`CentOS Linux release 7.1.1503 (Core)`),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
namespace.TestDetector(t, &RedhatReleaseNamespaceDetector{}, testData)
|
||||
featurens.TestDetector(t, &detector{}, testData)
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
// Copyright 2015 clair 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 detectors exposes functions to register and use container
|
||||
// information extractors.
|
||||
package detectors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
// The NamespaceDetector interface defines a way to detect a Namespace from input data.
|
||||
// A namespace is usually made of an Operating System name and its version.
|
||||
type NamespaceDetector interface {
|
||||
// Detect detects a Namespace and its version from input data.
|
||||
Detect(map[string][]byte) *database.Namespace
|
||||
// GetRequiredFiles returns the list of files required for Detect, without
|
||||
// leading /.
|
||||
GetRequiredFiles() []string
|
||||
}
|
||||
|
||||
var (
|
||||
nlog = capnslog.NewPackageLogger("github.com/coreos/clair", "worker/detectors")
|
||||
|
||||
namespaceDetectorsLock sync.Mutex
|
||||
namespaceDetectors = make(map[string]NamespaceDetector)
|
||||
)
|
||||
|
||||
// RegisterNamespaceDetector provides a way to dynamically register an implementation of a
|
||||
// NamespaceDetector.
|
||||
//
|
||||
// If RegisterNamespaceDetector is called twice with the same name if NamespaceDetector is nil,
|
||||
// or if the name is blank, it panics.
|
||||
func RegisterNamespaceDetector(name string, f NamespaceDetector) {
|
||||
if name == "" {
|
||||
panic("Could not register a NamespaceDetector with an empty name")
|
||||
}
|
||||
if f == nil {
|
||||
panic("Could not register a nil NamespaceDetector")
|
||||
}
|
||||
|
||||
namespaceDetectorsLock.Lock()
|
||||
defer namespaceDetectorsLock.Unlock()
|
||||
|
||||
if _, alreadyExists := namespaceDetectors[name]; alreadyExists {
|
||||
panic(fmt.Sprintf("Detector '%s' is already registered", name))
|
||||
}
|
||||
namespaceDetectors[name] = f
|
||||
}
|
||||
|
||||
// DetectNamespace finds the OS of the layer by using every registered NamespaceDetector.
|
||||
func DetectNamespace(data map[string][]byte) *database.Namespace {
|
||||
for name, detector := range namespaceDetectors {
|
||||
if namespace := detector.Detect(data); namespace != nil {
|
||||
nlog.Debugf("detector: %q; namespace: %q\n", name, namespace.Name)
|
||||
return namespace
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRequiredFilesNamespace returns the list of files required for DetectNamespace for every
|
||||
// registered NamespaceDetector, without leading /.
|
||||
func GetRequiredFilesNamespace() (files []string) {
|
||||
for _, detector := range namespaceDetectors {
|
||||
files = append(files, detector.GetRequiredFiles()...)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
// Copyright 2016 clair 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 namespace implements utilities common to implementations of
|
||||
// NamespaceDetector.
|
||||
package namespace
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/worker/detectors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestData represents the data used to test an implementation of
|
||||
// NameSpaceDetector.
|
||||
type TestData struct {
|
||||
Data map[string][]byte
|
||||
ExpectedNamespace *database.Namespace
|
||||
}
|
||||
|
||||
// TestDetector runs a detector on each provided instance of TestData and
|
||||
// asserts the output to be equal to the expected output.
|
||||
func TestDetector(t *testing.T, detector detectors.NamespaceDetector, testData []TestData) {
|
||||
for _, td := range testData {
|
||||
detectedNamespace := detector.Detect(td.Data)
|
||||
if detectedNamespace == nil {
|
||||
assert.Equal(t, td.ExpectedNamespace, detectedNamespace)
|
||||
} else {
|
||||
assert.Equal(t, td.ExpectedNamespace.Name, detectedNamespace.Name)
|
||||
}
|
||||
}
|
||||
}
|
@ -20,8 +20,10 @@ import (
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/featurens"
|
||||
"github.com/coreos/clair/ext/imagefmt"
|
||||
"github.com/coreos/clair/pkg/commonerr"
|
||||
"github.com/coreos/clair/pkg/tarutil"
|
||||
"github.com/coreos/clair/utils"
|
||||
"github.com/coreos/clair/worker/detectors"
|
||||
)
|
||||
@ -110,20 +112,23 @@ func Process(datastore database.Datastore, imageFormat, name, parentName, path s
|
||||
return datastore.InsertLayer(layer)
|
||||
}
|
||||
|
||||
// detectContent downloads a layer's archive and extracts its Namespace and Features.
|
||||
// detectContent downloads a layer's archive and extracts its Namespace and
|
||||
// Features.
|
||||
func detectContent(imageFormat, name, path string, headers map[string]string, parent *database.Layer) (namespace *database.Namespace, featureVersions []database.FeatureVersion, err error) {
|
||||
files, err := imagefmt.Extract(imageFormat, path, headers, append(detectors.GetRequiredFilesFeatures(), detectors.GetRequiredFilesNamespace()...))
|
||||
totalRequiredFiles := append(detectors.GetRequiredFilesFeatures(), featurens.RequiredFilenames()...)
|
||||
files, err := imagefmt.Extract(imageFormat, path, headers, totalRequiredFiles)
|
||||
if err != nil {
|
||||
log.Errorf("layer %s: failed to extract data from %s: %s", name, utils.CleanURL(path), err)
|
||||
return
|
||||
}
|
||||
|
||||
data := map[string][]byte(files)
|
||||
|
||||
// Detect namespace.
|
||||
namespace = detectNamespace(name, data, parent)
|
||||
namespace, err = detectNamespace(name, files, parent)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Detect features.
|
||||
data := map[string][]byte(files)
|
||||
featureVersions, err = detectFeatureVersions(name, data, namespace, parent)
|
||||
if err != nil {
|
||||
return
|
||||
@ -135,15 +140,17 @@ func detectContent(imageFormat, name, path string, headers map[string]string, pa
|
||||
return
|
||||
}
|
||||
|
||||
func detectNamespace(name string, data map[string][]byte, parent *database.Layer) (namespace *database.Namespace) {
|
||||
// Use registered detectors to get the Namespace.
|
||||
namespace = detectors.DetectNamespace(data)
|
||||
func detectNamespace(name string, files tarutil.FilesMap, parent *database.Layer) (namespace *database.Namespace, err error) {
|
||||
namespace, err = featurens.Detect(files)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if namespace != nil {
|
||||
log.Debugf("layer %s: detected namespace %q", name, namespace.Name)
|
||||
return
|
||||
}
|
||||
|
||||
// Use the parent's Namespace.
|
||||
// Fallback to the parent's namespace.
|
||||
if parent != nil {
|
||||
namespace = parent.Namespace
|
||||
if namespace != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user