Skip to content

Commit

Permalink
feat: added a bunch of shit
Browse files Browse the repository at this point in the history
  • Loading branch information
marianozunino committed Aug 21, 2024
1 parent 3483986 commit ef07270
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 64 deletions.
13 changes: 13 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ builds:
- linux
- darwin

ldflags: "-s -w -X github.com/marianozunino/sdm-ui/cmd.Version={{.Version}}"

release:
github:
owner: marianozunino
name: sdm-ui
name_template: "{{.ProjectName}}-v{{.Version}} {{.Env.USER}}"

# You can disable this pipe in order to not upload any artifacts to
# GitHub.
# Defaults to false.
disable: false

archives:
- format: tar.gz
# this name template makes the OS and Arch compatible with the results of `uname`.
Expand Down
10 changes: 8 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,14 @@ var confData conf = conf{}
var rootCmd = &cobra.Command{
Use: "sdm-ui",
Short: "SDM UI - Wrapper for SDM CLI",
Long: `SDM UI is a custom wrapper around StrongDM (SDM)
designed to improve the developer experience (DX) on Linux.`,
Long: `
___ ___ __ __ _ _ ___
/ __| \| \/ | | | | |_ _|
\__ \ |) | |\/| | | |_| || |
|___/___/|_| |_| \___/|___| ` + VersionFromBuild() + `
SDM UI is a custom wrapper around StrongDM (SDM) designed to improve the developer experience (DX) on Linux.`,

PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// You can bind cobra and viper in a few locations, but PersistencePreRunE on the root command works well
return initializeConfig(cmd)
Expand Down
43 changes: 43 additions & 0 deletions cmd/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package cmd

import (
"fmt"
"runtime/debug"

"github.com/spf13/cobra"
)

var Version = "0.0.0"

func init() {
rootCmd.AddCommand(versionCmd)
}

var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of sdm ui",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
version := VersionFromBuild()
fmt.Printf("SDM UI Version: %s\n", version)
},
}

// Version returns the version of txeh binary
func VersionFromBuild() (version string) {
// Version is managed with goreleaser
if Version != "0.0.0" {
return Version
}
// Version is managed by "go install"
b, ok := debug.ReadBuildInfo()
if !ok {
return "unknown"
}
if b == nil {
version = "nil"
} else {
version = b.Main.Version
}
return version
}
37 changes: 7 additions & 30 deletions internal/program/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package program

import (
"encoding/json"
"fmt"

"github.com/marianozunino/sdm-ui/internal/storage"
"github.com/rs/zerolog/log"
Expand Down Expand Up @@ -47,43 +46,21 @@ func parseDataSources(rawResources string) []storage.DataSource {

var dataSources []storage.DataSource
for _, resource := range resources {
if !isValidType(resource.Type) {
log.Debug().Msgf("Skipping invalid resource type: %s", resource.Type)
continue
}

dataSource := storage.DataSource{
Name: resource.Name,
Status: resource.ConnectionStatus,
Type: resource.Type,
Tags: resource.Tags,
Address: formatAddress(resource.Type, resource.Message),
Address: resource.Address,
WebURL: resource.WebURL,
}
dataSources = append(dataSources, dataSource)
}

return dataSources
}
if resource.Address == "" {
dataSource.Address = resource.Message
}

// isValidType checks if the resource type is one of the recognized types.
func isValidType(resourceType string) bool {
switch ResourceType(resourceType) {
case TypeRedis, TypePostgres, TypeAmazonEKS, TypeAmazonES, TypeAthena, TypeAmazonMQAMQP, TypeRawTCP:
return true
default:
return false
dataSources = append(dataSources, dataSource)
}
}

// formatAddress formats the address for certain resource types.
func formatAddress(resourceType string, message string) string {
switch ResourceType(resourceType) {
case TypeAmazonES:
return fmt.Sprintf("http://%s/_plugin/kibana/app/kibana", message)
case TypeRawTCP:
return fmt.Sprintf("https://%s", message)
default:
return message
}
return dataSources
}

11 changes: 8 additions & 3 deletions internal/program/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,17 @@ func (p *Program) validateAccount() error {

func printDataSources(dataSources []storage.DataSource, w io.Writer) {
const format = "%v\t%v\t%v\n"
tw := tabwriter.NewWriter(w, 0, 8, 1, '\t', 0)
tw := tabwriter.NewWriter(w, 0, 8, 2, '\t', 0)

for _, ds := range dataSources {
status := "🔒"
status := "🔌"

if ds.Status == "connected" {
status = "✅"
status = "⚡"
}

if ds.WebURL != "" {
status = "🌐"
}

fmt.Fprintf(tw, format, ds.Name, ellipsize(ds.Address, 20), status)
Expand Down
3 changes: 1 addition & 2 deletions internal/program/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package program

import (
"bytes"
"fmt"

"github.com/rs/zerolog/log"
)
Expand All @@ -15,7 +14,7 @@ func (p *Program) Sync() error {
statusesBuffer.Reset()
return p.sdmWrapper.Status(statusesBuffer)
}); err != nil {
fmt.Println("[sync] Failed to sync with SDM")
log.Debug().Msg("Failed to sync with SDM")
return err
}

Expand Down
89 changes: 63 additions & 26 deletions internal/storage/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ package storage
import (
"fmt"
"path/filepath"
"strconv"
"strings"

"github.com/rs/zerolog/log"
bolt "go.etcd.io/bbolt"
)

var datasourceBucketKey = []byte("datasource")
const (
datasourceBucketPrefix = "datasource"
currentDBVersion = 1 // increment this whenever the database schema changes
retentionPeriod = 2
)

type Storage struct {
*bolt.DB
Expand All @@ -19,27 +25,25 @@ type Storage struct {
func NewStorage(account string, path string) (*Storage, error) {
dbPath := filepath.Join(path, "sdm-sources.db")
log.Debug().Msgf("Opening database at %s", dbPath)

db, err := bolt.Open(dbPath, 0600, nil)
if err != nil {
return nil, fmt.Errorf("failed to open database: %w", err)
}

storage := &Storage{
DB: db,
account: account,
}

if err := storage.ensureBucketExists(); err != nil {
return nil, err
}

storage.removeOldBuckets(retentionPeriod)
return storage, nil
}

// ensureBucketExists ensures that the bucket for the given account exists in the database.
func (s *Storage) ensureBucketExists() error {
bucketKey := buildBucketKey(s.account)
bucketKey := buildBucketKey(s.account, currentDBVersion)
return s.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists(bucketKey)
if err != nil {
Expand All @@ -49,34 +53,31 @@ func (s *Storage) ensureBucketExists() error {
})
}

// buildBucketKey constructs a bucket key using the account and the datasource bucket key.
func buildBucketKey(account string) []byte {
return []byte(fmt.Sprintf("%s:%s", account, datasourceBucketKey))
// buildBucketKey constructs a bucket key using the account and the database version.
func buildBucketKey(account string, version int) []byte {
return []byte(fmt.Sprintf("%s:%s:v%d", account, datasourceBucketPrefix, version))
}

// StoreServers stores the provided datasources for the specified account.
func (s *Storage) StoreServers(datasources []DataSource) error {
bucketKey := buildBucketKey(s.account, currentDBVersion)
return s.Update(func(tx *bolt.Tx) error {
log.Debug().Msgf("Storing %d datasources", len(datasources))

bucket := tx.Bucket(buildBucketKey(s.account))
bucket := tx.Bucket(bucketKey)
if bucket == nil {
return fmt.Errorf("bucket for account %s not found", s.account)
}

for _, ds := range datasources {
// Encode the DataSource and handle any errors
encodedData, err := ds.Encode()
if err != nil {
return fmt.Errorf("failed to encode datasource %s: %w", ds.Name, err)
}

// Store the encoded DataSource in the bucket
if err := bucket.Put(ds.Key(), encodedData); err != nil {
return fmt.Errorf("failed to store datasource %s: %w", ds.Name, err)
}
}

log.Debug().Msgf("Successfully stored %d datasources", len(datasources))
return nil
})
Expand All @@ -85,14 +86,12 @@ func (s *Storage) StoreServers(datasources []DataSource) error {
// RetrieveDatasources retrieves all datasources for the specified account.
func (s *Storage) RetrieveDatasources() ([]DataSource, error) {
var datasources []DataSource
bucketKey := buildBucketKey(s.account)

bucketKey := buildBucketKey(s.account, currentDBVersion)
err := s.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(bucketKey)
if bucket == nil {
return fmt.Errorf("bucket for account %s not found", s.account)
}

cursor := bucket.Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
var ds DataSource
Expand All @@ -102,43 +101,81 @@ func (s *Storage) RetrieveDatasources() ([]DataSource, error) {
}
datasources = append(datasources, ds)
}

return nil
})

return datasources, err
}

// GetDatasource retrieves a single datasource by name for the specified account.
func (s *Storage) GetDatasource(name string) (DataSource, error) {
var datasource DataSource
bucketKey := buildBucketKey(s.account)

bucketKey := buildBucketKey(s.account, currentDBVersion)
err := s.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(bucketKey)
if bucket == nil {
return fmt.Errorf("bucket for account %s not found", bucketKey)
}

value := bucket.Get([]byte(name))
if value == nil {
return fmt.Errorf("datasource %s not found", name)
}

if err := datasource.Decode(value); err != nil {
return fmt.Errorf("failed to decode datasource %s: %w", name, err)
}

return nil
})

return datasource, err
}

// removeOldBuckets removes old buckets that are older than the specified retention period.
func (s *Storage) removeOldBuckets(retentionPeriod int) error {
return s.Update(func(tx *bolt.Tx) error {
c := tx.Cursor()
for k, _ := c.First(); k != nil; k, _ = c.Next() {
parts := strings.Split(string(k), ":")

if len(parts) == 2 {
log.Debug().Msgf("Removing bucket without version: %s", k)
if err := tx.DeleteBucket([]byte(k)); err != nil {
return fmt.Errorf("failed to delete bucket %s: %w", k, err)
}
continue
} else if len(parts) == 3 {
var version int
// Bucket has a version
var err error
version, err = strconv.Atoi(parts[len(parts)-1][1:])
if err != nil {
log.Error().Msgf("Failed to parse version from bucket: %s, error: %v", k, err)
continue
}

if currentDBVersion-version > retentionPeriod {
log.Debug().Msgf("Removing old bucket: %s", k)
if err := tx.DeleteBucket([]byte(k)); err != nil {
return fmt.Errorf("failed to delete bucket %s: %w", k, err)
}
}
} else {
log.Error().Msgf("Failed to parse bucket: %s", k)
continue
}

}
return nil
})
}

func (s *Storage) Wipe() error {
return s.Update(func(tx *bolt.Tx) error {
if err := tx.DeleteBucket(buildBucketKey(s.account)); err != nil {
return fmt.Errorf("failed to delete bucket for account %s: %w", s.account, err)
c := tx.Cursor()
for k, _ := c.First(); k != nil; k, _ = c.Next() {
log.Debug().Msgf("Removing bucket: %s", k)
if strings.HasPrefix(string(k), s.account) {
if err := tx.DeleteBucket([]byte(k)); err != nil {
return fmt.Errorf("failed to delete bucket %s: %w", k, err)
}
}
}
return nil
})
Expand Down
2 changes: 1 addition & 1 deletion internal/storage/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type DataSource struct {
Address string
Type string
Tags string
WebURL string
}

// Encode serializes the DataSource into a byte slice.
Expand All @@ -37,4 +38,3 @@ func (ds *DataSource) Decode(data []byte) error {
func (ds DataSource) Key() []byte {
return []byte(ds.Name)
}

0 comments on commit ef07270

Please sign in to comment.