Skip to content

Commit 344cb2d

Browse files
AdheipSinghnitisht
andauthored
feat: add installer for Parseable (#73)
This PR adds the install command for Parseable server. --------- Co-authored-by: Nitish Tiwari <[email protected]>
1 parent 40b503a commit 344cb2d

File tree

11 files changed

+2138
-47
lines changed

11 files changed

+2138
-47
lines changed

.github/workflows/build.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
- name: Set up Go
1313
uses: actions/setup-go@v4
1414
with:
15-
go-version: "1.23"
15+
go-version: "1.23.0"
1616
- name: Install gofumpt
1717
run: go install mvdan.cc/gofumpt@latest
1818
- name: Run gofumpt

cmd/installer.go

+257
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
package cmd
2+
3+
import (
4+
"encoding/base64"
5+
"encoding/json"
6+
"fmt"
7+
"net"
8+
"os"
9+
"os/exec"
10+
"pb/pkg/common"
11+
"pb/pkg/helm"
12+
"pb/pkg/installer"
13+
"runtime"
14+
"strings"
15+
"sync"
16+
"time"
17+
18+
"github.com/briandowns/spinner"
19+
"github.com/spf13/cobra"
20+
)
21+
22+
var (
23+
verbose bool
24+
)
25+
26+
var InstallOssCmd = &cobra.Command{
27+
Use: "oss",
28+
Short: "Deploy Parseable OSS",
29+
Example: "pb install oss",
30+
RunE: func(cmd *cobra.Command, _ []string) error {
31+
// Add verbose flag
32+
cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging")
33+
34+
// Print the banner
35+
printBanner()
36+
37+
// Prompt user to select a deployment plan
38+
selectedPlan, err := installer.PromptUserPlanSelection()
39+
if err != nil {
40+
return err
41+
}
42+
43+
fmt.Println(common.Green + "You selected the following plan:" + common.Reset)
44+
fmt.Printf(common.Cyan+"Plan: %s\n"+common.Yellow+"Ingestion Speed: %s\n"+common.Green+"Per Day Ingestion: %s\n"+
45+
common.Blue+"Query Performance: %s\n"+common.Red+"CPU & Memory: %s\n"+common.Reset,
46+
selectedPlan.Name, selectedPlan.IngestionSpeed, selectedPlan.PerDayIngestion,
47+
selectedPlan.QueryPerformance, selectedPlan.CPUAndMemorySpecs)
48+
49+
// Get namespace and chart values from installer
50+
valuesHolder, chartValues := installer.Installer(selectedPlan)
51+
52+
// Helm application configuration
53+
apps := []helm.Helm{
54+
{
55+
ReleaseName: "parseable",
56+
Namespace: valuesHolder.ParseableSecret.Namespace,
57+
RepoName: "parseable",
58+
RepoURL: "https://charts.parseable.com",
59+
ChartName: "parseable",
60+
Version: "1.6.5",
61+
Values: chartValues,
62+
},
63+
}
64+
65+
// Create a spinner
66+
spinner := createDeploymentSpinner(valuesHolder.ParseableSecret.Namespace)
67+
68+
// Redirect standard output if not in verbose mode
69+
var oldStdout *os.File
70+
if !verbose {
71+
oldStdout = os.Stdout
72+
_, w, _ := os.Pipe()
73+
os.Stdout = w
74+
}
75+
76+
spinner.Start()
77+
78+
// Deploy using Helm
79+
var wg sync.WaitGroup
80+
errCh := make(chan error, len(apps))
81+
for _, app := range apps {
82+
wg.Add(1)
83+
go func(app helm.Helm) {
84+
defer wg.Done()
85+
if err := helm.Apply(app, verbose); err != nil {
86+
errCh <- err
87+
return
88+
}
89+
}(app)
90+
}
91+
92+
wg.Wait()
93+
close(errCh)
94+
95+
// Stop the spinner and restore stdout
96+
spinner.Stop()
97+
if !verbose {
98+
//w.Close()
99+
os.Stdout = oldStdout
100+
}
101+
102+
// Check for errors
103+
for err := range errCh {
104+
if err != nil {
105+
return err
106+
}
107+
}
108+
109+
// Print success banner
110+
printSuccessBanner(valuesHolder.ParseableSecret.Namespace, string(valuesHolder.DeploymentType), apps[0].Version, valuesHolder.ParseableSecret.Username, valuesHolder.ParseableSecret.Password)
111+
112+
return nil
113+
},
114+
}
115+
116+
// printSuccessBanner remains the same as in the original code
117+
func printSuccessBanner(namespace, deployment, version, username, password string) {
118+
var ingestionUrl, serviceName string
119+
if deployment == "standalone" {
120+
ingestionUrl = "parseable." + namespace + ".svc.cluster.local"
121+
serviceName = "parseable"
122+
} else if deployment == "distributed" {
123+
ingestionUrl = "parseable-ingestor-svc." + namespace + ".svc.cluster.local"
124+
serviceName = "parseable-query-svc"
125+
}
126+
127+
// Encode credentials to Base64
128+
credentials := map[string]string{
129+
"username": username,
130+
"password": password,
131+
}
132+
credentialsJSON, err := json.Marshal(credentials)
133+
if err != nil {
134+
fmt.Printf("failed to marshal credentials: %v\n", err)
135+
return
136+
}
137+
138+
base64EncodedString := base64.StdEncoding.EncodeToString(credentialsJSON)
139+
140+
fmt.Println("\n" + common.Green + "🎉 Parseable Deployment Successful! 🎉" + common.Reset)
141+
fmt.Println(strings.Repeat("=", 50))
142+
143+
fmt.Printf("%s Deployment Details:\n", common.Blue+"ℹ️ ")
144+
fmt.Printf(" • Namespace: %s\n", common.Blue+namespace)
145+
fmt.Printf(" • Chart Version: %s\n", common.Blue+version)
146+
fmt.Printf(" • Ingestion URL: %s\n", ingestionUrl)
147+
148+
fmt.Println("\n" + common.Blue + "🔗 Resources:" + common.Reset)
149+
fmt.Println(common.Blue + " • Documentation: https://www.parseable.com/docs/server/introduction")
150+
fmt.Println(common.Blue + " • Stream Management: https://www.parseable.com/docs/server/api")
151+
152+
fmt.Println("\n" + common.Blue + "Happy Logging!" + common.Reset)
153+
154+
// Port-forward the service
155+
localPort := "8000"
156+
fmt.Printf(common.Green+"Port-forwarding %s service on port %s...\n"+common.Reset, serviceName, localPort)
157+
158+
err = startPortForward(namespace, serviceName, "80", localPort)
159+
if err != nil {
160+
fmt.Errorf(common.Red+"failed to port-forward service: %w", err)
161+
}
162+
163+
// Redirect to UI
164+
localURL := fmt.Sprintf("http://localhost:%s/login?q=%s", localPort, base64EncodedString)
165+
fmt.Printf(common.Green+"Opening Parseable UI at %s\n"+common.Reset, localURL)
166+
openBrowser(localURL)
167+
168+
}
169+
170+
func createDeploymentSpinner(namespace string) *spinner.Spinner {
171+
// Custom spinner with multiple character sets for dynamic effect
172+
spinnerChars := []string{
173+
"●", "○", "◉", "○", "◉", "○", "◉", "○", "◉",
174+
}
175+
176+
s := spinner.New(
177+
spinnerChars,
178+
120*time.Millisecond,
179+
spinner.WithColor(common.Yellow),
180+
spinner.WithSuffix(" ..."),
181+
)
182+
183+
s.Prefix = fmt.Sprintf(common.Yellow+"Deploying to %s ", namespace)
184+
185+
return s
186+
}
187+
188+
// printBanner displays a welcome banner
189+
func printBanner() {
190+
banner := `
191+
--------------------------------------
192+
Welcome to Parseable OSS Installation
193+
--------------------------------------
194+
`
195+
fmt.Println(common.Green + banner + common.Reset)
196+
}
197+
198+
func startPortForward(namespace, serviceName, remotePort, localPort string) error {
199+
// Build the port-forward command
200+
cmd := exec.Command("kubectl", "port-forward",
201+
fmt.Sprintf("svc/%s", serviceName),
202+
fmt.Sprintf("%s:%s", localPort, remotePort),
203+
"-n", namespace,
204+
)
205+
206+
// Redirect the command's output to the standard output for debugging
207+
if !verbose {
208+
cmd.Stdout = nil // Suppress standard output
209+
cmd.Stderr = nil // Suppress standard error
210+
} else {
211+
cmd.Stdout = os.Stdout
212+
cmd.Stderr = os.Stderr
213+
}
214+
215+
// Run the command in the background
216+
if err := cmd.Start(); err != nil {
217+
return fmt.Errorf("failed to start port-forward: %w", err)
218+
}
219+
220+
// Run in a goroutine to keep it alive
221+
go func() {
222+
_ = cmd.Wait()
223+
}()
224+
225+
// Check connection on the forwarded port
226+
retries := 10
227+
for i := 0; i < retries; i++ {
228+
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%s", localPort))
229+
if err == nil {
230+
conn.Close() // Connection successful, break out of the loop
231+
fmt.Println(common.Green + "Port-forwarding successfully established!")
232+
time.Sleep(5 * time.Second) // some delay
233+
return nil
234+
}
235+
time.Sleep(3 * time.Second) // Wait before retrying
236+
}
237+
238+
// If we reach here, port-forwarding failed
239+
cmd.Process.Kill() // Stop the kubectl process
240+
return fmt.Errorf(common.Red+"failed to establish port-forward connection to localhost:%s", localPort)
241+
}
242+
243+
func openBrowser(url string) {
244+
var cmd *exec.Cmd
245+
switch os := runtime.GOOS; os {
246+
case "windows":
247+
cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", url)
248+
case "darwin":
249+
cmd = exec.Command("open", url)
250+
case "linux":
251+
cmd = exec.Command("xdg-open", url)
252+
default:
253+
fmt.Printf("Please open the following URL manually: %s\n", url)
254+
return
255+
}
256+
cmd.Start()
257+
}

0 commit comments

Comments
 (0)