Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify asy conditionals and variables data structure #2568

Open
wants to merge 32 commits into
base: version/0-40-0-RC1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
74c9db1
Start unification of vars in conds and tmpls
daved Apr 1, 2023
5aa013b
Fix constraints conditional from value construction
daved Apr 12, 2023
647cb7f
Remove unused var tag constant from constraints pkg
daved Apr 12, 2023
23bea1b
Start use of reflect in project expander
daved Apr 13, 2023
25ad33d
Handle func fields in project expander
daved Apr 14, 2023
f377c1e
Fix err handling in project registry funcs
daved Apr 14, 2023
9fce069
Fix state main imports
daved Apr 14, 2023
5678f64
Handle asy var func notation
daved Apr 14, 2023
9b2ac3b
Fix project pkg expander tests
daved Apr 14, 2023
35fbde4
Ensure paths are bashified in expansions
daved Apr 14, 2023
d105f38
Fix expander unit test and usage in parallelize
daved Apr 14, 2023
b31ecb2
Rename isFunc project expander tag opt to asFunc
daved Apr 14, 2023
54c2876
Fix project expander unit test for windows
daved Apr 14, 2023
45056f9
Add project update callback to keep expander projectvars updated
daved Apr 17, 2023
7f9addc
Fix call to primer construction in svc
daved Apr 17, 2023
d51d1e2
Fix call to primer construction in installer
daved Apr 17, 2023
b6f7f6c
Fix call to primer construction in remote installer
daved Apr 17, 2023
8afd893
Guard projectfile call in project setupdatecallback
daved Apr 18, 2023
2918fd8
Move project/vars setup to own pkg
daved Apr 18, 2023
0dcce06
Fix project pkg expander tests
daved Apr 18, 2023
25a74f9
Return nil project when no projectfile provided
daved Apr 18, 2023
18438c3
Add vars pkg comment
daved Apr 18, 2023
3f2bb37
Clarify var naming in and add comments to vars work
daved Apr 19, 2023
685a93e
Improve more var names and comments for projectfile vars handling
daved Apr 19, 2023
09269dd
Trial direct registration of top level expanders
daved Jun 5, 2023
0c8f91c
Setup lazy expanderfunc to avoid projectfile set callback
daved Jun 6, 2023
ece577f
Move vars into project pkg, drop projget
daved Jun 6, 2023
1b955de
Move projectfile setpath back to old location
daved Jun 7, 2023
7809313
Fix project vars usage in parallelize script
daved Jun 7, 2023
cfd2ae6
Ensure struct expanderfunc context is not mutated
daved Jun 30, 2023
a8891b5
Return unhandled error in project
daved Jun 30, 2023
0b9ae89
Merge branch 'version/0-40-0-RC1' into green/fix_cond_vars-2.DX-1661
daved Jul 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/state-installer/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func main() {
"state-installer",
"",
"Installs or updates the State Tool",
primer.New(nil, out, nil, nil, nil, nil, cfg, nil, nil, an),
primer.New(nil, out, nil, nil, nil, cfg, nil, nil, an),
[]*captain.Flag{ // The naming of these flags is slightly inconsistent due to backwards compatibility requirements
{
Name: "command",
Expand Down
2 changes: 1 addition & 1 deletion cmd/state-remote-installer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func main() {
"state-installer",
"",
"Installs or updates the State Tool",
primer.New(nil, out, nil, nil, nil, nil, cfg, nil, nil, an),
primer.New(nil, out, nil, nil, nil, cfg, nil, nil, an),
[]*captain.Flag{ // The naming of these flags is slightly inconsistent due to backwards compatibility requirements
{
Name: "channel",
Expand Down
2 changes: 1 addition & 1 deletion cmd/state-svc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func run(cfg *config.Instance) error {
return runStart(out, "svc-start:mouse")
}

p := primer.New(nil, out, nil, nil, nil, nil, cfg, nil, nil, an)
p := primer.New(nil, out, nil, nil, nil, cfg, nil, nil, an)

cmd := captain.NewCommand(
path.Base(os.Args[0]), "", "", p, nil, nil,
Expand Down
42 changes: 10 additions & 32 deletions cmd/state/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,12 @@ import (
"strings"
"time"

"github.com/ActiveState/cli/cmd/state/internal/cmdtree/exechandlers/messenger"
"github.com/ActiveState/cli/internal/captain"

"github.com/ActiveState/cli/cmd/state/internal/cmdtree"
"github.com/ActiveState/cli/cmd/state/internal/cmdtree/exechandlers/messenger"
anAsync "github.com/ActiveState/cli/internal/analytics/client/async"
"github.com/ActiveState/cli/internal/captain"
"github.com/ActiveState/cli/internal/config"
"github.com/ActiveState/cli/internal/constants"
"github.com/ActiveState/cli/internal/constraints"
"github.com/ActiveState/cli/internal/errs"
"github.com/ActiveState/cli/internal/events"
"github.com/ActiveState/cli/internal/installation"
Expand All @@ -40,7 +38,6 @@ import (
"github.com/ActiveState/cli/pkg/platform/authentication"
"github.com/ActiveState/cli/pkg/platform/model"
"github.com/ActiveState/cli/pkg/project"
"github.com/ActiveState/cli/pkg/projectfile"
)

func main() {
Expand Down Expand Up @@ -169,34 +166,21 @@ func run(args []string, isInteractive bool, cfg *config.Instance, out output.Out
return logData
})

// Retrieve project file
pjPath, err := projectfile.GetProjectFilePath()
if err != nil && errs.Matches(err, &projectfile.ErrorNoProjectFromEnv{}) {
// Fail if we are meant to inherit the projectfile from the environment, but the file doesn't exist
return err
}
auth := authentication.New(cfg)
defer events.Close("auth", auth.Close)

// Set up project (if we have a valid path)
var pj *project.Project
if pjPath != "" {
pjf, err := projectfile.FromPath(pjPath)
if err != nil {
return err
}
pj, err = project.New(pjf, out)
if err != nil {
return err
}
sshell := subshell.New(cfg)

pj, err := project.NewWithVars(out, auth, sshell.Shell())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a separate constructor for vars? Is this notably slower or something? This feels very error prone as we will have instances of project initialized with vars and instances without, and we have to just know which is which.

I'd prefer we implement this in a way where this is always the case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done to not uproot existing usages that do not require Vars for this diff. I imagined a new story to cover making all project usages (more?) equal.

if err != nil {
return err
}

pjNamespace := ""
if pj != nil {
pjNamespace = pj.Namespace().String()
}

auth := authentication.New(cfg)
defer events.Close("auth", auth.Close)

if err := auth.Sync(); err != nil {
logging.Warning("Could not sync authenticated state: %s", err.Error())
}
Expand All @@ -211,16 +195,10 @@ func run(args []string, isInteractive bool, cfg *config.Instance, out output.Out
// Set up prompter
prompter := prompt.New(isInteractive, an)

// Set up conditional, which accesses a lot of primer data
sshell := subshell.New(cfg)

conditional := constraints.NewPrimeConditional(auth, pj, sshell.Shell())
project.RegisterConditional(conditional)
project.RegisterExpander("mixin", project.NewMixin(auth).Expander)
project.RegisterExpander("secrets", project.NewSecretPromptingExpander(secretsapi.Get(), prompter, cfg, auth))

// Run the actual command
cmds := cmdtree.New(primer.New(pj, out, auth, prompter, sshell, conditional, cfg, ipcClient, svcmodel, an), args...)
cmds := cmdtree.New(primer.New(pj, out, auth, prompter, sshell, cfg, ipcClient, svcmodel, an), args...)

childCmd, err := cmds.Command().Find(args[1:])
if err != nil {
Expand Down
112 changes: 28 additions & 84 deletions internal/constraints/constraints.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package constraints
import (
"bytes"
"fmt"
"path/filepath"
"reflect"
"regexp"
"sort"
"strings"
Expand All @@ -12,27 +12,10 @@ import (
"github.com/ActiveState/cli/internal/locale"
"github.com/ActiveState/cli/internal/logging"
"github.com/ActiveState/cli/internal/multilog"
"github.com/ActiveState/cli/internal/rtutils/ptr"
"github.com/ActiveState/cli/pkg/platform/authentication"
"github.com/ActiveState/cli/pkg/projectfile"
"github.com/ActiveState/cli/pkg/sysinfo"
"github.com/thoas/go-funk"
)

var cache = make(map[string]interface{})

func getCache(key string, getter func() (interface{}, error)) (interface{}, error) {
if v, ok := cache[key]; ok {
return v, nil
}
v, err := getter()
if err != nil {
return nil, err
}
cache[key] = v
return v, err
}

// For testing.
var osOverride, osVersionOverride, archOverride, libcOverride, compilerOverride string

Expand All @@ -41,22 +24,9 @@ type Conditional struct {
funcs template.FuncMap
}

func NewConditional(a *authentication.Auth) *Conditional {
func NewConditional() *Conditional {
c := &Conditional{map[string]interface{}{}, map[string]interface{}{}}

c.RegisterFunc("Mixin", func() map[string]interface{} {
res := map[string]string{
"Name": "",
"Email": "",
}
if a.Authenticated() {
res["Name"] = a.WhoAmI()
res["Email"] = a.Email()
}
return map[string]interface{}{
"User": res,
}
})
c.RegisterFunc("Contains", funk.Contains)
c.RegisterFunc("HasPrefix", strings.HasPrefix)
c.RegisterFunc("HasSuffix", strings.HasSuffix)
Expand All @@ -72,62 +42,36 @@ func NewConditional(a *authentication.Auth) *Conditional {
return c
}

type projectable interface {
Owner() string
Name() string
NamespaceString() string
CommitID() string
BranchName() string
Path() string
URL() string
}
func NewPrimeConditional(structure interface{}) *Conditional {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. We're no longer using this as a prime property so naming this PrimeConditional is misleading. Also I don't think anything outside of the prime package should deal with the concept of primes.

  2. It doesn't seem like you ever send this anything other than *project.Vars, so why the interface? Passing it the typed vars may also allow us to get rid of more reflection. If the issue is import cycles; we can move vars to its own package. I had an issue with the name projvars, but that was about naming more than anything.

  3. This should just be part of NewConditional() IMO. There is no scenario I'm aware of where we use conditionals without a project.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is old code written 2 years ago that I simplified.

c := NewConditional()

func NewPrimeConditional(auth *authentication.Auth, pj projectable, subshellName string) *Conditional {
var (
pjOwner string
pjName string
pjNamespace string
pjURL string
pjCommit string
pjBranch string
pjPath string
)
if !ptr.IsNil(pj) {
pjOwner = pj.Owner()
pjName = pj.Name()
pjNamespace = pj.NamespaceString()
pjURL = pj.URL()
pjCommit = pj.CommitID()
pjBranch = pj.BranchName()
pjPath = pj.Path()
if pjPath != "" {
pjPath = filepath.Dir(pjPath)
}
v := reflect.ValueOf(structure)
// deref if needed
if v.Kind() == reflect.Ptr {
v = v.Elem()
}

c := NewConditional(auth)
c.RegisterParam("Project", map[string]string{
"Owner": pjOwner,
"Name": pjName,
"Namespace": pjNamespace,
"Url": pjURL,
"Commit": pjCommit,
"Branch": pjBranch,
"Path": pjPath,

// Legacy
"NamespacePrefix": pjNamespace,
})
osVersion, err := sysinfo.OSVersion()
if err != nil {
multilog.Error("Could not detect OSVersion: %v", err)
fields := reflect.VisibleFields(v.Type())

// Work at depth 1: Vars.[Struct].Struct.Simple
for _, f := range fields {
d1Val := v.FieldByIndex(f.Index)
if d1Val.Kind() == reflect.Ptr {
d1Val = d1Val.Elem()
}

// Only nodes at depth 1 need to be registered since the generic type
// handling within the templating package will do the rest. If function
// registration is needed at greater depths, this will need to be
// reworked (and may not be possible without expansive refactoring).
switch d1Val.Type().Kind() {
case reflect.Func:
c.RegisterFunc(f.Name, d1Val.Interface())

default:
c.RegisterParam(f.Name, d1Val.Interface())
}
}
c.RegisterParam("OS", map[string]interface{}{
"Name": sysinfo.OS().String(),
"Version": osVersion,
"Architecture": sysinfo.Architecture().String(),
})
c.RegisterParam("Shell", subshellName)

return c
}
Expand Down
29 changes: 9 additions & 20 deletions internal/primer/primer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package primer
import (
"github.com/ActiveState/cli/internal/analytics"
"github.com/ActiveState/cli/internal/config"
"github.com/ActiveState/cli/internal/constraints"
"github.com/ActiveState/cli/internal/output"
"github.com/ActiveState/cli/internal/prompt"
"github.com/ActiveState/cli/internal/subshell"
Expand All @@ -21,7 +20,6 @@ type Values struct {
auth *authentication.Auth
prompt prompt.Prompter
subshell subshell.SubShell
conditional *constraints.Conditional
config *config.Instance
ipComm svcctl.IPCommunicator
svcModel *model.SvcModel
Expand All @@ -30,19 +28,18 @@ type Values struct {

func New(
project *project.Project, output output.Outputer, auth *authentication.Auth, prompt prompt.Prompter,
subshell subshell.SubShell, conditional *constraints.Conditional, config *config.Instance,
subshell subshell.SubShell, config *config.Instance,
ipComm svcctl.IPCommunicator, svcModel *model.SvcModel, an analytics.Dispatcher) *Values {

v := &Values{
output: output,
auth: auth,
prompt: prompt,
subshell: subshell,
conditional: conditional,
config: config,
ipComm: ipComm,
svcModel: svcModel,
analytics: an,
output: output,
auth: auth,
prompt: prompt,
subshell: subshell,
config: config,
ipComm: ipComm,
svcModel: svcModel,
analytics: an,
}
if project != nil {
v.project = project
Expand Down Expand Up @@ -91,10 +88,6 @@ type Subsheller interface {
Subshell() subshell.SubShell
}

type Conditioner interface {
Conditional() *constraints.Conditional
}

func (v *Values) Project() *project.Project {
return v.project
}
Expand Down Expand Up @@ -127,10 +120,6 @@ func (v *Values) SvcModel() *model.SvcModel {
return v.svcModel
}

func (v *Values) Conditional() *constraints.Conditional {
return v.conditional
}

func (v *Values) Config() *config.Instance {
return v.config
}
Expand Down
Loading