// 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 database import ( "strconv" "github.com/coreos/clair/utils" cerrors "github.com/coreos/clair/utils/errors" "github.com/google/cayley" "github.com/google/cayley/graph" "github.com/google/cayley/graph/path" ) const ( FieldLayerIsValue = "layer" FieldLayerID = "id" FieldLayerParent = "parent" FieldLayerSuccessors = "successors" FieldLayerOS = "os" FieldLayerInstalledPackages = "adds" FieldLayerRemovedPackages = "removes" FieldLayerEngineVersion = "engineVersion" FieldLayerPackages = "adds/removes" ) var FieldLayerAll = []string{FieldLayerID, FieldLayerParent, FieldLayerSuccessors, FieldLayerOS, FieldLayerPackages, FieldLayerEngineVersion} // Layer represents an unique container layer type Layer struct { Node string `json:"-"` ID string ParentNode string `json:"-"` SuccessorsNodes []string `json:"-"` OS string InstalledPackagesNodes []string `json:"-"` RemovedPackagesNodes []string `json:"-"` EngineVersion int } // GetNode returns the node name of a Layer // Requires the key field: ID func (l *Layer) GetNode() string { return FieldLayerIsValue + ":" + utils.Hash(l.ID) } // InsertLayer insert a single layer in the database // // ID, and EngineVersion fields are required. // ParentNode, OS, InstalledPackagesNodes and RemovedPackagesNodes are optional, // SuccessorsNodes is unnecessary. // // The ID MUST be unique for two different layers. // // // If the Layer already exists, nothing is done, except if the provided engine // version is higher than the existing one, in which case, the OS, // InstalledPackagesNodes and RemovedPackagesNodes fields will be replaced. // // The layer should only contains the newly installed/removed packages // There is no safeguard that prevents from marking a package as newly installed // while it has already been installed in one of its parent. func InsertLayer(layer *Layer) error { // Verify parameters if layer.ID == "" { log.Warning("could not insert a layer which has an empty ID") return cerrors.NewBadRequestError("could not insert a layer which has an empty ID") } // Create required data structures t := cayley.NewTransaction() layer.Node = layer.GetNode() // Try to find an existing layer existingLayer, err := FindOneLayerByNode(layer.Node, FieldLayerAll) if err != nil && err != cerrors.ErrNotFound { return err } if existingLayer != nil && existingLayer.EngineVersion >= layer.EngineVersion { // The layer exists and has an equal or higher engine verison, do nothing return nil } if existingLayer == nil { // Create case: add permanent nodes t.AddQuad(cayley.Quad(layer.Node, FieldIs, FieldLayerIsValue, "")) t.AddQuad(cayley.Quad(layer.Node, FieldLayerID, layer.ID, "")) t.AddQuad(cayley.Quad(layer.Node, FieldLayerParent, layer.ParentNode, "")) } else { // Update case: remove everything before we add updated data t.RemoveQuad(cayley.Quad(layer.Node, FieldLayerOS, existingLayer.OS, "")) for _, pkg := range existingLayer.InstalledPackagesNodes { t.RemoveQuad(cayley.Quad(layer.Node, FieldLayerInstalledPackages, pkg, "")) } for _, pkg := range existingLayer.RemovedPackagesNodes { t.RemoveQuad(cayley.Quad(layer.Node, FieldLayerRemovedPackages, pkg, "")) } t.RemoveQuad(cayley.Quad(layer.Node, FieldLayerEngineVersion, strconv.Itoa(existingLayer.EngineVersion), "")) } // Add OS/Packages t.AddQuad(cayley.Quad(layer.Node, FieldLayerOS, layer.OS, "")) for _, pkg := range layer.InstalledPackagesNodes { t.AddQuad(cayley.Quad(layer.Node, FieldLayerInstalledPackages, pkg, "")) } for _, pkg := range layer.RemovedPackagesNodes { t.AddQuad(cayley.Quad(layer.Node, FieldLayerRemovedPackages, pkg, "")) } t.AddQuad(cayley.Quad(layer.Node, FieldLayerEngineVersion, strconv.Itoa(layer.EngineVersion), "")) // Apply transaction if err = store.ApplyTransaction(t); err != nil { log.Errorf("failed transaction (InsertLayer): %s", err) return ErrTransaction } return nil } // FindOneLayerByID finds and returns a single layer having the given ID, // selecting the specified fields and hardcoding its ID func FindOneLayerByID(ID string, selectedFields []string) (*Layer, error) { t := &Layer{ID: ID} l, err := FindOneLayerByNode(t.GetNode(), selectedFields) if err != nil { return nil, err } l.ID = ID return l, nil } // FindOneLayerByNode finds and returns a single package by its node, selecting the specified fields func FindOneLayerByNode(node string, selectedFields []string) (*Layer, error) { l, err := toLayers(cayley.StartPath(store, node).Has(FieldIs, FieldLayerIsValue), selectedFields) if err != nil { return nil, err } if len(l) == 1 { return l[0], nil } if len(l) > 1 { log.Errorf("found multiple layers with identical node [Node: %s]", node) return nil, ErrInconsistent } return nil, cerrors.ErrNotFound } // FindAllLayersByAddedPackageNodes finds and returns all layers that add the // given packages (by their nodes), selecting the specified fields func FindAllLayersByAddedPackageNodes(nodes []string, selectedFields []string) ([]*Layer, error) { layers, err := toLayers(cayley.StartPath(store, nodes...).In(FieldLayerInstalledPackages), selectedFields) if err != nil { return []*Layer{}, err } return layers, nil } // FindAllLayersByPackageNode finds and returns all layers that have the given package (by its node), selecting the specified fields // func FindAllLayersByPackageNode(node string, only map[string]struct{}) ([]*Layer, error) { // var layers []*Layer // // // We need the successors field // if only != nil { // only[FieldLayerSuccessors] = struct{}{} // } // // // Get all the layers which remove the package // layersNodesRemoving, err := toValues(cayley.StartPath(store, node).In(FieldLayerRemovedPackages).Has(FieldIs, FieldLayerIsValue)) // if err != nil { // return []*Layer{}, err // } // layersNodesRemovingMap := make(map[string]struct{}) // for _, l := range layersNodesRemoving { // layersNodesRemovingMap[l] = struct{}{} // } // // layersToBrowse, err := toLayers(cayley.StartPath(store, node).In(FieldLayerInstalledPackages).Has(FieldIs, FieldLayerIsValue), only) // if err != nil { // return []*Layer{}, err // } // for len(layersToBrowse) > 0 { // var newLayersToBrowse []*Layer // for _, layerToBrowse := range layersToBrowse { // if _, layerRemovesPackage := layersNodesRemovingMap[layerToBrowse.Node]; !layerRemovesPackage { // layers = append(layers, layerToBrowse) // successors, err := layerToBrowse.Successors(only) // if err != nil { // return []*Layer{}, err // } // newLayersToBrowse = append(newLayersToBrowse, successors...) // } // layersToBrowse = newLayersToBrowse // } // } // // return layers, nil // } // toLayers converts a path leading to one or multiple layers to Layer structs, // selecting the specified fields func toLayers(path *path.Path, selectedFields []string) ([]*Layer, error) { var layers []*Layer saveFields(path, selectedFields, []string{FieldLayerSuccessors, FieldLayerPackages, FieldLayerInstalledPackages, FieldLayerRemovedPackages}) it, _ := path.BuildIterator().Optimize() defer it.Close() for cayley.RawNext(it) { tags := make(map[string]graph.Value) it.TagResults(tags) layer := Layer{Node: store.NameOf(it.Result())} for _, selectedField := range selectedFields { switch selectedField { case FieldLayerID: layer.ID = store.NameOf(tags[FieldLayerID]) case FieldLayerParent: layer.ParentNode = store.NameOf(tags[FieldLayerParent]) case FieldLayerSuccessors: var err error layer.SuccessorsNodes, err = toValues(cayley.StartPath(store, layer.Node).In(FieldLayerParent)) if err != nil { log.Errorf("could not get successors of layer %s: %s.", layer.Node, err.Error()) return nil, err } case FieldLayerOS: layer.OS = store.NameOf(tags[FieldLayerOS]) case FieldLayerPackages: var err error it, _ := cayley.StartPath(store, layer.Node).OutWithTags([]string{"predicate"}, FieldLayerInstalledPackages, FieldLayerRemovedPackages).BuildIterator().Optimize() defer it.Close() for cayley.RawNext(it) { tags := make(map[string]graph.Value) it.TagResults(tags) predicate := store.NameOf(tags["predicate"]) if predicate == FieldLayerInstalledPackages { layer.InstalledPackagesNodes = append(layer.InstalledPackagesNodes, store.NameOf(it.Result())) } else if predicate == FieldLayerRemovedPackages { layer.RemovedPackagesNodes = append(layer.RemovedPackagesNodes, store.NameOf(it.Result())) } } if it.Err() != nil { log.Errorf("could not get installed/removed packages of layer %s: %s.", layer.Node, it.Err()) return nil, err } case FieldLayerEngineVersion: layer.EngineVersion, _ = strconv.Atoi(store.NameOf(tags[FieldLayerEngineVersion])) default: panic("unknown selectedField") } } layers = append(layers, &layer) } if it.Err() != nil { log.Errorf("failed query in toLayers: %s", it.Err()) return []*Layer{}, ErrBackendException } return layers, nil } // Successors find and returns all layers that define l as their parent, // selecting the specified fields // It requires that FieldLayerSuccessors field has been selected on l // func (l *Layer) Successors(selectedFields []string) ([]*Layer, error) { // if len(l.SuccessorsNodes) == 0 { // return []*Layer{}, nil // } // // return toLayers(cayley.StartPath(store, l.SuccessorsNodes...), only) // } // Parent find and returns the parent layer of l, selecting the specified fields // It requires that FieldLayerParent field has been selected on l func (l *Layer) Parent(selectedFields []string) (*Layer, error) { if l.ParentNode == "" { return nil, nil } parent, err := toLayers(cayley.StartPath(store, l.ParentNode), selectedFields) if err != nil { return nil, err } if len(parent) == 1 { return parent[0], nil } if len(parent) > 1 { log.Errorf("found multiple layers when getting parent layer of layer %s", l.ParentNode) return nil, ErrInconsistent } return nil, nil } // Sublayers find and returns all layers that compose l, selecting the specified // fields // It requires that FieldLayerParent field has been selected on l // The base image comes first, and l is last // func (l *Layer) Sublayers(selectedFields []string) ([]*Layer, error) { // var sublayers []*Layer // // // We need the parent field // if only != nil { // only[FieldLayerParent] = struct{}{} // } // // parent, err := l.Parent(only) // if err != nil { // return []*Layer{}, err // } // if parent != nil { // parentSublayers, err := parent.Sublayers(only) // if err != nil { // return []*Layer{}, err // } // sublayers = append(sublayers, parentSublayers...) // } // // sublayers = append(sublayers, l) // // return sublayers, nil // } // AllPackages computes the full list of packages that l has and return them as // nodes. // It requires that FieldLayerParent, FieldLayerContentInstalledPackages, // FieldLayerContentRemovedPackages fields has been selected on l func (l *Layer) AllPackages() ([]string, error) { var allPackages []string parent, err := l.Parent([]string{FieldLayerParent, FieldLayerPackages}) if err != nil { return []string{}, err } if parent != nil { allPackages, err = parent.AllPackages() if err != nil { return []string{}, err } } return append(utils.CompareStringLists(allPackages, l.RemovedPackagesNodes), l.InstalledPackagesNodes...), nil } // OperatingSystem tries to find the Operating System of a layer using its // parents. // It requires that FieldLayerParent and FieldLayerOS fields has been // selected on l func (l *Layer) OperatingSystem() (string, error) { if l.OS != "" { return l.OS, nil } // Try from the parent parent, err := l.Parent([]string{FieldLayerParent, FieldLayerOS}) if err != nil { return "", err } if parent != nil { return parent.OperatingSystem() } return "", nil }