Beispiel #1
0
// 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)
	}
}
Beispiel #2
0
// 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())
	}
}
Beispiel #3
0
// 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
}
Beispiel #4
0
// 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
}
Beispiel #5
0
// 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)
}
Beispiel #6
0
// 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
}
Beispiel #7
0
// 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}
}
Beispiel #8
0
// 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
}
Beispiel #9
0
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, "")
	}
}
Beispiel #10
0
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
}
Beispiel #11
0
// 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
}
Beispiel #12
0
// 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
}
Beispiel #13
0
// 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
}