diff --git a/cmd/dimacs/cmd.go b/cmd/dimacs/cmd.go index 6c37636..43bffc9 100644 --- a/cmd/dimacs/cmd.go +++ b/cmd/dimacs/cmd.go @@ -53,7 +53,10 @@ func solve(path string) error { } // build solver - so := solver.NewDeppySolver(NewDimacsEntitySource(dimacs), NewDimacsVariableSource(dimacs)) + so, err := solver.NewDeppySolver(NewDimacsVariableSource(dimacs)) + if err != nil { + return err + } // get solution solution, err := so.Solve(context.Background(), solver.AddAllVariablesToSolution()) diff --git a/cmd/dimacs/dimacs_constraints.go b/cmd/dimacs/dimacs_constraints.go index 3b87989..86cbc82 100644 --- a/cmd/dimacs/dimacs_constraints.go +++ b/cmd/dimacs/dimacs_constraints.go @@ -21,16 +21,13 @@ func NewDimacsVariableSource(dimacs *Dimacs) *ConstraintGenerator { } } -func (d *ConstraintGenerator) GetVariables(ctx context.Context, entitySource input.EntitySource) ([]deppy.Variable, error) { +func (d *ConstraintGenerator) GetVariables(ctx context.Context) ([]deppy.Variable, error) { varMap := make(map[deppy.Identifier]*input.SimpleVariable, len(d.dimacs.variables)) variables := make([]deppy.Variable, 0, len(d.dimacs.variables)) - if err := entitySource.Iterate(ctx, func(entity *input.Entity) error { - variable := input.NewSimpleVariable(entity.Identifier()) + + for _, id := range d.dimacs.variables { + variable := input.NewSimpleVariable(deppy.IdentifierFromString(id)) variables = append(variables, variable) - varMap[entity.Identifier()] = variable - return nil - }); err != nil { - return nil, err } // create constraints out of the clauses diff --git a/cmd/dimacs/dimacs_source.go b/cmd/dimacs/dimacs_source.go deleted file mode 100644 index 0808528..0000000 --- a/cmd/dimacs/dimacs_source.go +++ /dev/null @@ -1,24 +0,0 @@ -package dimacs - -import ( - "github.com/operator-framework/deppy/pkg/deppy" - "github.com/operator-framework/deppy/pkg/deppy/input" -) - -var _ input.EntitySource = &EntitySource{} - -type EntitySource struct { - *input.CacheEntitySource -} - -func NewDimacsEntitySource(dimacs *Dimacs) *EntitySource { - entities := make(map[deppy.Identifier]input.Entity, len(dimacs.Variables())) - for _, variable := range dimacs.Variables() { - id := deppy.Identifier(variable) - entities[id] = *input.NewEntity(id, nil) - } - - return &EntitySource{ - CacheEntitySource: input.NewCacheQuerier(entities), - } -} diff --git a/cmd/sudoku/cmd.go b/cmd/sudoku/cmd.go index f6a3e80..891746a 100644 --- a/cmd/sudoku/cmd.go +++ b/cmd/sudoku/cmd.go @@ -23,8 +23,11 @@ func NewSudokuCommand() *cobra.Command { func solve() error { // build solver - sudoku := NewSudoku() - so := solver.NewDeppySolver(sudoku, sudoku) + sudoku := &Sudoku{} + so, err := solver.NewDeppySolver(sudoku) + if err != nil { + return err + } // get solution solution, err := so.Solve(context.Background()) diff --git a/cmd/sudoku/sudoku.go b/cmd/sudoku/sudoku.go index b3dd343..8eb5e59 100644 --- a/cmd/sudoku/sudoku.go +++ b/cmd/sudoku/sudoku.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "math/rand" - "strconv" "time" "github.com/operator-framework/deppy/pkg/deppy" @@ -12,12 +11,9 @@ import ( "github.com/operator-framework/deppy/pkg/deppy/input" ) -var _ input.EntitySource = &Sudoku{} var _ input.VariableSource = &Sudoku{} -type Sudoku struct { - *input.CacheEntitySource -} +type Sudoku struct{} func GetID(row int, col int, num int) deppy.Identifier { n := num @@ -27,25 +23,10 @@ func GetID(row int, col int, num int) deppy.Identifier { } func NewSudoku() *Sudoku { - var entities = make(map[deppy.Identifier]input.Entity, 9*9*9) - for row := 0; row < 9; row++ { - for col := 0; col < 9; col++ { - for num := 0; num < 9; num++ { - id := GetID(row, col, num) - entities[id] = *input.NewEntity(id, map[string]string{ - "row": strconv.Itoa(row), - "col": strconv.Itoa(col), - "num": strconv.Itoa(num), - }) - } - } - } - return &Sudoku{ - CacheEntitySource: input.NewCacheQuerier(entities), - } + return &Sudoku{} } -func (s Sudoku) GetVariables(_ context.Context, _ input.EntitySource) ([]deppy.Variable, error) { +func (s Sudoku) GetVariables(ctx context.Context) ([]deppy.Variable, error) { // adapted from: https://github.com/go-air/gini/blob/871d828a26852598db2b88f436549634ba9533ff/sudoku_test.go#L10 variables := make(map[deppy.Identifier]*input.SimpleVariable, 0) inorder := make([]deppy.Variable, 0) diff --git a/pkg/deppy/input/entity.go b/pkg/deppy/input/entity.go deleted file mode 100644 index 6bcd0b3..0000000 --- a/pkg/deppy/input/entity.go +++ /dev/null @@ -1,21 +0,0 @@ -package input - -import ( - "github.com/operator-framework/deppy/pkg/deppy" -) - -type Entity struct { - ID deppy.Identifier `json:"identifier"` - Properties map[string]string `json:"properties"` -} - -func (e *Entity) Identifier() deppy.Identifier { - return e.ID -} - -func NewEntity(id deppy.Identifier, properties map[string]string) *Entity { - return &Entity{ - ID: id, - Properties: properties, - } -} diff --git a/pkg/deppy/input/entity_source.go b/pkg/deppy/input/entity_source.go deleted file mode 100644 index 4cd7b01..0000000 --- a/pkg/deppy/input/entity_source.go +++ /dev/null @@ -1,31 +0,0 @@ -package input - -import ( - "context" - - "github.com/operator-framework/deppy/pkg/deppy" -) - -// IteratorFunction is executed for each entity when iterating over all entities -type IteratorFunction func(entity *Entity) error - -// SortFunction returns true if e1 is less than e2 -type SortFunction func(e1 *Entity, e2 *Entity) bool - -// GroupByFunction transforms an entity into a slice of keys (strings) -// over which the entities will be grouped by -type GroupByFunction func(e1 *Entity) []string - -// Predicate returns true if the entity should be kept when filtering -type Predicate func(entity *Entity) bool - -type EntityList []Entity -type EntityListMap map[string]EntityList - -// EntitySource provides a query and content acquisition interface for arbitrary entity stores -type EntitySource interface { - Get(ctx context.Context, id deppy.Identifier) (*Entity, error) - Filter(ctx context.Context, filter Predicate) (EntityList, error) - GroupBy(ctx context.Context, fn GroupByFunction) (EntityListMap, error) - Iterate(ctx context.Context, fn IteratorFunction) error -} diff --git a/pkg/deppy/input/entity_test.go b/pkg/deppy/input/entity_test.go deleted file mode 100644 index c926d6d..0000000 --- a/pkg/deppy/input/entity_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package input_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/operator-framework/deppy/pkg/deppy/input" - - "github.com/operator-framework/deppy/pkg/deppy" -) - -var _ = Describe("Entity", func() { - It("stores id and properties", func() { - entity := input.NewEntity("id", map[string]string{"prop": "value"}) - Expect(entity.Identifier()).To(Equal(deppy.Identifier("id"))) - - value, ok := entity.Properties["prop"] - Expect(ok).To(BeTrue()) - Expect(value).To(Equal("value")) - }) - - It("returns not found error when property is not found", func() { - entity := input.NewEntity("id", map[string]string{"foo": "value"}) - value, ok := entity.Properties["bar"] - Expect(value).To(Equal("")) - Expect(ok).To(BeFalse()) - }) -}) diff --git a/pkg/deppy/input/query.go b/pkg/deppy/input/query.go deleted file mode 100644 index 4838545..0000000 --- a/pkg/deppy/input/query.go +++ /dev/null @@ -1,62 +0,0 @@ -package input - -import ( - "sort" - - "github.com/operator-framework/deppy/pkg/deppy" -) - -func (r EntityList) Sort(fn SortFunction) EntityList { - sort.SliceStable(r, func(i, j int) bool { - return fn(&r[i], &r[j]) - }) - return r -} - -func (r EntityList) CollectIds() []deppy.Identifier { - ids := make([]deppy.Identifier, len(r)) - for i := range r { - ids[i] = r[i].Identifier() - } - return ids -} - -func (g EntityListMap) Sort(fn SortFunction) EntityListMap { - for key := range g { - sort.SliceStable(g[key], func(i, j int) bool { - return fn(&g[key][i], &g[key][j]) - }) - } - return g -} -func And(predicates ...Predicate) Predicate { - return func(entity *Entity) bool { - eval := true - for _, predicate := range predicates { - eval = eval && predicate(entity) - if !eval { - return false - } - } - return eval - } -} - -func Or(predicates ...Predicate) Predicate { - return func(entity *Entity) bool { - eval := false - for _, predicate := range predicates { - eval = eval || predicate(entity) - if eval { - return true - } - } - return eval - } -} - -func Not(predicate Predicate) Predicate { - return func(entity *Entity) bool { - return !predicate(entity) - } -} diff --git a/pkg/deppy/input/variable_source.go b/pkg/deppy/input/variable_source.go index 2490831..41514c5 100644 --- a/pkg/deppy/input/variable_source.go +++ b/pkg/deppy/input/variable_source.go @@ -8,7 +8,7 @@ import ( // VariableSource generates solver constraints given an entity querier interface type VariableSource interface { - GetVariables(ctx context.Context, entitySource EntitySource) ([]deppy.Variable, error) + GetVariables(ctx context.Context) ([]deppy.Variable, error) } var _ deppy.Variable = &SimpleVariable{} diff --git a/pkg/deppy/solver/solver.go b/pkg/deppy/solver/solver.go index d60036f..fb298dd 100644 --- a/pkg/deppy/solver/solver.go +++ b/pkg/deppy/solver/solver.go @@ -77,13 +77,11 @@ func AddAllVariablesToSolution() Option { // DeppySolver is a simple solver implementation that takes an entity source group and a constraint aggregator // to produce a Solution (or error if no solution can be found) type DeppySolver struct { - entitySource input.EntitySource variableSource input.VariableSource } -func NewDeppySolver(entitySource input.EntitySource, variableSource input.VariableSource) *DeppySolver { +func NewDeppySolver(variableSource input.VariableSource) *DeppySolver { return &DeppySolver{ - entitySource: entitySource, variableSource: variableSource, } } @@ -91,7 +89,7 @@ func NewDeppySolver(entitySource input.EntitySource, variableSource input.Variab func (d DeppySolver) Solve(ctx context.Context, options ...Option) (*Solution, error) { solutionOpts := defaultSolutionOptions().apply(options...) - vars, err := d.variableSource.GetVariables(ctx, d.entitySource) + vars, err := d.variableSource.GetVariables(ctx) if err != nil { return nil, err } diff --git a/pkg/deppy/solver/solver_test.go b/pkg/deppy/solver/solver_test.go index 449de25..68b6937 100644 --- a/pkg/deppy/solver/solver_test.go +++ b/pkg/deppy/solver/solver_test.go @@ -23,35 +23,22 @@ func TestSolver(t *testing.T) { RunSpecs(t, "Solver Suite") } -type EntitySourceStruct struct { +type VariableSourceStruct struct { variables []deppy.Variable - input.EntitySource } -func (c EntitySourceStruct) GetVariables(_ context.Context, _ input.EntitySource) ([]deppy.Variable, error) { +func (c VariableSourceStruct) GetVariables(_ context.Context) ([]deppy.Variable, error) { return c.variables, nil } -func NewEntitySource(variables []deppy.Variable) *EntitySourceStruct { - entities := make(map[deppy.Identifier]input.Entity, len(variables)) - for _, variable := range variables { - entityID := variable.Identifier() - entities[entityID] = *input.NewEntity(entityID, map[string]string{"x": "y"}) - } - return &EntitySourceStruct{ - variables: variables, - EntitySource: input.NewCacheQuerier(entities), - } -} - var _ = Describe("Entity", func() { It("should select a mandatory entity", func() { variables := []deppy.Variable{ input.NewSimpleVariable("1", constraint.Mandatory()), input.NewSimpleVariable("2"), } - s := NewEntitySource(variables) - so := solver.NewDeppySolver(s, s) + varSrcStruct := &VariableSourceStruct{variables: variables} + so := solver.NewDeppySolver(varSrcStruct) solution, err := so.Solve(context.Background()) Expect(err).ToNot(HaveOccurred()) Expect(solution.SelectedVariables()).To(MatchAllKeys(Keys{ @@ -65,8 +52,10 @@ var _ = Describe("Entity", func() { input.NewSimpleVariable("1", constraint.Mandatory()), input.NewSimpleVariable("2", constraint.Mandatory()), } - s := NewEntitySource(variables) - so := solver.NewDeppySolver(s, s) + + varSrcStruct := &VariableSourceStruct{variables: variables} + so := solver.NewDeppySolver(varSrcStruct) + solution, err := so.Solve(context.Background()) Expect(err).ToNot(HaveOccurred()) Expect(solution.SelectedVariables()).To(MatchAllKeys(Keys{ @@ -81,9 +70,10 @@ var _ = Describe("Entity", func() { input.NewSimpleVariable("2"), input.NewSimpleVariable("3"), } - s := NewEntitySource(variables) - so := solver.NewDeppySolver(s, s) + varSrcStruct := &VariableSourceStruct{variables: variables} + so := solver.NewDeppySolver(varSrcStruct) + solution, err := so.Solve(context.Background()) Expect(err).ToNot(HaveOccurred()) Expect(solution.SelectedVariables()).To(MatchAllKeys(Keys{ @@ -98,16 +88,17 @@ var _ = Describe("Entity", func() { input.NewSimpleVariable("2", constraint.Prohibited()), input.NewSimpleVariable("3"), } - s := NewEntitySource(variables) - so := solver.NewDeppySolver(s, s) + + varSrcStruct := &VariableSourceStruct{variables: variables} + so := solver.NewDeppySolver(varSrcStruct) + solution, err := so.Solve(context.Background()) Expect(err).ToNot(HaveOccurred()) Expect(solution.Error()).To(HaveOccurred()) }) It("should return peripheral errors", func() { - s := NewEntitySource(nil) - so := solver.NewDeppySolver(s, FailingVariableSource{}) + so := solver.NewDeppySolver(FailingVariableSource{}) solution, err := so.Solve(context.Background()) Expect(err).To(HaveOccurred()) Expect(solution).To(BeNil()) @@ -119,8 +110,10 @@ var _ = Describe("Entity", func() { input.NewSimpleVariable("2"), input.NewSimpleVariable("3", constraint.Prohibited()), } - s := NewEntitySource(variables) - so := solver.NewDeppySolver(s, s) + + varSrcStruct := &VariableSourceStruct{variables: variables} + so := solver.NewDeppySolver(varSrcStruct) + solution, err := so.Solve(context.Background()) Expect(err).ToNot(HaveOccurred()) Expect(solution.SelectedVariables()).To(MatchAllKeys(Keys{ @@ -135,8 +128,10 @@ var _ = Describe("Entity", func() { input.NewSimpleVariable("2"), input.NewSimpleVariable("3", constraint.Prohibited()), } - s := NewEntitySource(variables) - so := solver.NewDeppySolver(s, s) + + varSrcStruct := &VariableSourceStruct{variables: variables} + so := solver.NewDeppySolver(varSrcStruct) + solution, err := so.Solve(context.Background(), solver.AddAllVariablesToSolution()) Expect(err).ToNot(HaveOccurred()) Expect(solution.AllVariables()).To(Equal([]deppy.Variable{ @@ -153,8 +148,10 @@ var _ = Describe("Entity", func() { input.NewSimpleVariable("3", constraint.Prohibited()), input.NewSimpleVariable("4"), } - s := NewEntitySource(variables) - so := solver.NewDeppySolver(s, s) + + varSrcStruct := &VariableSourceStruct{variables: variables} + so := solver.NewDeppySolver(varSrcStruct) + solution, err := so.Solve(context.Background()) Expect(err).ToNot(HaveOccurred()) Expect(solution.SelectedVariables()).To(MatchAllKeys(Keys{ @@ -170,8 +167,10 @@ var _ = Describe("Entity", func() { input.NewSimpleVariable("3", constraint.AtMost(1, "3", "4")), input.NewSimpleVariable("4"), } - s := NewEntitySource(variables) - so := solver.NewDeppySolver(s, s) + + varSrcStruct := &VariableSourceStruct{variables: variables} + so := solver.NewDeppySolver(varSrcStruct) + solution, err := so.Solve(context.Background()) Expect(err).ToNot(HaveOccurred()) Expect(solution.SelectedVariables()).To(MatchAllKeys(Keys{ @@ -189,8 +188,8 @@ var _ = Describe("Entity", func() { input.NewSimpleVariable("5"), input.NewSimpleVariable("6"), } - s := NewEntitySource(variables) - so := solver.NewDeppySolver(s, s) + varSrcStruct := &VariableSourceStruct{variables: variables} + so := solver.NewDeppySolver(varSrcStruct) solution, err := so.Solve(context.Background()) Expect(err).ToNot(HaveOccurred()) Expect(solution.SelectedVariables()).To(MatchAllKeys(Keys{ @@ -207,6 +206,6 @@ var _ input.VariableSource = &FailingVariableSource{} type FailingVariableSource struct { } -func (f FailingVariableSource) GetVariables(_ context.Context, _ input.EntitySource) ([]deppy.Variable, error) { +func (f FailingVariableSource) GetVariables(ctx context.Context) ([]deppy.Variable, error) { return nil, fmt.Errorf("error") } diff --git a/pkg/ext/olm/constraints.go b/pkg/ext/olm/constraints.go new file mode 100644 index 0000000..8e7b699 --- /dev/null +++ b/pkg/ext/olm/constraints.go @@ -0,0 +1,63 @@ +package olm + +import ( + "context" + "strings" + + "github.com/operator-framework/deppy/pkg/deppy/input" + + "github.com/operator-framework/deppy/pkg/deppy" +) + +// Question: +// These are not needed since while constructing the variables, its upto the users +// to sort, group by etc while providing it to the solver. Is this understanding right? +// Which means we will do the filtering based on constraints before constructing variables. +// Maybe we should create helpers for these separately keeping "bundle" as the domain. + +const ( + PropertyOLMGVK = "olm.gvk" + PropertyOLMPackageName = "olm.packageName" + PropertyOLMVersion = "olm.version" + PropertyOLMChannel = "olm.channel" + PropertyOLMDefaultChannel = "olm.defaultChannel" + + propertyNotFound = "" +) + +var _ input.VariableSource = &requirePackage{} + +type requirePackage struct { + packageName string + versionRange string + channel string +} + +func (r *requirePackage) GetVariables(ctx context.Context) ([]deppy.Variable, error) { + return nil, nil +} + +// RequirePackage creates a constraint generator to describe that a package is wanted for installation +func RequirePackage(packageName string, versionRange string, channel string) input.VariableSource { + return &requirePackage{ + packageName: packageName, + versionRange: versionRange, + channel: channel, + } +} + +// compareProperty compares two entity property values. It works as strings.Compare +// except when one of the properties is not found (""). It computes not found properties as higher than found. +// If p1 is not found, it returns 1, if p2 is not found it returns -1 +func compareProperty(p1, p2 string) int { + if p1 == p2 { + return 0 + } + if p1 == propertyNotFound { + return 1 + } + if p2 == propertyNotFound { + return -1 + } + return strings.Compare(p1, p2) +}