Skip to content

Commit

Permalink
cluster environment
Browse files Browse the repository at this point in the history
Fixes ##541
  • Loading branch information
brandonbloom committed Feb 17, 2022
1 parent ea17535 commit 82591c3
Show file tree
Hide file tree
Showing 21 changed files with 555 additions and 188 deletions.
98 changes: 57 additions & 41 deletions internal/cli/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,50 +26,66 @@ from the current stack. If there is no current stack, the default cluster is
used.`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()

type clusterFragment struct {
ID string `json:"id"`
Name string `json:"name"`
Default bool `json:"default"`
cluster, err := lookupCluster(cmd)
if err != nil {
return err
}
cmdutil.PrintCueStruct(cluster)
return nil
},
}

type clusterFragment struct {
ID string
Name string
Default bool
Environment environmentFragment
}

var cluster *clusterFragment
if cmd.Flags().Lookup("cluster").Changed {
var q struct {
Cluster *clusterFragment `graphql:"clusterByRef(ref: $cluster)"`
}
if err := api.Query(ctx, svc, &q, map[string]interface{}{
"cluster": rootPersistentFlags.Cluster,
}); err != nil {
return err
}
cluster = q.Cluster
if cluster == nil {
return fmt.Errorf("no such cluster: %q", rootPersistentFlags.Cluster)
}
func lookupCluster(cmd *cobra.Command) (*clusterFragment, error) {
ctx := cmd.Context()
if cmd.Flags().Lookup("cluster").Changed {
var q struct {
Cluster *clusterFragment `graphql:"clusterByRef(ref: $cluster)"`
}
if err := api.Query(ctx, svc, &q, map[string]interface{}{
"cluster": rootPersistentFlags.Cluster,
}); err != nil {
return nil, err
}
if q.Cluster == nil {
return nil, fmt.Errorf("no such cluster: %q", rootPersistentFlags.Cluster)
}
return q.Cluster, nil
} else {
var q struct {
Stack *struct {
Cluster clusterFragment
} `graphql:"stackByRef(ref: $stack)"`
DefaultCluster clusterFragment `graphql:"defaultCluster"`
}
if err := api.Query(ctx, svc, &q, map[string]interface{}{
"stack": currentStackRef(),
}); err != nil {
return nil, err
}
if q.Stack != nil {
return &q.Stack.Cluster, nil
} else {
var q struct {
Stack *struct {
Cluster clusterFragment
} `graphql:"stackByRef(ref: $stack)"`
DefaultCluster *clusterFragment `graphql:"defaultCluster"`
}
if err := api.Query(ctx, svc, &q, map[string]interface{}{
"stack": currentStackRef(),
}); err != nil {
return err
}
if q.Stack != nil {
cluster = &q.Stack.Cluster
} else if q.DefaultCluster != nil {
cluster = q.DefaultCluster
} else {
return fmt.Errorf("no current cluster")
}
return &q.DefaultCluster, nil
}
}
}

cmdutil.PrintCueStruct(cluster)
return nil
},
func showCluster(cluster *clusterFragment) {
env := make(map[string]interface{}, len(cluster.Environment.Variables))
for _, v := range cluster.Environment.Variables {
env[v.Name] = v.Value
}
cmdutil.PrintCueStruct(map[string]interface{}{
"id": cluster.ID,
"name": cluster.Name,
"default": cluster.Default,
"environment": env,
})
}
28 changes: 28 additions & 0 deletions internal/cli/cluster_env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cli

import (
"github.com/spf13/cobra"
)

func init() {
clusterCmd.AddCommand(clusterEnvCmd)
}

var clusterEnvCmd = &cobra.Command{
Use: "env",
Short: "Show environment",
Long: `Show cluster environment.
Finds a cluster as per the root "cluster" command, and prints exclusively that
cluster's environment, formats the environment with formatting as per the root
"env" command.`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
cluster, err := lookupCluster(cmd)
if err != nil {
return err
}
showEnvironment(cluster.Environment)
return nil
},
}
40 changes: 40 additions & 0 deletions internal/cli/cluster_refresh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package cli

import (
"github.com/deref/exo/internal/api"
"github.com/spf13/cobra"
)

func init() {
clusterCmd.AddCommand(clusterRefreshCmd)
}

var clusterRefreshCmd = &cobra.Command{
Hidden: true,
Use: "refresh",
Short: "Refresh cluster model",
Long: `Forces a refreshes of a cluster's model.
Targets a cluster as per the root "cluster" command.
It is not normally necessary to call this command, since cluster models are
automatically refreshed at some periodic frequency.`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
cluster, err := lookupCluster(cmd)
if err != nil {
return err
}
var m struct {
Cluster *clusterFragment `graphql:"refreshCluster(ref: $cluster)"`
}
if err := api.Mutate(ctx, svc, &m, map[string]interface{}{
"cluster": cluster.ID,
}); err != nil {
return err
}
showCluster(m.Cluster)
return nil
},
}
1 change: 0 additions & 1 deletion internal/cli/cluster_show.go

This file was deleted.

32 changes: 3 additions & 29 deletions internal/cli/completion_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"

"github.com/deref/exo/internal/util/osutil"
"github.com/deref/exo/internal/util/shellutil"
"github.com/deref/exo/internal/util/which"
"github.com/spf13/cobra"
)
Expand All @@ -36,7 +37,7 @@ zsh, may require additional steps to clear completion caches.`,
}

if shell == "" {
shell = inferShell()
shell = shellutil.IdentifyUserShell()
}
if shell == "" {
return errors.New("cannot determine shell")
Expand Down Expand Up @@ -99,38 +100,11 @@ func completionPathCandidates(shell string) []string {
return paths[:dest]
}

// Returns the path of the current user's default shell.
func getUserShell() string {
sudoUser, _ := os.LookupEnv("SUDO_USER")
if sudoUser != "" {
// TODO: Query the user's shell from getent or similar on Linux.
return ""
}
shell, _ := os.LookupEnv("SHELL")
return shell
}

// Returns the name of the current user's default shell.
func inferShell() string {
shellPath := getUserShell()
for _, shellName := range []string{
"bash",
"zsh",
"fish",
} {
if strings.HasSuffix(shellPath, "/"+shellName) {
return shellName
}
}
// TODO: Detect powershell.
return ""
}

const completionPathBashLinux = "/etc/bash_completion.d/exo"
const completionPathBashMac = "/usr/local/etc/bash_completion.d/exo"

func completionPathZsh() string {
shell := getUserShell()
shell := shellutil.GetUserShellPath()
if !strings.HasSuffix(shell, "/zsh") {
shell, _ = which.Which("zsh")
}
Expand Down
39 changes: 24 additions & 15 deletions internal/cli/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,39 @@ var envCmd = &cobra.Command{
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
env := osutil.EnvMapToDotEnv(getEnvMap(ctx))
for _, kvp := range env {
fmt.Println(kvp)
}
showEnvironment(getWorkspaceEnvironment(ctx))
return nil
},
}

func getEnvMap(ctx context.Context) map[string]string {
func getWorkspaceEnvironment(ctx context.Context) environmentFragment {
var q struct {
Workspace *struct {
Environment struct {
Variables []struct {
Name string
Value string
}
}
Environment environmentFragment
} `graphql:"workspaceByRef(ref: $currentWorkspace)"`
}
mustQueryWorkspace(ctx, &q, nil)
vars := q.Workspace.Environment.Variables
m := make(map[string]string, len(vars))
for _, v := range vars {
m[v.Name] = v.Value
return q.Workspace.Environment
}

func showEnvironment(env environmentFragment) {
for _, v := range env.Variables {
fmt.Println(osutil.FormatDotEnvEntry(v.Name, v.Value))
}
}

type environmentFragment struct {
Variables []struct {
Name string
Value string
}
}

func environmentFragmentToMap(fragment environmentFragment) map[string]string {
variables := fragment.Variables
m := make(map[string]string, len(variables))
for _, variable := range variables {
m[variable.Name] = variable.Value
}
return m
}
8 changes: 6 additions & 2 deletions internal/cli/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@ var execCmd = &cobra.Command{
DisableFlagsInUseLine: true,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
env := osutil.EnvMapToEnvv(getEnvMap(ctx))
environment := getWorkspaceEnvironment(ctx)
envv := make([]string, 0, len(environment.Variables))
for _, variable := range environment.Variables {
envv = append(envv, osutil.FormatEnvvEntry(variable.Name, variable.Value))
}
program, err := which.Which(args[0])
if err != nil {
return fmt.Errorf("resolving program: %w", err)
}
err = syscall.Exec(program, args, env)
err = syscall.Exec(program, args, envv)
cmdutil.Fatalf("%v", err)
panic("unreachable")
},
Expand Down
11 changes: 5 additions & 6 deletions internal/environment/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package environment

import (
"os"
"strings"

"github.com/deref/exo/internal/util/osutil"
)

type OS struct{}
Expand All @@ -15,11 +16,9 @@ func (src *OS) ExtendEnvironment(b Builder) error {
// TODO: This should probably somehow shell-out to get the user's current
// environment, otherwise changes to shell profiles won't take effect until
// the exo daemon is exited and restarted.
for _, assign := range os.Environ() {
parts := strings.SplitN(assign, "=", 2)
key := parts[0]
val := parts[1]
b.AppendVariable(src, key, val)
for _, entry := range os.Environ() {
name, value := osutil.ParseEnvvEntry(entry)
b.AppendVariable(src, name, value)
}
return nil
}
8 changes: 4 additions & 4 deletions internal/providers/docker/components/container/description.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import (
"context"
"encoding/json"
"fmt"
"strings"
"time"

"github.com/deref/exo/internal/core/api"
"github.com/deref/exo/internal/providers/docker"
"github.com/deref/exo/internal/util/jsonutil"
"github.com/deref/exo/internal/util/osutil"
dockerclient "github.com/docker/docker/client"
"github.com/moby/moby/errdefs"
"golang.org/x/sync/errgroup"
Expand Down Expand Up @@ -48,9 +48,9 @@ func GetProcessDescription(ctx context.Context, dockerClient *dockerclient.Clien
process.CreateTime = &createTime

process.EnvVars = map[string]string{}
for _, env := range containerInfo.Config.Env {
decomposedEnv := strings.SplitN(env, "=", 2)
process.EnvVars[decomposedEnv[0]] = decomposedEnv[1]
for _, entry := range containerInfo.Config.Env {
name, value := osutil.ParseEnvvEntry(entry)
process.EnvVars[name] = value
}

process.Ports = []uint32{}
Expand Down
Loading

0 comments on commit 82591c3

Please sign in to comment.