mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2025-02-02 05:41:10 -08:00
feat(argocd): add context segment
This commit is contained in:
parent
4f56a96dca
commit
ddec1197df
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/jandedobbeleer/oh-my-posh/src/mock"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/platform"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/properties"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/segments"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -385,7 +386,7 @@ func TestMigratePreAndPostfix(t *testing.T) {
|
|||
},
|
||||
{
|
||||
Case: "Prefix",
|
||||
Expected: " {{ .Name }} ",
|
||||
Expected: segments.NameTemplate,
|
||||
Props: properties.Map{
|
||||
"prefix": " ",
|
||||
"template": "{{ .Name }}",
|
||||
|
@ -393,7 +394,7 @@ func TestMigratePreAndPostfix(t *testing.T) {
|
|||
},
|
||||
{
|
||||
Case: "Postfix",
|
||||
Expected: " {{ .Name }} ",
|
||||
Expected: segments.NameTemplate,
|
||||
Props: properties.Map{
|
||||
"postfix": " ",
|
||||
"template": "{{ .Name }} ",
|
||||
|
|
|
@ -98,6 +98,8 @@ const (
|
|||
|
||||
// ANGULAR writes which angular cli version us currently active
|
||||
ANGULAR SegmentType = "angular"
|
||||
// ARGOCD writes the current argocd context
|
||||
ARGOCD SegmentType = "argocd"
|
||||
// AWS writes the active aws context
|
||||
AWS SegmentType = "aws"
|
||||
// AZ writes the Azure subscription info we're currently in
|
||||
|
@ -246,6 +248,7 @@ const (
|
|||
// Consumers of the library can also add their own segment writer.
|
||||
var Segments = map[SegmentType]func() SegmentWriter{
|
||||
ANGULAR: func() SegmentWriter { return &segments.Angular{} },
|
||||
ARGOCD: func() SegmentWriter { return &segments.Argocd{} },
|
||||
AWS: func() SegmentWriter { return &segments.Aws{} },
|
||||
AZ: func() SegmentWriter { return &segments.Az{} },
|
||||
AZFUNC: func() SegmentWriter { return &segments.AzFunc{} },
|
||||
|
|
|
@ -38,6 +38,7 @@ require (
|
|||
github.com/hashicorp/hcl/v2 v2.16.2
|
||||
github.com/mattn/go-runewidth v0.0.14
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
golang.org/x/mod v0.9.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
@ -84,7 +85,6 @@ require (
|
|||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
github.com/zclconf/go-cty v1.13.1 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
|
|
110
src/segments/argocd.go
Normal file
110
src/segments/argocd.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
package segments
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/platform"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/properties"
|
||||
"github.com/spf13/pflag"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
argocdOptsEnv = "ARGOCD_OPTS"
|
||||
argocdInvalidFlag = "invalid flag"
|
||||
argocdInvalidYaml = "invalid yaml"
|
||||
argocdNoCurrent = "no current context"
|
||||
|
||||
NameTemplate = " {{ .Name }} "
|
||||
)
|
||||
|
||||
type ArgocdContext struct {
|
||||
Name string `yaml:"name"`
|
||||
Server string `yaml:"server"`
|
||||
User string `yaml:"user"`
|
||||
}
|
||||
|
||||
type ArgocdConfig struct {
|
||||
Contexts []*ArgocdContext `yaml:"contexts"`
|
||||
CurrentContext string `yaml:"current-context"`
|
||||
}
|
||||
|
||||
type Argocd struct {
|
||||
props properties.Properties
|
||||
env platform.Environment
|
||||
|
||||
ArgocdContext
|
||||
}
|
||||
|
||||
func (a *Argocd) Template() string {
|
||||
return NameTemplate
|
||||
}
|
||||
|
||||
func (a *Argocd) Init(props properties.Properties, env platform.Environment) {
|
||||
a.props = props
|
||||
a.env = env
|
||||
}
|
||||
|
||||
func (a *Argocd) Enabled() bool {
|
||||
// always parse config instead of using cli to save time
|
||||
configPath := a.getConfigPath()
|
||||
succeeded, err := a.parseConfig(configPath)
|
||||
if err != nil {
|
||||
a.env.Error(err)
|
||||
return false
|
||||
}
|
||||
return succeeded
|
||||
}
|
||||
|
||||
func (a *Argocd) getConfigPath() string {
|
||||
cp := path.Join(a.env.Home(), ".config", "argocd", "config")
|
||||
cpo := a.getConfigFromOpts()
|
||||
if len(cpo) > 0 {
|
||||
cp = cpo
|
||||
}
|
||||
return cp
|
||||
}
|
||||
|
||||
func (a *Argocd) getConfigFromOpts() string {
|
||||
// don't exit/panic when encountering invalid flags
|
||||
flags := pflag.NewFlagSet(os.Args[0], pflag.ContinueOnError)
|
||||
// ignore other valid and invalid flags
|
||||
flags.ParseErrorsWhitelist.UnknownFlags = true
|
||||
// only care about config
|
||||
flags.String("config", "", "get config from opts")
|
||||
|
||||
opts := a.env.Getenv(argocdOptsEnv)
|
||||
_ = flags.Parse(strings.Split(opts, " "))
|
||||
return flags.Lookup("config").Value.String()
|
||||
}
|
||||
|
||||
func (a *Argocd) parseConfig(file string) (bool, error) {
|
||||
config := a.env.FileContent(file)
|
||||
// missing or empty file content
|
||||
if len(config) == 0 {
|
||||
return false, errors.New(argocdInvalidYaml)
|
||||
}
|
||||
|
||||
var data ArgocdConfig
|
||||
err := yaml.Unmarshal([]byte(config), &data)
|
||||
if err != nil {
|
||||
a.env.Error(err)
|
||||
return false, errors.New(argocdInvalidYaml)
|
||||
}
|
||||
a.Name = data.CurrentContext
|
||||
for _, context := range data.Contexts {
|
||||
if context.Name == a.Name {
|
||||
// mandatory fields in yaml
|
||||
if len(context.Server) == 0 || len(context.User) == 0 {
|
||||
return false, errors.New(argocdInvalidYaml)
|
||||
}
|
||||
a.Server = context.Server
|
||||
a.User = context.User
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, errors.New(argocdNoCurrent)
|
||||
}
|
273
src/segments/argocd_test.go
Normal file
273
src/segments/argocd_test.go
Normal file
|
@ -0,0 +1,273 @@
|
|||
package segments
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/mock"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/properties"
|
||||
"github.com/stretchr/testify/assert"
|
||||
mock2 "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
const (
|
||||
poshHome = "/Users/posh"
|
||||
)
|
||||
|
||||
func TestArgocdGetConfigFromOpts(t *testing.T) {
|
||||
configFile := "/Users/posh/.config/argocd/config"
|
||||
cases := []struct {
|
||||
Case string
|
||||
Opts string
|
||||
Expected string
|
||||
}{
|
||||
{Case: "invalid flag in opts", Opts: "--invalid", Expected: ""},
|
||||
{Case: "no config in opts", Opts: "--grpc-web", Expected: ""},
|
||||
{
|
||||
Case: "config in opts",
|
||||
Opts: fmt.Sprintf("--grpc-web --config %s --plaintext", configFile),
|
||||
Expected: configFile,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
env := new(mock.MockedEnvironment)
|
||||
env.On("Getenv", argocdOptsEnv).Return(tc.Opts)
|
||||
|
||||
argocd := &Argocd{
|
||||
env: env,
|
||||
props: properties.Map{},
|
||||
}
|
||||
config := argocd.getConfigFromOpts()
|
||||
assert.Equal(t, tc.Expected, config, tc.Case)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArgocdGetConfigPath(t *testing.T) {
|
||||
configFile := path.Join(poshHome, ".config", "argocd", "config")
|
||||
cases := []struct {
|
||||
Case string
|
||||
Opts string
|
||||
Expected string
|
||||
ExpectedError string
|
||||
}{
|
||||
{Case: "without opts", Expected: configFile},
|
||||
{Case: "with opts", Opts: "--config /etc/argocd/config", Expected: "/etc/argocd/config"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
env := new(mock.MockedEnvironment)
|
||||
env.On("Home").Return(poshHome)
|
||||
env.On("Getenv", argocdOptsEnv).Return(tc.Opts)
|
||||
|
||||
argocd := &Argocd{
|
||||
env: env,
|
||||
props: properties.Map{},
|
||||
}
|
||||
assert.Equal(t, tc.Expected, argocd.getConfigPath())
|
||||
}
|
||||
}
|
||||
|
||||
func TestArgocdParseConfig(t *testing.T) {
|
||||
configFile := "/Users/posh/.config/argocd/config"
|
||||
cases := []struct {
|
||||
Case string
|
||||
Config string
|
||||
Expected bool
|
||||
ExpectedError string
|
||||
ExpectedContext ArgocdContext
|
||||
}{
|
||||
{Case: "missing or empty yaml", Config: "", ExpectedError: argocdInvalidYaml},
|
||||
{
|
||||
Case: "invalid yaml",
|
||||
ExpectedError: argocdInvalidYaml,
|
||||
Config: `
|
||||
[context]
|
||||
context
|
||||
`,
|
||||
},
|
||||
{
|
||||
Case: "invalid config",
|
||||
ExpectedError: argocdInvalidYaml,
|
||||
Config: `
|
||||
contexts:
|
||||
- name: context1
|
||||
server: server1
|
||||
user: user1
|
||||
- name: context2
|
||||
server: server2
|
||||
userr: user2
|
||||
current-context: context2
|
||||
servers:
|
||||
- grpc-web: true
|
||||
server: server1
|
||||
- grpc-web: false
|
||||
server: serve2
|
||||
`,
|
||||
},
|
||||
{
|
||||
Case: "no current context found",
|
||||
ExpectedError: argocdNoCurrent,
|
||||
Config: `
|
||||
contexts:
|
||||
- name: context1
|
||||
server: server1
|
||||
user: user1
|
||||
- name: context2
|
||||
server: server2
|
||||
user: user2
|
||||
`,
|
||||
},
|
||||
{
|
||||
Case: "current context found",
|
||||
Expected: true,
|
||||
Config: `
|
||||
contexts:
|
||||
- name: context1
|
||||
server: server1
|
||||
user: user1
|
||||
- name: context2
|
||||
server: server2
|
||||
user: user2
|
||||
current-context: context2
|
||||
servers:
|
||||
- grpc-web: true
|
||||
server: server1
|
||||
- grpc-web: false
|
||||
server: serve2
|
||||
users:
|
||||
- auth-token: authtoken1
|
||||
name: user1
|
||||
refresh-token: refreshtoken1
|
||||
- auth-token: authtoken2
|
||||
name: user2
|
||||
refresh-token: refreshtoken2
|
||||
`,
|
||||
ExpectedContext: ArgocdContext{
|
||||
Name: "context2",
|
||||
Server: "server2",
|
||||
User: "user2",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
env := new(mock.MockedEnvironment)
|
||||
env.On("FileContent", configFile).Return(tc.Config)
|
||||
env.On("Error", mock2.Anything).Return()
|
||||
|
||||
argocd := &Argocd{
|
||||
env: env,
|
||||
props: properties.Map{},
|
||||
}
|
||||
if len(tc.ExpectedError) > 0 {
|
||||
_, err := argocd.parseConfig(configFile)
|
||||
assert.EqualError(t, err, tc.ExpectedError, tc.Case)
|
||||
continue
|
||||
}
|
||||
config, err := argocd.parseConfig(configFile)
|
||||
assert.NoErrorf(t, err, tc.Case)
|
||||
assert.Equal(t, tc.Expected, config, tc.Case)
|
||||
assert.Equal(t, tc.ExpectedContext, argocd.ArgocdContext, tc.Case)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArgocdSegment(t *testing.T) {
|
||||
configFile := path.Join(poshHome, ".config", "argocd", "config")
|
||||
cases := []struct {
|
||||
Case string
|
||||
Opts string
|
||||
Config string
|
||||
Template string
|
||||
ExpectedString string
|
||||
ExpectedEnabled bool
|
||||
ExpectedError string
|
||||
ExpectedContext ArgocdContext
|
||||
}{
|
||||
{
|
||||
Case: "default template",
|
||||
Opts: "",
|
||||
Config: `
|
||||
contexts:
|
||||
- name: context1
|
||||
server: server1
|
||||
user: user1
|
||||
- name: context2
|
||||
server: server2
|
||||
user: user2
|
||||
current-context: context2
|
||||
servers:
|
||||
- grpc-web: true
|
||||
server: server1
|
||||
- grpc-web: false
|
||||
server: serve2
|
||||
`,
|
||||
ExpectedString: "context2",
|
||||
ExpectedEnabled: true,
|
||||
ExpectedContext: ArgocdContext{
|
||||
Name: "context2",
|
||||
Server: "server2",
|
||||
User: "user2",
|
||||
},
|
||||
},
|
||||
{
|
||||
Case: "full template",
|
||||
Opts: "",
|
||||
Config: `
|
||||
contexts:
|
||||
- name: context1
|
||||
server: server1
|
||||
user: user1
|
||||
- name: context2
|
||||
server: server2
|
||||
user: user2
|
||||
current-context: context2
|
||||
servers:
|
||||
- grpc-web: true
|
||||
server: server1
|
||||
- grpc-web: false
|
||||
server: serve2
|
||||
`,
|
||||
Template: "{{ .Name }}:{{ .User}}@{{ .Server }}",
|
||||
ExpectedString: "context2:user2@server2",
|
||||
ExpectedEnabled: true,
|
||||
ExpectedContext: ArgocdContext{
|
||||
Name: "context2",
|
||||
Server: "server2",
|
||||
User: "user2",
|
||||
},
|
||||
},
|
||||
{
|
||||
Case: "broken config",
|
||||
Config: `}`,
|
||||
ExpectedEnabled: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
env := new(mock.MockedEnvironment)
|
||||
env.On("Home").Return(poshHome)
|
||||
env.On("Getenv", argocdOptsEnv).Return(tc.Opts)
|
||||
env.On("FileContent", configFile).Return(tc.Config)
|
||||
env.On("Error", mock2.Anything).Return()
|
||||
|
||||
argocd := &Argocd{
|
||||
env: env,
|
||||
props: properties.Map{},
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.ExpectedEnabled, argocd.Enabled(), tc.Case)
|
||||
|
||||
if !tc.ExpectedEnabled {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.ExpectedContext, argocd.ArgocdContext, tc.Case)
|
||||
if len(tc.Template) > 0 {
|
||||
assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, argocd), tc.Case)
|
||||
} else {
|
||||
assert.Equal(t, tc.ExpectedString, renderTemplate(env, argocd.Template(), argocd), tc.Case)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -71,7 +71,7 @@ type AzurePowerShellSubscription struct {
|
|||
}
|
||||
|
||||
func (a *Az) Template() string {
|
||||
return " {{ .Name }} "
|
||||
return NameTemplate
|
||||
}
|
||||
|
||||
func (a *Az) Init(props properties.Properties, env platform.Environment) {
|
||||
|
|
|
@ -109,8 +109,7 @@ func TestAzSegment(t *testing.T) {
|
|||
|
||||
for _, tc := range cases {
|
||||
env := new(mock.MockedEnvironment)
|
||||
home := "/Users/posh"
|
||||
env.On("Home").Return(home)
|
||||
env.On("Home").Return(poshHome)
|
||||
var azureProfile, azureRmContext string
|
||||
|
||||
if tc.HasCLI {
|
||||
|
@ -123,7 +122,7 @@ func TestAzSegment(t *testing.T) {
|
|||
}
|
||||
|
||||
env.On("GOOS").Return(platform.LINUX)
|
||||
env.On("FileContent", filepath.Join(home, ".azure", "azureProfile.json")).Return(azureProfile)
|
||||
env.On("FileContent", filepath.Join(poshHome, ".azure", "azureProfile.json")).Return(azureProfile)
|
||||
env.On("Getenv", "POSH_AZURE_SUBSCRIPTION").Return(azureRmContext)
|
||||
env.On("Getenv", "AZURE_CONFIG_DIR").Return("")
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ func TestEnabledInWorkingDirectory(t *testing.T) {
|
|||
env.On("IsWsl").Return(false)
|
||||
env.On("HasParentFilePath", ".git").Return(fileInfo, nil)
|
||||
env.On("PathSeparator").Return("/")
|
||||
env.On("Home").Return("/Users/posh")
|
||||
env.On("Home").Return(poshHome)
|
||||
env.On("Getenv", poshGitEnv).Return("")
|
||||
env.On("DirMatchesOneOf", mock2.Anything, mock2.Anything).Return(false)
|
||||
g := &Git{
|
||||
|
|
|
@ -39,7 +39,7 @@ func TestMercurialEnabledInWorkingDirectory(t *testing.T) {
|
|||
env.On("IsWsl").Return(false)
|
||||
env.On("HasParentFilePath", ".hg").Return(fileInfo, nil)
|
||||
env.On("PathSeparator").Return("/")
|
||||
env.On("Home").Return("/Users/posh")
|
||||
env.On("Home").Return(poshHome)
|
||||
env.On("Getenv", poshGitEnv).Return("")
|
||||
|
||||
hg := &Mercurial{
|
||||
|
@ -153,7 +153,7 @@ A Added.File
|
|||
env.On("IsWsl").Return(false)
|
||||
env.On("HasParentFilePath", ".hg").Return(fileInfo, nil)
|
||||
env.On("PathSeparator").Return("/")
|
||||
env.On("Home").Return("/Users/posh")
|
||||
env.On("Home").Return(poshHome)
|
||||
env.On("Getenv", poshGitEnv).Return("")
|
||||
env.MockHgCommand(fileInfo.Path, tc.LogOutput, "log", "-r", ".", "--template", hgLogTemplate)
|
||||
env.MockHgCommand(fileInfo.Path, tc.StatusOutput, "status")
|
||||
|
|
|
@ -21,7 +21,7 @@ const (
|
|||
)
|
||||
|
||||
func (s *Shell) Template() string {
|
||||
return " {{ .Name }} "
|
||||
return NameTemplate
|
||||
}
|
||||
|
||||
func (s *Shell) Enabled() bool {
|
||||
|
|
42
website/docs/segments/argocd.mdx
Normal file
42
website/docs/segments/argocd.mdx
Normal file
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
id: argocd
|
||||
title: ArgoCD Context
|
||||
sidebar_label: ArgoCD
|
||||
---
|
||||
|
||||
## What
|
||||
|
||||
Display the current ArgoCD context name, user and/or server.
|
||||
|
||||
## Sample Configuration
|
||||
|
||||
import Config from "@site/src/components/Config.js";
|
||||
|
||||
<Config
|
||||
data={{
|
||||
type: "argocd",
|
||||
style: "powerline",
|
||||
powerline_symbol: "\uE0B0",
|
||||
foreground: "#ffffff",
|
||||
background: "#FFA400",
|
||||
template: " {{ .Name }}:{{ .User }}@{{ .Server }} ",
|
||||
}}
|
||||
/>
|
||||
|
||||
## Template ([info][templates])
|
||||
|
||||
:::note default template
|
||||
|
||||
```template
|
||||
{{ .Name }}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Description |
|
||||
| --------- | -------- | --------------------------------- |
|
||||
| `.Name` | `string` | the current context name |
|
||||
| `.Server` | `string` | the server of the current context |
|
||||
| `.User` | `string` | the user of the current context |
|
||||
|
||||
[templates]: /docs/configuration/templates
|
|
@ -52,6 +52,7 @@ module.exports = {
|
|||
collapsed: true,
|
||||
items: [
|
||||
"segments/angular",
|
||||
"segments/argocd",
|
||||
"segments/aws",
|
||||
"segments/az",
|
||||
"segments/azfunc",
|
||||
|
|
Loading…
Reference in a new issue