Skip to content

Commit c3119aa

Browse files
authored
Namespace versioning & upgrade path (#129)
* rule ast new * Upgrade strategy * comment * make sure eval still works * comments
1 parent 16e1880 commit c3119aa

File tree

19 files changed

+442
-457
lines changed

19 files changed

+442
-457
lines changed

cmd/lekko/main.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func formatCmd() *cobra.Command {
104104
}
105105

106106
func compileCmd() *cobra.Command {
107-
var force, dryRun bool
107+
var force, dryRun, upgrade bool
108108
cmd := &cobra.Command{
109109
Use: "compile [namespace[/feature]]",
110110
Short: "compiles features based on individual definitions",
@@ -142,7 +142,8 @@ func compileCmd() *cobra.Command {
142142
IgnoreBackwardsCompatibility: force,
143143
// don't verify file structure, since we may have not yet generated
144144
// the DSLs for newly added features.
145-
Verify: false,
145+
Verify: false,
146+
Upgrade: upgrade,
146147
}); err != nil {
147148
return errors.Wrap(err, "compile")
148149
}
@@ -151,6 +152,7 @@ func compileCmd() *cobra.Command {
151152
}
152153
cmd.Flags().BoolVarP(&force, "force", "f", false, "force compilation, ignoring validation check failures.")
153154
cmd.Flags().BoolVarP(&dryRun, "dry-run", "d", false, "skip persisting any newly compiled changes to disk.")
155+
cmd.Flags().BoolVarP(&upgrade, "upgrade", "u", false, "upgrade any of the requested namespaces that are behind the latest version.")
154156
return cmd
155157
}
156158

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/cli/browser v1.0.0
1010
github.com/go-git/go-billy/v5 v5.3.1
1111
github.com/go-git/go-git/v5 v5.4.2
12-
github.com/lekkodev/rules v1.4.0
12+
github.com/lekkodev/rules v1.4.1-0.20230316183402-c634bcfe3adf
1313
github.com/mitchellh/go-homedir v1.1.0
1414
github.com/spf13/cobra v1.5.0
1515
github.com/stretchr/testify v1.7.0
@@ -26,6 +26,7 @@ require (
2626
github.com/PuerkitoBio/purell v1.1.1 // indirect
2727
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
2828
github.com/acomagu/bufpipe v1.0.3 // indirect
29+
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect
2930
github.com/cli/safeexec v1.0.0 // indirect
3031
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
3132
github.com/emirpasic/gods v1.12.0 // indirect
@@ -55,6 +56,7 @@ require (
5556
github.com/sergi/go-diff v1.1.0 // indirect
5657
github.com/xanzy/ssh-agent v0.3.0 // indirect
5758
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
59+
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
5860
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
5961
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
6062
golang.org/x/text v0.3.7 // indirect
@@ -73,8 +75,6 @@ require (
7375
)
7476

7577
require (
76-
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220415221154-79c36419192d // indirect
77-
github.com/blang/semver v3.5.1+incompatible // indirect
7878
github.com/cenkalti/backoff/v4 v4.2.0
7979
github.com/cli/oauth v0.9.0
8080
github.com/davecgh/go-spew v1.1.1 // indirect

go.sum

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,12 @@ github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk
5050
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
5151
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
5252
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
53-
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220415221154-79c36419192d h1:Kts4bTFz6tSE4bPFhcZxpCafiBnSOPSnUaame7SqSDc=
54-
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220415221154-79c36419192d/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
53+
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18=
54+
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM=
5555
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
5656
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
5757
github.com/bazelbuild/buildtools v0.0.0-20220907133145-b9bfff5d7f91 h1:7xSt0nPZ74liqR8jjBYmjentrQVrdQEhoW4/+4BrmoM=
5858
github.com/bazelbuild/buildtools v0.0.0-20220907133145-b9bfff5d7f91/go.mod h1:689QdV3hBP7Vo9dJMmzhoYIyo/9iMhEmHkJcnaPRCbo=
59-
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
60-
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
6159
github.com/bufbuild/connect-go v0.3.0 h1:B0vyZrTR11SNEYpodL6P0NzluDCsuqmr8CNKblXvVto=
6260
github.com/bufbuild/connect-go v0.3.0/go.mod h1:4efZ2eXFENwd4p7tuLaL9m0qtTsCOzuBvrohvRGevDM=
6361
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
@@ -214,8 +212,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
214212
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
215213
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
216214
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
217-
github.com/lekkodev/rules v1.4.0 h1:88mp56JxGXuSfnLi79JSenr3CtlYSVMNHmg8ABTb/RI=
218-
github.com/lekkodev/rules v1.4.0/go.mod h1:YYiuiYOsmdgoV5thcmmyoQv7ZacVkPgcEAXySY328v0=
215+
github.com/lekkodev/rules v1.4.1-0.20230316183402-c634bcfe3adf h1:wO8Nxokdz3cdqIla/R3KILA5a/7KTyQRg8/nrNM5PRQ=
216+
github.com/lekkodev/rules v1.4.1-0.20230316183402-c634bcfe3adf/go.mod h1:Htxk+nSZEY46blL2nniZdmbrVffAfFAF5JfoNAIFulQ=
219217
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
220218
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
221219
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
@@ -305,6 +303,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
305303
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
306304
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
307305
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
306+
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
307+
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
308308
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
309309
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
310310
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

pkg/encoding/encoding.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,13 @@ import (
3232
// type.
3333
func ParseFeature(ctx context.Context, rootPath string, featureFile feature.FeatureFile, nsMD *metadata.NamespaceConfigRepoMetadata, provider fs.Provider) (feature.EvaluableFeature, error) {
3434
switch nsMD.Version {
35-
case "v1beta3":
35+
case feature.NamespaceVersionV1Beta1.String():
36+
fallthrough
37+
case feature.NamespaceVersionV1Beta2.String():
38+
fallthrough
39+
case feature.NamespaceVersionV1Beta3.String():
40+
fallthrough
41+
case feature.NamespaceVersionV1Beta4.String():
3642
var f featurev1beta1.Feature
3743
contents, err := provider.GetFileContents(ctx, filepath.Join(rootPath, nsMD.Name, featureFile.CompiledProtoBinFileName))
3844
if err != nil {

pkg/feature/feature.go

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
featurev1beta1 "github.com/lekkodev/cli/pkg/gen/proto/go/lekko/feature/v1beta1"
2323
rulesv1beta2 "github.com/lekkodev/cli/pkg/gen/proto/go/lekko/rules/v1beta2"
24+
rulesv1beta3 "github.com/lekkodev/cli/pkg/gen/proto/go/lekko/rules/v1beta3"
2425
"github.com/pkg/errors"
2526
"google.golang.org/protobuf/encoding/protojson"
2627
"google.golang.org/protobuf/proto"
@@ -99,9 +100,10 @@ func FeatureTypeFromProto(ft featurev1beta1.FeatureType) FeatureType {
99100
var ErrTypeMismatch = fmt.Errorf("type mismatch")
100101

101102
type Rule struct {
102-
Condition string // source of truth
103-
ConditionAST *rulesv1beta2.Rule // by-product of Condition
104-
Value interface{}
103+
Condition string // source of truth
104+
ConditionAST *rulesv1beta2.Rule // by-product of Condition
105+
ConditionASTV3 *rulesv1beta3.Rule
106+
Value interface{}
105107
}
106108

107109
type UnitTest struct {
@@ -311,19 +313,20 @@ func valFromJSON(encoded []byte) (*structpb.Value, error) {
311313
return val, nil
312314
}
313315

314-
func (f *Feature) AddBoolRule(rule string, ast *rulesv1beta2.Rule, val bool) error {
316+
func (f *Feature) AddBoolRule(rule string, ast *rulesv1beta2.Rule, astNew *rulesv1beta3.Rule, val bool) error {
315317
if f.FeatureType != FeatureTypeBool {
316318
return newTypeMismatchErr(FeatureTypeBool, f.FeatureType)
317319
}
318320
f.Rules = append(f.Rules, &Rule{
319-
Condition: rule,
320-
ConditionAST: ast,
321-
Value: val,
321+
Condition: rule,
322+
ConditionAST: ast,
323+
ConditionASTV3: astNew,
324+
Value: val,
322325
})
323326
return nil
324327
}
325328

326-
func (f *Feature) AddStringRule(rule string, ast *rulesv1beta2.Rule, val string) error {
329+
func (f *Feature) AddStringRule(rule string, ast *rulesv1beta2.Rule, astNew *rulesv1beta3.Rule, val string) error {
327330
if f.FeatureType != FeatureTypeString {
328331
return newTypeMismatchErr(FeatureTypeString, f.FeatureType)
329332
}
@@ -335,38 +338,41 @@ func (f *Feature) AddStringRule(rule string, ast *rulesv1beta2.Rule, val string)
335338
return nil
336339
}
337340

338-
func (f *Feature) AddIntRule(rule string, ast *rulesv1beta2.Rule, val int64) error {
341+
func (f *Feature) AddIntRule(rule string, ast *rulesv1beta2.Rule, astNew *rulesv1beta3.Rule, val int64) error {
339342
if f.FeatureType != FeatureTypeInt {
340343
return newTypeMismatchErr(FeatureTypeInt, f.FeatureType)
341344
}
342345
f.Rules = append(f.Rules, &Rule{
343-
Condition: rule,
344-
ConditionAST: ast,
345-
Value: val,
346+
Condition: rule,
347+
ConditionAST: ast,
348+
ConditionASTV3: astNew,
349+
Value: val,
346350
})
347351
return nil
348352
}
349353

350-
func (f *Feature) AddFloatRule(rule string, ast *rulesv1beta2.Rule, val float64) error {
354+
func (f *Feature) AddFloatRule(rule string, ast *rulesv1beta2.Rule, astNew *rulesv1beta3.Rule, val float64) error {
351355
if f.FeatureType != FeatureTypeFloat {
352356
return newTypeMismatchErr(FeatureTypeFloat, f.FeatureType)
353357
}
354358
f.Rules = append(f.Rules, &Rule{
355-
Condition: rule,
356-
ConditionAST: ast,
357-
Value: val,
359+
Condition: rule,
360+
ConditionAST: ast,
361+
ConditionASTV3: astNew,
362+
Value: val,
358363
})
359364
return nil
360365
}
361366

362-
func (f *Feature) AddJSONRule(rule string, ast *rulesv1beta2.Rule, val *structpb.Value) error {
367+
func (f *Feature) AddJSONRule(rule string, ast *rulesv1beta2.Rule, astNew *rulesv1beta3.Rule, val *structpb.Value) error {
363368
if f.FeatureType != FeatureTypeJSON {
364369
return newTypeMismatchErr(FeatureTypeJSON, f.FeatureType)
365370
}
366371
f.Rules = append(f.Rules, &Rule{
367-
Condition: rule,
368-
ConditionAST: ast,
369-
Value: val,
372+
Condition: rule,
373+
ConditionAST: ast,
374+
ConditionASTV3: astNew,
375+
Value: val,
370376
})
371377
return nil
372378
}
@@ -399,9 +405,10 @@ func (f *Feature) ToProto() (*featurev1beta1.Feature, error) {
399405
return nil, errors.Wrap(err, "rule value to any")
400406
}
401407
tree.Constraints = append(tree.Constraints, &featurev1beta1.Constraint{
402-
Rule: rule.Condition,
403-
RuleAst: rule.ConditionAST,
404-
Value: ruleAny,
408+
Rule: rule.Condition,
409+
RuleAst: rule.ConditionAST,
410+
RuleAstNew: rule.ConditionASTV3,
411+
Value: ruleAny,
405412
})
406413
}
407414
ret.Tree = tree
@@ -428,9 +435,10 @@ func FromProto(fProto *featurev1beta1.Feature, registry *protoregistry.Types) (*
428435
return nil, errors.Wrap(err, "rule any to val")
429436
}
430437
ret.Rules = append(ret.Rules, &Rule{
431-
Condition: constraint.Rule,
432-
ConditionAST: constraint.RuleAst,
433-
Value: ruleVal,
438+
Condition: constraint.Rule,
439+
ConditionAST: constraint.RuleAst,
440+
ConditionASTV3: constraint.RuleAstNew,
441+
Value: ruleVal,
434442
})
435443
}
436444
return ret, nil

pkg/feature/feature_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424

2525
func TestFeatureProtoRoundTripBool(t *testing.T) {
2626
f := NewBoolFeature(false)
27-
require.NoError(t, f.AddBoolRule("foo", nil, true))
27+
require.NoError(t, f.AddBoolRule("foo", nil, nil, true))
2828
proto, err := f.ToProto()
2929
require.NoError(t, err)
3030
require.NotNil(t, proto)
@@ -36,7 +36,7 @@ func TestFeatureProtoRoundTripBool(t *testing.T) {
3636

3737
func TestFeatureProtoRoundTripString(t *testing.T) {
3838
f := NewStringFeature("a")
39-
require.NoError(t, f.AddStringRule("foo", nil, "b"))
39+
require.NoError(t, f.AddStringRule("foo", nil, nil, "b"))
4040
proto, err := f.ToProto()
4141
require.NoError(t, err)
4242
require.NotNil(t, proto)
@@ -48,7 +48,7 @@ func TestFeatureProtoRoundTripString(t *testing.T) {
4848

4949
func TestFeatureProtoRoundTripInt(t *testing.T) {
5050
f := NewIntFeature(1)
51-
require.NoError(t, f.AddIntRule("foo", nil, 2))
51+
require.NoError(t, f.AddIntRule("foo", nil, nil, 2))
5252
proto, err := f.ToProto()
5353
require.NoError(t, err)
5454
require.NotNil(t, proto)
@@ -60,7 +60,7 @@ func TestFeatureProtoRoundTripInt(t *testing.T) {
6060

6161
func TestFeatureProtoRoundTripFloat(t *testing.T) {
6262
f := NewFloatFeature(1.2)
63-
require.NoError(t, f.AddFloatRule("foo", nil, 3.0))
63+
require.NoError(t, f.AddFloatRule("foo", nil, nil, 3.0))
6464
proto, err := f.ToProto()
6565
require.NoError(t, err)
6666
require.NotNil(t, proto)
@@ -81,7 +81,7 @@ func TestFeatureProtoRoundTripJSON(t *testing.T) {
8181
"a": 1,
8282
})
8383
require.NoError(t, err)
84-
require.NoError(t, f.AddJSONRule("foo", nil, ruleVal))
84+
require.NoError(t, f.AddJSONRule("foo", nil, nil, ruleVal))
8585
proto, err := f.ToProto()
8686
require.NoError(t, err)
8787
require.NotNil(t, proto)

pkg/feature/version.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2022 Lekko Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package feature
16+
17+
import (
18+
"errors"
19+
"fmt"
20+
)
21+
22+
type NamespaceVersion string
23+
24+
const (
25+
NamespaceVersionV1Beta1 NamespaceVersion = "v1beta1"
26+
NamespaceVersionV1Beta2 NamespaceVersion = "v1beta2"
27+
NamespaceVersionV1Beta3 NamespaceVersion = "v1beta3"
28+
// Supports generating n-ary rules AST
29+
NamespaceVersionV1Beta4 NamespaceVersion = "v1beta4"
30+
)
31+
32+
var (
33+
ErrUnsupportedVersion error = errors.New("namespace version is unsupported, please upgrade")
34+
)
35+
36+
func NewNamespaceVersion(v string) (*NamespaceVersion, error) {
37+
for _, nv := range AllNamespaceVersions() {
38+
if string(nv) == v {
39+
return &nv, nil
40+
}
41+
}
42+
return nil, fmt.Errorf("version %s not found", v)
43+
}
44+
45+
// Returns all namespace versions in the order that they were released
46+
func AllNamespaceVersions() []NamespaceVersion {
47+
return []NamespaceVersion{
48+
NamespaceVersionV1Beta1,
49+
NamespaceVersionV1Beta2,
50+
NamespaceVersionV1Beta3,
51+
NamespaceVersionV1Beta4,
52+
}
53+
}
54+
55+
// Returns the list of namespace versions that are supported.
56+
func SupportedNamespaceVersions() []NamespaceVersion {
57+
all := AllNamespaceVersions()
58+
start := 0
59+
for i, v := range all {
60+
if v == NamespaceVersionV1Beta3 { // Last supported version
61+
start = i
62+
break
63+
}
64+
}
65+
return all[start:]
66+
}
67+
68+
// Returns the latest namespace version.
69+
func LatestNamespaceVersion() NamespaceVersion {
70+
all := SupportedNamespaceVersions()
71+
return all[len(all)-1]
72+
}
73+
74+
func (nv NamespaceVersion) IsLatest() bool {
75+
return nv == LatestNamespaceVersion()
76+
}
77+
78+
func (nv NamespaceVersion) Supported() error {
79+
all := SupportedNamespaceVersions()
80+
for _, v := range all {
81+
if v == nv {
82+
return nil
83+
}
84+
}
85+
return ErrUnsupportedVersion
86+
}
87+
88+
func (nv NamespaceVersion) Before(cmp NamespaceVersion) bool {
89+
var myIdx, cmpIdx int
90+
for i, v := range AllNamespaceVersions() {
91+
if v == nv {
92+
myIdx = i
93+
}
94+
if v == cmp {
95+
cmpIdx = i
96+
}
97+
}
98+
return myIdx < cmpIdx
99+
}
100+
101+
func (nv NamespaceVersion) String() string {
102+
return string(nv)
103+
}

0 commit comments

Comments
 (0)