// 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("locked_by", owner).Out("locked_until")) if err == nil && currentExpiration != "" { // Renew our lock if currentExpiration == untilString { return true, until } t := cayley.NewTransaction() t.RemoveQuad(cayley.Quad(name, "locked_until", currentExpiration, "")) t.AddQuad(cayley.Quad(name, "locked_until", 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.Quad(name, "locked", "locked", "")) // Necessary to make the transaction fails if the lock already exists (and has not been pruned) t.AddQuad(cayley.Quad(name, "locked_until", untilString, "")) t.AddQuad(cayley.Quad(name, "locked_by", owner, "")) glog.SetStderrThreshold("FATAL") success := store.ApplyTransaction(t) == nil glog.SetStderrThreshold("ERROR") return success, until }
// 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 tr := cayley.NewTransaction() it, _ := cayley.StartPath(store, "locked").In("locked").Save("locked_until", "locked_until").Save("locked_by", "locked_by").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["locked_until"]) o := store.NameOf(tags["locked_by"]) tt, _ := strconv.ParseInt(t, 10, 64) if now.Unix() > tt { log.Debugf("Lock %s owned by %s has expired.", n, o) tr.RemoveQuad(cayley.Quad(n, "locked", "locked", "")) tr.RemoveQuad(cayley.Quad(n, "locked_until", t, "")) tr.RemoveQuad(cayley.Quad(n, "locked_by", o, "")) } } store.ApplyTransaction(tr) }
// 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 = "flag:" + name if currentValue != "" { t.RemoveQuad(cayley.Quad(name, "value", currentValue, "")) } t.AddQuad(cayley.Quad(name, "value", value, "")) // Apply transaction if err = store.ApplyTransaction(t); err != nil { log.Errorf("failed transaction (UpdateFlag): %s", err) return ErrTransaction } // Return return nil }
// 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()) } }
func (ng *NodeGraph) removeNode(nd *Node) (err error) { if len(nd.Children()) > 0 { return errors.New("Can't delete node with children, must delete children first") } transaction := cayley.NewTransaction() if nd.Mode() > 0 { transaction.RemoveQuad(cayley.Quad(nd.Id, modeLink, fmt.Sprint(int(nd.Mode())), "")) } if !nd.MTime().IsZero() { transaction.RemoveQuad(cayley.Quad(nd.Id, mTimeLink, fmt.Sprint(nd.MTime().Format(timeFormat)), "")) } if nd.Name() != "" { transaction.RemoveQuad(cayley.Quad(nd.Id, nameLink, nd.Name(), "")) } if nd.Parent() != nil { transaction.RemoveQuad(cayley.Quad(nd.Id, parentLink, nd.Parent().Id, "")) } if nd.Type() != "" { transaction.RemoveQuad(cayley.Quad(nd.Id, typeLink, nd.Type(), "")) } err = ng.ApplyTransaction(transaction) if err == nil { nd.mode = os.FileMode(0) nd.mTime = time.Time{} nd.name = "" nd.parentId = "" nd.Id = "" } return }
// 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 }
// MarkNotificationAsSent marks a notification as sent. func MarkNotificationAsSent(node string) { // Initialize transaction t := cayley.NewTransaction() t.RemoveQuad(cayley.Quad(node, "isSent", strconv.FormatBool(false), "")) t.AddQuad(cayley.Quad(node, "isSent", strconv.FormatBool(true), "")) // Apply transaction store.ApplyTransaction(t) }
// 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} }
// Unlock unlocks a lock specified by its name if I own it func Unlock(name, owner string) { pruneLocks() t := cayley.NewTransaction() it, _ := cayley.StartPath(store, name).Has("locked", "locked").Has("locked_by", owner).Save("locked_until", "locked_until").BuildIterator().Optimize() defer it.Close() for cayley.RawNext(it) { tags := make(map[string]graph.Value) it.TagResults(tags) t.RemoveQuad(cayley.Quad(name, "locked", "locked", "")) t.RemoveQuad(cayley.Quad(name, "locked_until", store.NameOf(tags["locked_until"]), "")) t.RemoveQuad(cayley.Quad(name, "locked_by", owner, "")) } store.ApplyTransaction(t) }
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 }
// 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 }
// 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 := "notification:" + uuid.New() t.AddQuad(cayley.Quad(node, FieldIs, "notification", "")) t.AddQuad(cayley.Quad(node, "type", wrappedNotification.Type, "")) t.AddQuad(cayley.Quad(node, "data", wrappedNotification.Data, "")) t.AddQuad(cayley.Quad(node, "isSent", strconv.FormatBool(false), "")) } // Apply transaction if err := store.ApplyTransaction(t); err != nil { log.Errorf("failed transaction (InsertNotifications): %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.Quad(endPackage.Node, FieldIs, FieldPackageIsValue, "")) t.AddQuad(cayley.Quad(endPackage.Node, FieldPackageOS, endPackage.OS, "")) t.AddQuad(cayley.Quad(endPackage.Node, FieldPackageName, endPackage.Name, "")) t.AddQuad(cayley.Quad(endPackage.Node, FieldPackageVersion, endPackage.Version.String(), "")) t.AddQuad(cayley.Quad(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.Quad(newPackage.Node, FieldIs, FieldPackageIsValue, "")) t.AddQuad(cayley.Quad(newPackage.Node, FieldPackageOS, newPackage.OS, "")) t.AddQuad(cayley.Quad(newPackage.Node, FieldPackageName, newPackage.Name, "")) t.AddQuad(cayley.Quad(newPackage.Node, FieldPackageVersion, newPackage.Version.String(), "")) t.AddQuad(cayley.Quad(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.Quad(startPackage.Node, FieldIs, FieldPackageIsValue, "")) t.AddQuad(cayley.Quad(startPackage.Node, FieldPackageOS, startPackage.OS, "")) t.AddQuad(cayley.Quad(startPackage.Node, FieldPackageName, startPackage.Name, "")) t.AddQuad(cayley.Quad(startPackage.Node, FieldPackageVersion, startPackage.Version.String(), "")) if !insertingStartPackage && !insertingEndPackage { t.AddQuad(cayley.Quad(startPackage.Node, FieldPackageNextVersion, newPackage.Node, "")) } else { t.AddQuad(cayley.Quad(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.Quad(newPackage.Node, FieldIs, FieldPackageIsValue, "")) t.AddQuad(cayley.Quad(newPackage.Node, FieldPackageOS, newPackage.OS, "")) t.AddQuad(cayley.Quad(newPackage.Node, FieldPackageName, newPackage.Name, "")) t.AddQuad(cayley.Quad(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.Quad(pred.Node, FieldPackageNextVersion, succ.Node, "")) pred.NextVersionNode = newPackage.Node t.AddQuad(cayley.Quad(pred.Node, FieldPackageNextVersion, newPackage.Node, "")) newPackage.NextVersionNode = succ.Node t.AddQuad(cayley.Quad(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 }