// Unlock unlocks a lock specified by its name if I own it func Unlock(name, owner string) { unlocked := 0 it, _ := cayley.StartPath(store, name).Has(fieldLockLocked, fieldLockLockedValue).Has(fieldLockLockedBy, owner).Save(fieldLockLockedUntil, fieldLockLockedUntil).BuildIterator().Optimize() defer it.Close() for cayley.RawNext(it) { tags := make(map[string]graph.Value) it.TagResults(tags) t := cayley.NewTransaction() t.RemoveQuad(cayley.Triple(name, fieldLockLocked, fieldLockLockedValue)) t.RemoveQuad(cayley.Triple(name, fieldLockLockedUntil, store.NameOf(tags[fieldLockLockedUntil]))) t.RemoveQuad(cayley.Triple(name, fieldLockLockedBy, owner)) err := store.ApplyTransaction(t) if err != nil { log.Errorf("failed transaction (Unlock): %s", err) } unlocked++ } if it.Err() != nil { log.Errorf("failed query in Unlock: %s", it.Err()) } if unlocked > 1 { // We should never see this, it would mean that our database doesn't ensure quad uniqueness // and that the entire lock system is jeopardized. log.Errorf("found inconsistency in Unlock: matched %d times a locked named: %s", unlocked, name) } }
// pruneLocks removes every expired locks from the database func pruneLocks() { now := time.Now() // Delete every expired locks it, _ := cayley.StartPath(store, "locked").In("locked").Save(fieldLockLockedUntil, fieldLockLockedUntil).Save(fieldLockLockedBy, fieldLockLockedBy).BuildIterator().Optimize() defer it.Close() for cayley.RawNext(it) { tags := make(map[string]graph.Value) it.TagResults(tags) n := store.NameOf(it.Result()) t := store.NameOf(tags[fieldLockLockedUntil]) o := store.NameOf(tags[fieldLockLockedBy]) tt, _ := strconv.ParseInt(t, 10, 64) if now.Unix() > tt { log.Debugf("lock %s owned by %s has expired.", n, o) tr := cayley.NewTransaction() tr.RemoveQuad(cayley.Triple(n, fieldLockLocked, fieldLockLockedValue)) tr.RemoveQuad(cayley.Triple(n, fieldLockLockedUntil, t)) tr.RemoveQuad(cayley.Triple(n, fieldLockLockedBy, o)) err := store.ApplyTransaction(tr) if err != nil { log.Errorf("failed transaction (pruneLocks): %s", err) continue } log.Debugf("lock %s has been successfully pruned.", n) } } if it.Err() != nil { log.Errorf("failed query in Unlock: %s", it.Err()) } }
// Lock tries to set a temporary lock in the database. // If a lock already exists with the given name/owner, then the lock is renewed // // Lock does not block, instead, it returns true and its expiration time // is the lock has been successfully acquired or false otherwise func Lock(name string, duration time.Duration, owner string) (bool, time.Time) { pruneLocks() until := time.Now().Add(duration) untilString := strconv.FormatInt(until.Unix(), 10) // Try to get the expiration time of a lock with the same name/owner currentExpiration, err := toValue(cayley.StartPath(store, name).Has(fieldLockLockedBy, owner).Out(fieldLockLockedUntil)) if err == nil && currentExpiration != "" { // Renew our lock if currentExpiration == untilString { return true, until } t := cayley.NewTransaction() t.RemoveQuad(cayley.Triple(name, fieldLockLockedUntil, currentExpiration)) t.AddQuad(cayley.Triple(name, fieldLockLockedUntil, untilString)) // It is not necessary to verify if the lock is ours again in the transaction // because if someone took it, the lock's current expiration probably changed and the transaction will fail return store.ApplyTransaction(t) == nil, until } t := cayley.NewTransaction() t.AddQuad(cayley.Triple(name, fieldLockLocked, fieldLockLockedValue)) // Necessary to make the transaction fails if the lock already exists (and has not been pruned) t.AddQuad(cayley.Triple(name, fieldLockLockedUntil, untilString)) t.AddQuad(cayley.Triple(name, fieldLockLockedBy, owner)) glog.SetStderrThreshold("FATAL") success := store.ApplyTransaction(t) == nil glog.SetStderrThreshold("ERROR") return success, until }
// UpdateFlag creates a flag or update an existing flag's value func UpdateFlag(name, value string) error { if name == "" || value == "" { log.Warning("could not insert a flag which has an empty name or value") return cerrors.NewBadRequestError("could not insert a flag which has an empty name or value") } // Initialize transaction t := cayley.NewTransaction() // Get current flag value currentValue, err := GetFlagValue(name) if err != nil { return err } // Build transaction name = flagNodePrefix + ":" + name if currentValue != "" { t.RemoveQuad(cayley.Triple(name, fieldFlagValue, currentValue)) } t.AddQuad(cayley.Triple(name, fieldFlagValue, value)) // Apply transaction if err = store.ApplyTransaction(t); err != nil { log.Errorf("failed transaction (UpdateFlag): %s", err) return ErrTransaction } // Return return nil }
// MarkNotificationAsSent marks a notification as sent. func MarkNotificationAsSent(node string) { // Initialize transaction t := cayley.NewTransaction() t.RemoveQuad(cayley.Triple(node, fieldNotificationIsSent, strconv.FormatBool(false))) t.AddQuad(cayley.Triple(node, fieldNotificationIsSent, strconv.FormatBool(true))) // Apply transaction store.ApplyTransaction(t) }
// DeleteVulnerability deletes the vulnerability having the given ID func DeleteVulnerability(id string) error { vulnerability, err := FindOneVulnerability(id, FieldVulnerabilityAll) if err != nil { return err } t := cayley.NewTransaction() t.RemoveQuad(cayley.Triple(vulnerability.Node, FieldVulnerabilityID, vulnerability.ID)) t.RemoveQuad(cayley.Triple(vulnerability.Node, FieldVulnerabilityLink, vulnerability.Link)) t.RemoveQuad(cayley.Triple(vulnerability.Node, FieldVulnerabilityPriority, string(vulnerability.Priority))) t.RemoveQuad(cayley.Triple(vulnerability.Node, FieldVulnerabilityDescription, vulnerability.Description)) for _, p := range vulnerability.FixedInNodes { t.RemoveQuad(cayley.Triple(vulnerability.Node, FieldVulnerabilityFixedIn, p)) } if err := store.ApplyTransaction(t); err != nil { log.Errorf("failed transaction (DeleteVulnerability): %s", err) return ErrTransaction } return nil }
// Healthcheck simply adds and then remove a quad in Cayley to ensure it is working // It returns true when everything is ok func Healthcheck() health.Status { var err error if store != nil { t := cayley.NewTransaction() q := cayley.Triple("cayley", "is", "healthy") t.AddQuad(q) t.RemoveQuad(q) glog.SetStderrThreshold("FATAL") // TODO REMOVE ME err = store.ApplyTransaction(t) glog.SetStderrThreshold("ERROR") // TODO REMOVE ME } return health.Status{IsEssential: true, IsHealthy: err == nil, Details: nil} }
// InsertNotifications stores multiple Notification in the database // It uses the given NotificationWrapper to convert these notifications to // something that can be stored in the database. func InsertNotifications(notifications []Notification, wrapper NotificationWrapper) error { if len(notifications) == 0 { return nil } // Do not send notifications if there are too many of them (first update for example) if len(notifications) > maxNotifications { log.Noticef("Ignoring %d notifications", len(notifications)) return nil } // Initialize transaction t := cayley.NewTransaction() // Iterate over all the vulnerabilities we need to insert for _, notification := range notifications { // Wrap notification wrappedNotification, err := wrapper.Wrap(notification) if err != nil { return err } node := fieldNotificationIsValue + ":" + uuid.New() t.AddQuad(cayley.Triple(node, fieldIs, fieldNotificationIsValue)) t.AddQuad(cayley.Triple(node, fieldNotificationType, wrappedNotification.Type)) t.AddQuad(cayley.Triple(node, fieldNotificationData, wrappedNotification.Data)) t.AddQuad(cayley.Triple(node, fieldNotificationIsSent, strconv.FormatBool(false))) } // Apply transaction if err := store.ApplyTransaction(t); err != nil { log.Errorf("failed transaction (InsertNotifications): %s", err) return ErrTransaction } return nil }
func TestToValue(t *testing.T) { Open(&config.DatabaseConfig{Type: "memstore"}) defer Close() // toValue() v, err := toValue(cayley.StartPath(store, "tests").Out("are")) assert.Nil(t, err, "toValue should work even if the requested path leads to nothing") assert.Equal(t, "", v, "toValue should return an empty string if the requested path leads to nothing") store.AddQuad(cayley.Triple("tests", "are", "awesome")) v, err = toValue(cayley.StartPath(store, "tests").Out("are")) assert.Nil(t, err, "toValue should have worked") assert.Equal(t, "awesome", v, "toValue did not return the expected value") store.AddQuad(cayley.Triple("tests", "are", "running")) v, err = toValue(cayley.StartPath(store, "tests").Out("are")) assert.NotNil(t, err, "toValue should return an error and an empty string if the path leads to multiple values") assert.Equal(t, "", v, "toValue should return an error and an empty string if the path leads to multiple values") // toValues() vs, err := toValues(cayley.StartPath(store, "CoreOS").Out(fieldIs)) assert.Nil(t, err, "toValues should work even if the requested path leads to nothing") assert.Len(t, vs, 0, "toValue should return an empty array if the requested path leads to nothing") words := []string{"powerful", "lightweight"} for i, word := range words { store.AddQuad(cayley.Triple("CoreOS", fieldIs, word)) v, err := toValues(cayley.StartPath(store, "CoreOS").Out(fieldIs)) assert.Nil(t, err, "toValues should have worked") assert.Len(t, v, i+1, "toValues did not return the right amount of values") for _, e := range words[:i+1] { assert.Contains(t, v, e, "toValues did not return the values we expected") } } // toValue(s)() and empty values store.AddQuad(cayley.Triple("bob", "likes", "")) v, err = toValue(cayley.StartPath(store, "bob").Out("likes")) assert.Nil(t, err, "toValue should work even if the requested path leads to nothing") assert.Equal(t, "", v, "toValue should return an empty string if the requested path leads to nothing") store.AddQuad(cayley.Triple("bob", "likes", "running")) v, err = toValue(cayley.StartPath(store, "bob").Out("likes")) assert.NotNil(t, err, "toValue should return an error and an empty string if the path leads to multiple values") assert.Equal(t, "", v, "toValue should return an error and an empty string if the path leads to multiple values") store.AddQuad(cayley.Triple("bob", "likes", "swimming")) va, err := toValues(cayley.StartPath(store, "bob").Out("likes")) assert.Nil(t, err, "toValues should have worked") if assert.Len(t, va, 3, "toValues should have returned 2 values") { assert.Contains(t, va, "running") assert.Contains(t, va, "swimming") assert.Contains(t, va, "") } }
func deleteLayerTreeFrom(node string, t *graph.Transaction) error { // Determine if that function call is the root call of the recursivity // And create transaction if its the case. root := (t == nil) if root { t = cayley.NewTransaction() } // Find layer. layer, err := FindOneLayerByNode(node, FieldLayerAll) if err != nil { // Ignore missing layer. return nil } // Remove all successor layers. for _, succNode := range layer.SuccessorsNodes { deleteLayerTreeFrom(succNode, t) } // Remove layer. t.RemoveQuad(cayley.Triple(layer.Node, fieldIs, fieldLayerIsValue)) t.RemoveQuad(cayley.Triple(layer.Node, FieldLayerID, layer.ID)) t.RemoveQuad(cayley.Triple(layer.Node, FieldLayerParent, layer.ParentNode)) t.RemoveQuad(cayley.Triple(layer.Node, FieldLayerOS, layer.OS)) t.RemoveQuad(cayley.Triple(layer.Node, FieldLayerEngineVersion, strconv.Itoa(layer.EngineVersion))) for _, pkg := range layer.InstalledPackagesNodes { t.RemoveQuad(cayley.Triple(layer.Node, fieldLayerInstalledPackages, pkg)) } for _, pkg := range layer.RemovedPackagesNodes { t.RemoveQuad(cayley.Triple(layer.Node, fieldLayerRemovedPackages, pkg)) } // Apply transaction if root call. if root { if err = store.ApplyTransaction(t); err != nil { log.Errorf("failed transaction (deleteLayerTreeFrom): %s", err) return ErrTransaction } } return nil }
// InsertVulnerabilities inserts or updates several vulnerabilities in the database in one transaction // During an update, if the vulnerability was previously fixed by a version in a branch and a new package of that branch is specified, the previous one is deleted // Otherwise, it simply adds the defined packages, there is currently no way to delete affected packages. // // ID, Link, Priority and FixedInNodes fields have to be specified. Description is optionnal. func InsertVulnerabilities(vulnerabilities []*Vulnerability) ([]Notification, error) { if len(vulnerabilities) == 0 { return []Notification{}, nil } // Create required data structure var err error t := cayley.NewTransaction() cachedVulnerabilities := make(map[string]*Vulnerability) var notifications []Notification newVulnerabilityNotifications := make(map[string]*NewVulnerabilityNotification) vulnerabilityPriorityIncreasedNotifications := make(map[string]*VulnerabilityPriorityIncreasedNotification) vulnerabilityPackageChangedNotifications := make(map[string]*VulnerabilityPackageChangedNotification) // Iterate over all the vulnerabilities we need to insert/update for _, vulnerability := range vulnerabilities { // Check if the vulnerability already exists existingVulnerability, _ := cachedVulnerabilities[vulnerability.ID] if existingVulnerability == nil { existingVulnerability, err = FindOneVulnerability(vulnerability.ID, FieldVulnerabilityAll) if err != nil && err != cerrors.ErrNotFound { return []Notification{}, err } if existingVulnerability != nil { cachedVulnerabilities[vulnerability.ID] = existingVulnerability } } // Insert/Update vulnerability if existingVulnerability == nil { // The vulnerability does not exist, create it // Verify parameters if vulnerability.ID == "" || vulnerability.Link == "" || vulnerability.Priority == "" { log.Warningf("could not insert an incomplete vulnerability [ID: %s, Link: %s, Priority: %s]", vulnerability.ID, vulnerability.Link, vulnerability.Priority) return []Notification{}, cerrors.NewBadRequestError("Could not insert an incomplete vulnerability") } if !vulnerability.Priority.IsValid() { log.Warningf("could not insert a vulnerability which has an invalid priority [ID: %s, Link: %s, Priority: %s]. Valid priorities are: %v.", vulnerability.ID, vulnerability.Link, vulnerability.Priority, types.Priorities) return []Notification{}, cerrors.NewBadRequestError("Could not insert a vulnerability which has an invalid priority") } if len(vulnerability.FixedInNodes) == 0 { log.Warningf("could not insert a vulnerability which doesn't affect any package [ID: %s].", vulnerability.ID) return []Notification{}, cerrors.NewBadRequestError("could not insert a vulnerability which doesn't affect any package") } // Insert it vulnerability.Node = vulnerability.GetNode() t.AddQuad(cayley.Triple(vulnerability.Node, fieldIs, fieldVulnerabilityIsValue)) t.AddQuad(cayley.Triple(vulnerability.Node, FieldVulnerabilityID, vulnerability.ID)) t.AddQuad(cayley.Triple(vulnerability.Node, FieldVulnerabilityLink, vulnerability.Link)) t.AddQuad(cayley.Triple(vulnerability.Node, FieldVulnerabilityPriority, string(vulnerability.Priority))) t.AddQuad(cayley.Triple(vulnerability.Node, FieldVulnerabilityDescription, vulnerability.Description)) for _, p := range vulnerability.FixedInNodes { t.AddQuad(cayley.Triple(vulnerability.Node, FieldVulnerabilityFixedIn, p)) } // Add a notification notification := &NewVulnerabilityNotification{VulnerabilityID: vulnerability.ID} notifications = append(notifications, notification) newVulnerabilityNotifications[vulnerability.ID] = notification cachedVulnerabilities[vulnerability.ID] = vulnerability } else { // The vulnerability already exists, update it if vulnerability.Link != "" && existingVulnerability.Link != vulnerability.Link { t.RemoveQuad(cayley.Triple(existingVulnerability.Node, FieldVulnerabilityLink, existingVulnerability.Link)) t.AddQuad(cayley.Triple(existingVulnerability.Node, FieldVulnerabilityLink, vulnerability.Link)) existingVulnerability.Link = vulnerability.Link } if vulnerability.Priority != "" && vulnerability.Priority != types.Unknown && existingVulnerability.Priority != vulnerability.Priority { if !vulnerability.Priority.IsValid() { log.Warningf("could not update a vulnerability which has an invalid priority [ID: %s, Link: %s, Priority: %s]. Valid priorities are: %v.", vulnerability.ID, vulnerability.Link, vulnerability.Priority, types.Priorities) return []Notification{}, cerrors.NewBadRequestError("Could not update a vulnerability which has an invalid priority") } // Add a notification about the priority change if the new priority is higher and the vulnerability is not new if vulnerability.Priority.Compare(existingVulnerability.Priority) > 0 { if _, newVulnerabilityNotificationExists := newVulnerabilityNotifications[vulnerability.ID]; !newVulnerabilityNotificationExists { // Any priorityChangeNotification already ? if existingPriorityNotification, _ := vulnerabilityPriorityIncreasedNotifications[vulnerability.ID]; existingPriorityNotification != nil { // There is a priority change notification, replace it but keep the old priority field existingPriorityNotification.NewPriority = vulnerability.Priority } else { // No previous notification, just add a new one notification := &VulnerabilityPriorityIncreasedNotification{OldPriority: existingVulnerability.Priority, NewPriority: vulnerability.Priority, VulnerabilityID: existingVulnerability.ID} notifications = append(notifications, notification) vulnerabilityPriorityIncreasedNotifications[vulnerability.ID] = notification } } } t.RemoveQuad(cayley.Triple(existingVulnerability.Node, FieldVulnerabilityPriority, string(existingVulnerability.Priority))) t.AddQuad(cayley.Triple(existingVulnerability.Node, FieldVulnerabilityPriority, string(vulnerability.Priority))) existingVulnerability.Priority = vulnerability.Priority } if vulnerability.Description != "" && existingVulnerability.Description != vulnerability.Description { t.RemoveQuad(cayley.Triple(existingVulnerability.Node, FieldVulnerabilityDescription, existingVulnerability.Description)) t.AddQuad(cayley.Triple(existingVulnerability.Node, FieldVulnerabilityDescription, vulnerability.Description)) existingVulnerability.Description = vulnerability.Description } newFixedInNodes := utils.CompareStringLists(vulnerability.FixedInNodes, existingVulnerability.FixedInNodes) if len(newFixedInNodes) > 0 { var removedNodes []string var addedNodes []string existingVulnerabilityFixedInPackages, err := FindAllPackagesByNodes(existingVulnerability.FixedInNodes, []string{FieldPackageOS, FieldPackageName, FieldPackageVersion}) if err != nil { return []Notification{}, err } newFixedInPackages, err := FindAllPackagesByNodes(newFixedInNodes, []string{FieldPackageOS, FieldPackageName, FieldPackageVersion}) if err != nil { return []Notification{}, err } for _, p := range newFixedInPackages { for _, ep := range existingVulnerabilityFixedInPackages { if p.Branch() == ep.Branch() { // A link to this package branch already exist and is not the same version, we will delete it t.RemoveQuad(cayley.Triple(existingVulnerability.Node, FieldVulnerabilityFixedIn, ep.Node)) var index int for i, n := range existingVulnerability.FixedInNodes { if n == ep.Node { index = i break } } existingVulnerability.FixedInNodes = append(existingVulnerability.FixedInNodes[index:], existingVulnerability.FixedInNodes[index+1:]...) removedNodes = append(removedNodes, ep.Node) } } t.AddQuad(cayley.Triple(existingVulnerability.Node, FieldVulnerabilityFixedIn, p.Node)) existingVulnerability.FixedInNodes = append(existingVulnerability.FixedInNodes, p.Node) addedNodes = append(addedNodes, p.Node) } // Add notification about the FixedIn modification if the vulnerability is not new if _, newVulnerabilityNotificationExists := newVulnerabilityNotifications[vulnerability.ID]; !newVulnerabilityNotificationExists { // Any VulnerabilityPackageChangedNotification already ? if existingPackageNotification, _ := vulnerabilityPackageChangedNotifications[vulnerability.ID]; existingPackageNotification != nil { // There is a priority change notification, add the packages modifications to it existingPackageNotification.AddedFixedInNodes = append(existingPackageNotification.AddedFixedInNodes, addedNodes...) existingPackageNotification.RemovedFixedInNodes = append(existingPackageNotification.RemovedFixedInNodes, removedNodes...) } else { // No previous notification, just add a new one notification := &VulnerabilityPackageChangedNotification{VulnerabilityID: vulnerability.ID, AddedFixedInNodes: addedNodes, RemovedFixedInNodes: removedNodes} notifications = append(notifications, notification) vulnerabilityPackageChangedNotifications[vulnerability.ID] = notification } } } } } // Apply transaction if err = store.ApplyTransaction(t); err != nil { log.Errorf("failed transaction (InsertVulnerabilities): %s", err) return []Notification{}, ErrTransaction } return notifications, nil }
// InsertPackages inserts several packages in the database in one transaction // Packages are stored in linked lists, one per Branch. Each linked list has a start package and an end package defined with types.MinVersion/types.MaxVersion versions // // OS, Name and Version fields have to be specified. // If the insertion is successfull, the Node field is filled and represents the graph node identifier. func InsertPackages(packageParameters []*Package) error { if len(packageParameters) == 0 { return nil } // Verify parameters for _, pkg := range packageParameters { if pkg.OS == "" || pkg.Name == "" || pkg.Version.String() == "" { log.Warningf("could not insert an incomplete package [OS: %s, Name: %s, Version: %s]", pkg.OS, pkg.Name, pkg.Version) return cerrors.NewBadRequestError("could not insert an incomplete package") } } // Iterate over all the packages we need to insert for _, packageParameter := range packageParameters { t := cayley.NewTransaction() // Is the package already existing ? pkg, err := FindOnePackage(packageParameter.OS, packageParameter.Name, packageParameter.Version, []string{}) if err != nil && err != cerrors.ErrNotFound { return err } if pkg != nil { packageParameter.Node = pkg.Node continue } // Get all packages of the same branch (both from local cache and database) branchPackages, err := FindAllPackagesByBranch(packageParameter.OS, packageParameter.Name, []string{FieldPackageOS, FieldPackageName, FieldPackageVersion, FieldPackageNextVersion}) if err != nil { return err } if len(branchPackages) == 0 { // The branch does not exist yet insertingStartPackage := packageParameter.Version == types.MinVersion insertingEndPackage := packageParameter.Version == types.MaxVersion // Create and insert a end package endPackage := &Package{ OS: packageParameter.OS, Name: packageParameter.Name, Version: types.MaxVersion, } endPackage.Node = endPackage.GetNode() t.AddQuad(cayley.Triple(endPackage.Node, fieldIs, fieldPackageIsValue)) t.AddQuad(cayley.Triple(endPackage.Node, FieldPackageOS, endPackage.OS)) t.AddQuad(cayley.Triple(endPackage.Node, FieldPackageName, endPackage.Name)) t.AddQuad(cayley.Triple(endPackage.Node, FieldPackageVersion, endPackage.Version.String())) t.AddQuad(cayley.Triple(endPackage.Node, FieldPackageNextVersion, "")) // Create the inserted package if it is different than a start/end package var newPackage *Package if !insertingStartPackage && !insertingEndPackage { newPackage = &Package{ OS: packageParameter.OS, Name: packageParameter.Name, Version: packageParameter.Version, } newPackage.Node = newPackage.GetNode() t.AddQuad(cayley.Triple(newPackage.Node, fieldIs, fieldPackageIsValue)) t.AddQuad(cayley.Triple(newPackage.Node, FieldPackageOS, newPackage.OS)) t.AddQuad(cayley.Triple(newPackage.Node, FieldPackageName, newPackage.Name)) t.AddQuad(cayley.Triple(newPackage.Node, FieldPackageVersion, newPackage.Version.String())) t.AddQuad(cayley.Triple(newPackage.Node, FieldPackageNextVersion, endPackage.Node)) packageParameter.Node = newPackage.Node } // Create and insert a start package startPackage := &Package{ OS: packageParameter.OS, Name: packageParameter.Name, Version: types.MinVersion, } startPackage.Node = startPackage.GetNode() t.AddQuad(cayley.Triple(startPackage.Node, fieldIs, fieldPackageIsValue)) t.AddQuad(cayley.Triple(startPackage.Node, FieldPackageOS, startPackage.OS)) t.AddQuad(cayley.Triple(startPackage.Node, FieldPackageName, startPackage.Name)) t.AddQuad(cayley.Triple(startPackage.Node, FieldPackageVersion, startPackage.Version.String())) if !insertingStartPackage && !insertingEndPackage { t.AddQuad(cayley.Triple(startPackage.Node, FieldPackageNextVersion, newPackage.Node)) } else { t.AddQuad(cayley.Triple(startPackage.Node, FieldPackageNextVersion, endPackage.Node)) } // Set package node if insertingEndPackage { packageParameter.Node = endPackage.Node } else if insertingStartPackage { packageParameter.Node = startPackage.Node } } else { // The branch already exists // Create the package newPackage := &Package{OS: packageParameter.OS, Name: packageParameter.Name, Version: packageParameter.Version} newPackage.Node = "package:" + utils.Hash(newPackage.Key()) packageParameter.Node = newPackage.Node t.AddQuad(cayley.Triple(newPackage.Node, fieldIs, fieldPackageIsValue)) t.AddQuad(cayley.Triple(newPackage.Node, FieldPackageOS, newPackage.OS)) t.AddQuad(cayley.Triple(newPackage.Node, FieldPackageName, newPackage.Name)) t.AddQuad(cayley.Triple(newPackage.Node, FieldPackageVersion, newPackage.Version.String())) // Sort branchPackages by version (including the new package) branchPackages = append(branchPackages, newPackage) sort.Sort(ByVersion(branchPackages)) // Find my prec/succ GraphID in the sorted slice now newPackageKey := newPackage.Key() var pred, succ *Package var found bool for _, p := range branchPackages { equal := p.Key() == newPackageKey if !equal && !found { pred = p } else if found { succ = p break } else if equal { found = true continue } } if pred == nil || succ == nil { log.Warningf("could not find any package predecessor/successor of: [OS: %s, Name: %s, Version: %s].", packageParameter.OS, packageParameter.Name, packageParameter.Version) return cerrors.NewBadRequestError("could not find package predecessor/successor") } // Link the new packages with the branch t.RemoveQuad(cayley.Triple(pred.Node, FieldPackageNextVersion, succ.Node)) pred.NextVersionNode = newPackage.Node t.AddQuad(cayley.Triple(pred.Node, FieldPackageNextVersion, newPackage.Node)) newPackage.NextVersionNode = succ.Node t.AddQuad(cayley.Triple(newPackage.Node, FieldPackageNextVersion, succ.Node)) } // Apply transaction if err := store.ApplyTransaction(t); err != nil { log.Errorf("failed transaction (InsertPackages): %s", err) return ErrTransaction } } // Return return nil }
// 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.Triple(layer.Node, fieldIs, fieldLayerIsValue)) t.AddQuad(cayley.Triple(layer.Node, FieldLayerID, layer.ID)) t.AddQuad(cayley.Triple(layer.Node, FieldLayerParent, layer.ParentNode)) } else { // Update case: remove everything before we add updated data t.RemoveQuad(cayley.Triple(layer.Node, FieldLayerOS, existingLayer.OS)) for _, pkg := range existingLayer.InstalledPackagesNodes { t.RemoveQuad(cayley.Triple(layer.Node, fieldLayerInstalledPackages, pkg)) } for _, pkg := range existingLayer.RemovedPackagesNodes { t.RemoveQuad(cayley.Triple(layer.Node, fieldLayerRemovedPackages, pkg)) } t.RemoveQuad(cayley.Triple(layer.Node, FieldLayerEngineVersion, strconv.Itoa(existingLayer.EngineVersion))) } // Add OS/Packages t.AddQuad(cayley.Triple(layer.Node, FieldLayerOS, layer.OS)) for _, pkg := range layer.InstalledPackagesNodes { t.AddQuad(cayley.Triple(layer.Node, fieldLayerInstalledPackages, pkg)) } for _, pkg := range layer.RemovedPackagesNodes { t.AddQuad(cayley.Triple(layer.Node, fieldLayerRemovedPackages, pkg)) } t.AddQuad(cayley.Triple(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 }