diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..780ab3d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/main.go" + } + ] +} diff --git a/config.toml b/config.toml index b6167a4..d9ab5a1 100644 --- a/config.toml +++ b/config.toml @@ -1,16 +1,18 @@ # Wether to us the built-in Zot registry or not bring_own_registry = false -# URL of own registry -own_registry_adr = "127.0.0.1:5000" +# IP address and port of own registry +own_registry_adr = "127.0.0.1" +own_registry_port = "8585" # URL of remote registry OR local file path -# url_or_file = "https://demo.goharbor.io/v2/library/busy" url_or_file = "https://demo.goharbor.io/v2/myproject/album-server" -# url_or_file = "http://localhost:5001/v2/library/busybox" -# Endpoint of ground_control -ground_control = "http://localhost:8080/satellite/images" +# Default path for Zot registry config.json +zotConfigPath = "./registry/config.json" + +# Set logging level +log_level = "info" # For testing purposes : # https://demo.goharbor.io/v2/myproject/album-server diff --git a/go.mod b/go.mod index c2b7ccc..91e4cf2 100644 --- a/go.mod +++ b/go.mod @@ -15,8 +15,10 @@ require ( ) require ( + github.com/rs/zerolog v1.33.0 github.com/spf13/viper v1.19.0 - github.com/stretchr/testify v1.9.0 + go.uber.org/zap v1.27.0 + gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) require ( @@ -55,7 +57,7 @@ require ( github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/Microsoft/hcsshim v0.12.3 // indirect + github.com/Microsoft/hcsshim v0.12.4 // indirect github.com/OneOfOne/xxhash v1.2.8 // indirect github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect github.com/ThalesIgnite/crypto11 v1.2.5 // indirect @@ -121,7 +123,7 @@ require ( github.com/cheggaaa/pb/v3 v3.1.5 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/containerd/cgroups/v3 v3.0.3 // indirect - github.com/containerd/containerd v1.7.17 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/continuity v0.4.3 // indirect github.com/containerd/errdefs v0.1.0 // indirect github.com/containerd/fifo v1.1.0 // indirect @@ -147,7 +149,7 @@ require ( github.com/dlclark/regexp2 v1.4.0 // indirect github.com/docker/cli v26.1.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker v26.1.3+incompatible // indirect + github.com/docker/docker v27.0.0+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.1 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect @@ -171,7 +173,7 @@ require ( github.com/go-ini/ini v1.67.0 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/go-ldap/ldap/v3 v3.4.8 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.23.0 // indirect github.com/go-openapi/errors v0.22.0 // indirect @@ -239,7 +241,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/compress v1.17.8 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f // indirect github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422 // indirect @@ -255,6 +257,7 @@ require ( github.com/liamg/memoryfs v1.6.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -323,7 +326,6 @@ require ( github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rs/zerolog v1.32.0 // indirect github.com/rubenv/sql-migrate v1.5.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect @@ -334,6 +336,7 @@ require ( github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect + github.com/shirou/gopsutil/v3 v3.24.5 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/sigstore/cosign/v2 v2.2.4 // indirect github.com/sigstore/fulcio v1.4.5 // indirect @@ -362,6 +365,7 @@ require ( github.com/thales-e-security/pool v0.0.2 // indirect github.com/theupdateframework/go-tuf v0.7.0 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect + github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/transparency-dev/merkle v0.0.2 // indirect github.com/twitchtv/twirp v8.1.3+incompatible // indirect github.com/ulikunitz/xz v0.5.12 // indirect @@ -392,7 +396,6 @@ require ( go.opentelemetry.io/otel/trace v1.27.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.24.0 // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/mod v0.17.0 // indirect @@ -407,9 +410,9 @@ require ( google.golang.org/api v0.172.0 // indirect google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect google.golang.org/grpc v1.64.0 // indirect - google.golang.org/protobuf v1.34.1 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 50d147e..b033410 100644 --- a/go.sum +++ b/go.sum @@ -277,8 +277,8 @@ github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA4 github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.12.3 h1:LS9NXqXhMoqNCplK1ApmVSfB4UnVLRDWRapB6EIlxE0= -github.com/Microsoft/hcsshim v0.12.3/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ= +github.com/Microsoft/hcsshim v0.12.4 h1:Ev7YUMHAHoWNm+aDSPzc5W9s6E2jyL1szpVDJeZ/Rr4= +github.com/Microsoft/hcsshim v0.12.4/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= @@ -517,8 +517,8 @@ github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= -github.com/containerd/containerd v1.7.17 h1:KjNnn0+tAVQHAoaWRjmdak9WlvnFR/8rU1CHHy8Rm2A= -github.com/containerd/containerd v1.7.17/go.mod h1:vK+hhT4TIv2uejlcDlbVIc8+h/BqtKLIyNrtCZol8lI= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= @@ -596,8 +596,8 @@ github.com/docker/cli v26.1.3+incompatible h1:bUpXT/N0kDE3VUHI2r5VMsYQgi38kYuoC0 github.com/docker/cli v26.1.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v26.1.3+incompatible h1:lLCzRbrVZrljpVNobJu1J2FHk8V0s4BawoZippkc+xo= -github.com/docker/docker v26.1.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.0.0+incompatible h1:JRugTYuelmWlW0M3jakcIadDx2HUoUO6+Tf2C5jVfwA= +github.com/docker/docker v27.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo= github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -689,8 +689,8 @@ github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= @@ -1026,8 +1026,8 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= -github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f h1:GvCU5GXhHq+7LeOzx/haG7HSIZokl3/0GkoUFzsRJjg= @@ -1068,8 +1068,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a h1:3Bm7EwfUQUvhNeKIkUct/gl9eod1TcXuj8stxvi/GoI= -github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -1297,8 +1297,8 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99 github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= -github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= @@ -1329,8 +1329,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= -github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y= -github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -1442,10 +1442,10 @@ github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= -github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= -github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= -github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= -github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= +github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= +github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= +github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= +github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU= @@ -2089,8 +2089,8 @@ google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 h1:ImUcDPHjTrAqNhl google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7/go.mod h1:/3XmxOjePkvmKrHuBy4zNFw7IzxJXtAgdpXi8Ll990U= google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ= google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -2144,8 +2144,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -2163,6 +2163,8 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= diff --git a/image-list/images.json b/image-list/images.json index 93da74a..271496a 100644 --- a/image-list/images.json +++ b/image-list/images.json @@ -5,13 +5,10 @@ "repository": "myproject", "images": [ { - "name": "album-server@sha256:71df27326a806ef2946ce502d26212efa11d70e4dcea06ceae612eb29cba398b" + "name": "album-server@sha256:39879890008f12c25ea14125aa8e9ec8ef3e167f0b0ed88057e955a8fa32c430" }, { - "name": "album-server" - }, - { - "name": "album-server:v1-manifest-app" + "name": "album-server:busybox" } ] } diff --git a/internal/replicate/replicate.go b/internal/replicate/replicate.go index 1654dc0..5a2267a 100644 --- a/internal/replicate/replicate.go +++ b/internal/replicate/replicate.go @@ -9,11 +9,13 @@ import ( "strings" "container-registry.com/harbor-satellite/internal/store" + "container-registry.com/harbor-satellite/logger" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/crane" ) type Replicator interface { + // Replicate copies images from the source registry to the local registry. Replicate(ctx context.Context, image string) error DeleteExtraImages(ctx context.Context, imgs []store.Image) error } @@ -34,20 +36,20 @@ type RegistryInfo struct { Repositories []Repository `json:"repositories"` } -func NewReplicator() Replicator { +func NewReplicator(context context.Context) Replicator { return &BasicReplicator{} } -// Replicate copies an image from source registry to local registry. func (r *BasicReplicator) Replicate(ctx context.Context, image string) error { - source := getPullSource(image) - if source == "" { - return fmt.Errorf("source not found for image: %s", image) + + source := getPullSource(ctx, image) + + if source != "" { + CopyImage(ctx, source) } - return CopyImage(source) + return nil } -// stripPrefix removes prefix from image name. func stripPrefix(imageName string) string { if idx := strings.Index(imageName, ":"); idx != -1 { return imageName[idx+1:] @@ -56,117 +58,151 @@ func stripPrefix(imageName string) string { } func (r *BasicReplicator) DeleteExtraImages(ctx context.Context, imgs []store.Image) error { - localRegistry := getEnvRegistryPath() + log := logger.FromContext(ctx) + zotUrl := os.Getenv("ZOT_URL") + registry := os.Getenv("REGISTRY") + repository := os.Getenv("REPOSITORY") + image := os.Getenv("IMAGE") - fmt.Println("Syncing local registry:", localRegistry) + localRegistry := fmt.Sprintf("%s/%s/%s/%s", zotUrl, registry, repository, image) + log.Info().Msgf("Local registry: %s", localRegistry) + // Get the list of images from the local registry localImages, err := crane.ListTags(localRegistry) if err != nil { - return fmt.Errorf("failed to get local registry catalog: %w", err) + log.Error().Msgf("failed to list tags: %v", err) + return err } + // Create a map for quick lookup of the provided image list imageMap := make(map[string]struct{}) for _, img := range imgs { - imageMap[stripPrefix(img.Name)] = struct{}{} + // Strip the "album-server:" prefix from the image name + strippedName := stripPrefix(img.Name) + imageMap[strippedName] = struct{}{} } + // Iterate over the local images and delete those not in the provided image list for _, localImage := range localImages { if _, exists := imageMap[localImage]; !exists { - if err := crane.Delete(fmt.Sprintf("%s:%s", localRegistry, localImage)); err != nil { - return fmt.Errorf("failed to delete image %s: %v\n", localImage, err) + // Image is not in the provided list, delete it + log.Info().Msgf("Deleting image: %s", localImage) + err := crane.Delete(fmt.Sprintf("%s:%s", localRegistry, localImage)) + if err != nil { + log.Error().Msgf("failed to delete image: %v", err) + return err } - fmt.Printf("Deleted image: %s\n", localImage) + log.Info().Msgf("Image deleted: %s", localImage) } } return nil } -// getPullSource constructs source URL for pulling an image. -func getPullSource(image string) string { +func getPullSource(ctx context.Context, image string) string { + log := logger.FromContext(ctx) + input := os.Getenv("USER_INPUT") scheme := os.Getenv("SCHEME") if strings.HasPrefix(scheme, "http://") || strings.HasPrefix(scheme, "https://") { - return fmt.Sprintf("%s/%s/%s", os.Getenv("HOST"), os.Getenv("REGISTRY"), image) + url := os.Getenv("REGISTRY") + "/" + os.Getenv("REPOSITORY") + "/" + image + return url + } else { + registryInfo, err := getFileInfo(ctx, input) + if err != nil { + log.Error().Msgf("Error getting file info: %v", err) + return "" + } + registryURL := registryInfo.RegistryUrl + registryURL = strings.TrimPrefix(registryURL, "https://") + registryURL = strings.TrimSuffix(registryURL, "/v2/") + + // TODO: Handle multiple repositories + repositoryName := registryInfo.Repositories[0].Repository + + return registryURL + "/" + repositoryName + "/" + image } +} - registryInfo, err := getFileInfo(os.Getenv("USER_INPUT")) +func getFileInfo(ctx context.Context, input string) (*RegistryInfo, error) { + log := logger.FromContext(ctx) + // Get the current working directory + workingDir, err := os.Getwd() if err != nil { - fmt.Printf("Error loading file info: %v\n", err) - return "" + log.Error().Msgf("Error getting current directory: %v", err) + return nil, err } - registryURL := strings.TrimSuffix( - strings.TrimPrefix(registryInfo.RegistryUrl, "https://"), - "v2/", - ) - repositoryName := registryInfo.Repositories[0].Repository - - return fmt.Sprintf("%s/%s/%s", registryURL, repositoryName, image) -} + // Construct the full path by joining the working directory and the input path + fullPath := filepath.Join(workingDir, input) -// getFileInfo reads and unmarshals registry info from a JSON file. -func getFileInfo(input string) (*RegistryInfo, error) { - fullPath := filepath.Join(getWorkingDir(), input) - data, err := os.ReadFile(fullPath) + // Read the file + jsonData, err := os.ReadFile(fullPath) if err != nil { - return nil, fmt.Errorf("failed to read file: %w", err) + log.Error().Msgf("Error reading file: %v", err) + return nil, err } var registryInfo RegistryInfo - if err := json.Unmarshal(data, ®istryInfo); err != nil { - return nil, fmt.Errorf("failed to unmarshal JSON: %w", err) + err = json.Unmarshal(jsonData, ®istryInfo) + if err != nil { + log.Error().Msgf("Error unmarshalling JSON data: %v", err) + return nil, err } return ®istryInfo, nil } -// CopyImage pulls an image from source and pushes it to destination. -func CopyImage(imageName string) error { - fmt.Println("Copying image:", imageName) - destRef := fmt.Sprintf("%s/%s", os.Getenv("ZOT_URL"), removeHostName(imageName)) +func CopyImage(ctx context.Context, imageName string) error { + log := logger.FromContext(ctx) + log.Info().Msgf("Copying image: %s", imageName) + zotUrl := os.Getenv("ZOT_URL") + if zotUrl == "" { + log.Error().Msg("ZOT_URL environment variable is not set") + return fmt.Errorf("ZOT_URL environment variable is not set") + } + + // Build the destination reference + destRef := fmt.Sprintf("%s/%s", zotUrl, imageName) + log.Info().Msgf("Destination reference: %s", destRef) + + // Get credentials from environment variables + username := os.Getenv("HARBOR_USERNAME") + password := os.Getenv("HARBOR_PASSWORD") + if username == "" || password == "" { + log.Error().Msg("HARBOR_USERNAME or HARBOR_PASSWORD environment variable is not set") + return fmt.Errorf("HARBOR_USERNAME or HARBOR_PASSWORD environment variable is not set") + } auth := authn.FromConfig(authn.AuthConfig{ - Username: os.Getenv("HARBOR_USERNAME"), - Password: os.Getenv("HARBOR_PASSWORD"), + Username: username, + Password: password, }) - err := crane.Copy(imageName, destRef, crane.Insecure, crane.WithAuth(auth)) + // Pull the image with authentication + srcImage, err := crane.Pull(imageName, crane.WithAuth(auth), crane.Insecure) if err != nil { - return fmt.Errorf("Error in copying image from %s to %s: %v", imageName, destRef, err) + log.Error().Msgf("Failed to pull image: %v", err) + return fmt.Errorf("failed to pull image: %w", err) + } else { + log.Info().Msg("Image pulled successfully") } - fmt.Println("Image pushed successfully") - fmt.Printf("Pushed image to: %s\n", destRef) + // Push the image to the destination registry + err = crane.Push(srcImage, destRef, crane.Insecure) + if err != nil { + log.Error().Msgf("Failed to push image: %v", err) + return fmt.Errorf("failed to push image: %w", err) + } else { + log.Info().Msg("Image pushed successfully") + } + // Delete ./local-oci-layout directory + // This is required because it is a temporary directory used by crane to pull and push images to and from + // And crane does not automatically clean it if err := os.RemoveAll("./local-oci-layout"); err != nil { - return fmt.Errorf("failed to remove temporary directory: %w", err) + log.Error().Msgf("Failed to remove directory: %v", err) + return fmt.Errorf("failed to remove directory: %w", err) } return nil } - -// removeHostName removes hostname from image name. -func removeHostName(imageName string) string { - parts := strings.SplitN(imageName, "/", 2) - if len(parts) > 1 { - return parts[1] - } - return imageName -} - -// getEnvRegistryPath constructs local registry URL from environment variables. -func getEnvRegistryPath() string { - return fmt.Sprintf("%s/%s/%s", - os.Getenv("ZOT_URL"), - os.Getenv("REGISTRY"), - os.Getenv("REPOSITORY")) -} - -// getWorkingDir returns current working directory. -func getWorkingDir() string { - workingDir, err := os.Getwd() - if err != nil { - panic(fmt.Errorf("failed to get working directory: %w", err)) - } - return workingDir -} diff --git a/internal/satellite/satellite.go b/internal/satellite/satellite.go index ebc3816..b2f6b0a 100644 --- a/internal/satellite/satellite.go +++ b/internal/satellite/satellite.go @@ -2,11 +2,11 @@ package satellite import ( "context" - "fmt" "time" "container-registry.com/harbor-satellite/internal/replicate" "container-registry.com/harbor-satellite/internal/store" + "container-registry.com/harbor-satellite/logger" ) type Satellite struct { @@ -14,7 +14,7 @@ type Satellite struct { replicator replicate.Replicator } -func NewSatellite(storer store.Storer, replicator replicate.Replicator) *Satellite { +func NewSatellite(ctx context.Context, storer store.Storer, replicator replicate.Replicator) *Satellite { return &Satellite{ storer: storer, replicator: replicator, @@ -22,27 +22,27 @@ func NewSatellite(storer store.Storer, replicator replicate.Replicator) *Satelli } func (s *Satellite) Run(ctx context.Context) error { + log := logger.FromContext(ctx) + // Execute the initial operation immediately without waiting for the ticker imgs, err := s.storer.List(ctx) if err != nil { - + log.Error().Err(err).Msg("Error listing images") return err } if len(imgs) == 0 { - fmt.Println("No images to replicate") + log.Info().Msg("No images to replicate") } else { for _, img := range imgs { err = s.replicator.Replicate(ctx, img.Name) if err != nil { + log.Error().Err(err).Msg("Error replicating image") return err } } - err = s.replicator.DeleteExtraImages(ctx, imgs) - if err != nil { - return err - } + s.replicator.DeleteExtraImages(ctx, imgs) } - fmt.Print("--------------------------------\n") + log.Info().Msg("--------------------------------\n") // Temporarily set to faster tick rate for testing purposes ticker := time.NewTicker(3 * time.Second) @@ -55,23 +55,22 @@ func (s *Satellite) Run(ctx context.Context) error { case <-ticker.C: imgs, err := s.storer.List(ctx) if err != nil { + log.Error().Err(err).Msg("Error listing images") return err } if len(imgs) == 0 { - fmt.Println("No images to replicate") + log.Info().Msg("No images to replicate") } else { for _, img := range imgs { err = s.replicator.Replicate(ctx, img.Name) if err != nil { + log.Error().Err(err).Msg("Error replicating image") return err } } - err = s.replicator.DeleteExtraImages(ctx, imgs) - if err != nil { - return err - } + s.replicator.DeleteExtraImages(ctx, imgs) } } - fmt.Print("--------------------------------\n") + log.Info().Msg("--------------------------------\n") } } diff --git a/internal/store/file-fetch.go b/internal/store/file-fetch.go index 4c895f5..fe32f63 100644 --- a/internal/store/file-fetch.go +++ b/internal/store/file-fetch.go @@ -3,9 +3,10 @@ package store import ( "context" "encoding/json" - "fmt" "os" "path/filepath" + + "container-registry.com/harbor-satellite/logger" ) type FileImageList struct { @@ -24,15 +25,16 @@ type ImageData struct { Repositories []Repository `json:"repositories"` } -func (f *FileImageList) SourceType() string { +func (f *FileImageList) Type(ctx context.Context) string { return "File" } -func FileImageListFetcher(relativePath string) *FileImageList { +func FileImageListFetcher(ctx context.Context, relativePath string) *FileImageList { + log := logger.FromContext(ctx) // Get the current working directory dir, err := os.Getwd() if err != nil { - fmt.Println("Error getting current directory:", err) + log.Error().Err(err).Msg("Error getting current directory") return nil } @@ -45,11 +47,13 @@ func FileImageListFetcher(relativePath string) *FileImageList { } func (client *FileImageList) List(ctx context.Context) ([]Image, error) { + log := logger.FromContext(ctx) var images []Image // Read the file data, err := os.ReadFile(client.Path) if err != nil { + log.Error().Err(err).Msg("Error reading file") return nil, err } @@ -57,6 +61,7 @@ func (client *FileImageList) List(ctx context.Context) ([]Image, error) { // Parse the JSON data err = json.Unmarshal(data, &imageData) if err != nil { + log.Error().Err(err).Msg("Error unmarshalling JSON data") return nil, err } @@ -73,6 +78,5 @@ func (client *FileImageList) List(ctx context.Context) ([]Image, error) { } func (client *FileImageList) GetDigest(ctx context.Context, tag string) (string, error) { - // TODO: Implement GetDigest for FileImageList return "Not implemented yet", nil } diff --git a/internal/store/http-fetch.go b/internal/store/http-fetch.go index dbe975f..922a454 100644 --- a/internal/store/http-fetch.go +++ b/internal/store/http-fetch.go @@ -9,12 +9,14 @@ import ( "net/http" "os" "strings" + "time" + "container-registry.com/harbor-satellite/logger" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/crane" ) -type RemoteImageSource struct { +type RemoteImageList struct { BaseURL string } @@ -23,115 +25,96 @@ type TagListResponse struct { Tags []string `json:"tags"` } -func NewRemoteImageSource(url string) *RemoteImageSource { - return &RemoteImageSource{BaseURL: url} +func RemoteImageListFetcher(ctx context.Context, url string) *RemoteImageList { + return &RemoteImageList{ + BaseURL: url, + } } -func (r *RemoteImageSource) SourceType() string { +func (r *RemoteImageList) Type(ctx context.Context) string { return "Remote" } -// retrieves a list of images from remote repository. -func (r *RemoteImageSource) List(ctx context.Context) ([]Image, error) { - url := r.BaseURL + "/tags/list" - authHeader, err := createAuthHeader() - if err != nil { - return nil, fmt.Errorf("error creating auth header: %w", err) - } - - body, err := fetchResponseBody(url, authHeader) - if err != nil { - return nil, fmt.Errorf("error fetching tags list from %s: %w", url, err) - } +func (client *RemoteImageList) List(ctx context.Context) ([]Image, error) { + log := logger.FromContext(ctx) + // Construct the URL for fetching tags + url := client.BaseURL + "/tags/list" - images, err := parseTagsResponse(body) - if err != nil { - return nil, fmt.Errorf("error parsing tags response from %s: %w", url, err) - } - - fmt.Println("Fetched", len(images), "images:", images) - return images, nil -} - -// fetches digest for a specific image. -func (r *RemoteImageSource) GetDigest(ctx context.Context, tag string) (string, error) { - imageRef := fmt.Sprintf("%s:%s", r.BaseURL, tag) - imageRef = cleanImageReference(imageRef) - - digest, err := fetchImageDigest(imageRef) - if err != nil { - return "", fmt.Errorf("error fetching digest for %s: %w", imageRef, err) - } - - return digest, nil -} - -// createAuthHeader generates authorization header for HTTP requests. -func createAuthHeader() (string, error) { + // Encode credentials for Basic Authentication username := os.Getenv("HARBOR_USERNAME") password := os.Getenv("HARBOR_PASSWORD") - if username == "" || password == "" { - return "", fmt.Errorf("environment variables HARBOR_USERNAME or HARBOR_PASSWORD not set") - } auth := base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) - return "Basic " + auth, nil -} -// fetchResponseBody makes an HTTP GET request and returns response body. -func fetchResponseBody(url, authHeader string) ([]byte, error) { + // Create a new HTTP request req, err := http.NewRequest("GET", url, nil) if err != nil { - return nil, fmt.Errorf("failed to create request for %s: %w", url, err) + log.Error().Msgf("failed to create request: %v", err) + return nil, err + } + + // Set the Authorization header + req.Header.Set("Authorization", "Basic "+auth) + + // Configure the HTTP client with a timeout + httpClient := &http.Client{ + Timeout: 5 * time.Second, } - req.Header.Set("Authorization", authHeader) - client := &http.Client{} - resp, err := client.Do(req) + // Send the HTTP request + log.Info().Msgf("Sending request to %s", url) + resp, err := httpClient.Do(req) if err != nil { - return nil, fmt.Errorf("failed to fetch response from %s: %w", url, err) + log.Error().Msgf("failed to send request: %v", err) + return nil, err } defer resp.Body.Close() + // Read the response body body, err := io.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("failed to read response body from %s: %w", url, err) + log.Error().Msgf("failed to read response body: %v", err) + return nil, err } - return body, nil -} - -// parseTagsResponse unmarshals the tags list response and constructs image references. -func parseTagsResponse(body []byte) ([]Image, error) { - var tagList TagListResponse - if err := json.Unmarshal(body, &tagList); err != nil { - return nil, fmt.Errorf("failed to unmarshal JSON response: %w", err) + // Unmarshal the JSON response + var tagListResponse TagListResponse + if err := json.Unmarshal(body, &tagListResponse); err != nil { + log.Error().Msgf("failed to unmarshal response: %v", err) + return nil, err } + // Prepare a slice to store the images var images []Image - for _, tag := range tagList.Tags { - images = append(images, Image{Name: fmt.Sprintf("%s:%s", tagList.Name, tag)}) - } + // Iterate over the tags and construct the image references + for _, tag := range tagListResponse.Tags { + images = append(images, Image{ + Name: fmt.Sprintf("%s:%s", tagListResponse.Name, tag), + }) + } return images, nil } -// cleanImageReference cleans up the image reference string. -func cleanImageReference(imageRef string) string { +func (client *RemoteImageList) GetDigest(ctx context.Context, tag string) (string, error) { + log := logger.FromContext(ctx) + // Construct the image reference + imageRef := fmt.Sprintf("%s:%s", client.BaseURL, tag) + // Remove extra characters from the URL imageRef = imageRef[strings.Index(imageRef, "//")+2:] - return strings.ReplaceAll(imageRef, "/v2", "") -} + imageRef = strings.ReplaceAll(imageRef, "/v2", "") -// fetchImageDigest retrieves digest for an image reference. -func fetchImageDigest(imageRef string) (string, error) { + // Encode credentials for Basic Authentication username := os.Getenv("HARBOR_USERNAME") password := os.Getenv("HARBOR_PASSWORD") + // Use crane.Digest to get the digest of the image digest, err := crane.Digest(imageRef, crane.WithAuth(&authn.Basic{ Username: username, Password: password, }), crane.Insecure) if err != nil { - return "", fmt.Errorf("failed to fetch digest for %s: %w", imageRef, err) + log.Error().Msgf("failed to get digest using crane: %v", err) + return "", nil } return digest, nil diff --git a/internal/store/in-memory-store.go b/internal/store/in-memory-store.go index 40cf50e..da25d3f 100644 --- a/internal/store/in-memory-store.go +++ b/internal/store/in-memory-store.go @@ -3,10 +3,10 @@ package store import ( "context" "fmt" - "log" "os" "strings" + "container-registry.com/harbor-satellite/logger" "github.com/google/go-containerregistry/pkg/crane" ) @@ -22,241 +22,207 @@ type inMemoryStore struct { type Storer interface { List(ctx context.Context) ([]Image, error) - Add(ctx context.Context, digest, image string) error - Remove(ctx context.Context, digest, image string) error + Add(ctx context.Context, digest string, image string) error + Remove(ctx context.Context, digest string, image string) error } type ImageFetcher interface { List(ctx context.Context) ([]Image, error) GetDigest(ctx context.Context, tag string) (string, error) - SourceType() string + Type(ctx context.Context) string } -func NewInMemoryStore(fetcher ImageFetcher) Storer { - return &inMemoryStore{ +func NewInMemoryStore(ctx context.Context, fetcher ImageFetcher) (context.Context, Storer) { + return ctx, &inMemoryStore{ images: make(map[string]string), fetcher: fetcher, } } -// List retrieves and synchronizes the list of images func (s *inMemoryStore) List(ctx context.Context) ([]Image, error) { - // fetch List of images - imageList, err := s.fetcher.List(ctx) - if err != nil { - return nil, err - } - - var changeDetected bool + log := logger.FromContext(ctx) + var imageList []Image + var change bool + err := error(nil) - switch s.fetcher.SourceType() { - case "File": - changeDetected, err = s.handleFileSource(ctx, imageList) - case "Remote": - changeDetected, err = s.handleRemoteSource(ctx, imageList) - default: - return nil, fmt.Errorf("unknown source type") - } + // Fetch images from the file/remote source + imageList, err = s.fetcher.List(ctx) if err != nil { return nil, err } - if changeDetected { - fmt.Println("Changes detected in store") - return s.getImageList(), nil - } else { - fmt.Println("No changes detected in the store") - return nil, nil - } -} - -// handleFileSource handles image from a file -func (s *inMemoryStore) handleFileSource(ctx context.Context, imageList []Image) (bool, error) { - var change bool - for _, img := range imageList { - // Check if image already exists in store - if _, exists := s.images[img.Name]; !exists { - // Add image to store - s.AddImage(ctx, img.Name) - change = true - } else { - fmt.Printf("Image %s already exists in store\n", img.Name) - } - } - - // Iterate over s.images and remove any image that is not found in imageList - for image := range s.images { - found := false + // Handle File and Remote fetcher types differently + switch s.fetcher.Type(ctx) { + case "File": for _, img := range imageList { - if img.Name == image { - found = true - break + // Check if the image already exists in the store + if _, exists := s.images[img.Name]; !exists { + // Add the image to the store + s.AddImage(ctx, img.Name) + change = true + } else { + log.Info().Msgf("Image %s already exists in the store", img.Name) } } - if !found { - if err := s.RemoveImage(ctx, image); err != nil { - return false, err + + // Iterate over s.images and remove any image that is not found in imageList + for image := range s.images { + found := false + for _, img := range imageList { + if img.Name == image { + found = true + break + } + } + if !found { + s.RemoveImage(ctx, image) + change = true } - change = true } - } - - // Empty and refill imageList with contents from s.images - imageList = imageList[:0] - for name, digest := range s.images { - imageList = append(imageList, Image{Name: name, Digest: digest}) - } - - // Print out entire store for debugging purposes - fmt.Println("Current store:") - for image := range s.images { - fmt.Printf("Image: %s\n", image) - } - - return change, nil -} -// handleRemoteSource handles images fetched from a remote source. -func (s *inMemoryStore) handleRemoteSource(ctx context.Context, imageList []Image) (bool, error) { - var change bool - // Trim the imageList elements to remove project name from image reference - for i, img := range imageList { - parts := strings.Split(img.Name, "/") - if len(parts) > 1 { - // Take second part as new Reference - imageList[i].Name = parts[1] + // Empty and refill imageList with the contents from s.images + imageList = imageList[:0] + for name, digest := range s.images { + imageList = append(imageList, Image{Name: name, Digest: digest}) } - } - // iterate over imageList and call GetDigest for each tag - for _, img := range imageList { - // Split image reference to get tag - tagParts := strings.Split(img.Name, ":") - // Check if there is a tag part, min length is 1 char - if len(tagParts) < 2 { - fmt.Println("No tag part found in image reference") - } - // Use last part as tag - tag := tagParts[len(tagParts)-1] - // Get digest for tag - digest, err := s.fetcher.GetDigest(ctx, tag) - if err != nil { - return false, err + + // Print out the entire store for debugging purposes + log.Info().Msg("Current store:") + for image := range s.images { + log.Info().Msgf("Image: %s", image) } - // Check if image exists and matches the digest - if !(s.checkImageAndDigest(digest, img.Name)) { - change = true + case "Remote": + // Trim the imageList elements to remove the project name from the image reference + for i, img := range imageList { + parts := strings.Split(img.Name, "/") + if len(parts) > 1 { + // Take the second part as the new Reference + imageList[i].Name = parts[1] + } } + // iterate over imageList and call GetDigest for each tag + for _, img := range imageList { + // Split the image reference to get the tag + tagParts := strings.Split(img.Name, ":") + // Check if there is a tag part, min length is 1 char + if len(tagParts) < 2 { + log.Error().Msgf("Invalid image reference: %s", img.Name) + } + // Use the last part as the tag + tag := tagParts[len(tagParts)-1] + // Get the digest for the tag + digest, err := s.fetcher.GetDigest(ctx, tag) + if err != nil { + return nil, err + } - } + // Check if the image exists and matches the digest + if !(s.checkImageAndDigest(ctx, digest, img.Name)) { + change = true + } - // Create imageMap filled with all images from remote imageList - imageMap := make(map[string]bool) - for _, img := range imageList { - imageMap[img.Name] = true - } + } + + // Create imageMap filled with all images from remote imageList + imageMap := make(map[string]bool) + for _, img := range imageList { + imageMap[img.Name] = true + } - // Iterate over in memory store and remove any image that is not found in imageMap - for digest, image := range s.images { - if _, exists := imageMap[image]; !exists { - if err := s.Remove(ctx, digest, image); err != nil { - return false, err + // Iterate over in memory store and remove any image that is not found in imageMap + for digest, image := range s.images { + if _, exists := imageMap[image]; !exists { + s.Remove(ctx, digest, image) + change = true } + } + // Print out the entire store for debugging purposes + log.Info().Msg("Current store:") + for digest, imageRef := range s.images { + log.Info().Msgf("Image: %s, Digest: %s", imageRef, digest) + } - change = true + // Empty and refill imageList with the contents from s.images + imageList = imageList[:0] + for _, name := range s.images { + imageList = append(imageList, Image{Digest: "", Name: name}) } - } - // Print out the entire store for debugging purposes - fmt.Println("Current store:") - for digest, imageRef := range s.images { - fmt.Printf("Digest: %s, Image: %s\n", digest, imageRef) - } - // Empty and refill imageList with contents from s.images - imageList = imageList[:0] - for _, name := range s.images { - imageList = append(imageList, Image{Digest: "", Name: name}) } - - return change, nil -} - -// getImageList converts the in-memory store to a list of Image structs. -func (s *inMemoryStore) getImageList() []Image { - var imageList []Image - // Empty and refill imageList with contents from s.images - for _, name := range s.images { - imageList = append(imageList, Image{Digest: "", Name: name}) + if change { + log.Info().Msg("Changes detected in the store") + change = false + return imageList, nil + } else { + log.Info().Msg("No changes detected in the store") + return nil, nil } - return imageList } -// Add image and its digest to store func (s *inMemoryStore) Add(ctx context.Context, digest string, image string) error { - // Check if image already exists in store + log := logger.FromContext(ctx) + // Check if the image already exists in the store if _, exists := s.images[digest]; exists { - fmt.Printf("Image: %s, digest: %s already exists in store.\n", image, digest) - return fmt.Errorf("image %s already exists in store", image) + log.Info().Msgf("Image: %s, digest: %s already exists in the store.", image, digest) + return fmt.Errorf("image %s already exists in the store", image) } else { + // Add the image and its digest to the store s.images[digest] = image - fmt.Printf("Successfully added image: %s, digest: %s\n", image, digest) + log.Info().Msgf("Successfully added image: %s, digest: %s", image, digest) return nil } } -// Add image to store -func (s *inMemoryStore) AddImage(ctx context.Context, image string) { - if _, exists := s.images[image]; exists { - fmt.Printf( - "Warning: Image %s already exists in store. Proceeding with the addition.\n", - image, - ) - } +func (s *inMemoryStore) AddImage(ctx context.Context, image string) error { + log := logger.FromContext(ctx) + // Add the image to the store s.images[image] = "" - fmt.Printf("Added image: %s\n", image) + log.Info().Msgf("Added image: %s", image) + return nil } -// Removes image from store func (s *inMemoryStore) Remove(ctx context.Context, digest string, image string) error { - // Check if image exists in store + log := logger.FromContext(ctx) + // Check if the image exists in the store if _, exists := s.images[digest]; exists { - // Remove image and its digest from store + // Remove the image and its digest from the store delete(s.images, digest) - fmt.Printf("Successfully removed image: %s, digest: %s\n", image, digest) + log.Info().Msgf("Successfully removed image: %s, digest: %s", image, digest) return nil } else { - fmt.Printf("Failed to remove image: %s, digest: %s. Not found in the store.\n", image, digest) + log.Warn().Msgf("Failed to remove image: %s, digest: %s. Not found in the store.", image, digest) return fmt.Errorf("image %s not found in the store", image) } } -// Remove image from store func (s *inMemoryStore) RemoveImage(ctx context.Context, image string) error { - if _, exists := s.images[image]; !exists { - return fmt.Errorf("image %s not found in the store", image) - } + log := logger.FromContext(ctx) + // Remove the image from the store delete(s.images, image) - fmt.Printf("Removed image: %s\n", image) + log.Info().Msgf("Removed image: %s", image) return nil } // TODO: Rework complicated logic and add support for multiple repositories -// checkImageAndDigest checks if image exists in store and if the digest matches the image reference -func (s *inMemoryStore) checkImageAndDigest(digest string, image string) bool { - // Check if received image exists in store +// checkImageAndDigest checks if the image exists in the store and if the digest matches the image reference +func (s *inMemoryStore) checkImageAndDigest(ctx context.Context, digest string, image string) bool { + log := logger.FromContext(ctx) + + // Check if the received image exists in the store for storeDigest, storeImage := range s.images { if storeImage == image { - // Image exists, now check if digest matches + // Image exists, now check if the digest matches if storeDigest == digest { - // Digest exists and matches current image's - // Remove what comes before ":" in image and set it to tag variable + // Digest exists and matches the current image's + // Remove what comes before the ":" in image and set it to tag variable tag := strings.Split(image, ":")[1] localRegistryDigest, err := GetLocalDigest(context.Background(), tag) if err != nil { - fmt.Println("Error getting digest from local registry:", err) + log.Error().Msgf("Error getting digest from local registry: %v", err) return false } else { - // Check if digest from local registry matches digest from store + // Check if the digest from the local registry matches the digest from the store if digest == localRegistryDigest { return true } else { @@ -264,53 +230,40 @@ func (s *inMemoryStore) checkImageAndDigest(digest string, image string) bool { } } } else { - // Digest exists but does not match current image reference - if err := s.Remove(context.Background(), storeDigest, storeImage); err != nil { - log.Println("Error: %w", err) - return false - } - - if err := s.Add(context.Background(), digest, image); err != nil { - log.Fatalf("Error in adding image to store: %v", err) - } + // Digest exists but does not match the current image reference + s.Remove(context.Background(), storeDigest, storeImage) + s.Add(context.Background(), digest, image) return false } } } - // Try to add image to store - // Add will check if it already exists in store before adding + // Try to add the image to the store + // Add will check if it already exists in the store before adding // If adding was successful, return true, else return false err := s.Add(context.Background(), digest, image) return err != nil } func GetLocalDigest(ctx context.Context, tag string) (string, error) { + log := logger.FromContext(ctx) + zotUrl := os.Getenv("ZOT_URL") userURL := os.Getenv("USER_INPUT") - // Remove extra characters from URLs + + // Remove extra characters from the URLs userURL = userURL[strings.Index(userURL, "//")+2:] userURL = strings.ReplaceAll(userURL, "/v2", "") - regUrl := removeHostName(userURL) - // Construct URL for fetching digest - url := zotUrl + "/" + regUrl + ":" + tag + // Construct the URL for fetching the digest + url := zotUrl + "/" + userURL + ":" + tag - // Use crane.Digest to get digest of image + // Use crane.Digest to get the digest of the image digest, err := crane.Digest(url) if err != nil { + log.Error().Msgf("Error getting digest using crane: %v", err) return "", fmt.Errorf("failed to get digest using crane: %w", err) } return digest, nil } - -// Split imageName by "/" and take only parts after hostname -func removeHostName(imageName string) string { - parts := strings.Split(imageName, "/") - if len(parts) > 1 { - return strings.Join(parts[1:], "/") - } - - return imageName -} diff --git a/logger/logger.go b/logger/logger.go new file mode 100644 index 0000000..78664dc --- /dev/null +++ b/logger/logger.go @@ -0,0 +1,50 @@ +package logger + +import ( + "context" + "os" + + "github.com/rs/zerolog" +) + +type contextKey string + +const loggerKey contextKey = "logger" + +// AddLoggerToContext creates a new context with a zerolog logger for stdout adn stderr and sets the global log level. +func AddLoggerToContext(ctx context.Context, logLevel string) context.Context { + // Set log level to configured value + switch logLevel { + case "debug": + zerolog.SetGlobalLevel(zerolog.DebugLevel) + case "info": + zerolog.SetGlobalLevel(zerolog.InfoLevel) + case "warn": + zerolog.SetGlobalLevel(zerolog.WarnLevel) + case "error": + zerolog.SetGlobalLevel(zerolog.ErrorLevel) + case "fatal": + zerolog.SetGlobalLevel(zerolog.FatalLevel) + case "panic": + zerolog.SetGlobalLevel(zerolog.PanicLevel) + default: + zerolog.SetGlobalLevel(zerolog.InfoLevel) + } + + logger := zerolog.New(os.Stderr).With().Timestamp().Logger() + ctx = context.WithValue(ctx, loggerKey, &logger) + + return ctx +} + +// FromContext extracts the main logger from the context. +func FromContext(ctx context.Context) *zerolog.Logger { + logger, ok := ctx.Value(loggerKey).(*zerolog.Logger) + if !ok { + // Fallback to a default logger if none is found in the context. + defaultLogger := zerolog.New(os.Stderr).With().Timestamp().Logger() + defaultLogger.Error().Msg("Failed to extract logger from context") + return &defaultLogger + } + return logger +} diff --git a/main.go b/main.go index 82494bc..78ff515 100644 --- a/main.go +++ b/main.go @@ -2,9 +2,10 @@ package main import ( "context" + "encoding/json" "errors" "fmt" - "log" + "net" "net/http" "net/http/pprof" "net/url" @@ -15,24 +16,44 @@ import ( "syscall" "time" - "container-registry.com/harbor-satellite/internal/images" "container-registry.com/harbor-satellite/internal/replicate" "container-registry.com/harbor-satellite/internal/satellite" "container-registry.com/harbor-satellite/internal/store" + "container-registry.com/harbor-satellite/logger" "container-registry.com/harbor-satellite/registry" "golang.org/x/sync/errgroup" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/spf13/viper" - _ "github.com/joho/godotenv/autoload" + "github.com/joho/godotenv" ) +type ImageList struct { + RegistryURL string `json:"registryUrl"` + Repositories []struct { + Repository string `json:"repository"` + Images []struct { + Name string `json:"name"` + } `json:"images"` + } `json:"repositories"` +} + func main() { + viper.SetConfigName("config") + viper.SetConfigType("toml") + viper.AddConfigPath(".") + if err := viper.ReadInConfig(); err != nil { + fmt.Println("Error reading config file, ", err) + fmt.Println("Exiting Satellite") + os.Exit(1) + } + err := run() if err != nil { - log.Fatalf("Error running satellite: %v", err) + os.Exit(1) } } @@ -43,13 +64,11 @@ func run() error { defer cancel() g, ctx := errgroup.WithContext(ctx) - viper.SetConfigName("config") - viper.SetConfigType("toml") - viper.AddConfigPath(".") + logLevel := viper.GetString("log_level") + ctx = logger.AddLoggerToContext(ctx, logLevel) - if err := viper.ReadInConfig(); err != nil { - return fmt.Errorf("fatal error config file: %w", err) - } + log := logger.FromContext(ctx) + log.Info().Msg("Satellite starting") mux := http.NewServeMux() mux.Handle("/metrics", promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{})) @@ -83,113 +102,126 @@ func run() error { if bringOwnRegistry { registryAdr := viper.GetString("own_registry_adr") - os.Setenv("ZOT_URL", registryAdr) - fmt.Println("Registry URL set to:", registryAdr) + // Validate registryAdr format + ip := net.ParseIP(registryAdr) + if ip == nil { + log.Error().Msg("Invalid IP address") + return errors.New("invalid IP address") + } + if ip.To4() != nil { + log.Info().Msg("IP address is valid IPv4") + } else { + log.Error().Msg("IP address is IPv6 format and unsupported") + return errors.New("IP address is IPv6 format and unsupported") + } + registryPort := viper.GetString("own_registry_port") + os.Setenv("ZOT_URL", registryAdr+":"+registryPort) } else { + log.Info().Msg("Launching default registry") g.Go(func() error { - launch, err := registry.LaunchRegistry() + launch, err := registry.LaunchRegistry(viper.GetString("zotConfigPath")) if launch { cancel() - return nil - } else { - log.Println("Error launching registry :", err) + return err + } + if err != nil { cancel() + log.Error().Err(err).Msg("Failed to launch default registry") return err } + return nil }) } - gc := viper.GetString("ground_control") - _, err := url.Parse(gc) - if err != nil { - return fmt.Errorf("invalid ground_control URL: %v", err) - } - input := viper.GetString("url_or_file") - // Attempt to parse the input as a URL parsedURL, err := url.Parse(input) - // If parsing as URL fails or no scheme detected, treat it as a file path if err != nil || parsedURL.Scheme == "" { - // Treat input as a file path - err = processFilePath(input) + if strings.ContainsAny(input, "\\:*?\"<>|") { + log.Error().Msg("Path contains invalid characters. Please check the configuration.") + return err + } + dir, err := os.Getwd() if err != nil { - log.Fatalf("Error in processing file path: %v", err) + log.Error().Err(err).Msg("Error getting current directory") + return err } - } + absPath := filepath.Join(dir, input) + if _, err := os.Stat(absPath); os.IsNotExist(err) { + log.Error().Err(err).Msg("No URL or file found. Please check the configuration.") + return err + } + log.Info().Msg("Input is a valid file path.") + fetcher = store.FileImageListFetcher(ctx, input) + os.Setenv("USER_INPUT", input) - imgUrl, err := images.GetImages(gc) - if err != nil { - return fmt.Errorf("error processing ground_control endpoint: %v", err) + // Parse images.json and set environment variables + file, err := os.Open(absPath) + if err != nil { + log.Error().Err(err).Msg("Error opening images.json file") + return err + } + defer file.Close() + + var imageList ImageList + if err := json.NewDecoder(file).Decode(&imageList); err != nil { + log.Error().Err(err).Msg("Error decoding images.json file") + return err + } + + registryURL := imageList.RegistryURL + registryParts := strings.Split(registryURL, "/") + if len(registryParts) < 3 { + log.Error().Msg("Invalid registryUrl format in images.json") + return errors.New("invalid registryUrl format in images.json") + } + registry := registryParts[2] + os.Setenv("REGISTRY", registry) + + if len(imageList.Repositories) > 0 { + repository := imageList.Repositories[0].Repository + os.Setenv("REPOSITORY", repository) + } else { + log.Error().Msg("No repositories found in images.json") + return errors.New("no repositories found in images.json") + } + } else { + log.Info().Msg("Input is a valid URL.") + fetcher = store.RemoteImageListFetcher(ctx, input) + os.Setenv("USER_INPUT", input) + parts := strings.SplitN(input, "://", 2) + scheme := parts[0] + "://" + os.Setenv("SCHEME", scheme) + registryAndPath := parts[1] + registryParts := strings.Split(registryAndPath, "/") + registry := registryParts[0] + os.Setenv("REGISTRY", registry) + apiVersion := registryParts[1] + os.Setenv("API_VERSION", apiVersion) + repository := registryParts[2] + os.Setenv("REPOSITORY", repository) + image := registryParts[3] + os.Setenv("IMAGE", image) } - repoUrl, err := processURL(imgUrl) + + err = godotenv.Load() if err != nil { - return fmt.Errorf("error in processing URL: %v", err) + log.Error().Err(err).Msg("Error loading.env file") + return err } - fetcher = store.NewRemoteImageSource(repoUrl) - storer := store.NewInMemoryStore(fetcher) - replicator := replicate.NewReplicator() - s := satellite.NewSatellite(storer, replicator) + ctx, storer := store.NewInMemoryStore(ctx, fetcher) + replicator := replicate.NewReplicator(ctx) + s := satellite.NewSatellite(ctx, storer, replicator) g.Go(func() error { return s.Run(ctx) }) + log.Info().Msg("Satellite running") err = g.Wait() if err != nil { + log.Error().Err(err).Msg("Error running satellite") return err } return nil } - -func processFilePath(input string) error { - // Check for invalid characters in file path - if strings.ContainsAny(input, "\\:*?\"<>|") { - fmt.Println("Path contains invalid characters. Please check the configuration.") - return fmt.Errorf("invalid file path") - } - dir, err := os.Getwd() - if err != nil { - fmt.Println("Error getting current directory:", err) - return err - } - absPath := filepath.Join(dir, input) - if _, err := os.Stat(absPath); os.IsNotExist(err) { - fmt.Println("No URL or file found. Please check the configuration.") - return fmt.Errorf("file not found") - } - fmt.Println("Input is a valid file path.") - os.Setenv("USER_INPUT", input) - - return nil -} - -func processURL(input string) (string, error) { - fmt.Println("Input is a valid URL.") - - // Set environment variables - os.Setenv("USER_INPUT", input) - - // Extract URL components - parts := strings.SplitN(input, "://", 2) - scheme := parts[0] + "://" - os.Setenv("SCHEME", scheme) - - hostAndPath := parts[1] - hostParts := strings.Split(hostAndPath, "/") - host := hostParts[0] - os.Setenv("HOST", host) - - apiVersion := "v2" - os.Setenv("API_VERSION", apiVersion) - - registry := hostParts[1] - os.Setenv("REGISTRY", registry) - - repository := hostParts[2] - os.Setenv("REPOSITORY", repository) - - url := fmt.Sprintf("%s%s/%s/%s/%s", scheme, host, apiVersion, registry, repository) - - return url, nil -} diff --git a/registry/config.json b/registry/config.json index 34e2b82..3401d70 100644 --- a/registry/config.json +++ b/registry/config.json @@ -1,13 +1,13 @@ { - "distSpecVersion": "1.1.0", - "storage": { - "rootDirectory": "/tmp/zot" - }, - "http": { - "address": "127.0.0.1", - "port": "8585" - }, - "log": { - "level": "" - } + "distSpecVersion": "1.1.0", + "storage": { + "rootDirectory": "./zot" + }, + "http": { + "address": "127.0.0.1", + "port": "8585" + }, + "log": { + "level": "info" + } } diff --git a/registry/launch-registry.go b/registry/launch-registry.go index b4cac8d..1d7c536 100644 --- a/registry/launch-registry.go +++ b/registry/launch-registry.go @@ -1,24 +1,20 @@ package registry import ( - "log" - "zotregistry.dev/zot/pkg/cli/server" ) -func LaunchRegistry() (bool, error) { - log.Println("Launching Registry") +func LaunchRegistry(zotConfigPath string) (bool, error) { // Create the root command for the server rootCmd := server.NewServerRootCmd() // Set the arguments - rootCmd.SetArgs([]string{"serve", "./registry/config.json"}) + rootCmd.SetArgs([]string{"serve", zotConfigPath}) // Execute the root command err := rootCmd.Execute() if err != nil { - log.Fatalf("Error executing server root command: %v", err) return false, err }