@@ -16,41 +16,97 @@ import (
1616 "golang.org/x/oauth2/google"
1717)
1818
19- type MinimalLogger struct {
20- startTime time.Time
21- lastLayer string
22- lastProgress string
19+ // Color codes
20+ const (
21+ colorReset = "\033 [0m"
22+ colorRed = "\033 [31m"
23+ colorGreen = "\033 [32m"
24+ colorYellow = "\033 [33m"
25+ colorBlue = "\033 [34m"
26+ colorMagenta = "\033 [35m"
27+ colorCyan = "\033 [36m"
28+ colorWhite = "\033 [37m"
29+ )
30+
31+ type FancyLogger struct {
32+ startTime time.Time
33+ lastUpdate map [string ]float64
34+ layerWidth int
35+ spinnerStates []string
36+ spinnerIndex int
37+ }
38+
39+ func NewFancyLogger () * FancyLogger {
40+ return & FancyLogger {
41+ startTime : time .Now (),
42+ lastUpdate : make (map [string ]float64 ),
43+ layerWidth : 12 , // Truncate long layer IDs
44+ spinnerStates : []string {"⣷" , "⣯" , "⣟" , "⡿" , "⢿" , "⣻" , "⣽" , "⣾" },
45+ }
2346}
2447
25- func NewMinimalLogger () * MinimalLogger {
26- return & MinimalLogger {startTime : time .Now ()}
48+ func (l * FancyLogger ) getSpinner () string {
49+ l .spinnerIndex = (l .spinnerIndex + 1 ) % len (l .spinnerStates )
50+ return l .spinnerStates [l .spinnerIndex ]
2751}
2852
29- func (l * MinimalLogger ) logStep (message string ) {
30- fmt .Printf ("[%s] → %s\n " , time .Since (l .startTime ).Round (time .Millisecond ), message )
53+ func (l * FancyLogger ) logHeader (message string ) {
54+ fmt .Printf ("%s [%s] %s %s%s%s \n " , colorBlue , time .Since (l .startTime ).Round (time .Millisecond ), "⚡" , colorCyan , message , colorReset )
3155}
3256
33- func (l * MinimalLogger ) logSuccess (message string ) {
34- fmt .Printf ("[%s] ✓ %s\n " , time .Since (l .startTime ).Round (time .Millisecond ), message )
57+ func (l * FancyLogger ) logSuccess (message string ) {
58+ fmt .Printf ("%s [%s] %s %s%s%s \n " , colorGreen , time .Since (l .startTime ).Round (time .Millisecond ), "✓" , colorGreen , message , colorReset )
3559}
3660
37- func (l * MinimalLogger ) logProgress (layer , operation string , current , total int64 ) {
38- progress := ""
39- if total > 0 {
40- progress = fmt .Sprintf (" (%.1f%%)" , float64 (current )/ float64 (total )* 100 )
61+ func (l * FancyLogger ) logProgress (layer , operation string , percent float64 ) {
62+ // Truncate layer ID for display
63+ displayLayer := layer
64+ if len (layer ) > l .layerWidth {
65+ displayLayer = layer [:l .layerWidth ] + "..."
4166 }
42- msg := fmt .Sprintf ("%s: %s%s" , layer , operation , progress )
4367
44- if msg != l .lastProgress {
45- fmt .Printf ("[%s] ↳ %s\n " , time .Since (l .startTime ).Round (time .Millisecond ), msg )
46- l .lastProgress = msg
68+ // Only update if:
69+ // - Operation changed
70+ // - Progress increased by at least 5%
71+ // - It's the first update for this layer
72+ lastPercent , exists := l .lastUpdate [layer ]
73+ if ! exists || operation != "" || percent - lastPercent >= 5 || percent == 100 {
74+ bar := l .progressBar (percent )
75+ spinner := l .getSpinner ()
76+ fmt .Printf ("\r %s[%s] %s %s: %s %s %s" ,
77+ colorYellow ,
78+ time .Since (l .startTime ).Round (time .Millisecond ),
79+ spinner ,
80+ colorMagenta + displayLayer + colorReset ,
81+ operation ,
82+ bar ,
83+ fmt .Sprintf ("%5.1f%%" , percent ))
84+
85+ if percent == 100 {
86+ fmt .Println () // New line when complete
87+ }
88+ l .lastUpdate [layer ] = percent
4789 }
4890}
4991
50- func (l * MinimalLogger ) logFinal (message string ) {
51- fmt .Printf ("[%s] ★ %s\n " , time .Since (l .startTime ).Round (time .Millisecond ), message )
92+ func (l * FancyLogger ) progressBar (percent float64 ) string {
93+ const width = 20
94+ completed := int (percent / 5 )
95+ if completed > width {
96+ completed = width
97+ }
98+ return fmt .Sprintf ("%s%s%s%s" ,
99+ colorGreen ,
100+ strings .Repeat ("█" , completed ),
101+ colorWhite ,
102+ strings .Repeat ("░" , width - completed ))
103+ }
104+
105+ func (l * FancyLogger ) logFinal (message string ) {
106+ fmt .Printf ("%s[%s] %s %s%s%s\n " , colorMagenta , time .Since (l .startTime ).Round (time .Millisecond ), "✨" , colorMagenta , message , colorReset )
52107}
53108
109+ // Helper function to parse image name and tag
54110func parseImageName (imageNameWithTag string ) (string , string ) {
55111 parts := strings .Split (imageNameWithTag , ":" )
56112 if len (parts ) == 2 {
@@ -59,13 +115,17 @@ func parseImageName(imageNameWithTag string) (string, string) {
59115 return imageNameWithTag , "latest"
60116}
61117
118+ // Helper function to build tagged image name
62119func buildTaggedImageName (projectID , imageName , imageTag string ) string {
120+ // If image already contains registry info, use as-is
63121 if strings .Contains (imageName , "gcr.io" ) || strings .Contains (imageName , "docker.pkg.dev" ) {
64122 return fmt .Sprintf ("%s:%s" , imageName , imageTag )
65123 }
124+ // Otherwise prepend gcr.io registry
66125 return fmt .Sprintf ("gcr.io/%s/%s:%s" , projectID , imageName , imageTag )
67126}
68127
128+ // Helper function to build GCR console link
69129func buildGCRLink (projectID , imageName string ) string {
70130 registryType := "gcr"
71131 if strings .Contains (imageName , "docker.pkg.dev" ) {
@@ -76,95 +136,81 @@ func buildGCRLink(projectID, imageName string) string {
76136}
77137
78138func PushImageToGCR (projectID , imageNameWithTag string ) error {
79- logger := NewMinimalLogger ()
139+ logger := NewFancyLogger ()
80140 ctx := context .Background ()
81141
82- // Initial setup
83- logger .logStep ("Starting image push to GCR" )
142+ logger .logHeader ("Starting GCR image push" )
84143 defer func () {
85- logger .logStep ("Push operation completed" )
144+ logger .logHeader ("Push operation completed" )
86145 }()
87146
88147 // Authentication
89- logger .logStep ("Authenticating with Google Cloud" )
90148 creds , err := google .FindDefaultCredentials (ctx , "https://www.googleapis.com/auth/cloud-platform" )
91149 if err != nil {
92- return fmt .Errorf ("authentication failed: %v" , err )
150+ return fmt .Errorf ("%sauthentication failed%s : %v" , colorRed , colorReset , err )
93151 }
94152 logger .logSuccess ("Google Cloud authenticated" )
95153
96- logger .logStep ("Obtaining access token" )
97154 token , err := creds .TokenSource .Token ()
98155 if err != nil {
99- return fmt .Errorf ("token acquisition failed: %v" , err )
156+ return fmt .Errorf ("%stoken acquisition failed%s : %v" , colorRed , colorReset , err )
100157 }
101158
102159 // Docker client
103- logger .logStep ("Creating Docker client" )
104160 dockerClient , err := client .NewClientWithOpts (client .FromEnv , client .WithAPIVersionNegotiation ())
105161 if err != nil {
106- return fmt .Errorf ("docker client creation failed: %v" , err )
162+ return fmt .Errorf ("%sdocker client creation failed%s : %v" , colorRed , colorReset , err )
107163 }
108164
109165 // Image tagging
110166 imageName , imageTag := parseImageName (imageNameWithTag )
111167 taggedImage := buildTaggedImageName (projectID , imageName , imageTag )
112168
113- logger .logStep (fmt .Sprintf ("Tagging image as: %s" , taggedImage ))
114169 if err := dockerClient .ImageTag (ctx , fmt .Sprintf ("%s:%s" , imageName , imageTag ), taggedImage ); err != nil {
115- return fmt .Errorf ("image tagging failed: %v" , err )
170+ return fmt .Errorf ("%simage tagging failed%s : %v" , colorRed , colorReset , err )
116171 }
117- logger .logSuccess ("Image successfully tagged" )
172+ logger .logSuccess (fmt . Sprintf ( "Image tagged: %s%s%s" , colorCyan , taggedImage , colorReset ) )
118173
119174 // Image push
120- logger .logStep ("Preparing to push image" )
121175 authConfig := registry.AuthConfig {
122176 Username : "oauth2accesstoken" ,
123177 Password : token .AccessToken ,
124178 ServerAddress : "https://gcr.io" ,
125179 }
126180 encodedAuth , err := encodeAuthToBase64 (authConfig )
127181 if err != nil {
128- return fmt .Errorf ("auth encoding failed: %v" , err )
182+ return fmt .Errorf ("%sauth encoding failed%s : %v" , colorRed , colorReset , err )
129183 }
130184
131185 pushResponse , err := dockerClient .ImagePush (ctx , taggedImage , image.PushOptions {
132186 RegistryAuth : encodedAuth ,
133187 })
134188 if err != nil {
135- return fmt .Errorf ("image push failed: %v" , err )
189+ return fmt .Errorf ("%spush failed%s : %v" , colorRed , colorReset , err )
136190 }
137191 defer pushResponse .Close ()
138192
139- // Minimal push progress logging
140- logger .logStep ( "Starting image push " )
193+ // Fancy progress logging
194+ logger .logHeader ( "Pushing image layers " )
141195 dec := json .NewDecoder (pushResponse )
142196 for {
143197 var event jsonmessage.JSONMessage
144198 if err := dec .Decode (& event ); err != nil {
145199 if err == io .EOF {
146200 break
147201 }
148- return fmt .Errorf ("push response read failed: %v" , err )
202+ return fmt .Errorf ("%spush response failed%s : %v" , colorRed , colorReset , err )
149203 }
150204 if event .Error != nil {
151- return fmt .Errorf ("push failed: %v" , event .Error )
205+ return fmt .Errorf ("%spush failed%s : %v" , colorRed , colorReset , event .Error )
152206 }
153207
154- if event .ID != "" && event .Status != "" {
155- current := int64 (0 )
156- total := int64 (0 )
157- if event .Progress != nil {
158- current = event .Progress .Current
159- total = event .Progress .Total
160- }
161-
162- // Only log significant events
163- if event .ID != logger .lastLayer ||
164- (total > 0 && (current == 0 || current == total || current % (total / 10 ) == 0 )) {
165- logger .logProgress (event .ID , event .Status , current , total )
166- logger .lastLayer = event .ID
208+ if event .ID != "" {
209+ percent := 0.0
210+ if event .Progress != nil && event .Progress .Total > 0 {
211+ percent = float64 (event .Progress .Current ) / float64 (event .Progress .Total ) * 100
167212 }
213+ logger .logProgress (event .ID , event .Status , percent )
168214 }
169215 }
170216
@@ -177,9 +223,9 @@ func PushImageToGCR(projectID, imageNameWithTag string) error {
177223 }
178224 }
179225
180- logger .logFinal (fmt .Sprintf ("Image successfully pushed to GCR " ))
181- logger .logFinal (fmt .Sprintf ("View in console: %s" , link ))
182- logger .logFinal (fmt .Sprintf ("Image reference: %s" , taggedImage ))
226+ logger .logFinal (fmt .Sprintf ("Image pushed successfully! " ))
227+ logger .logFinal (fmt .Sprintf ("View in console: %s%s%s " , colorCyan , link , colorReset ))
228+ logger .logFinal (fmt .Sprintf ("Image reference: %s%s%s " , colorCyan , taggedImage , colorReset ))
183229
184230 return nil
185231}
0 commit comments