detached 88f6307

Enhances input validation and error messages for Push command

- Improves image name validation with regex and adds default ":latest" tag if missing
- Enhances port mapping and target domain validation with detailed error messages
- Modifies URL availability checking logic in case of http or https protocol
- Adds protocol auto-completion for ports and target domain flags
  inputs
commit
88f6307c26b81a59a1c9f56cc4349a1b285c5dbc
author
bnema · · 2023-11-01 09:44 UTC
parent
dbdc5cd
signature
unsigned
2 files · +68 -24
M internal/cli/cmd/push.go +58 -24
@@ -35,10 +35,29 @@ func NewPushCommand(a *cli.App) *cobra.Command {
3535 imageName := args[0]
3636 fmt.Println("Exporting image:", imageName)
3737
38- // Check if the image name is valid or if a tag is specified
39- match, _ := regexp.MatchString(`^([a-zA-Z0-9\-_.]+\/)?[a-zA-Z0-9\-_.]+(:[a-zA-Z0-9\-_.]+)?$`, imageName)
38+ // Check if the image name is valid
39+ match, _ := regexp.MatchString("^([a-zA-Z0-9_\\-\\.]+\\/)*[a-zA-Z0-9_\\-\\.]+(:[a-zA-Z0-9_\\-\\.]+)?$", imageName)
4040 if !match {
41- fmt.Println("Invalid image name or no tag specified")
41+ fmt.Println("You must specify a valid image name in the form (registry/)image:tag, check your container engine image list")
42+ return
43+ }
44+
45+ // If there is :tag at the end of the image name, we append :latest
46+ if !strings.Contains(imageName, ":") {
47+ imageName += ":latest"
48+ }
49+
50+ // Check the ports struct port:port (proto is optional)
51+ match, _ = regexp.MatchString("^[0-9]+:[0-9]+(\\/(tcp|udp))?$", port)
52+ if !match {
53+ fmt.Println("You must specify a port mapping in the form port:port/proto, if no protocol is specified, TCP is used")
54+ return
55+ }
56+
57+ // Check the target domain struct http(s)://domain.tld (https is optional)
58+ match, _ = regexp.MatchString("^(https?:\\/\\/)?([a-zA-Z0-9\\-_\\.]+\\.)+[a-zA-Z0-9\\-_\\.]+(:[0-9]+)?$", targetDomain)
59+ if !match {
60+ fmt.Println("You must specify a valid target domain in the form http(s)://domain.tld, if no protocol is specified, HTTPS is used")
4261 return
4362 }
4463
@@ -92,45 +111,60 @@ func NewPushCommand(a *cli.App) *cobra.Command {
92111 targetDomain = strings.Trim(targetDomain, "\"") // Remove leading and trailing quotes
93112
94113 // Notify user
95- fmt.Println("Wait while Traefik is setting up the domain and certificate...")
114+ fmt.Println("Container is running !")
115+ // Initialize counter for retries
116+ retryCount := 0
117+ maxRetries := 40
96118 progressIndicator := ""
97- // Check URL availability
98- for {
99- client := &http.Client{
100- Timeout: time.Second * 20,
101- Transport: &http.Transport{
102- TLSClientConfig: &tls.Config{InsecureSkipVerify: false}, // set false to ensure certificate is validated
103- },
119+
120+ // Determine if the target is HTTPS or HTTP
121+ isHTTPS := strings.HasPrefix(targetDomain, "https://")
122+
123+ // Initialize HTTP client
124+ client := &http.Client{
125+ Timeout: 5 * time.Second,
126+ }
127+
128+ // Only set TLS config if target is HTTPS
129+ if isHTTPS {
130+ client.Transport = &http.Transport{
131+ TLSClientConfig: &tls.Config{InsecureSkipVerify: false}, // set false to ensure certificate is validated
104132 }
133+ }
105134
135+ // Check URL availability
136+ for {
106137 // Making GET request to the target domain
107- resp, err := client.Get(targetDomain)
138+ _, err := client.Get(targetDomain)
108139 if err != nil {
109- // we do nothing here, we just wait for the domain to be available
110-
140+ retryCount++
141+ if retryCount >= maxRetries {
142+ fmt.Println("\nImpossible to access the domain. Are you sure it is correct and that Traefik recognizes it?")
143+ break
144+ }
111145 } else {
112- fmt.Println("Domain is available at:", targetDomain)
146+ fmt.Println("\nDomain is available at:", targetDomain)
113147 break
114148 }
115149
116- // Close the response body, if non-nil
117- if resp != nil {
118- resp.Body.Close()
119- }
120-
121- // Update and print the progress indicator
150+ // Update the progress indicator
122151 progressIndicator += "."
123152
124153 // Limit the progress indicator to 5 dots
125154 if len(progressIndicator) > 5 {
126- progressIndicator = ""
155+ flush := "\r"
156+ for i := 0; i < len(progressIndicator); i++ {
157+ flush += " "
158+ }
127159 }
128160
129- fmt.Printf("\r%s", progressIndicator)
161+ // Use \r to move the cursor to the beginning of the line
162+ fmt.Printf("\r%s%s", "Wait while Traefik is setting up the domain and certificate", progressIndicator)
130163
131- // Wait for 1 secondsbefore next check
164+ // Wait for 1 second before the next check
132165 time.Sleep(time.Second)
133166 }
167+
134168 },
135169 }
136170
@@ -35,10 +35,29 @@ func NewPushCommand(a *cli.App) *cobra.Command {
35 imageName := args[0]35 imageName := args[0]
36 fmt.Println("Exporting image:", imageName)36 fmt.Println("Exporting image:", imageName)
37 37
38- // Check if the image name is valid or if a tag is specified38+ // Check if the image name is valid
39- match, _ := regexp.MatchString(`^([a-zA-Z0-9\-_.]+\/)?[a-zA-Z0-9\-_.]+(:[a-zA-Z0-9\-_.]+)?$`, imageName)39+ match, _ := regexp.MatchString("^([a-zA-Z0-9_\\-\\.]+\\/)*[a-zA-Z0-9_\\-\\.]+(:[a-zA-Z0-9_\\-\\.]+)?$", imageName)
40 if !match {40 if !match {
41- fmt.Println("Invalid image name or no tag specified")41+ fmt.Println("You must specify a valid image name in the form (registry/)image:tag, check your container engine image list")
42+ return
43+ }
44+
45+ // If there is :tag at the end of the image name, we append :latest
46+ if !strings.Contains(imageName, ":") {
47+ imageName += ":latest"
48+ }
49+
50+ // Check the ports struct port:port (proto is optional)
51+ match, _ = regexp.MatchString("^[0-9]+:[0-9]+(\\/(tcp|udp))?$", port)
52+ if !match {
53+ fmt.Println("You must specify a port mapping in the form port:port/proto, if no protocol is specified, TCP is used")
54+ return
55+ }
56+
57+ // Check the target domain struct http(s)://domain.tld (https is optional)
58+ match, _ = regexp.MatchString("^(https?:\\/\\/)?([a-zA-Z0-9\\-_\\.]+\\.)+[a-zA-Z0-9\\-_\\.]+(:[0-9]+)?$", targetDomain)
59+ if !match {
60+ fmt.Println("You must specify a valid target domain in the form http(s)://domain.tld, if no protocol is specified, HTTPS is used")
42 return61 return
43 }62 }
44 63
@@ -92,45 +111,60 @@ func NewPushCommand(a *cli.App) *cobra.Command {
92 targetDomain = strings.Trim(targetDomain, "\"") // Remove leading and trailing quotes111 targetDomain = strings.Trim(targetDomain, "\"") // Remove leading and trailing quotes
93 112
94 // Notify user113 // Notify user
95- fmt.Println("Wait while Traefik is setting up the domain and certificate...")114+ fmt.Println("Container is running !")
115+ // Initialize counter for retries
116+ retryCount := 0
117+ maxRetries := 40
96 progressIndicator := ""118 progressIndicator := ""
97- // Check URL availability119+
98- for {120+ // Determine if the target is HTTPS or HTTP
99- client := &http.Client{121+ isHTTPS := strings.HasPrefix(targetDomain, "https://")
100- Timeout: time.Second * 20,122+
101- Transport: &http.Transport{123+ // Initialize HTTP client
102- TLSClientConfig: &tls.Config{InsecureSkipVerify: false}, // set false to ensure certificate is validated124+ client := &http.Client{
103- },125+ Timeout: 5 * time.Second,
126+ }
127+
128+ // Only set TLS config if target is HTTPS
129+ if isHTTPS {
130+ client.Transport = &http.Transport{
131+ TLSClientConfig: &tls.Config{InsecureSkipVerify: false}, // set false to ensure certificate is validated
104 }132 }
133+ }
105 134
135+ // Check URL availability
136+ for {
106 // Making GET request to the target domain137 // Making GET request to the target domain
107- resp, err := client.Get(targetDomain)138+ _, err := client.Get(targetDomain)
108 if err != nil {139 if err != nil {
109- // we do nothing here, we just wait for the domain to be available140+ retryCount++
110-141+ if retryCount >= maxRetries {
142+ fmt.Println("\nImpossible to access the domain. Are you sure it is correct and that Traefik recognizes it?")
143+ break
144+ }
111 } else {145 } else {
112- fmt.Println("Domain is available at:", targetDomain)146+ fmt.Println("\nDomain is available at:", targetDomain)
113 break147 break
114 }148 }
115 149
116- // Close the response body, if non-nil150+ // Update the progress indicator
117- if resp != nil {
118- resp.Body.Close()
119- }
120-
121- // Update and print the progress indicator
122 progressIndicator += "."151 progressIndicator += "."
123 152
124 // Limit the progress indicator to 5 dots153 // Limit the progress indicator to 5 dots
125 if len(progressIndicator) > 5 {154 if len(progressIndicator) > 5 {
126- progressIndicator = ""155+ flush := "\r"
156+ for i := 0; i < len(progressIndicator); i++ {
157+ flush += " "
158+ }
127 }159 }
128 160
129- fmt.Printf("\r%s", progressIndicator)161+ // Use \r to move the cursor to the beginning of the line
162+ fmt.Printf("\r%s%s", "Wait while Traefik is setting up the domain and certificate", progressIndicator)
130 163
131- // Wait for 1 secondsbefore next check164+ // Wait for 1 second before the next check
132 time.Sleep(time.Second)165 time.Sleep(time.Second)
133 }166 }
167+
134 },168 },
135 }169 }
136 170
M internal/httpserve/handler/clientendpoins.go +10 -0
@@ -83,6 +83,16 @@ func PostPush(c echo.Context, a *server.App) error {
8383 imageFileName = regexp.MustCompile(`(:[a-zA-Z0-9\-_.]+)?$`).ReplaceAllString(imageFileName, "")
8484 imageFileName = imageFileName + ".tar"
8585
86+ // Check the ports struct, if there is no /tcp, or /udp, add /tcp
87+ if !regexp.MustCompile(`\/(tcp|udp)$`).MatchString(payload.Ports) {
88+ payload.Ports = payload.Ports + "/tcp"
89+ }
90+
91+ // Check the target domain, if there is no https:// or http://, add https://
92+ if !regexp.MustCompile(`^https?:\/\/`).MatchString(payload.TargetDomain) {
93+ payload.TargetDomain = "https://" + payload.TargetDomain
94+ }
95+
8696 // Save the image tar in the storage
8797 imageFilePath, err := store.SaveImageToStorage(&a.Config, imageFileName, imageReader)
8898 if err != nil {
@@ -83,6 +83,16 @@ func PostPush(c echo.Context, a *server.App) error {
83 imageFileName = regexp.MustCompile(`(:[a-zA-Z0-9\-_.]+)?$`).ReplaceAllString(imageFileName, "")83 imageFileName = regexp.MustCompile(`(:[a-zA-Z0-9\-_.]+)?$`).ReplaceAllString(imageFileName, "")
84 imageFileName = imageFileName + ".tar"84 imageFileName = imageFileName + ".tar"
85 85
86+ // Check the ports struct, if there is no /tcp, or /udp, add /tcp
87+ if !regexp.MustCompile(`\/(tcp|udp)$`).MatchString(payload.Ports) {
88+ payload.Ports = payload.Ports + "/tcp"
89+ }
90+
91+ // Check the target domain, if there is no https:// or http://, add https://
92+ if !regexp.MustCompile(`^https?:\/\/`).MatchString(payload.TargetDomain) {
93+ payload.TargetDomain = "https://" + payload.TargetDomain
94+ }
95+
86 // Save the image tar in the storage96 // Save the image tar in the storage
87 imageFilePath, err := store.SaveImageToStorage(&a.Config, imageFileName, imageReader)97 imageFilePath, err := store.SaveImageToStorage(&a.Config, imageFileName, imageReader)
88 if err != nil {98 if err != nil {

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