Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
176 changes: 89 additions & 87 deletions README.md

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import (
"github.com/akyriako/o7k/internal/resources/networking/subnets"
swiftcontainers "github.com/akyriako/o7k/internal/resources/objectstorage/containers"
swiftobjects "github.com/akyriako/o7k/internal/resources/objectstorage/objects"
"github.com/akyriako/o7k/internal/resources/orchestration/softwaredeployments"
"github.com/akyriako/o7k/internal/resources/orchestration/stackevents"
"github.com/akyriako/o7k/internal/resources/orchestration/stackresources"
"github.com/akyriako/o7k/internal/resources/orchestration/stacks"
Expand Down Expand Up @@ -456,6 +457,10 @@ func registerAll(r *resource.Registry, openstackContext *openstack.Context) (err
errs = errors.Join(errs, fmt.Errorf("registering stacks resources: %w", err))
}

if err := r.Register(softwaredeployments.New(openstackContext)); err != nil {
errs = errors.Join(errs, fmt.Errorf("registering stacks resource: %w", err))
}

if err := r.Register(zones.New(openstackContext)); err != nil {
errs = errors.Join(errs, fmt.Errorf("registering dns zones resource: %w", err))
}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
github.com/charmbracelet/x/ansi v0.11.6
github.com/dc9-dev/promptveil v0.0.0-20260914101915-1cf7b430f7a8
github.com/gophercloud/gophercloud/v2 v2.14.0
github.com/hashicorp/go-hclog v1.6.3
github.com/hashicorp/go-plugin v1.8.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsV
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dc9-dev/promptveil v0.0.0-20260914101915-1cf7b430f7a8 h1:FZCEwbfqRR8oOBnIsfaNOaT/QM8DefOdnzT1n8hHTx0=
github.com/dc9-dev/promptveil v0.0.0-20260914101915-1cf7b430f7a8/go.mod h1:PScsCB4AI1DHxgKWUrGN3DMsIwIWSAnMkz70qw0gFcM=
github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY=
github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
Expand Down
7 changes: 4 additions & 3 deletions internal/resource/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ type NavigateFilteredMultiMsg struct {
}

type DetailsMsg struct {
ID string
Content any
Err error
ID string
Content any
Err error
WrapContent *bool
}

type NavigateScopedMsg struct {
Expand Down
116 changes: 116 additions & 0 deletions internal/resources/orchestration/softwaredeployments/commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package softwaredeployments

import (
"context"
"encoding/json"
"fmt"

"github.com/akyriako/o7k/internal/resource"
tea "github.com/charmbracelet/bubbletea"
"github.com/dc9-dev/promptveil"
)

func (r *Resource) show(row resource.Row) tea.Cmd {
return func() tea.Msg {
client, err := r.context.OrchestrationV1()
if err != nil {
return resource.DetailsMsg{Err: err}
}

var result struct {
Deployment Deployment `json:"software_deployment"`
}

_, err = client.Get(context.Background(), client.ServiceURL("software_deployments", row.ID), &result, nil)
if err != nil {
return resource.DetailsMsg{Err: fmt.Errorf("getting software deployment %q: %w", row.ID, err)}
}

return resource.DetailsMsg{
ID: result.Deployment.ID,
Content: result.Deployment,
}
}
}

func (r *Resource) outputs(row resource.Row) tea.Cmd {
return func() tea.Msg {
client, err := r.context.OrchestrationV1()
if err != nil {
return resource.DetailsMsg{Err: err}
}

var result struct {
SoftwareDeployment struct {
OutputValues map[string]any `json:"output_values"`
} `json:"software_deployment"`
}

_, err = client.Get(context.Background(), client.ServiceURL("software_deployments", row.ID), &result, nil)
if err != nil {
return resource.DetailsMsg{Err: fmt.Errorf("getting software deployment outputs %q: %w", row.ID, err)}
}

outputs := result.SoftwareDeployment.OutputValues

content := fmt.Sprintf(
"deploy_stdout:\n%s\n\ndeploy_stderr:\n%s\n\ndeploy_status_code: %v",
outputs["deploy_stdout"],
outputs["deploy_stderr"],
outputs["deploy_status_code"],
)

redacted := promptveil.Redact(content)
return resource.DetailsMsg{
ID: row.ID,
Content: redacted.Text,
WrapContent: new(true),
}
}
}

func (r *Resource) config(row resource.Row) tea.Cmd {
return func() tea.Msg {
client, err := r.context.OrchestrationV1()
if err != nil {
return resource.DetailsMsg{Err: err}
}

var deployment struct {
SoftwareDeployment struct {
ConfigID string `json:"config_id"`
} `json:"software_deployment"`
}

_, err = client.Get(context.Background(), client.ServiceURL("software_deployments", row.ID), &deployment, nil)
if err != nil {
return resource.DetailsMsg{Err: fmt.Errorf("getting software deployment %q: %w", row.ID, err)}
}

configID := deployment.SoftwareDeployment.ConfigID
if configID == "" {
return resource.DetailsMsg{Err: fmt.Errorf("software deployment %q has no config_id", row.ID)}
}

var result struct {
SoftwareConfig map[string]any `json:"software_config"`
}

_, err = client.Get(context.Background(), client.ServiceURL("software_configs", configID), &result, nil)
if err != nil {
return resource.DetailsMsg{Err: fmt.Errorf("getting software config %q: %w", configID, err)}
}

content, err := json.MarshalIndent(result.SoftwareConfig, "", " ")
if err != nil {
return resource.DetailsMsg{Err: fmt.Errorf("marshalling software config %q: %w", configID, err)}
}

redacted := promptveil.Redact(string(content))
return resource.DetailsMsg{
ID: configID,
Content: redacted.Text,
WrapContent: new(true),
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package softwaredeployments

import (
"context"
"fmt"

"github.com/akyriako/o7k/internal/openstack"
"github.com/akyriako/o7k/internal/resource"
tea "github.com/charmbracelet/bubbletea"
)

type Resource struct {
context *openstack.Context
}

func New(context *openstack.Context) *Resource {
return &Resource{context: context}
}

func (r *Resource) Title() string {
return "Heat Software Deployments"
}

func (r *Resource) Kind() string {
return "software-deployments"
}

func (r *Resource) Aliases() []string {
return []string{"software-deployment"}
}

func (r *Resource) Columns() []resource.Column {
return []resource.Column{
{Key: "id", Title: "ID", MinWidth: 36, Flex: 0},
{Key: "config_id", Title: "CONFIG ID", MinWidth: 36, Flex: 0},
{Key: "server_id", Title: "SERVER ID", MinWidth: 36, Flex: 0},
{Key: "action", Title: "ACTION", MinWidth: 6, Flex: 2},
{Key: "status", Title: "STATUS", MinWidth: 6, Flex: 2},
{Key: "status_reason", Title: "REASON", MinWidth: 21, Flex: 1},
}
}

func (r *Resource) Commands() []resource.Command {
return []resource.Command{
{Key: "s", Description: "Show", Default: true},
{Key: "o", Description: "Outputs"},
{Key: "i", Description: "Config"},
}
}

type Deployment struct {
ID string `json:"id"`
ConfigID string `json:"config_id"`
ServerID string `json:"server_id"`
Action string `json:"action"`
Status string `json:"status"`
StatusReason string `json:"status_reason"`
CreationTime string `json:"creation_time"`
}

func (r *Resource) List(ctx context.Context) ([]resource.Row, error) {
client, err := r.context.OrchestrationV1()
if err != nil {
return nil, err
}

var result struct {
Deployments []Deployment `json:"software_deployments"`
}

_, err = client.Get(ctx, client.ServiceURL("software_deployments"), &result, nil)
if err != nil {
return nil, fmt.Errorf("listing software deployments: %w", err)
}

rows := make([]resource.Row, 0, len(result.Deployments))
for _, deployment := range result.Deployments {
rows = append(rows, resource.Row{
ID: deployment.ID,
Fields: map[string]string{
"id": deployment.ID,
"config_id": deployment.ConfigID,
"server_id": deployment.ServerID,
"action": deployment.Action,
"status": deployment.Status,
"status_reason": deployment.StatusReason,
},
})
}

return rows, nil
}

func (r *Resource) Execute(command resource.Command, row resource.Row) tea.Cmd {
switch command.Key {
case "s":
return r.show(row)
case "o":
return r.outputs(row)
case "i":
return r.config(row)
}

return nil
}
61 changes: 61 additions & 0 deletions internal/resources/orchestration/stacks/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package stacks

import (
"context"
"encoding/json"
"fmt"

"github.com/akyriako/o7k/internal/resource"
tea "github.com/charmbracelet/bubbletea"
"github.com/dc9-dev/promptveil"
"github.com/gophercloud/gophercloud/v2/openstack/orchestration/v1/stackresources"
orchestrationstacks "github.com/gophercloud/gophercloud/v2/openstack/orchestration/v1/stacks"
)

Expand Down Expand Up @@ -58,3 +61,61 @@ func (r *Resource) events(row resource.Row) tea.Cmd {
}
}
}

func (r *Resource) deployments(row resource.Row) tea.Cmd {
return func() tea.Msg {
client, err := r.context.OrchestrationV1()
if err != nil {
return resource.ErrorMsg{Err: err}
}

pages, err := stackresources.List(client, row.Fields["name"], row.ID, &stackresources.ListOpts{Depth: 99}).AllPages(context.Background())
if err != nil {
return resource.ErrorMsg{Err: fmt.Errorf("listing stack resources: %w", err)}
}

items, err := stackresources.ExtractResources(pages)
if err != nil {
return resource.ErrorMsg{Err: fmt.Errorf("extracting stack resources: %w", err)}
}

ids := make([]string, 0)
for _, item := range items {
if item.Type == "OS::Heat::SoftwareDeployment" && item.PhysicalID != "" {
ids = append(ids, item.PhysicalID)
}
}

return resource.NavigateFilteredMultiMsg{
Resource: "software-deployments",
Field: "id",
Values: ids,
}
}
}

func (r *Resource) environment(row resource.Row) tea.Cmd {
return func() tea.Msg {
client, err := r.context.OrchestrationV1()
if err != nil {
return resource.DetailsMsg{Err: err}
}

var environment map[string]any
_, err = client.Get(context.Background(), client.ServiceURL("stacks", row.Fields["name"], row.ID, "environment"), &environment, nil)
if err != nil {
return resource.DetailsMsg{Err: fmt.Errorf("getting stack environment %q: %w", row.ID, err)}
}

content, err := json.MarshalIndent(environment, "", " ")
if err != nil {
return resource.DetailsMsg{Err: fmt.Errorf("marshalling stack environment %q: %w", row.ID, err)}
}

redacted := promptveil.Redact(string(content))
return resource.DetailsMsg{
ID: row.ID,
Content: redacted.Text,
}
}
}
7 changes: 6 additions & 1 deletion internal/resources/orchestration/stacks/stacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ func (r *Resource) Commands() []resource.Command {
{Key: "s", Description: "Show"},
{Key: "shift-r", Description: "Resources"},
{Key: "shift-e", Description: "Events", Default: true},
{Key: "shift-d", Description: "Deployments"},
{Key: "e", Description: "Environment"},
}
}
func (r *Resource) List(ctx context.Context) ([]resource.Row, error) {
Expand Down Expand Up @@ -89,7 +91,10 @@ func (r *Resource) Execute(command resource.Command, row resource.Row) tea.Cmd {
return r.resources(row)
case "shift-e":
return r.events(row)

case "shift-d":
return r.deployments(row)
case "e":
return r.environment(row)
}

return nil
Expand Down
Loading
Loading