Skip to content

Commit

Permalink
feat: add installer for Parseable (#73)
Browse files Browse the repository at this point in the history
This PR adds the install command for 
Parseable server.

---------

Co-authored-by: Nitish Tiwari <[email protected]>
  • Loading branch information
AdheipSingh and nitisht authored Dec 16, 2024
1 parent 40b503a commit 344cb2d
Show file tree
Hide file tree
Showing 11 changed files with 2,138 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.23"
go-version: "1.23.0"
- name: Install gofumpt
run: go install mvdan.cc/gofumpt@latest
- name: Run gofumpt
Expand Down
257 changes: 257 additions & 0 deletions cmd/installer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
package cmd

import (
"encoding/base64"
"encoding/json"
"fmt"
"net"
"os"
"os/exec"
"pb/pkg/common"
"pb/pkg/helm"
"pb/pkg/installer"
"runtime"
"strings"
"sync"
"time"

"github.com/briandowns/spinner"
"github.com/spf13/cobra"
)

var (
verbose bool
)

var InstallOssCmd = &cobra.Command{
Use: "oss",
Short: "Deploy Parseable OSS",
Example: "pb install oss",
RunE: func(cmd *cobra.Command, _ []string) error {
// Add verbose flag
cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging")

// Print the banner
printBanner()

// Prompt user to select a deployment plan
selectedPlan, err := installer.PromptUserPlanSelection()
if err != nil {
return err
}

fmt.Println(common.Green + "You selected the following plan:" + common.Reset)
fmt.Printf(common.Cyan+"Plan: %s\n"+common.Yellow+"Ingestion Speed: %s\n"+common.Green+"Per Day Ingestion: %s\n"+
common.Blue+"Query Performance: %s\n"+common.Red+"CPU & Memory: %s\n"+common.Reset,
selectedPlan.Name, selectedPlan.IngestionSpeed, selectedPlan.PerDayIngestion,
selectedPlan.QueryPerformance, selectedPlan.CPUAndMemorySpecs)

// Get namespace and chart values from installer
valuesHolder, chartValues := installer.Installer(selectedPlan)

// Helm application configuration
apps := []helm.Helm{
{
ReleaseName: "parseable",
Namespace: valuesHolder.ParseableSecret.Namespace,
RepoName: "parseable",
RepoURL: "https://charts.parseable.com",
ChartName: "parseable",
Version: "1.6.5",
Values: chartValues,
},
}

// Create a spinner
spinner := createDeploymentSpinner(valuesHolder.ParseableSecret.Namespace)

// Redirect standard output if not in verbose mode
var oldStdout *os.File
if !verbose {
oldStdout = os.Stdout
_, w, _ := os.Pipe()
os.Stdout = w
}

spinner.Start()

// Deploy using Helm
var wg sync.WaitGroup
errCh := make(chan error, len(apps))
for _, app := range apps {
wg.Add(1)
go func(app helm.Helm) {
defer wg.Done()
if err := helm.Apply(app, verbose); err != nil {
errCh <- err
return
}
}(app)
}

wg.Wait()
close(errCh)

// Stop the spinner and restore stdout
spinner.Stop()
if !verbose {
//w.Close()
os.Stdout = oldStdout
}

// Check for errors
for err := range errCh {
if err != nil {
return err
}
}

// Print success banner
printSuccessBanner(valuesHolder.ParseableSecret.Namespace, string(valuesHolder.DeploymentType), apps[0].Version, valuesHolder.ParseableSecret.Username, valuesHolder.ParseableSecret.Password)

return nil
},
}

// printSuccessBanner remains the same as in the original code
func printSuccessBanner(namespace, deployment, version, username, password string) {
var ingestionUrl, serviceName string
if deployment == "standalone" {
ingestionUrl = "parseable." + namespace + ".svc.cluster.local"
serviceName = "parseable"
} else if deployment == "distributed" {
ingestionUrl = "parseable-ingestor-svc." + namespace + ".svc.cluster.local"
serviceName = "parseable-query-svc"
}

// Encode credentials to Base64
credentials := map[string]string{
"username": username,
"password": password,
}
credentialsJSON, err := json.Marshal(credentials)
if err != nil {
fmt.Printf("failed to marshal credentials: %v\n", err)
return
}

base64EncodedString := base64.StdEncoding.EncodeToString(credentialsJSON)

fmt.Println("\n" + common.Green + "🎉 Parseable Deployment Successful! 🎉" + common.Reset)
fmt.Println(strings.Repeat("=", 50))

fmt.Printf("%s Deployment Details:\n", common.Blue+"ℹ️ ")
fmt.Printf(" • Namespace: %s\n", common.Blue+namespace)
fmt.Printf(" • Chart Version: %s\n", common.Blue+version)
fmt.Printf(" • Ingestion URL: %s\n", ingestionUrl)

fmt.Println("\n" + common.Blue + "🔗 Resources:" + common.Reset)
fmt.Println(common.Blue + " • Documentation: https://www.parseable.com/docs/server/introduction")
fmt.Println(common.Blue + " • Stream Management: https://www.parseable.com/docs/server/api")

fmt.Println("\n" + common.Blue + "Happy Logging!" + common.Reset)

// Port-forward the service
localPort := "8000"
fmt.Printf(common.Green+"Port-forwarding %s service on port %s...\n"+common.Reset, serviceName, localPort)

err = startPortForward(namespace, serviceName, "80", localPort)
if err != nil {
fmt.Errorf(common.Red+"failed to port-forward service: %w", err)
}

// Redirect to UI
localURL := fmt.Sprintf("http://localhost:%s/login?q=%s", localPort, base64EncodedString)
fmt.Printf(common.Green+"Opening Parseable UI at %s\n"+common.Reset, localURL)
openBrowser(localURL)

}

func createDeploymentSpinner(namespace string) *spinner.Spinner {
// Custom spinner with multiple character sets for dynamic effect
spinnerChars := []string{
"●", "○", "◉", "○", "◉", "○", "◉", "○", "◉",
}

s := spinner.New(
spinnerChars,
120*time.Millisecond,
spinner.WithColor(common.Yellow),
spinner.WithSuffix(" ..."),
)

s.Prefix = fmt.Sprintf(common.Yellow+"Deploying to %s ", namespace)

return s
}

// printBanner displays a welcome banner
func printBanner() {
banner := `
--------------------------------------
Welcome to Parseable OSS Installation
--------------------------------------
`
fmt.Println(common.Green + banner + common.Reset)
}

func startPortForward(namespace, serviceName, remotePort, localPort string) error {
// Build the port-forward command
cmd := exec.Command("kubectl", "port-forward",
fmt.Sprintf("svc/%s", serviceName),
fmt.Sprintf("%s:%s", localPort, remotePort),
"-n", namespace,
)

// Redirect the command's output to the standard output for debugging
if !verbose {
cmd.Stdout = nil // Suppress standard output
cmd.Stderr = nil // Suppress standard error
} else {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}

// Run the command in the background
if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to start port-forward: %w", err)
}

// Run in a goroutine to keep it alive
go func() {
_ = cmd.Wait()
}()

// Check connection on the forwarded port
retries := 10
for i := 0; i < retries; i++ {
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%s", localPort))
if err == nil {
conn.Close() // Connection successful, break out of the loop
fmt.Println(common.Green + "Port-forwarding successfully established!")
time.Sleep(5 * time.Second) // some delay
return nil
}
time.Sleep(3 * time.Second) // Wait before retrying
}

// If we reach here, port-forwarding failed
cmd.Process.Kill() // Stop the kubectl process
return fmt.Errorf(common.Red+"failed to establish port-forward connection to localhost:%s", localPort)
}

func openBrowser(url string) {
var cmd *exec.Cmd
switch os := runtime.GOOS; os {
case "windows":
cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", url)
case "darwin":
cmd = exec.Command("open", url)
case "linux":
cmd = exec.Command("xdg-open", url)
default:
fmt.Printf("Please open the following URL manually: %s\n", url)
return
}
cmd.Start()
}
Loading

0 comments on commit 344cb2d

Please sign in to comment.