detached 12b4b0f

Refactor and enhance push command and various bug fixes

- Added progress bar for image upload using github.com/cheggaaa/pb
- Refactored HTTP request handling to support streaming
- Enhanced image name and size validation
- Refactored PostPush to streamline image validation and storage
- Update UploadImagePOSTHandler to remove image from storage after import
- Minor HTML and CSS adjustments for better UI consistency
- Standardized error messages in Docker package
- Fix ExportImageFromEngine not returning metadata
- Rewrite GetImageInfo, GetImageSize, and GetImageTag methods
commit
12b4b0fae334f2cb02226780fa9abc411e5a3562
author
bnema · · 2023-10-31 18:37 UTC
parent
16c3320
signature
unsigned
17 files · +262 -171
M .gitignore +2 -1
@@ -10,4 +10,5 @@ bun.lockb
1010 main
1111 gordon
1212 dist/
13-data/⏎̸
13+data/
14+setup.sh⏎̸
@@ -10,4 +10,5 @@ bun.lockb
10 main10 main
11 gordon11 gordon
12 dist/12 dist/
13-data/⏎̸13+data/
14+setup.sh⏎̸
M .goreleaser.yaml +2 -0
@@ -12,6 +12,8 @@ builds:
1212 - arm64
1313 env:
1414 - CGO_ENABLED=0
15+ ldflags:
16+ - -s -w -X main.build={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}
1517
1618 dockers:
1719 - image_templates:
@@ -12,6 +12,8 @@ builds:
12 - arm6412 - arm64
13 env:13 env:
14 - CGO_ENABLED=014 - CGO_ENABLED=0
15+ ldflags:
16+ - -s -w -X main.build={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}
15 17
16 dockers:18 dockers:
17 - image_templates:19 - image_templates:
M go.mod +3 -0
@@ -4,6 +4,7 @@ go 1.21.3
44
55 require (
66 github.com/PuerkitoBio/goquery v1.8.1
7+ github.com/cheggaaa/pb v1.0.29
78 github.com/docker/docker v24.0.6+incompatible
89 github.com/docker/go-connections v0.4.0
910 github.com/gorilla/sessions v1.2.1
@@ -21,8 +22,10 @@ require (
2122 github.com/inconshreveable/mousetrap v1.1.0 // indirect
2223 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
2324 github.com/kr/text v0.2.0 // indirect
25+ github.com/mattn/go-runewidth v0.0.15 // indirect
2426 github.com/mattn/go-sqlite3 v1.14.17 // indirect
2527 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
28+ github.com/rivo/uniseg v0.4.4 // indirect
2629 github.com/spf13/pflag v1.0.5 // indirect
2730 github.com/stretchr/testify v1.8.2 // indirect
2831 lukechampine.com/uint128 v1.3.0 // indirect
@@ -4,6 +4,7 @@ go 1.21.3
4 4
5 require (5 require (
6 github.com/PuerkitoBio/goquery v1.8.16 github.com/PuerkitoBio/goquery v1.8.1
7+ github.com/cheggaaa/pb v1.0.29
7 github.com/docker/docker v24.0.6+incompatible8 github.com/docker/docker v24.0.6+incompatible
8 github.com/docker/go-connections v0.4.09 github.com/docker/go-connections v0.4.0
9 github.com/gorilla/sessions v1.2.110 github.com/gorilla/sessions v1.2.1
@@ -21,8 +22,10 @@ require (
21 github.com/inconshreveable/mousetrap v1.1.0 // indirect22 github.com/inconshreveable/mousetrap v1.1.0 // indirect
22 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect23 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
23 github.com/kr/text v0.2.0 // indirect24 github.com/kr/text v0.2.0 // indirect
25+ github.com/mattn/go-runewidth v0.0.15 // indirect
24 github.com/mattn/go-sqlite3 v1.14.17 // indirect26 github.com/mattn/go-sqlite3 v1.14.17 // indirect
25 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect27 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
28+ github.com/rivo/uniseg v0.4.4 // indirect
26 github.com/spf13/pflag v1.0.5 // indirect29 github.com/spf13/pflag v1.0.5 // indirect
27 github.com/stretchr/testify v1.8.2 // indirect30 github.com/stretchr/testify v1.8.2 // indirect
28 lukechampine.com/uint128 v1.3.0 // indirect31 lukechampine.com/uint128 v1.3.0 // indirect
M go.sum +15 -0
@@ -8,6 +8,8 @@ github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x0
88 github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
99 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
1010 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
11+github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo=
12+github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30=
1113 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
1214 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
1315 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -23,6 +25,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
2325 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
2426 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
2527 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
28+github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
29+github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
2630 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
2731 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
2832 github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
@@ -59,13 +63,19 @@ github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUU
5963 github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ=
6064 github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
6165 github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
66+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
6267 github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
6368 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
6469 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
70+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
71+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
6572 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
6673 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
6774 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
6875 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
76+github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
77+github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
78+github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
6979 github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
7080 github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
7181 github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg=
@@ -84,6 +94,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
8494 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
8595 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
8696 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
97+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
98+github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
99+github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
87100 github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
88101 github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
89102 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -135,7 +148,9 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
135148 golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
136149 golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
137150 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
151+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
138152 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
153+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
139154 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
140155 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
141156 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -8,6 +8,8 @@ github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x0
8 github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=8 github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
9 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=9 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
10 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=10 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
11+github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo=
12+github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30=
11 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=13 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
12 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=14 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
13 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=15 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -23,6 +25,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
23 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=25 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
24 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=26 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
25 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=27 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
28+github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
29+github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
26 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=30 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
27 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=31 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
28 github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=32 github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
@@ -59,13 +63,19 @@ github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUU
59 github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ=63 github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ=
60 github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=64 github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
61 github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=65 github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
66+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
62 github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=67 github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
63 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=68 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
64 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=69 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
70+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
71+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
65 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=72 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
66 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=73 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
67 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=74 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
68 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=75 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
76+github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
77+github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
78+github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
69 github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=79 github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
70 github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=80 github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
71 github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg=81 github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg=
@@ -84,6 +94,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
84 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=94 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
85 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=95 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
86 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=96 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
97+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
98+github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
99+github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
87 github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=100 github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
88 github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=101 github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
89 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=102 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -135,7 +148,9 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
135 golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=148 golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
136 golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=149 golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
137 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=150 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
151+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
138 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=152 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
153+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
139 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=154 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
140 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=155 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
141 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=156 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
M internal/cli/cmd/push.go +42 -24
@@ -3,34 +3,20 @@ package cmd
33 import (
44 "fmt"
55 "os"
6+ "regexp"
67
78 "github.com/bnema/gordon/internal/cli"
89 "github.com/bnema/gordon/internal/cli/handler"
910 "github.com/bnema/gordon/internal/common"
1011 "github.com/bnema/gordon/pkg/docker"
12+ "github.com/cheggaaa/pb"
1113 "github.com/spf13/cobra"
1214 )
1315
14-// the push command is used to push a container image to gordon's endpoint
15-
16-// Pseudo code of the steps for the push command
17-
18-// 1. Extract the container image as .tar and store it in a temporary directory
19-
20-// 2. Prepare a payload with the .tar as a byte array, the image name and tag (if any)
21-
22-// 3. append the payload to the request body with the type "push" and the token
23-
24-// 4. Send the request to the backend
25-
26-// 5. If the response is 200, print the success message
27-
2816 func NewPushCommand(a *cli.App) *cobra.Command {
2917 var port string
3018 var targetDomain string
3119
32- // handler.FieldCheck(a)
33-
3420 pushCmd := &cobra.Command{
3521 Use: "push [image:tag]",
3622 Short: "Push an image to the server, if no tag is specified, latest is used",
@@ -44,32 +30,64 @@ func NewPushCommand(a *cli.App) *cobra.Command {
4430 Run: func(cmd *cobra.Command, args []string) {
4531 imageName := args[0]
4632 fmt.Println("Exporting image:", imageName)
47- // Export the image using Docker client
48- data, err := docker.ExportImageFromEngine(imageName)
33+
34+ // Check if the image name is valid or if a tag is specified
35+ match, _ := regexp.MatchString(`^([a-zA-Z0-9\-_.]+\/)?[a-zA-Z0-9\-_.]+(:[a-zA-Z0-9\-_.]+)?$`, imageName)
36+ if !match {
37+ fmt.Println("Invalid image name or no tag specified")
38+ return
39+ }
40+
41+ imageID, err := docker.GetImageID(imageName)
42+ if err != nil {
43+ fmt.Println("Error getting image ID:", err)
44+ return
45+ }
46+
47+ totalSize, err := docker.GetImageSize(imageID)
48+ if err != nil {
49+ fmt.Println("Error estimating image size:", err)
50+ return
51+ }
52+
53+ // Create a new progress bar
54+ bar := pb.New64(totalSize).SetUnits(pb.U_BYTES)
55+ reader, err := docker.ExportImageFromEngine(imageID)
4956 if err != nil {
5057 fmt.Println("Error exporting image:", err)
5158 return
5259 }
53- fmt.Println("Image exported successfully")
60+ // Wrap the original reader with progress bar reader
61+ progressReader := bar.NewProxyReader(reader)
62+
5463 // Create a RequestPayload and populate it
55- reqPayload := common.RequestPayload{ // <- TODO : Load RequestPayLoad from cli.App
64+ reqPayload := common.RequestPayload{
5665 Type: "push",
57- Payload: common.PushPayload{ // <- Same
66+ Payload: common.PushPayload{
5867 Ports: port,
5968 TargetDomain: targetDomain,
6069 ImageName: imageName,
61- Data: data,
70+ Data: progressReader,
6271 },
6372 }
64- fmt.Println("Sending image to backend")
73+
74+ // Start the progress bar
75+ bar.Start()
6576 // Send the request to the backend
6677 resp, err := handler.SendHTTPRequest(a, &reqPayload, "POST", "/push")
6778 if err != nil {
6879 fmt.Println("Error sending HTTP request:", err)
6980 return
7081 }
82+ // Stop the progress bar
83+ bar.Finish()
84+
85+ if resp.StatusCode != 200 {
86+ fmt.Println("Unexpected status code:", resp.StatusCode)
87+ return
88+ }
7189
72- fmt.Print(string(resp.Body))
90+ fmt.Println(resp.Body)
7391 },
7492 }
7593
@@ -3,34 +3,20 @@ package cmd
3 import (3 import (
4 "fmt"4 "fmt"
5 "os"5 "os"
6+ "regexp"
6 7
7 "github.com/bnema/gordon/internal/cli"8 "github.com/bnema/gordon/internal/cli"
8 "github.com/bnema/gordon/internal/cli/handler"9 "github.com/bnema/gordon/internal/cli/handler"
9 "github.com/bnema/gordon/internal/common"10 "github.com/bnema/gordon/internal/common"
10 "github.com/bnema/gordon/pkg/docker"11 "github.com/bnema/gordon/pkg/docker"
12+ "github.com/cheggaaa/pb"
11 "github.com/spf13/cobra"13 "github.com/spf13/cobra"
12 )14 )
13 15
14-// the push command is used to push a container image to gordon's endpoint
15-
16-// Pseudo code of the steps for the push command
17-
18-// 1. Extract the container image as .tar and store it in a temporary directory
19-
20-// 2. Prepare a payload with the .tar as a byte array, the image name and tag (if any)
21-
22-// 3. append the payload to the request body with the type "push" and the token
23-
24-// 4. Send the request to the backend
25-
26-// 5. If the response is 200, print the success message
27-
28 func NewPushCommand(a *cli.App) *cobra.Command {16 func NewPushCommand(a *cli.App) *cobra.Command {
29 var port string17 var port string
30 var targetDomain string18 var targetDomain string
31 19
32- // handler.FieldCheck(a)
33-
34 pushCmd := &cobra.Command{20 pushCmd := &cobra.Command{
35 Use: "push [image:tag]",21 Use: "push [image:tag]",
36 Short: "Push an image to the server, if no tag is specified, latest is used",22 Short: "Push an image to the server, if no tag is specified, latest is used",
@@ -44,32 +30,64 @@ func NewPushCommand(a *cli.App) *cobra.Command {
44 Run: func(cmd *cobra.Command, args []string) {30 Run: func(cmd *cobra.Command, args []string) {
45 imageName := args[0]31 imageName := args[0]
46 fmt.Println("Exporting image:", imageName)32 fmt.Println("Exporting image:", imageName)
47- // Export the image using Docker client33+
48- data, err := docker.ExportImageFromEngine(imageName)34+ // Check if the image name is valid or if a tag is specified
35+ match, _ := regexp.MatchString(`^([a-zA-Z0-9\-_.]+\/)?[a-zA-Z0-9\-_.]+(:[a-zA-Z0-9\-_.]+)?$`, imageName)
36+ if !match {
37+ fmt.Println("Invalid image name or no tag specified")
38+ return
39+ }
40+
41+ imageID, err := docker.GetImageID(imageName)
42+ if err != nil {
43+ fmt.Println("Error getting image ID:", err)
44+ return
45+ }
46+
47+ totalSize, err := docker.GetImageSize(imageID)
48+ if err != nil {
49+ fmt.Println("Error estimating image size:", err)
50+ return
51+ }
52+
53+ // Create a new progress bar
54+ bar := pb.New64(totalSize).SetUnits(pb.U_BYTES)
55+ reader, err := docker.ExportImageFromEngine(imageID)
49 if err != nil {56 if err != nil {
50 fmt.Println("Error exporting image:", err)57 fmt.Println("Error exporting image:", err)
51 return58 return
52 }59 }
53- fmt.Println("Image exported successfully")60+ // Wrap the original reader with progress bar reader
61+ progressReader := bar.NewProxyReader(reader)
62+
54 // Create a RequestPayload and populate it63 // Create a RequestPayload and populate it
55- reqPayload := common.RequestPayload{ // <- TODO : Load RequestPayLoad from cli.App64+ reqPayload := common.RequestPayload{
56 Type: "push",65 Type: "push",
57- Payload: common.PushPayload{ // <- Same66+ Payload: common.PushPayload{
58 Ports: port,67 Ports: port,
59 TargetDomain: targetDomain,68 TargetDomain: targetDomain,
60 ImageName: imageName,69 ImageName: imageName,
61- Data: data,70+ Data: progressReader,
62 },71 },
63 }72 }
64- fmt.Println("Sending image to backend")73+
74+ // Start the progress bar
75+ bar.Start()
65 // Send the request to the backend76 // Send the request to the backend
66 resp, err := handler.SendHTTPRequest(a, &reqPayload, "POST", "/push")77 resp, err := handler.SendHTTPRequest(a, &reqPayload, "POST", "/push")
67 if err != nil {78 if err != nil {
68 fmt.Println("Error sending HTTP request:", err)79 fmt.Println("Error sending HTTP request:", err)
69 return80 return
70 }81 }
82+ // Stop the progress bar
83+ bar.Finish()
84+
85+ if resp.StatusCode != 200 {
86+ fmt.Println("Unexpected status code:", resp.StatusCode)
87+ return
88+ }
71 89
72- fmt.Print(string(resp.Body))90+ fmt.Println(resp.Body)
73 },91 },
74 }92 }
75 93
M internal/cli/handler/http.go +46 -13
@@ -23,8 +23,7 @@ func CreateNewRequest(method, url string, body []byte) (*http.Request, error) {
2323 }
2424
2525 // SetRequestHeaders sets the headers for the request
26-func SetRequestHeaders(req *http.Request, token string) {
27- req.Header.Set("Content-Type", "application/json")
26+func setAuthRequestHeaders(req *http.Request, token string) {
2827 req.Header.Set("Authorization", "Bearer "+token)
2928 }
3029
@@ -35,7 +34,8 @@ func SendRequest(req *http.Request) (*http.Response, error) {
3534 }
3635
3736 type Response struct {
38- Body []byte
37+ Body []byte
38+ StatusCode int
3939 }
4040
4141 // SendHTTPRequest sends the HTTP request
@@ -43,29 +43,62 @@ func SendHTTPRequest(a *cli.App, rp *common.RequestPayload, method string, endpo
4343 apiUrl := a.Config.Http.BackendURL + "/api"
4444 token := a.Config.General.Token
4545
46- fmt.Printf("Final url:", apiUrl+endpoint)
46+ var req *http.Request
47+ var err error
48+
49+ // Handle push type payload
50+ if rp.Type == "push" {
51+ pushPayload, ok := rp.Payload.(common.PushPayload)
52+ if !ok {
53+ return nil, fmt.Errorf("invalid payload type for push")
54+ }
55+
56+ req, err = http.NewRequest(method, apiUrl+endpoint, pushPayload.Data)
57+ if err != nil {
58+ return nil, fmt.Errorf("failed to create new streaming request: %w", err)
59+ }
4760
48- // Prepare the entire RequestPayload, not just the inner Payload
61+ setAuthRequestHeaders(req, token)
62+ req.Header.Set("Content-Type", "application/octet-stream")
63+ req.Header.Set("X-Ports", pushPayload.Ports)
64+ req.Header.Set("X-Target-Domain", pushPayload.TargetDomain)
65+ req.Header.Set("X-Image-Name", pushPayload.ImageName)
66+
67+ client := &http.Client{}
68+ resp, err := client.Do(req)
69+ if err != nil {
70+ return nil, fmt.Errorf("failed to send request: %w", err)
71+ }
72+ defer resp.Body.Close()
73+
74+ body, err := io.ReadAll(resp.Body)
75+ if err != nil {
76+ return nil, fmt.Errorf("failed to read response body: %w", err)
77+ }
78+ return &Response{Body: body}, nil
79+ }
80+
81+ // Handle other types of payloads
4982 jsonPayload, err := json.Marshal(rp)
5083 if err != nil {
5184 return nil, fmt.Errorf("failed to marshal payload: %w", err)
5285 }
53- // Create a new request
54- req, err := CreateNewRequest(method, apiUrl+endpoint, jsonPayload)
86+
87+ req, err = http.NewRequest(method, apiUrl+endpoint, bytes.NewBuffer(jsonPayload))
5588 if err != nil {
56- return nil, fmt.Errorf("failed to create new request: %w", err)
89+ return nil, fmt.Errorf("failed to create new JSON request: %w", err)
5790 }
58- // Set the request headers
59- SetRequestHeaders(req, token)
6091
61- // Send the request
62- resp, err := SendRequest(req)
92+ setAuthRequestHeaders(req, token)
93+ req.Header.Set("Content-Type", "application/json")
94+
95+ client := &http.Client{}
96+ resp, err := client.Do(req)
6397 if err != nil {
6498 return nil, fmt.Errorf("failed to send request: %w", err)
6599 }
66100 defer resp.Body.Close()
67101
68- // Read the response body
69102 body, err := io.ReadAll(resp.Body)
70103 if err != nil {
71104 return nil, fmt.Errorf("failed to read response body: %w", err)
@@ -23,8 +23,7 @@ func CreateNewRequest(method, url string, body []byte) (*http.Request, error) {
23 }23 }
24 24
25 // SetRequestHeaders sets the headers for the request25 // SetRequestHeaders sets the headers for the request
26-func SetRequestHeaders(req *http.Request, token string) {26+func setAuthRequestHeaders(req *http.Request, token string) {
27- req.Header.Set("Content-Type", "application/json")
28 req.Header.Set("Authorization", "Bearer "+token)27 req.Header.Set("Authorization", "Bearer "+token)
29 }28 }
30 29
@@ -35,7 +34,8 @@ func SendRequest(req *http.Request) (*http.Response, error) {
35 }34 }
36 35
37 type Response struct {36 type Response struct {
38- Body []byte37+ Body []byte
38+ StatusCode int
39 }39 }
40 40
41 // SendHTTPRequest sends the HTTP request41 // SendHTTPRequest sends the HTTP request
@@ -43,29 +43,62 @@ func SendHTTPRequest(a *cli.App, rp *common.RequestPayload, method string, endpo
43 apiUrl := a.Config.Http.BackendURL + "/api"43 apiUrl := a.Config.Http.BackendURL + "/api"
44 token := a.Config.General.Token44 token := a.Config.General.Token
45 45
46- fmt.Printf("Final url:", apiUrl+endpoint)46+ var req *http.Request
47+ var err error
48+
49+ // Handle push type payload
50+ if rp.Type == "push" {
51+ pushPayload, ok := rp.Payload.(common.PushPayload)
52+ if !ok {
53+ return nil, fmt.Errorf("invalid payload type for push")
54+ }
55+
56+ req, err = http.NewRequest(method, apiUrl+endpoint, pushPayload.Data)
57+ if err != nil {
58+ return nil, fmt.Errorf("failed to create new streaming request: %w", err)
59+ }
47 60
48- // Prepare the entire RequestPayload, not just the inner Payload61+ setAuthRequestHeaders(req, token)
62+ req.Header.Set("Content-Type", "application/octet-stream")
63+ req.Header.Set("X-Ports", pushPayload.Ports)
64+ req.Header.Set("X-Target-Domain", pushPayload.TargetDomain)
65+ req.Header.Set("X-Image-Name", pushPayload.ImageName)
66+
67+ client := &http.Client{}
68+ resp, err := client.Do(req)
69+ if err != nil {
70+ return nil, fmt.Errorf("failed to send request: %w", err)
71+ }
72+ defer resp.Body.Close()
73+
74+ body, err := io.ReadAll(resp.Body)
75+ if err != nil {
76+ return nil, fmt.Errorf("failed to read response body: %w", err)
77+ }
78+ return &Response{Body: body}, nil
79+ }
80+
81+ // Handle other types of payloads
49 jsonPayload, err := json.Marshal(rp)82 jsonPayload, err := json.Marshal(rp)
50 if err != nil {83 if err != nil {
51 return nil, fmt.Errorf("failed to marshal payload: %w", err)84 return nil, fmt.Errorf("failed to marshal payload: %w", err)
52 }85 }
53- // Create a new request86+
54- req, err := CreateNewRequest(method, apiUrl+endpoint, jsonPayload)87+ req, err = http.NewRequest(method, apiUrl+endpoint, bytes.NewBuffer(jsonPayload))
55 if err != nil {88 if err != nil {
56- return nil, fmt.Errorf("failed to create new request: %w", err)89+ return nil, fmt.Errorf("failed to create new JSON request: %w", err)
57 }90 }
58- // Set the request headers
59- SetRequestHeaders(req, token)
60 91
61- // Send the request92+ setAuthRequestHeaders(req, token)
62- resp, err := SendRequest(req)93+ req.Header.Set("Content-Type", "application/json")
94+
95+ client := &http.Client{}
96+ resp, err := client.Do(req)
63 if err != nil {97 if err != nil {
64 return nil, fmt.Errorf("failed to send request: %w", err)98 return nil, fmt.Errorf("failed to send request: %w", err)
65 }99 }
66 defer resp.Body.Close()100 defer resp.Body.Close()
67 101
68- // Read the response body
69 body, err := io.ReadAll(resp.Body)102 body, err := io.ReadAll(resp.Body)
70 if err != nil {103 if err != nil {
71 return nil, fmt.Errorf("failed to read response body: %w", err)104 return nil, fmt.Errorf("failed to read response body: %w", err)
M internal/common/payload.go +3 -1
@@ -3,6 +3,8 @@ package common
33 import (
44 "encoding/json"
55 "fmt"
6+
7+ "io"
68 )
79
810 type RequestPayload struct {
@@ -71,5 +73,5 @@ type PushPayload struct {
7173 Ports string `json:"ports"`
7274 TargetDomain string `json:"targetdomain"`
7375 ImageName string `json:"imagename"`
74- Data []byte `json:"data"`
76+ Data io.ReadCloser
7577 }
@@ -3,6 +3,8 @@ package common
3 import (3 import (
4 "encoding/json"4 "encoding/json"
5 "fmt"5 "fmt"
6+
7+ "io"
6 )8 )
7 9
8 type RequestPayload struct {10 type RequestPayload struct {
@@ -71,5 +73,5 @@ type PushPayload struct {
71 Ports string `json:"ports"`73 Ports string `json:"ports"`
72 TargetDomain string `json:"targetdomain"`74 TargetDomain string `json:"targetdomain"`
73 ImageName string `json:"imagename"`75 ImageName string `json:"imagename"`
74- Data []byte `json:"data"`76+ Data io.ReadCloser
75 }77 }
M internal/httpserve/handler/clientendpoins.go +46 -41
@@ -5,6 +5,7 @@ import (
55 "fmt"
66 "io"
77 "net/http"
8+ "regexp"
89
910 "github.com/bnema/gordon/internal/common"
1011 "github.com/bnema/gordon/internal/server"
@@ -60,64 +61,68 @@ func GetInfos(c echo.Context, a *server.App) error {
6061 }
6162
6263 func PostPush(c echo.Context, a *server.App) error {
63- body, _ := io.ReadAll(c.Request().Body)
64- c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // Reset the body
6564
66- payload := new(common.RequestPayload)
67- if err := c.Bind(payload); err != nil {
68- fmt.Println("Bind Error:", err)
69- return c.JSON(http.StatusBadRequest, err.Error())
65+ // Initialize pushPayload object
66+ payload := &common.PushPayload{
67+ Ports: c.Request().Header.Get("X-Ports"),
68+ ImageName: c.Request().Header.Get("X-Image-Name"),
69+ TargetDomain: c.Request().Header.Get("X-Target-Domain"),
7070 }
7171
72- if payload.Type != "push" {
73- return c.JSON(http.StatusBadRequest, "Invalid payload type")
72+ if payload.ImageName == "" {
73+ return c.JSON(http.StatusBadRequest, "Invalid image name")
7474 }
7575
76- pushPayload, ok := payload.Payload.(common.PushPayload)
77- if !ok {
78- return c.JSON(http.StatusBadRequest, "Invalid payload type")
79- }
76+ imageReader := c.Request().Body
77+ defer imageReader.Close()
8078
81- // Push payload contains Ports as a string, Image as string (image:tag), and the .tar image as a byte array
82- // Validate the ports
83- ports := pushPayload.Ports
84- if ports == "" {
85- return c.JSON(http.StatusBadRequest, "Ports cannot be empty")
86- }
87-
88- // Validate the target domain
89- domain := pushPayload.TargetDomain
90- if domain == "" {
91- return c.JSON(http.StatusBadRequest, "Image cannot be empty")
92- }
79+ // Rename the image to a valid name so that it can be saved (remove user/, :tag and add .tar)
80+ imageFileName := payload.ImageName
81+ imageFileName = regexp.MustCompile(`^([a-zA-Z0-9\-_.]+\/)?`).ReplaceAllString(imageFileName, "")
82+ imageFileName = regexp.MustCompile(`(:[a-zA-Z0-9\-_.]+)?$`).ReplaceAllString(imageFileName, "")
83+ imageFileName = imageFileName + ".tar"
9384
94- // Validate the image tar
95- imageTar := pushPayload.Data
96- if imageTar == nil {
97- return c.JSON(http.StatusBadRequest, "Image tar cannot be empty")
85+ // Save the image tar in the storage
86+ imageFilePath, err := store.SaveImageToStorage(&a.Config, imageFileName, imageReader)
87+ if err != nil {
88+ return err
9889 }
9990
100- imageName := pushPayload.ImageName
101- if imageName == "" {
102- return c.JSON(http.StatusBadRequest, "Image name cannot be empty")
91+ // Import the tar in docker
92+ err = docker.ImportImageToEngine(imageFilePath)
93+ if err != nil {
94+ return err
10395 }
10496
105- // 1 - Save the image tar in the storage
106- imagePath, err := store.SaveImageToStorage(&a.Config, imageName, bytes.NewBuffer(imageTar))
97+ // Remove the image from the storage
98+ err = store.RemoveFromStorage()
10799 if err != nil {
108- return c.JSON(http.StatusBadRequest, err.Error())
100+ return err
109101 }
110- // 2 - Import the tar in docker
111- imageID, err := docker.ImportImageToEngine(imagePath)
102+
103+ // append localhost to the image name since it's a manually imported image and not from a registry
104+ payload.ImageName = "localhost/" + payload.ImageName
105+
106+ // Get the image ID
107+ imageID, err := docker.GetImageID(payload.ImageName)
112108 if err != nil {
113- return c.JSON(http.StatusBadRequest, err.Error())
109+ return err
114110 }
115111
116- fmt.Println("Import successful, ImageID:", imageID)
112+ // Print the imageID short version
113+ fmt.Println("Image ID:", imageID[:12])
117114
118- // 3 - Create the container with traefik labels
115+ // // Create the container with traefik labels
116+ // err = addTraefikLabels(payload)
117+ // if err != nil {
118+ // return err
119+ // }
119120
120- // 4 - Start the container
121+ // // Start the container
122+ // err = docker.StartContainer(imageID)
123+ // if err != nil {
124+ // return err
125+ // }
121126
122- return c.JSON(http.StatusOK, "OK")
127+ return c.JSON(http.StatusOK, "Image pushed successfully")
123128 }
@@ -5,6 +5,7 @@ import (
5 "fmt"5 "fmt"
6 "io"6 "io"
7 "net/http"7 "net/http"
8+ "regexp"
8 9
9 "github.com/bnema/gordon/internal/common"10 "github.com/bnema/gordon/internal/common"
10 "github.com/bnema/gordon/internal/server"11 "github.com/bnema/gordon/internal/server"
@@ -60,64 +61,68 @@ func GetInfos(c echo.Context, a *server.App) error {
60 }61 }
61 62
62 func PostPush(c echo.Context, a *server.App) error {63 func PostPush(c echo.Context, a *server.App) error {
63- body, _ := io.ReadAll(c.Request().Body)
64- c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // Reset the body
65 64
66- payload := new(common.RequestPayload)65+ // Initialize pushPayload object
67- if err := c.Bind(payload); err != nil {66+ payload := &common.PushPayload{
68- fmt.Println("Bind Error:", err)67+ Ports: c.Request().Header.Get("X-Ports"),
69- return c.JSON(http.StatusBadRequest, err.Error())68+ ImageName: c.Request().Header.Get("X-Image-Name"),
69+ TargetDomain: c.Request().Header.Get("X-Target-Domain"),
70 }70 }
71 71
72- if payload.Type != "push" {72+ if payload.ImageName == "" {
73- return c.JSON(http.StatusBadRequest, "Invalid payload type")73+ return c.JSON(http.StatusBadRequest, "Invalid image name")
74 }74 }
75 75
76- pushPayload, ok := payload.Payload.(common.PushPayload)76+ imageReader := c.Request().Body
77- if !ok {77+ defer imageReader.Close()
78- return c.JSON(http.StatusBadRequest, "Invalid payload type")
79- }
80 78
81- // Push payload contains Ports as a string, Image as string (image:tag), and the .tar image as a byte array79+ // Rename the image to a valid name so that it can be saved (remove user/, :tag and add .tar)
82- // Validate the ports80+ imageFileName := payload.ImageName
83- ports := pushPayload.Ports81+ imageFileName = regexp.MustCompile(`^([a-zA-Z0-9\-_.]+\/)?`).ReplaceAllString(imageFileName, "")
84- if ports == "" {82+ imageFileName = regexp.MustCompile(`(:[a-zA-Z0-9\-_.]+)?$`).ReplaceAllString(imageFileName, "")
85- return c.JSON(http.StatusBadRequest, "Ports cannot be empty")83+ imageFileName = imageFileName + ".tar"
86- }
87-
88- // Validate the target domain
89- domain := pushPayload.TargetDomain
90- if domain == "" {
91- return c.JSON(http.StatusBadRequest, "Image cannot be empty")
92- }
93 84
94- // Validate the image tar85+ // Save the image tar in the storage
95- imageTar := pushPayload.Data86+ imageFilePath, err := store.SaveImageToStorage(&a.Config, imageFileName, imageReader)
96- if imageTar == nil {87+ if err != nil {
97- return c.JSON(http.StatusBadRequest, "Image tar cannot be empty")88+ return err
98 }89 }
99 90
100- imageName := pushPayload.ImageName91+ // Import the tar in docker
101- if imageName == "" {92+ err = docker.ImportImageToEngine(imageFilePath)
102- return c.JSON(http.StatusBadRequest, "Image name cannot be empty")93+ if err != nil {
94+ return err
103 }95 }
104 96
105- // 1 - Save the image tar in the storage97+ // Remove the image from the storage
106- imagePath, err := store.SaveImageToStorage(&a.Config, imageName, bytes.NewBuffer(imageTar))98+ err = store.RemoveFromStorage()
107 if err != nil {99 if err != nil {
108- return c.JSON(http.StatusBadRequest, err.Error())100+ return err
109 }101 }
110- // 2 - Import the tar in docker102+
111- imageID, err := docker.ImportImageToEngine(imagePath)103+ // append localhost to the image name since it's a manually imported image and not from a registry
104+ payload.ImageName = "localhost/" + payload.ImageName
105+
106+ // Get the image ID
107+ imageID, err := docker.GetImageID(payload.ImageName)
112 if err != nil {108 if err != nil {
113- return c.JSON(http.StatusBadRequest, err.Error())109+ return err
114 }110 }
115 111
116- fmt.Println("Import successful, ImageID:", imageID)112+ // Print the imageID short version
113+ fmt.Println("Image ID:", imageID[:12])
117 114
118- // 3 - Create the container with traefik labels115+ // // Create the container with traefik labels
116+ // err = addTraefikLabels(payload)
117+ // if err != nil {
118+ // return err
119+ // }
119 120
120- // 4 - Start the container121+ // // Start the container
122+ // err = docker.StartContainer(imageID)
123+ // if err != nil {
124+ // return err
125+ // }
121 126
122- return c.JSON(http.StatusOK, "OK")127+ return c.JSON(http.StatusOK, "Image pushed successfully")
123 }128 }
M internal/httpserve/handler/uploadcontainerimage.go +6 -2
@@ -64,12 +64,16 @@ func UploadImagePOSTHandler(c echo.Context, a *server.App) error {
6464 }
6565
6666 // Import the image into Docker
67- imageId, err := docker.ImportImageToEngine(saveInPath)
67+ err = docker.ImportImageToEngine(saveInPath)
6868 if err != nil {
6969 return fmt.Errorf("failed to import image to Docker: %v", err)
7070 }
7171
72- fmt.Println("Image ID:", imageId)
72+ // Remove the image from the storage directory
73+ err = store.RemoveFromStorage()
74+ if err != nil {
75+ return sendError(c, err)
76+ }
7377
7478 return c.HTML(http.StatusOK, ActionSuccess(a))
7579 }
@@ -64,12 +64,16 @@ func UploadImagePOSTHandler(c echo.Context, a *server.App) error {
64 }64 }
65 65
66 // Import the image into Docker66 // Import the image into Docker
67- imageId, err := docker.ImportImageToEngine(saveInPath)67+ err = docker.ImportImageToEngine(saveInPath)
68 if err != nil {68 if err != nil {
69 return fmt.Errorf("failed to import image to Docker: %v", err)69 return fmt.Errorf("failed to import image to Docker: %v", err)
70 }70 }
71 71
72- fmt.Println("Image ID:", imageId)72+ // Remove the image from the storage directory
73+ err = store.RemoveFromStorage()
74+ if err != nil {
75+ return sendError(c, err)
76+ }
73 77
74 return c.HTML(http.StatusOK, ActionSuccess(a))78 return c.HTML(http.StatusOK, ActionSuccess(a))
75 }79 }
M internal/httpserve/middleware/secure.go +1 -3
@@ -44,11 +44,9 @@ func InitSessionMiddleware(a *server.App) echo.MiddlewareFunc {
4444 log.Printf("Error in session middleware: %v", err)
4545 return err
4646 }
47- sess, err := session.Get("session", c)
47+ _, err = session.Get("session", c)
4848 if err != nil {
4949 log.Printf("Could not retrieve session: %v", err)
50- } else {
51- log.Printf("Session values: %+v", sess.Values)
5250 }
5351 return nil
5452 }
@@ -44,11 +44,9 @@ func InitSessionMiddleware(a *server.App) echo.MiddlewareFunc {
44 log.Printf("Error in session middleware: %v", err)44 log.Printf("Error in session middleware: %v", err)
45 return err45 return err
46 }46 }
47- sess, err := session.Get("session", c)47+ _, err = session.Get("session", c)
48 if err != nil {48 if err != nil {
49 log.Printf("Could not retrieve session: %v", err)49 log.Printf("Could not retrieve session: %v", err)
50- } else {
51- log.Printf("Session values: %+v", sess.Values)
52 }50 }
53 return nil51 return nil
54 }52 }
M internal/templating/cmdparams/_fromcli.go +21 -10
@@ -1,23 +1,34 @@
11 package cmdparams
22
33 import (
4+ "io"
5+
46 "github.com/bnema/gordon/internal/common"
57 "github.com/bnema/gordon/pkg/docker"
68 )
79
10+type PushPayload struct {
11+ Ports string `json:"ports"`
12+ TargetDomain string `json:"targetdomain"`
13+ ImageName string `json:"imagename"`
14+ Data io.ReadCloser
15+}
16+
817 // FromYAMLStructToCmdParams converts a YAMLContainerParams struct to a ContainerCommandParams struct
9-func FromPayloadStructToCmdParams(PushPayload *common.PushPayload) (docker.ContainerCommandParams, error) {
18+func FromPayloadStructToCmdParams(ppl *common.PushPayload) (docker.ContainerCommandParams, error) {
1019
1120 params := docker.ContainerCommandParams{
12- ContainerName: yamlParams.Name,
13- ImageName: yamlParams.Image,
14- ImageID: imageID,
15- Volumes: yamlParams.Volumes,
16- Environment: yamlParams.Environment,
17- Labels: labels,
18- Network: network,
19- Restart: yamlParams.Restart,
20- PortMappings: portMappings,
21+ IsSSL: ppl.IsSSL,
22+ ContainerName: ppl.ContainerName,
23+ ServiceName: ppl.ServiceName,
24+ Domain: ppl.Domain,
25+ ImageName: ppl.ImageName,
26+ ImageID: ppl.ImageID,
27+ Restart: ppl.Restart,
28+ Volumes: ppl.Volumes,
29+ Environment: ppl.Environment,
30+ Network: ppl.Network,
31+ PortMappings: ppl.PortMappings,
2132 }
2233
2334 return params, nil
@@ -1,23 +1,34 @@
1 package cmdparams1 package cmdparams
2 2
3 import (3 import (
4+ "io"
5+
4 "github.com/bnema/gordon/internal/common"6 "github.com/bnema/gordon/internal/common"
5 "github.com/bnema/gordon/pkg/docker"7 "github.com/bnema/gordon/pkg/docker"
6 )8 )
7 9
10+type PushPayload struct {
11+ Ports string `json:"ports"`
12+ TargetDomain string `json:"targetdomain"`
13+ ImageName string `json:"imagename"`
14+ Data io.ReadCloser
15+}
16+
8 // FromYAMLStructToCmdParams converts a YAMLContainerParams struct to a ContainerCommandParams struct17 // FromYAMLStructToCmdParams converts a YAMLContainerParams struct to a ContainerCommandParams struct
9-func FromPayloadStructToCmdParams(PushPayload *common.PushPayload) (docker.ContainerCommandParams, error) {18+func FromPayloadStructToCmdParams(ppl *common.PushPayload) (docker.ContainerCommandParams, error) {
10 19
11 params := docker.ContainerCommandParams{20 params := docker.ContainerCommandParams{
12- ContainerName: yamlParams.Name,21+ IsSSL: ppl.IsSSL,
13- ImageName: yamlParams.Image,22+ ContainerName: ppl.ContainerName,
14- ImageID: imageID,23+ ServiceName: ppl.ServiceName,
15- Volumes: yamlParams.Volumes,24+ Domain: ppl.Domain,
16- Environment: yamlParams.Environment,25+ ImageName: ppl.ImageName,
17- Labels: labels,26+ ImageID: ppl.ImageID,
18- Network: network,27+ Restart: ppl.Restart,
19- Restart: yamlParams.Restart,28+ Volumes: ppl.Volumes,
20- PortMappings: portMappings,29+ Environment: ppl.Environment,
30+ Network: ppl.Network,
31+ PortMappings: ppl.PortMappings,
21 }32 }
22 33
23 return params, nil34 return params, nil
M internal/templating/models/html/fragments/header.gohtml +0 -1
@@ -10,5 +10,4 @@
1010 <script src="/libs/htmx-sse.js"></script>
1111 <script defer src="/libs/hyperscript.min.js"></script>
1212 <script defer src="/assets/js/custom.js"></script>
13- <!-- script wait for the page to be loaded and insert <link rel="stylesheet" href="/libs/iconoir.css" /> -->
1413 </head>
@@ -10,5 +10,4 @@
10 <script src="/libs/htmx-sse.js"></script>10 <script src="/libs/htmx-sse.js"></script>
11 <script defer src="/libs/hyperscript.min.js"></script>11 <script defer src="/libs/hyperscript.min.js"></script>
12 <script defer src="/assets/js/custom.js"></script>12 <script defer src="/assets/js/custom.js"></script>
13- <!-- script wait for the page to be loaded and insert <link rel="stylesheet" href="/libs/iconoir.css" /> -->
14 </head>13 </head>
M internal/templating/models/html/fragments/menu.gohtml +8 -9
@@ -1,11 +1,9 @@
11 <div
22 id="menubar"
3- class="w-full h-12 md:h-16 fixed top-0 left-0 bg-gordonmascot-lblue flex flex-row justify-evenly items-center px-4 IBMPlexSans"
4->
3+ class="w-full h-12 md:h-16 fixed top-0 left-0 bg-gordonmascot-lblue flex flex-row justify-evenly items-center px-4 IBMPlexSans">
54 <div class="w-full flex justify-start">
65 <h1
7- class="gordon-title text-3xl md:text-4xl text-gordonmascot-hblue tracking-tighter"
8- >
6+ class="gordon-title text-3xl md:text-4xl text-gordonmascot-hblue tracking-tighter">
97 Gordon
108 </h1>
119 <span
@@ -15,11 +13,10 @@
1513 </div>
1614 <div class="w-full">
1715 <ul
18- class="flex flex-row space-y-6 font-semibold text-gray-100 text-lg md:text-xl justify-center"
19- >
16+ class="flex flex-row space-y-6 font-semibold text-gray-100 text-lg md:text-xl justify-center">
2017 <li class="">
2118 <span class="JetBrainsMonoBoldNF pr-1"> </span>
22- <a href="/admin/manager">Manager</a>
19+ <a href="manager">Manager</a>
2320 </li>
2421 </ul>
2522 </div>
@@ -28,12 +25,14 @@
2825 href="/admin/settings"
2926 title="User settings"
3027 class="iconf text-gordonmascot-mblue text-lg md:text-xl"
31- ></a>
28+ ></a
29+ >
3230 <a
3331 href="/admin/logout"
3432 title="Logout"
3533 class="iconf text-gordonmascot-mblue text-lg md:text-xl"
36- >󰍃</a>
34+ >󰍃</a
35+ >
3736 </div>
3837 </div>
3938
@@ -1,11 +1,9 @@
1 <div1 <div
2 id="menubar"2 id="menubar"
3- class="w-full h-12 md:h-16 fixed top-0 left-0 bg-gordonmascot-lblue flex flex-row justify-evenly items-center px-4 IBMPlexSans"3+ class="w-full h-12 md:h-16 fixed top-0 left-0 bg-gordonmascot-lblue flex flex-row justify-evenly items-center px-4 IBMPlexSans">
4->
5 <div class="w-full flex justify-start">4 <div class="w-full flex justify-start">
6 <h15 <h1
7- class="gordon-title text-3xl md:text-4xl text-gordonmascot-hblue tracking-tighter"6+ class="gordon-title text-3xl md:text-4xl text-gordonmascot-hblue tracking-tighter">
8- >
9 Gordon7 Gordon
10 </h1>8 </h1>
11 <span9 <span
@@ -15,11 +13,10 @@
15 </div>13 </div>
16 <div class="w-full">14 <div class="w-full">
17 <ul15 <ul
18- class="flex flex-row space-y-6 font-semibold text-gray-100 text-lg md:text-xl justify-center"16+ class="flex flex-row space-y-6 font-semibold text-gray-100 text-lg md:text-xl justify-center">
19- >
20 <li class="">17 <li class="">
21 <span class="JetBrainsMonoBoldNF pr-1"> </span>18 <span class="JetBrainsMonoBoldNF pr-1"> </span>
22- <a href="/admin/manager">Manager</a>19+ <a href="manager">Manager</a>
23 </li>20 </li>
24 </ul>21 </ul>
25 </div>22 </div>
@@ -28,12 +25,14 @@
28 href="/admin/settings"25 href="/admin/settings"
29 title="User settings"26 title="User settings"
30 class="iconf text-gordonmascot-mblue text-lg md:text-xl"27 class="iconf text-gordonmascot-mblue text-lg md:text-xl"
31- ></a>28+ ></a
29+ >
32 <a30 <a
33 href="/admin/logout"31 href="/admin/logout"
34 title="Logout"32 title="Logout"
35 class="iconf text-gordonmascot-mblue text-lg md:text-xl"33 class="iconf text-gordonmascot-mblue text-lg md:text-xl"
36- >󰍃</a>34+ >󰍃</a
35+ >
37 </div>36 </div>
38 </div>37 </div>
39 38
M internal/templating/render/localization.go +0 -2
@@ -17,8 +17,6 @@ func GetLocalization(lang string, a *server.App) (map[string]interface{}, error)
1717 return nil, fmt.Errorf("language %s is not supported", lang)
1818 }
1919
20- fmt.Println("Language:", lang)
21-
2220 // 2. Get the localizations from the strings.yml file
2321 localizations, err := getLocalizations(a)
2422 if err != nil {
@@ -17,8 +17,6 @@ func GetLocalization(lang string, a *server.App) (map[string]interface{}, error)
17 return nil, fmt.Errorf("language %s is not supported", lang)17 return nil, fmt.Errorf("language %s is not supported", lang)
18 }18 }
19 19
20- fmt.Println("Language:", lang)
21-
22 // 2. Get the localizations from the strings.yml file20 // 2. Get the localizations from the strings.yml file
23 localizations, err := getLocalizations(a)21 localizations, err := getLocalizations(a)
24 if err != nil {22 if err != nil {
M pkg/docker/containers.go +1 -1
@@ -99,7 +99,7 @@ func ListRunningContainers() ([]types.Container, error) {
9999 }
100100 // Check if the list is empty
101101 if len(containers) == 0 {
102- return nil, fmt.Errorf("No containers found")
102+ return nil, fmt.Errorf("no containers found")
103103 }
104104 return containers, nil
105105 }
@@ -99,7 +99,7 @@ func ListRunningContainers() ([]types.Container, error) {
99 }99 }
100 // Check if the list is empty100 // Check if the list is empty
101 if len(containers) == 0 {101 if len(containers) == 0 {
102- return nil, fmt.Errorf("No containers found")102+ return nil, fmt.Errorf("no containers found")
103 }103 }
104 return containers, nil104 return containers, nil
105 }105 }
M pkg/docker/images.go +55 -43
@@ -1,8 +1,8 @@
11 package docker
22
33 import (
4- "bytes"
54 "context"
5+ "fmt"
66 "io"
77 "os"
88
@@ -33,13 +33,10 @@ func DeleteContainerImage(imageID string) error {
3333 }
3434
3535 func GetImageID(imageName string) (string, error) {
36- // Check if the Docker client has been initialized
37- CheckIfInitialized()
3836
39- // List all images
4037 images, err := dockerCli.ImageList(context.Background(), types.ImageListOptions{})
4138 if err != nil {
42- return "", err
39+ return "", fmt.Errorf("failed to list images: %w", err)
4340 }
4441
4542 // Search for the image we just loaded
@@ -53,80 +50,95 @@ func GetImageID(imageName string) (string, error) {
5350 }
5451
5552 if imageID == "" {
56- return "", err
53+ return "", fmt.Errorf("image not found")
5754 }
5855
5956 return imageID, nil
6057 }
6158
6259 // ImportImageToEngine imports an image to the Docker engine
63-func ImportImageToEngine(imagePath string) (string, error) {
60+func ImportImageToEngine(imageFilePath string) error {
6461 // Open the image file
65- imageFile, err := os.Open(imagePath)
62+ imageFile, err := os.Open(imageFilePath)
6663 if err != nil {
67- return "", err
64+ return fmt.Errorf("failed to open image file: %w", err)
6865 }
66+ defer imageFile.Close()
6967
70- _, err = dockerCli.ImageLoad(context.Background(), imageFile, false)
68+ // Import the image using the Docker client
69+ importedImage, err := dockerCli.ImageLoad(context.Background(), imageFile, false)
7170 if err != nil {
72- return "", err
71+ return fmt.Errorf("failed to import image: %w", err)
7372 }
74- // Close the image file
75- imageFile.Close()
73+ defer importedImage.Body.Close()
7674
77- // List all images
78- images, err := dockerCli.ImageList(context.Background(), types.ImageListOptions{})
75+ return nil
76+}
77+
78+// ExportImageFromEngine exports an image from the Docker engine and returns it as an io.Reader
79+func ExportImageFromEngine(imageID string) (io.ReadCloser, error) {
80+ // Check if the Docker client has been initialized
81+ CheckIfInitialized()
82+
83+ // Get the image information using the Docker client
84+ imageInfo, err := GetImageInfo(imageID)
7985 if err != nil {
80- return "", err
86+ return nil, err
8187 }
8288
83- // Search for the image we just loaded
84- var imageID string
85- for _, image := range images {
86- for _, tag := range image.RepoTags {
87- if tag == "<none>:<none>" {
88- imageID = image.ID
89- }
90- }
89+ if len(imageInfo.RepoTags) == 0 {
90+ return nil, fmt.Errorf("image has no tag")
9191 }
9292
93- if imageID == "" {
94- return "", err
93+ // Export the image using the Docker client
94+ imageReader, err := dockerCli.ImageSave(context.Background(), []string{imageInfo.RepoTags[0]})
95+ if err != nil {
96+ return nil, fmt.Errorf("failed to export image: %w", err)
9597 }
9698
97- return imageID, nil
99+ return imageReader, nil
98100 }
99101
100-// ExportImageFromEngine exports an image from the Docker engine and returns it as a byte array
101-func ExportImageFromEngine(imageName string) ([]byte, error) {
102- // we check if the image exists
103- _, err := GetImageID(imageName)
102+// From an ID, get the all the information about the image
103+func GetImageInfo(imageID string) (*types.ImageInspect, error) {
104+ // Get the image information using the Docker client
105+ imageInfo, _, err := dockerCli.ImageInspectWithRaw(context.Background(), imageID)
104106 if err != nil {
105107 return nil, err
106108 }
107109
108- image, err := dockerCli.ImageSave(context.Background(), []string{imageName})
110+ return &imageInfo, nil
111+}
112+
113+// GetImageSize returns the size of an image
114+func GetImageSize(imageID string) (int64, error) {
115+ // Get the image information using the Docker client
116+ imageInfo, err := GetImageInfo(imageID)
109117 if err != nil {
110- return nil, err
118+ return 0, err
111119 }
112- defer image.Close()
113120
114- var buf bytes.Buffer
115- _, err = io.Copy(&buf, image)
121+ return imageInfo.Size, nil
122+}
123+
124+// GetImageTag returns the tag of an image
125+func GetImageTag(imageID string) (string, error) {
126+ // Get the image information using the Docker client
127+ imageInfo, err := GetImageInfo(imageID)
116128 if err != nil {
117- return nil, err
129+ return "", err
118130 }
119131
120- return buf.Bytes(), nil
132+ return imageInfo.RepoTags[0], nil
121133 }
122134
123-// From an ID, get the all the information about the image
124-func GetImageInfo(imageID string) (*types.ImageInspect, error) {
135+// GetImageName returns the name of an image
136+func GetImageName(imageID string) (string, error) {
125137 // Get the image information using the Docker client
126- imageInfo, _, err := dockerCli.ImageInspectWithRaw(context.Background(), imageID)
138+ imageInfo, err := GetImageInfo(imageID)
127139 if err != nil {
128- return nil, err
140+ return "", err
129141 }
130142
131- return &imageInfo, nil
143+ return imageInfo.RepoDigests[0], nil
132144 }
@@ -1,8 +1,8 @@
1 package docker1 package docker
2 2
3 import (3 import (
4- "bytes"
5 "context"4 "context"
5+ "fmt"
6 "io"6 "io"
7 "os"7 "os"
8 8
@@ -33,13 +33,10 @@ func DeleteContainerImage(imageID string) error {
33 }33 }
34 34
35 func GetImageID(imageName string) (string, error) {35 func GetImageID(imageName string) (string, error) {
36- // Check if the Docker client has been initialized
37- CheckIfInitialized()
38 36
39- // List all images
40 images, err := dockerCli.ImageList(context.Background(), types.ImageListOptions{})37 images, err := dockerCli.ImageList(context.Background(), types.ImageListOptions{})
41 if err != nil {38 if err != nil {
42- return "", err39+ return "", fmt.Errorf("failed to list images: %w", err)
43 }40 }
44 41
45 // Search for the image we just loaded42 // Search for the image we just loaded
@@ -53,80 +50,95 @@ func GetImageID(imageName string) (string, error) {
53 }50 }
54 51
55 if imageID == "" {52 if imageID == "" {
56- return "", err53+ return "", fmt.Errorf("image not found")
57 }54 }
58 55
59 return imageID, nil56 return imageID, nil
60 }57 }
61 58
62 // ImportImageToEngine imports an image to the Docker engine59 // ImportImageToEngine imports an image to the Docker engine
63-func ImportImageToEngine(imagePath string) (string, error) {60+func ImportImageToEngine(imageFilePath string) error {
64 // Open the image file61 // Open the image file
65- imageFile, err := os.Open(imagePath)62+ imageFile, err := os.Open(imageFilePath)
66 if err != nil {63 if err != nil {
67- return "", err64+ return fmt.Errorf("failed to open image file: %w", err)
68 }65 }
66+ defer imageFile.Close()
69 67
70- _, err = dockerCli.ImageLoad(context.Background(), imageFile, false)68+ // Import the image using the Docker client
69+ importedImage, err := dockerCli.ImageLoad(context.Background(), imageFile, false)
71 if err != nil {70 if err != nil {
72- return "", err71+ return fmt.Errorf("failed to import image: %w", err)
73 }72 }
74- // Close the image file73+ defer importedImage.Body.Close()
75- imageFile.Close()
76 74
77- // List all images75+ return nil
78- images, err := dockerCli.ImageList(context.Background(), types.ImageListOptions{})76+}
77+
78+// ExportImageFromEngine exports an image from the Docker engine and returns it as an io.Reader
79+func ExportImageFromEngine(imageID string) (io.ReadCloser, error) {
80+ // Check if the Docker client has been initialized
81+ CheckIfInitialized()
82+
83+ // Get the image information using the Docker client
84+ imageInfo, err := GetImageInfo(imageID)
79 if err != nil {85 if err != nil {
80- return "", err86+ return nil, err
81 }87 }
82 88
83- // Search for the image we just loaded89+ if len(imageInfo.RepoTags) == 0 {
84- var imageID string90+ return nil, fmt.Errorf("image has no tag")
85- for _, image := range images {
86- for _, tag := range image.RepoTags {
87- if tag == "<none>:<none>" {
88- imageID = image.ID
89- }
90- }
91 }91 }
92 92
93- if imageID == "" {93+ // Export the image using the Docker client
94- return "", err94+ imageReader, err := dockerCli.ImageSave(context.Background(), []string{imageInfo.RepoTags[0]})
95+ if err != nil {
96+ return nil, fmt.Errorf("failed to export image: %w", err)
95 }97 }
96 98
97- return imageID, nil99+ return imageReader, nil
98 }100 }
99 101
100-// ExportImageFromEngine exports an image from the Docker engine and returns it as a byte array102+// From an ID, get the all the information about the image
101-func ExportImageFromEngine(imageName string) ([]byte, error) {103+func GetImageInfo(imageID string) (*types.ImageInspect, error) {
102- // we check if the image exists104+ // Get the image information using the Docker client
103- _, err := GetImageID(imageName)105+ imageInfo, _, err := dockerCli.ImageInspectWithRaw(context.Background(), imageID)
104 if err != nil {106 if err != nil {
105 return nil, err107 return nil, err
106 }108 }
107 109
108- image, err := dockerCli.ImageSave(context.Background(), []string{imageName})110+ return &imageInfo, nil
111+}
112+
113+// GetImageSize returns the size of an image
114+func GetImageSize(imageID string) (int64, error) {
115+ // Get the image information using the Docker client
116+ imageInfo, err := GetImageInfo(imageID)
109 if err != nil {117 if err != nil {
110- return nil, err118+ return 0, err
111 }119 }
112- defer image.Close()
113 120
114- var buf bytes.Buffer121+ return imageInfo.Size, nil
115- _, err = io.Copy(&buf, image)122+}
123+
124+// GetImageTag returns the tag of an image
125+func GetImageTag(imageID string) (string, error) {
126+ // Get the image information using the Docker client
127+ imageInfo, err := GetImageInfo(imageID)
116 if err != nil {128 if err != nil {
117- return nil, err129+ return "", err
118 }130 }
119 131
120- return buf.Bytes(), nil132+ return imageInfo.RepoTags[0], nil
121 }133 }
122 134
123-// From an ID, get the all the information about the image135+// GetImageName returns the name of an image
124-func GetImageInfo(imageID string) (*types.ImageInspect, error) {136+func GetImageName(imageID string) (string, error) {
125 // Get the image information using the Docker client137 // Get the image information using the Docker client
126- imageInfo, _, err := dockerCli.ImageInspectWithRaw(context.Background(), imageID)138+ imageInfo, err := GetImageInfo(imageID)
127 if err != nil {139 if err != nil {
128- return nil, err140+ return "", err
129 }141 }
130 142
131- return &imageInfo, nil143+ return imageInfo.RepoDigests[0], nil
132 }144 }
M pkg/store/storeimages.go +11 -20
@@ -7,20 +7,9 @@ import (
77 "path/filepath"
88
99 "github.com/bnema/gordon/internal/common"
10- "github.com/docker/docker/api/types"
1110 )
1211
13-type StorageConfig struct {
14- StorageDir string
15- ImageID string
16- Images []types.ImageSummary
17-}
18-
19-func NewStorageConfig(config *common.Config) *StorageConfig {
20- return &StorageConfig{
21- StorageDir: config.General.StorageDir,
22- }
23-}
12+var imagePath string
2413
2514 func SaveImageToStorage(config *common.Config, filename string, buf io.Reader) (string, error) {
2615 // Check if the folder exist if not create it
@@ -32,10 +21,10 @@ func SaveImageToStorage(config *common.Config, filename string, buf io.Reader) (
3221 }
3322
3423 // Define the path where the image will be saved
35- saveInPath := filepath.Join(config.General.StorageDir, filename)
24+ imagePath = filepath.Join(config.General.StorageDir, filename)
3625
3726 // Create or open a file for appending.
38- outFile, err := os.Create(saveInPath)
27+ outFile, err := os.Create(imagePath)
3928 if err != nil {
4029 return "", fmt.Errorf("failed to open file: %v", err)
4130 }
@@ -47,20 +36,22 @@ func SaveImageToStorage(config *common.Config, filename string, buf io.Reader) (
4736 }
4837
4938 // Check if file exists
50- if _, err := os.Stat(saveInPath); os.IsNotExist(err) {
39+ if _, err := os.Stat(imagePath); os.IsNotExist(err) {
5140 return "", fmt.Errorf("file does not exist: %v", err)
5241 }
5342
54- return saveInPath, nil
43+ return imagePath, nil
5544 }
5645
57-func (sc *StorageConfig) DeleteImageFromStorage(imageId string) error {
58- // Delete the image from the storage directory
59- imagePath := filepath.Join(sc.StorageDir, imageId+".tar")
46+func RemoveFromStorage() error {
47+ // Delete the file
6048 err := os.Remove(imagePath)
6149 if err != nil {
62- return fmt.Errorf("failed to delete image from storage directory: %v", err)
50+ return fmt.Errorf("failed to remove file: %v", err)
6351 }
6452
53+ // clean up the path
54+ imagePath = ""
55+
6556 return nil
6657 }
@@ -7,20 +7,9 @@ import (
7 "path/filepath"7 "path/filepath"
8 8
9 "github.com/bnema/gordon/internal/common"9 "github.com/bnema/gordon/internal/common"
10- "github.com/docker/docker/api/types"
11 )10 )
12 11
13-type StorageConfig struct {12+var imagePath string
14- StorageDir string
15- ImageID string
16- Images []types.ImageSummary
17-}
18-
19-func NewStorageConfig(config *common.Config) *StorageConfig {
20- return &StorageConfig{
21- StorageDir: config.General.StorageDir,
22- }
23-}
24 13
25 func SaveImageToStorage(config *common.Config, filename string, buf io.Reader) (string, error) {14 func SaveImageToStorage(config *common.Config, filename string, buf io.Reader) (string, error) {
26 // Check if the folder exist if not create it15 // Check if the folder exist if not create it
@@ -32,10 +21,10 @@ func SaveImageToStorage(config *common.Config, filename string, buf io.Reader) (
32 }21 }
33 22
34 // Define the path where the image will be saved23 // Define the path where the image will be saved
35- saveInPath := filepath.Join(config.General.StorageDir, filename)24+ imagePath = filepath.Join(config.General.StorageDir, filename)
36 25
37 // Create or open a file for appending.26 // Create or open a file for appending.
38- outFile, err := os.Create(saveInPath)27+ outFile, err := os.Create(imagePath)
39 if err != nil {28 if err != nil {
40 return "", fmt.Errorf("failed to open file: %v", err)29 return "", fmt.Errorf("failed to open file: %v", err)
41 }30 }
@@ -47,20 +36,22 @@ func SaveImageToStorage(config *common.Config, filename string, buf io.Reader) (
47 }36 }
48 37
49 // Check if file exists38 // Check if file exists
50- if _, err := os.Stat(saveInPath); os.IsNotExist(err) {39+ if _, err := os.Stat(imagePath); os.IsNotExist(err) {
51 return "", fmt.Errorf("file does not exist: %v", err)40 return "", fmt.Errorf("file does not exist: %v", err)
52 }41 }
53 42
54- return saveInPath, nil43+ return imagePath, nil
55 }44 }
56 45
57-func (sc *StorageConfig) DeleteImageFromStorage(imageId string) error {46+func RemoveFromStorage() error {
58- // Delete the image from the storage directory47+ // Delete the file
59- imagePath := filepath.Join(sc.StorageDir, imageId+".tar")
60 err := os.Remove(imagePath)48 err := os.Remove(imagePath)
61 if err != nil {49 if err != nil {
62- return fmt.Errorf("failed to delete image from storage directory: %v", err)50+ return fmt.Errorf("failed to remove file: %v", err)
63 }51 }
64 52
53+ // clean up the path
54+ imagePath = ""
55+
65 return nil56 return nil
66 }57 }

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