mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2024-12-26 19:39:39 -08:00
feat: add pulumi segment
This commit is contained in:
parent
0674681fc8
commit
8791965f3f
|
@ -202,6 +202,8 @@ const (
|
|||
PLASTIC SegmentType = "plastic"
|
||||
// Project version
|
||||
PROJECT SegmentType = "project"
|
||||
// PULUMI writes the pulumi user, store and stack
|
||||
PULUMI SegmentType = "pulumi"
|
||||
// PYTHON writes the virtual env name
|
||||
PYTHON SegmentType = "python"
|
||||
// QUASAR writes the QUASAR version and context
|
||||
|
@ -322,6 +324,7 @@ var Segments = map[SegmentType]func() SegmentWriter{
|
|||
PHP: func() SegmentWriter { return &segments.Php{} },
|
||||
PLASTIC: func() SegmentWriter { return &segments.Plastic{} },
|
||||
PROJECT: func() SegmentWriter { return &segments.Project{} },
|
||||
PULUMI: func() SegmentWriter { return &segments.Pulumi{} },
|
||||
PYTHON: func() SegmentWriter { return &segments.Python{} },
|
||||
QUASAR: func() SegmentWriter { return &segments.Quasar{} },
|
||||
R: func() SegmentWriter { return &segments.R{} },
|
||||
|
|
220
src/segments/pulumi.go
Normal file
220
src/segments/pulumi.go
Normal file
|
@ -0,0 +1,220 @@
|
|||
package segments
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/platform"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/properties"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
FetchStack properties.Property = "fetch_stack"
|
||||
FetchAbout properties.Property = "fetch_about"
|
||||
|
||||
JSON string = "json"
|
||||
YAML string = "yaml"
|
||||
|
||||
pulumiJSON string = "Pulumi.json"
|
||||
pulumiYAML string = "Pulumi.yaml"
|
||||
)
|
||||
|
||||
type Pulumi struct {
|
||||
props properties.Properties
|
||||
env platform.Environment
|
||||
|
||||
Stack string
|
||||
Name string
|
||||
|
||||
workspaceSHA1 string
|
||||
|
||||
backend
|
||||
}
|
||||
|
||||
type backend struct {
|
||||
URL string `json:"url"`
|
||||
User string `json:"user"`
|
||||
}
|
||||
|
||||
type pulumiFileSpec struct {
|
||||
Name string `yaml:"name" json:"name"`
|
||||
}
|
||||
|
||||
type pulumiWorkSpaceFileSpec struct {
|
||||
Stack string `json:"stack"`
|
||||
}
|
||||
|
||||
func (p *Pulumi) Template() string {
|
||||
return "\U000f0d46 {{ .Stack }}{{if .User }} :: {{ .User }}@{{ end }}{{ if .URL }}{{ .URL }}{{ end }}"
|
||||
}
|
||||
|
||||
func (p *Pulumi) Init(props properties.Properties, env platform.Environment) {
|
||||
p.props = props
|
||||
p.env = env
|
||||
}
|
||||
|
||||
func (p *Pulumi) Enabled() bool {
|
||||
if !p.env.HasCommand("pulumi") {
|
||||
return false
|
||||
}
|
||||
|
||||
err := p.getProjectName()
|
||||
if err != nil {
|
||||
p.env.Error(err)
|
||||
return false
|
||||
}
|
||||
|
||||
if p.props.GetBool(FetchStack, false) {
|
||||
p.getPulumiStackName()
|
||||
}
|
||||
|
||||
if p.props.GetBool(FetchAbout, false) {
|
||||
p.getPulumiAbout()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *Pulumi) getPulumiStackName() {
|
||||
if len(p.Name) == 0 || len(p.workspaceSHA1) == 0 {
|
||||
p.env.Debug("pulumi project name or workspace sha1 is empty")
|
||||
return
|
||||
}
|
||||
|
||||
stackNameFile := p.Name + "-" + p.workspaceSHA1 + "-" + "workspace.json"
|
||||
|
||||
homedir := p.env.Home()
|
||||
|
||||
workspaceCacheDir := filepath.Join(homedir, ".pulumi", "workspaces")
|
||||
if !p.env.HasFolder(workspaceCacheDir) || !p.env.HasFilesInDir(workspaceCacheDir, stackNameFile) {
|
||||
return
|
||||
}
|
||||
|
||||
workspaceCacheFile := filepath.Join(workspaceCacheDir, stackNameFile)
|
||||
workspaceCacheFileContent := p.env.FileContent(workspaceCacheFile)
|
||||
|
||||
var pulumiWorkspaceSpec pulumiWorkSpaceFileSpec
|
||||
err := json.Unmarshal([]byte(workspaceCacheFileContent), &pulumiWorkspaceSpec)
|
||||
if err != nil {
|
||||
p.env.Error(fmt.Errorf("pulumi workspace file decode error"))
|
||||
return
|
||||
}
|
||||
|
||||
p.env.DebugF("pulumi stack name: %s", pulumiWorkspaceSpec.Stack)
|
||||
p.Stack = pulumiWorkspaceSpec.Stack
|
||||
}
|
||||
|
||||
func (p *Pulumi) getProjectName() error {
|
||||
var kind, fileName string
|
||||
for _, file := range []string{pulumiYAML, pulumiJSON} {
|
||||
if p.env.HasFiles(file) {
|
||||
fileName = file
|
||||
kind = filepath.Ext(file)[1:]
|
||||
}
|
||||
}
|
||||
|
||||
if len(kind) == 0 {
|
||||
return fmt.Errorf("no pulumi spec file found")
|
||||
}
|
||||
|
||||
var pulumiFileSpec pulumiFileSpec
|
||||
var err error
|
||||
|
||||
pulumiFile := p.env.FileContent(fileName)
|
||||
|
||||
switch kind {
|
||||
case YAML:
|
||||
err = yaml.Unmarshal([]byte(pulumiFile), &pulumiFileSpec)
|
||||
case JSON:
|
||||
err = json.Unmarshal([]byte(pulumiFile), &pulumiFileSpec)
|
||||
default:
|
||||
err = fmt.Errorf("unknown pulumi spec file format")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
p.env.Error(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
p.Name = pulumiFileSpec.Name
|
||||
|
||||
sha1HexString := func(value string) string {
|
||||
h := sha1.New()
|
||||
|
||||
_, err := h.Write([]byte(value))
|
||||
if err != nil {
|
||||
p.env.Error(err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
p.workspaceSHA1 = sha1HexString(p.env.Pwd() + "/" + fileName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pulumi) getPulumiAbout() {
|
||||
if len(p.Stack) == 0 {
|
||||
p.env.Error(fmt.Errorf("pulumi stack name is empty, use `fetch_stack` property to enable stack fetching"))
|
||||
return
|
||||
}
|
||||
|
||||
cacheKey := "pulumi-" + p.Name + "-" + p.Stack + "-" + p.workspaceSHA1 + "-about"
|
||||
|
||||
getAboutCache := func(key string) (*backend, error) {
|
||||
aboutBackend, OK := p.env.Cache().Get(key)
|
||||
if (!OK || len(aboutBackend) == 0) || (OK && len(aboutBackend) == 0) {
|
||||
return nil, fmt.Errorf("no data in cache")
|
||||
}
|
||||
|
||||
var backend *backend
|
||||
err := json.Unmarshal([]byte(aboutBackend), &backend)
|
||||
if err != nil {
|
||||
p.env.DebugF("unable to decode about cache: %s", aboutBackend)
|
||||
p.env.Error(fmt.Errorf("pulling about cache decode error"))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return backend, nil
|
||||
}
|
||||
|
||||
aboutBackend, err := getAboutCache(cacheKey)
|
||||
if err == nil {
|
||||
p.backend = *aboutBackend
|
||||
return
|
||||
}
|
||||
|
||||
aboutOutput, err := p.env.RunCommand("pulumi", "about", "--json")
|
||||
|
||||
if err != nil {
|
||||
p.env.Error(fmt.Errorf("unable to get pulumi about output"))
|
||||
return
|
||||
}
|
||||
|
||||
var about struct {
|
||||
Backend *backend `json:"backend"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(aboutOutput), &about)
|
||||
if err != nil {
|
||||
p.env.Error(fmt.Errorf("pulumi about output decode error"))
|
||||
return
|
||||
}
|
||||
|
||||
if about.Backend == nil {
|
||||
p.env.Debug("pulumi about backend is not set")
|
||||
return
|
||||
}
|
||||
|
||||
p.backend = *about.Backend
|
||||
|
||||
cacheTimeout := p.props.GetInt(properties.CacheTimeout, 43800)
|
||||
jso, _ := json.Marshal(about.Backend)
|
||||
p.env.Cache().Set(cacheKey, string(jso), cacheTimeout)
|
||||
}
|
234
src/segments/pulumi_test.go
Normal file
234
src/segments/pulumi_test.go
Normal file
|
@ -0,0 +1,234 @@
|
|||
package segments
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestPulumi(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
YAMLConfig string
|
||||
JSONConfig string
|
||||
|
||||
HasCommand bool
|
||||
|
||||
FetchStack bool
|
||||
Stack string
|
||||
StackError error
|
||||
|
||||
HasWorkspaceFolder bool
|
||||
WorkSpaceFile string
|
||||
|
||||
FetchAbout bool
|
||||
About string
|
||||
AboutError error
|
||||
AboutCache string
|
||||
|
||||
ExpectedString string
|
||||
ExpectedEnabled bool
|
||||
}{
|
||||
{
|
||||
Case: "no pulumi command",
|
||||
ExpectedEnabled: false,
|
||||
HasCommand: false,
|
||||
},
|
||||
{
|
||||
Case: "pulumi command is present, but no pulumi file",
|
||||
ExpectedEnabled: false,
|
||||
HasCommand: true,
|
||||
},
|
||||
{
|
||||
Case: "pulumi file YAML is present",
|
||||
ExpectedString: "\U000f0d46",
|
||||
ExpectedEnabled: true,
|
||||
HasCommand: true,
|
||||
YAMLConfig: `
|
||||
name: oh-my-posh
|
||||
runtime: golang
|
||||
description: A Console App
|
||||
`,
|
||||
},
|
||||
{
|
||||
Case: "pulumi file JSON is present",
|
||||
ExpectedString: "\U000f0d46",
|
||||
ExpectedEnabled: true,
|
||||
HasCommand: true,
|
||||
JSONConfig: `{ "name": "oh-my-posh" }`,
|
||||
},
|
||||
{
|
||||
Case: "no stack present",
|
||||
ExpectedString: "\U000f0d46 1337",
|
||||
ExpectedEnabled: true,
|
||||
HasCommand: true,
|
||||
HasWorkspaceFolder: true,
|
||||
FetchStack: true,
|
||||
JSONConfig: `{ "name": "oh-my-posh" }`,
|
||||
WorkSpaceFile: `{ "stack": "1337" }`,
|
||||
},
|
||||
{
|
||||
Case: "pulumi stack",
|
||||
ExpectedString: "\U000f0d46 1337",
|
||||
ExpectedEnabled: true,
|
||||
HasCommand: true,
|
||||
HasWorkspaceFolder: true,
|
||||
FetchStack: true,
|
||||
JSONConfig: `{ "name": "oh-my-posh" }`,
|
||||
WorkSpaceFile: `{ "stack": "1337" }`,
|
||||
},
|
||||
{
|
||||
Case: "pulumi URL",
|
||||
ExpectedString: "\U000f0d46 1337 :: posh-user@s3://test-pulumi-state-test",
|
||||
ExpectedEnabled: true,
|
||||
HasCommand: true,
|
||||
HasWorkspaceFolder: true,
|
||||
FetchStack: true,
|
||||
FetchAbout: true,
|
||||
JSONConfig: `{ "name": "oh-my-posh" }`,
|
||||
WorkSpaceFile: `{ "stack": "1337" }`,
|
||||
About: `{ "backend": { "url": "s3://test-pulumi-state-test", "user":"posh-user" } }`,
|
||||
},
|
||||
{
|
||||
Case: "pulumi URL - cache",
|
||||
ExpectedString: "\U000f0d46 1337 :: posh-user@s3://test-pulumi-state-test",
|
||||
ExpectedEnabled: true,
|
||||
HasCommand: true,
|
||||
HasWorkspaceFolder: true,
|
||||
FetchStack: true,
|
||||
FetchAbout: true,
|
||||
JSONConfig: `{ "name": "oh-my-posh" }`,
|
||||
WorkSpaceFile: `{ "stack": "1337" }`,
|
||||
AboutCache: `{ "url": "s3://test-pulumi-state-test", "user":"posh-user" }`,
|
||||
},
|
||||
// Error flows
|
||||
{
|
||||
Case: "pulumi file JSON error",
|
||||
ExpectedString: "\U000f0d46",
|
||||
ExpectedEnabled: true,
|
||||
FetchStack: true,
|
||||
HasCommand: true,
|
||||
JSONConfig: `{`,
|
||||
},
|
||||
{
|
||||
Case: "pulumi workspace file JSON error",
|
||||
ExpectedString: "\U000f0d46",
|
||||
ExpectedEnabled: true,
|
||||
FetchStack: true,
|
||||
HasCommand: true,
|
||||
JSONConfig: `{ "name": "oh-my-posh" }`,
|
||||
WorkSpaceFile: `{`,
|
||||
HasWorkspaceFolder: true,
|
||||
},
|
||||
{
|
||||
Case: "pulumi URL, no fetch_stack set",
|
||||
ExpectedString: "\U000f0d46",
|
||||
ExpectedEnabled: true,
|
||||
HasCommand: true,
|
||||
FetchAbout: true,
|
||||
JSONConfig: `{ "name": "oh-my-posh" }`,
|
||||
},
|
||||
{
|
||||
Case: "pulumi URL - cache error",
|
||||
ExpectedString: "\U000f0d46 1337 :: posh-user@s3://test-pulumi-state-test-output",
|
||||
ExpectedEnabled: true,
|
||||
HasCommand: true,
|
||||
HasWorkspaceFolder: true,
|
||||
FetchStack: true,
|
||||
FetchAbout: true,
|
||||
JSONConfig: `{ "name": "oh-my-posh" }`,
|
||||
WorkSpaceFile: `{ "stack": "1337" }`,
|
||||
AboutCache: `{`,
|
||||
About: `{ "backend": { "url": "s3://test-pulumi-state-test-output", "user":"posh-user" } }`,
|
||||
},
|
||||
{
|
||||
Case: "pulumi URL - about error",
|
||||
ExpectedString: "\U000f0d46 1337",
|
||||
ExpectedEnabled: true,
|
||||
HasCommand: true,
|
||||
HasWorkspaceFolder: true,
|
||||
FetchStack: true,
|
||||
FetchAbout: true,
|
||||
JSONConfig: `{ "name": "oh-my-posh" }`,
|
||||
WorkSpaceFile: `{ "stack": "1337" }`,
|
||||
AboutError: errors.New("error"),
|
||||
},
|
||||
{
|
||||
Case: "pulumi URL - about decode error",
|
||||
ExpectedString: "\U000f0d46 1337",
|
||||
ExpectedEnabled: true,
|
||||
HasCommand: true,
|
||||
HasWorkspaceFolder: true,
|
||||
FetchStack: true,
|
||||
FetchAbout: true,
|
||||
JSONConfig: `{ "name": "oh-my-posh" }`,
|
||||
WorkSpaceFile: `{ "stack": "1337" }`,
|
||||
About: `{`,
|
||||
},
|
||||
{
|
||||
Case: "pulumi URL - about backend is nil",
|
||||
ExpectedString: "\U000f0d46 1337",
|
||||
ExpectedEnabled: true,
|
||||
HasCommand: true,
|
||||
HasWorkspaceFolder: true,
|
||||
FetchStack: true,
|
||||
FetchAbout: true,
|
||||
JSONConfig: `{ "name": "oh-my-posh" }`,
|
||||
WorkSpaceFile: `{ "stack": "1337" }`,
|
||||
About: `{}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
env := new(mock.MockedEnvironment)
|
||||
|
||||
env.On("HasCommand", "pulumi").Return(tc.HasCommand)
|
||||
env.On("RunCommand", "pulumi", []string{"stack", "ls", "--json"}).Return(tc.Stack, tc.StackError)
|
||||
env.On("RunCommand", "pulumi", []string{"about", "--json"}).Return(tc.About, tc.AboutError)
|
||||
|
||||
env.On("Pwd").Return("/home/foobar/Work/oh-my-posh/pulumi/projects/awesome-project")
|
||||
env.On("Home").Return(filepath.Clean("/home/foobar"))
|
||||
env.On("Error", mock2.Anything)
|
||||
env.On("Debug", mock2.Anything)
|
||||
env.On("DebugF", mock2.Anything, mock2.Anything)
|
||||
|
||||
env.On("HasFiles", pulumiYAML).Return(len(tc.YAMLConfig) > 0)
|
||||
env.On("FileContent", pulumiYAML).Return(tc.YAMLConfig, nil)
|
||||
|
||||
env.On("HasFiles", pulumiJSON).Return(len(tc.JSONConfig) > 0)
|
||||
env.On("FileContent", pulumiJSON).Return(tc.JSONConfig, nil)
|
||||
|
||||
env.On("HasFolder", filepath.Clean("/home/foobar/.pulumi/workspaces")).Return(tc.HasWorkspaceFolder)
|
||||
workspaceFile := "oh-my-posh-c62b7b6786c5c5a85896576e46a25d7c9f888e92-workspace.json"
|
||||
env.On("HasFilesInDir", filepath.Clean("/home/foobar/.pulumi/workspaces"), workspaceFile).Return(len(tc.WorkSpaceFile) > 0)
|
||||
env.On("FileContent", filepath.Clean("/home/foobar/.pulumi/workspaces/"+workspaceFile)).Return(tc.WorkSpaceFile, nil)
|
||||
|
||||
cache := &mock.MockedCache{}
|
||||
cache.On("Get", "pulumi-oh-my-posh-1337-c62b7b6786c5c5a85896576e46a25d7c9f888e92-about").Return(tc.AboutCache, len(tc.AboutCache) > 0)
|
||||
cache.On("Set", mock2.Anything, mock2.Anything, mock2.Anything)
|
||||
|
||||
env.On("Cache").Return(cache)
|
||||
|
||||
pulumi := &Pulumi{
|
||||
env: env,
|
||||
props: properties.Map{
|
||||
FetchStack: tc.FetchStack,
|
||||
FetchAbout: tc.FetchAbout,
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.ExpectedEnabled, pulumi.Enabled(), tc.Case)
|
||||
|
||||
if !tc.ExpectedEnabled {
|
||||
continue
|
||||
}
|
||||
|
||||
var got = renderTemplate(env, pulumi.Template(), pulumi)
|
||||
assert.Equal(t, tc.ExpectedString, got, tc.Case)
|
||||
}
|
||||
}
|
|
@ -295,6 +295,7 @@
|
|||
"php",
|
||||
"plastic",
|
||||
"project",
|
||||
"pulumi",
|
||||
"root",
|
||||
"ruby",
|
||||
"rust",
|
||||
|
@ -1957,6 +1958,19 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "pulumi"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"title": "Pulumi Segment",
|
||||
"description": "https://ohmyposh.dev/docs/segments/pulumi"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
|
|
56
website/docs/segments/pulumi.mdx
Normal file
56
website/docs/segments/pulumi.mdx
Normal file
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
id: pulumi
|
||||
title: Pulumi
|
||||
sidebar_label: Pulumi
|
||||
---
|
||||
|
||||
## What
|
||||
|
||||
Display the currently active pulumi logged-in user, url and stack.
|
||||
|
||||
:::caution
|
||||
This requires a pulumi binary in your PATH and will only show in directories that contain a `Pulumi.yaml` file.
|
||||
:::
|
||||
|
||||
## Sample Configuration
|
||||
|
||||
import Config from "@site/src/components/Config.js";
|
||||
|
||||
<Config
|
||||
data={{
|
||||
type: "pulumi",
|
||||
style: "diamond",
|
||||
powerline_symbol: "\uE0CF",
|
||||
foreground: "#ffffff",
|
||||
background: "#662d91",
|
||||
template:
|
||||
"{{ .Stack }}{{if .User }} :: {{ .User }}@{{ end }}{{ if .URL }}{{ .URL }}{{ end }}",
|
||||
}}
|
||||
/>
|
||||
|
||||
## Properties
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| ------------- | --------- | ------- | ---------------------------------------------------------------------------------- |
|
||||
| `fetch_stack` | `boolean` | `false` | fetch the current stack name |
|
||||
| `fetch_about` | `boolean` | `false` | fetch the URL and user for the current stask. Requires `fetch_stack` set to `true` |
|
||||
|
||||
## Template ([info][templates])
|
||||
|
||||
:::note default template
|
||||
|
||||
```template
|
||||
{{ .Stack }}{{if .User }} :: {{ .User }}@{{ end }}{{ if .URL }}{{ .URL }}{{ end }}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Description |
|
||||
| -------- | -------- | -------------------------------------------------- |
|
||||
| `.Stack` | `string` | the current stack name |
|
||||
| `.User` | `string` | is the current logged in user |
|
||||
| `.Url` | `string` | the URL of the state where pulumi stores resources |
|
||||
|
||||
[templates]: /docs/configuration/templates
|
|
@ -104,6 +104,7 @@ module.exports = {
|
|||
"segments/php",
|
||||
"segments/plastic",
|
||||
"segments/project",
|
||||
"segments/pulumi",
|
||||
"segments/python",
|
||||
"segments/quasar",
|
||||
"segments/r",
|
||||
|
|
Loading…
Reference in a new issue