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
69 // Create a progress function to update the progress bar
70 progressReader := &mvu.ProgressReader{
71 Reader: reader, // This is the actual reader from exportDockerImage
72 Total: actualSize, // This is the total size of the data to be read
73 ProgressCh: progressCh, // This is the channel used to send progress updates
74 }
75
76 // Create a RequestPayload and populate it
77 reqPayload := common.RequestPayload{
78 Type: "deploy",
79 Payload: common.DeployPayload{
80 Ports: port,
81 TargetDomain: targetDomain,
82 ImageName: imageName,
83 Data: progressReader,
84 },
85 }
86
87 go func() {
88 resp, err := handler.SendHTTPRequest(a, &reqPayload, "POST", "/push")
89 if err != nil {
90 fmt.Println("Error sending HTTP request:", err)
91 // Don't forget to close the channel if there's an error
92 close(progressCh)
93 return
94 }
95
96 // Check the response
97 targetDomain := string(resp.Body)
98 targetDomain = strings.TrimSpace(targetDomain) // Remove leading and trailing whitespace
99 targetDomain = strings.Trim(targetDomain, "\"") // Remove leading and trailing quotes
100 // Close the progress channel after the upload is complete
101 // Determine if the target is HTTPS or HTTP
102 isHTTPS := strings.HasPrefix(targetDomain, "https://")
103
104 // Initialize HTTP client
105 client := &http.Client{
106 Timeout: 5 * time.Second,
107 }
108
109 // Only set TLS config if target is HTTPS
110 if isHTTPS {
111 client.Transport = &http.Transport{
112 TLSClientConfig: &tls.Config{InsecureSkipVerify: false}, // set false to ensure certificate is validated
113 }
114 }
115
116 // Run the TUI program
117 finalModel, err := mvu.RunDeploymentTUI(client, imageName, targetDomain, port)
118 if err != nil {
119 fmt.Println("Error running deployment TUI:", err)
120 return
121 }
122
123 // Check if the deployment was successful
124 if !finalModel.DeploymentDone {
125 fmt.Println("Deployment failed check your configuration and try again.")
126 return
127 }
128
129 // Print the final message
130 color.Blue(" Deployment successful!")
131 fmt.Println(" Your application is now available at:", targetDomain)
132 close(progressCh)
133 }()
134
135 // Run the progress bar TUI
136 m, err := mvu.RunProgressBarTUI(progressCh)
137 if err != nil {
138 fmt.Println("Error running progress bar TUI:", err)
139 return
140 }
141
142 // Close the reader
143 err = progressReader.Close()
144 if err != nil {
145 fmt.Println("Error closing reader:", err)
146 return
147 }
148
149 m.Done = true
150
151 },
152 }
153
154 // Add flags
155 deployCmd.Flags().StringVarP(&port, "port", "p", "", "Port mapping for the container")
156 deployCmd.Flags().StringVarP(&targetDomain, "target", "t", "", "Target domain for Traefik")
157
158 return deployCmd
159}
160
161// export the docker image to a reader and return its true size
162func exportDockerImage(imageName string) (io.ReadCloser, int64, error) {
163 imageID, err := docker.GetImageID(imageName)
164 if err != nil {
165 return nil, 0, fmt.Errorf("error getting image ID: %w", err)
166 }
167
168 actualSize, err := docker.GetImageSizeFromReader(imageID)
169 if err != nil {
170 return nil, 0, fmt.Errorf("error getting image size: %w", err)
171 }
172
173 reader, err := docker.ExportImageFromEngine(imageID)
174 if err != nil {
175 return nil, 0, fmt.Errorf("error exporting image: %w", err)
176 }
177
178 // Return the wrapped reader.
179 return reader, actualSize, nil
180}