0.1.21 fbfa93c

deploy.go RawHistoryFolder

1package cmd
2
3import (
4	"crypto/tls"
5	"fmt"
6	"io"
7	"net/http"
8	"os"
9	"strings"
10	"time"
11
12	"github.com/bnema/gordon/internal/cli"
13	"github.com/bnema/gordon/internal/cli/handler"
14	"github.com/bnema/gordon/internal/cli/mvu"
15	"github.com/bnema/gordon/internal/common"
16	"github.com/bnema/gordon/pkg/docker"
17	"github.com/fatih/color"
18	"github.com/spf13/cobra"
19)
20
21func NewDeployCommand(a *cli.App) *cobra.Command {
22	var port string
23	var targetDomain string
24
25	deployCmd := &cobra.Command{
26		Use:   "deploy [image:tag]",
27		Short: "Deploy an image to your remote Gordon instance",
28		Args:  cobra.ExactArgs(1),
29		PreRun: func(cmd *cobra.Command, args []string) {
30			if err := handler.FieldCheck(a); err != nil {
31				fmt.Println("Field check failed:", err)
32				os.Exit(1)
33			}
34		},
35		Run: func(cmd *cobra.Command, args []string) {
36			imageName := args[0]
37			color.White("Pushing image: %s", imageName)
38
39			// Validate the image name
40			if err := handler.ValidateImageName(imageName); err != nil {
41				fmt.Println(err)
42				return
43			}
44
45			// Ensure the image has a tag
46			handler.EnsureImageTag(&imageName)
47
48			// Validate the port mapping
49			if err := handler.ValidatePortMapping(port); err != nil {
50				fmt.Println(err)
51				return
52			}
53
54			// Validate the target domain
55			if err := handler.ValidateTargetDomain(targetDomain); err != nil {
56				fmt.Println(err)
57				return
58			}
59
60			// Export the image to a reader and return its true size
61			reader, actualSize, err := exportDockerImage(imageName)
62			if err != nil {
63				fmt.Println("Error exporting image:", err)
64				return
65			}
66
67			progressCh := make(chan mvu.ProgressMsg)
68			errCh := make(chan error, 1) // Buffer of 1 to prevent goroutine leak in case of non-blocking send
69
70			// Create a progress function to update the progress bar
71			progressReader := &mvu.ProgressReader{
72				Reader:     reader,     // This is the actual reader from exportDockerImage
73				Total:      actualSize, // This is the total size of the data to be read
74				ProgressCh: progressCh, // This is the channel used to send progress updates
75			}
76
77			// Create a RequestPayload and populate it
78			reqPayload := common.RequestPayload{
79				Type: "deploy",
80				Payload: common.DeployPayload{
81					Ports:        port,
82					TargetDomain: targetDomain,
83					ImageName:    imageName,
84					Data:         progressReader,
85				},
86			}
87
88			go func() {
89				resp, err := handler.SendHTTPRequest(a, &reqPayload, "POST", "/push")
90				if err != nil {
91					errCh <- fmt.Errorf("error sending HTTP request: %w", err)
92					return
93				}
94
95				// Check the response
96				targetDomain := string(resp.Body)
97				targetDomain = strings.TrimSpace(targetDomain)  // Remove leading and trailing whitespace
98				targetDomain = strings.Trim(targetDomain, "\"") // Remove leading and trailing quotes
99				// Close the progress channel after the upload is complete
100				// Determine if the target is HTTPS or HTTP
101				isHTTPS := strings.HasPrefix(targetDomain, "https://")
102
103				// Initialize HTTP client
104				client := &http.Client{
105					Timeout: 5 * time.Second,
106				}
107
108				// Only set TLS config if target is HTTPS
109				if isHTTPS {
110					client.Transport = &http.Transport{
111						TLSClientConfig: &tls.Config{InsecureSkipVerify: false}, // set false to ensure certificate is validated
112					}
113				}
114
115				// Run the TUI program
116				finalModel, err := mvu.RunDeploymentTUI(client, imageName, targetDomain, port)
117				if err != nil {
118					errCh <- fmt.Errorf("error running deployment TUI: %w", err)
119					return
120				}
121
122				// Check if the deployment was successful
123				if !finalModel.DeploymentDone {
124					errCh <- fmt.Errorf("deployment failed")
125					return
126				}
127
128				// Print the final message
129				color.Blue("Deployment successful!")
130				fmt.Println("Your application is now available at:", targetDomain)
131				close(progressCh)
132			}()
133			// Run the progress bar TUI
134			m, err := mvu.RunProgressBarTUI(progressCh)
135			if err != nil {
136				errCh <- fmt.Errorf("error running progress bar TUI: %w", err)
137				return
138			}
139			done := false
140
141			for !done {
142				select {
143				case err := <-errCh:
144					if err != nil {
145						fmt.Println(err)
146						return
147					}
148				case <-time.After(1 * time.Second):
149					// Check if the progress bar is done
150					if m.Done {
151						done = true
152					}
153				}
154			}
155
156			// Close the reader
157			err = progressReader.Close()
158			if err != nil {
159				fmt.Println("Error closing reader:", err)
160				return
161			}
162
163			m.Done = true
164
165		},
166	}
167
168	// Add flags
169	deployCmd.Flags().StringVarP(&port, "port", "p", "", "Port mapping for the container")
170	deployCmd.Flags().StringVarP(&targetDomain, "target", "t", "", "Target domain for Traefik")
171
172	return deployCmd
173}
174
175// export the docker image to a reader and return its true size
176func exportDockerImage(imageName string) (io.ReadCloser, int64, error) {
177	imageID, err := docker.GetImageID(imageName)
178	if err != nil {
179		return nil, 0, fmt.Errorf("error getting image ID: %w", err)
180	}
181
182	actualSize, err := docker.GetImageSizeFromReader(imageID)
183	if err != nil {
184		return nil, 0, fmt.Errorf("error getting image size: %w", err)
185	}
186
187	reader, err := docker.ExportImageFromEngine(imageID)
188	if err != nil {
189		return nil, 0, fmt.Errorf("error exporting image: %w", err)
190	}
191
192	// Return the wrapped reader.
193	return reader, actualSize, nil
194}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close