oras-go-1.1.1/0000755000175000017500000000000014212212744012406 5ustar nileshnileshoras-go-1.1.1/go.sum0000644000175000017500000044341514212212744013554 0ustar nileshnileshbazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v56.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest v0.11.20/go.mod h1:o3tqFY+QR40VOlk+pV4d77mORO64jOXSgEnPQgLK6JY= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/adal v0.9.15/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= github.com/Microsoft/hcsshim v0.9.2 h1:wB06W5aYFfUB3IvootYAY2WnOmIdgPGfqSI6tufQNnY= github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= github.com/containerd/containerd v1.6.1 h1:oa2uY0/0G+JX4X7hpGCYvkp9FjUancz56kSNnb1sG3o= github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= github.com/containerd/go-cni v1.1.0/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= github.com/containerd/go-cni v1.1.3/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= github.com/containerd/imgcrypt v1.1.3/go.mod h1:/TPA1GIDXMzbj01yd8pIbQiLdQxed5ue1wb8bP7PQu4= github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y= github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE= github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684 h1:DBZ2sN7CK6dgvHVpQsQj4sRMCbWTmd17l+5SUCjnQSY= github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684/go.mod h1:UfCu3YXJJCI+IdnqGgYP82dk2+Joxmv+mUTVBES6wac= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.11+incompatible h1:tXU1ezXcruZQRrMP8RN2z9N91h+6egZTS1gsPsKantc= github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.11+incompatible h1:OqzI/g/W54LczvhnccGqniFoQghHx3pklbLuhfXpqGo= github.com/docker/docker v20.10.11+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 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.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f h1:2+myh5ml7lgEU/51gbeLHfKGNfgEQQIWrlbdaOsidbQ= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 h1:yH0SvLzcbZxcJXho2yh7CqdENGMQe73Cw3woZBpPli0= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug= github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d h1:62NvYBuaanGXR2ZOfwDFkhhl6X1DUgf8qg3GuQvxZsE= golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 h1:Et6SkiuvnBn+SgrSYXs/BrUpGB4mbdwt4R3vaPIlicA= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 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.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= 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-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= k8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y= k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= k8s.io/component-base v0.22.5/go.mod h1:VK3I+TjuF9eaa+Ln67dKxhGar5ynVbwnGrUiNF4MqCI= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= k8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= oras-go-1.1.1/README.md0000644000175000017500000000155514212212744013673 0ustar nileshnilesh# ORAS Go library [![GitHub Actions status](https://github.com/oras-project/oras-go/workflows/build/badge.svg)](https://github.com/oras-project/oras-go/actions/workflows/build.yml?query=workflow%3Abuild) [![Go Report Card](https://goreportcard.com/badge/oras.land/oras-go)](https://goreportcard.com/report/oras.land/oras-go) [![GoDoc](https://godoc.org/github.com/oras.land?status.svg)](https://godoc.org/oras.land/oras-go) ![ORAS](https://github.com/oras-project/oras-www/raw/main/docs/assets/images/oras.png) ## Docs Documentation for the ORAS Go library is located on the project website: [oras.land/client_libraries/go](https://oras.land/client_libraries/go/) ## Code of Conduct This project has adopted the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for further details. oras-go-1.1.1/testdata/0000755000175000017500000000000014212212744014217 5ustar nileshnileshoras-go-1.1.1/testdata/charts/0000755000175000017500000000000014212212744015503 5ustar nileshnileshoras-go-1.1.1/testdata/charts/chartmuseum-1.8.2.tgz0000644000175000017500000002515414212212744021241 0ustar nileshnilesh‹ÿ)+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=Helmì½osÛ¸8~õ*pÎÞmÒO¬$Mÿçzs®ã¦¾Mlí´Ÿ½N'¡%ÚæFµ"•4Ûä÷ÚC’(YvœÔÛö¾S=h‰AA½‰å<4™ïµÔo÷†ÌƒÙ賿¿¿ÿòùsü¿üÿþ‹—‡Ùo|pøìéþ¿Àf±Xò$B’ø_ö÷¿N¹sÿG±÷4Œ‡ ¸:pHeÜ}ǧ‹Y$ñÕ;.$Üð$~Â;Ì™4â‚Iß83>§ ˜I‰ÆÞÞ”ÉY2v=>ß›Ñ`¾gñ›Ã<3-“kW—N=JÊÊŠ{s"$÷>åOÝ(œ:—ôæšÇ¾h8u°[¨ƒª›¾Óˆ;sÂBIXHc,Oç„ à Obþ·Ç}:‰©˜¹Œ;!QÊ^ÖÏkÍhðÿöžø‚*|ó*Ù뼂…ÝOÕ«b ÷Å7Wéȸ¯Ü§ÎFÆß&ë *6/VÏÿƒgûÏÊóÿÅóŸóÿ›<1æ‘OBÙ€GȘH:½i8ò&¢ ð `áô,ò‰¤@lÿ­ŠÌÉç³\q@°ï°9™âÇ8 ^.ÈI¦ ¸Ò J‚ ÏæÝ4 3érÙ© ¡thx¥`ª©ªÞ!yL¦ÆÄ»¤¡¿  aL‡ø¤÷H° $`c2&»@æä/î”ói@waμ˜ >‘o8ê šÇmSÍ4Á…€qâ]R ’cƒ4•'§ Ë˜ØðΛ'7Í7Íó7g­ßÚ£÷(¦öù«öí·6,lièGœ…_o»{ÔïuºÆ‚ÆW4Á| 4ôâ\€S39›¯jÀš×IšíáÓç/€ÇðÛ鰉ᰵ¸r$plWwì´ù¿½îÆamÅQˆéT†O–£\dÐ>îôº)H‡D²+ªÀ¤£ZUñ¡UâóÔ[Éòzò¬¤Êq¯w|Ò^ŸÐëƒ,Zé ¸–WBÍf÷JÀ§Ö 7ì½·zÝQ³ÓmÖÀø¡°‚7*#’x—+a÷úíîpÔlýö ¼ {cçp²¾,PäêˆÈ™BŒ@« %Œ“Ð("§uË r%Ë[Ýn¶ÚƒŒ»&<žÃ„ÑÀ‡ëófpÍ‚@ œ?3êcrfˆP0j&Bh½kFçýÞptþ¶78=ÛiŸw›§m³t=´(æW4$¡Gëzï—´¥ª™¦zE¡¨R!©¯—SÍ~I ™Dð7.Œf|:!I Á§‘œ°Û‚…Ó€b1i„ÂÂ>j÷GïÔJmäÅŒ_ƒOÇÉæT2¥Â{svÜ€ D&´%ŠéãÄ“IL}øTð‡à!:éŸÿϰ×m€Œ“´žÏ„Ò jeúôsÝ#ÞŒ¢Æ©›ë ›oNÚçÃQsÔ~Û9i‹m§0ú1ŸS9£‰€9•1óD¡úi{4è´†•Í“ €˜'’ 3I¨×LÎ`D¬¥Ùï à׆yŒ*.#)Ä´žD'>Õ³¨yrÒûpÞ{ß|tFíb'ÈXð ‘’8À¡tåô/,Ô4É©¡òlpÒ(`@BÞÌy"à¸=R“%&q¹žÑH¢?).g£wçÍn¯ûûiïlx~ÜÑT d×1†G?Kœ¥‰^wÔþçè¼ß½Ë§pL‚€ Ô’°9“Ó+Õ1ò Ó=jÿóü¤sÚåü†ã®eÓ‚ÊSŸ Ø(¹¢0Hx‰ÀY8§sß躚‹[ÍÖ»lÑ"¾S³e€ ¿3ÂöŒ Ùˆx,­ZçƒöQgxÞ<:Ê„¦®åIzh ¨§¦™H+ú„Ô“ pŽÞèΡ\Hö{Gç~„$2nÄýNßÔ‹©L ©Ý®Úëa'ÇD0·Ä84”ÌÃÑÅÂošÃNëôlh {"„Úþ> B¿9¦Ž[ýŒZÄóÔfg3Ê-,cßÖ }ÔîŽ:Í“¡žáÒÅôÏ„ÅTá“j 'lšÄ9 6Õzƒ£†ãÓ(à7s"]¶¶´]Aoà(û$ ¹Ôüî€ýW¾Ü©šî¥q7ëdLƒåŸçDz3UfY³Cª@k ¿ˆû‘R">ÌÕRLÆ<‘p™ŒéSFæŠË”ÀWŠñ<Å¿jÑ>¬2 ü!Ç ÷ÒªWFæ®VSɵÀr1W›0õoÄ¡c¶œ‡?±{Ù¯$BÒYs ¼ô¯tµ¦N?£ôy,ðjÿ•âóû_TvØ¢¹SmVÑn–¢¡~xQÒ€ƒýý¹þSOñ<}uÊÔ+ÅRT‹¿*—~ñì”9Ns5ŠªyvEC*„æN2ÉHpDr3¤}Ñ€çø)¢1ã~öò@‹'Éæ”'2oE‚Ã3šÅTÌxà§ï'„IL­÷‡¸ï%>û®8¤ãÞÔóYááÅTíÙ3ñ¿¥Ýq¶à¬s´wÜ9RC±¯–i)JBAÍÚ±×pu:[Z¡Ê”YÜ÷+yKNÕš¦wõ@B_qþUº ÎäòB .Ür+j&$ª+Z=u¡3Ñz¡aMg |N„\šQΘØߨŠÔKb&oZz9CÖ+¿SÒZÇ<‰÷G1õå=5óJ¤Ëk>~r2™¨±¼ÑŸ ¶4ÔÓ‹†JÅðsë}Ê}Ú@ ð!f’öBO}ì/Ú€WÇì~µµM˜“0!Ap£~)õ¡Ÿ6.á=’9E"·Âæ©ÌAò °Ðt3f³µ¥èëÓ ©¿ ý÷-˜'Bª¥O³‹Ÿ·<¦%Á®tk©Ú;æ n¶€~fB‘qh8‹S­­xÍF¤!q2Œ€_»H˜ÿ´_ýWVCèýj­^«ªT«í…Ýè‚ü›Ì™§%°RèX8Í %¡Á¶­Ù°^˜[YË4v¶§ÑSà!4? UHè“Ø\8Îñoí]õþz ‡j“´ƒË†h4ð­®®‘ùÑ•–H ¼ª>¥ €G"â)>Ç¿ 2ìjÖ'"«‰Š@ú—Ò+•ÀÙ‚N8EuMÏ}âØj¯;L©˜Q%vJAèéãlmá²`&“^"ÀW3·†ÿóÚ³ „½„8ö²f€©¥7©¤Âe|ÏàézšÞá”…Ÿ+ Ê@Ô‰‡<¨fÝr,”¢ªÆ@á§fõÀ6É 2‘’&ÅVUÍð´dµëó9a¡R Ò¯ê©Ãž1CÞY;e8ŽN†%­nk †¨ÒŠLtd"#•!,D¶ÇÎEÄ£)Ú2©ºQ‹»åcºf º R\ ]­/é¬ZÔš¾oDNr,Ô2eV˜*ô€£ul7k \m=U›Uf‚ÍÙ2À@ΈL×=½Zfúò.BP›U[}špµL)Th(cF…3õ¢Fi'P1!·… Ÿ ”¥-ÓÐãŠ?²ýw>Ó‘Kz£k*õÀB—Ë^Ò›†/_íH \„÷½(~>ëcŸÉH:"©ØëöFí¡+?Ë´qÏùÿááóòùÿóƒŸçÿßæyòú%‚âJC$SjÙõŒÔ2•2cªÄ—Þ¥SžÅU»Q¸»spËñXB·wÔ>ï÷£×¿l«Õ“L©„z=[GàËp»àv³·wwPç(á”ÂñºöÅU™« ŠûŸÜtûzWK÷ÄBAJg@a·ãN’ P ÖÀ…»»~~ ;[< G&éÑ3#cÑ¢øÎüqWC¼GAcoïƒFã—Œ^ªAC]^¹<¢¡kòàînO -Ð 'þ£Š­¤œqh(’Ø,“ÚR¾â"÷.ÿjéÑuIœÙ¢Ì†#йG)®åOž¨–?éÍô¢ˆ”‚D)Ö °‡A\ykÂõÚx)¶ÏFؼï´*`í–×mÖf“_¿¤ÜXå]T1 ‹î~]à‘Ù†Åé¨Ú&¸»[}zƒ b¼ë ˳õ;“#Õ©WE!½1²TϪÌÈVƒ5_¿§ƒJ”Œ¸¿®`  F¢èõR’æä¬aáXCy]†¨ ,Ss*‰ÚÒ#´EÁtðô¥»ïî»Wû¯ö×¢dSXÑ¡>áñµÚÿ’Ín-:˜Ñ}õû{/µ?äS­ÿÏhÑX¸2Ú€+Ø=úßóÃeÿ¯ûOêßâQócï :ƒ›’ËŒôT/´ö¡Ëèß®ãŒð¸›^¨Uúš ŸÏµ™H½ë1ãIàƒ›T0©ÿµ ûõÿ€(Hø\¶»ƒvLŸˆl×wvñ/]×Q›vúYéðâ'žT|ê8}“9•èÔéÔ3Q£šî]Ñ8fÚPDézÚj=¥œ`çð XΘ€)»¢!¾,ÁéãyqôÿÖÇiÀÇ$(”9ÆW戹g˜L°Œþ¿NZÆÀº(Ò™DQÀ¨<öєрÚÔn þ‘ÿÀ®ýÃT‡ÀÔWsœ3F½_µ½¹¦¸aõzQ¿»«ýê<ÙS?P¸j¦©(l¾ÿbšl¼ÎlžÛ>óäN©×À_ððÕ*îj¿t\ª†5¯9âBÍZ-mÞ›¼øbéÅÑÎK‹d²¢´/ •Åâ¿, o^Ç©ñ¢˜…ÒtñÓÔÀÆ5Ìb•[ø5ádœ„<†?ÙÜ4P«×²j¬§kâÞ'Õª¬y/&É8¸$d&fÚ£¤Çxj{çñù˜¡­\Ÿøë}V“µRHàçYR¤äØ„´HaÝ'1Êå–I´Ü}’£\îG‘¶¿–É*lLŠlãÔš@íßDýßD­¤åZ2fg)G¬%hŠCºŽ°YVc¹À)óZBgY•o*xRÉc´>­q³Ã.DD¤{{ýÎ&tb*iŽÎŠé·@b ô¥eú¨²ÍÊ}¼WÂH÷ƒDQcù5ßMé¾­(Š¿c:1Åg4fg‘½‡ûÇ-ü™p‰;#e {»P•˜¯j<¥2±d3JOã׿:NGêSö9 Ùœ@…G"%±Õr¨ím¿e§\ùˆ´?“yPã&Øpìöëûî3÷ù}ôÏ)”–е †Ú?jP;¯¥³ÕD*Á-”ç¶¥1”™ð{+Ü?ØS½ÿ˽±6 tOüÏþ³/Ëû¿ç‡/îÿ¾ÅcÇÿÑÏ’†èÖºwu0¦’8—,ô–ÿ“JÑF³öå °Ð ¥Ñ®ä&æ(ùÈ'ó [Ô,ž³JÂ-:“†ži(ééþ²f¤·[aN$*š5ë–Y)¶ÕØ™DD=ú„îZd8v¼•îPqµØjúm 1Õ^#ï˜<¾9as&ÑwlyïrGGÓnêèdB¸¤7;Éi»¼ûœ «%@¶øè×dÔës”½14t­fW9˜”}RŸ‡µ×õ´ÂÚëðFyìÕR>C‡ž‡´¦Î‘íåÐꉎѳ™߸yÈÜÝ5>K2]€Ò·"ö*äá|v=ŒåÃÅ„SªµÊ]øý”BZ¶[dU{»m:ä)¿c¶:žªªÙ¸d®.ÚïAÒMVhQ‹?×ÀU†”…M`û6æó†õÒ8›è¤øÖ¼ï9{p-¦z‘«]NRŸÁ*¼_x³ß?é´š£N¯kûˆ/¼¶G¥·gçúI»û1S›ˆ ÆqêM•µ—΂{Ïžf”&æÅFÖà¶(­µ»¹›¢“ËêƒýâzaÁ|3|Ê?Ž ´sé©Rg Ã:Wo´ÊØ¡Ìë)÷h¢hMY´‹RgõÔ‘úÔ‹[½@›`®˜¨‡"/¤¡öøTsN©‰…l_õ¼VÁƒ½º‘W6ÈU-¤®î––:¿%dË«>n»Ú?¾µpã\íš·SüÜ}¨&jï/i±(¸—´·FÝûÈX h°›,E:<ˆœé„ZP%Kó5çS‹¡ñŸùþk×)OEO½X Rªd·¹÷¾Ü)M.5ˆ¶BªVü#›( ›0u«âº*RýáR%ÅÄö .7™«,î’¼¢Ê`;U?h¿¥tô°G§®Õ%-W,d îÄYŸÊä_@µ‚È‹Ñ2 «ë_Ò›²Bu/šE)ÿÿœ/Iµý/lØL* {ü?ž?_°ÿíü´ÿ}‹Çì®p B77k{Té—Oé¯Ýu·dõÅ-fÊf–ð¬×ëÎZ6IŽò7$SìÖ³FVvl•µ§Tf©9ñцÎÌ™z-·vÄ3.ä.ü¢¤Ÿ°‡/dŠÔR¸_Òç-q¼²Ž5Õ“%\¥îÊõö‰ûÂ&Ç>HOÖU×:Heo1Œ6 ¢]Þ×´‚›&gÒ+Ulk®¯‘.Ú&³˜\‘»D8Ñ«?ÍL v©Š˜Ù©Ò"ù½¹üç³ìY&ÿ½ .÷êÿ/ËòÿùÁÏüŸßæ1Rš„þJÎvÈåýÖ˜\(ªW—¢´ˆ|…òþ·‹ðë×*µ¾¢Ü½žë‹÷e²Ý²,ë¦YsŒŽ|ÏÂ#Ø_ëöÝÎ1aݦ¢Ïß}UvR§‡Åì¥Õ½¢È2ì‹(-œ¦æÚeLž'È€íÕŠÁÞʨ±) hJ?Š©Zþ{Ù†–€{äÿþ‹§ òÿųŸòÿ›<•Z½Î± éühã…NMÖ‹ÈŸ uò Å£ôÎóóãN}’^[bÔ®¥zSîîÎvªÈ7û™ÜZRòÆ/žÑÐ[íñÍå²ù¯-ßÄþût1þoÿçüÿ&Ï’ùÿ|3Ÿò‡•OpÉV~IÉMoä7å š¢»JÃ+•Ya¸­m›“×1CWÕ»W£Ôâ³bÒXóÌêiù{ÊØëUVÒ´p–/R—²ÒGV´^*\Ê™›PÝ8µÜŸi¢JÍÛ^Ú­,ÿÈŽÞÔ˜•¡\8M²³“g)/áË—eÅX@’xJeßvÍ)·G1—ÜãAF­~ž¾)u‰*ºé>È]u­mÎ÷@?Ÿïú¬\ÿM*à¯UîÓÿËëÿËÃ?Ͽɳ–ïT½â`¶¬,˜*ßuÓðCŸ´ý˜=ÿÝ ælò˜n´ûæÿó…ü‡/žþœÿßäÙ‚>‘JÂô˜zðõEㄘr+"Þ%™Rá:[˜»D¡~bFƒ£Âuü §»JëÐWÖàqPþž„¾³!¢ ÛÅÛþuÇ…^Üà-‘F "CÀBê:îÑð|(yL-hñùœ‡ð¾5ŸÅÂq§Lîá¿}Çÿïá¿é‹ÙtOý“þ)®Â½Иx—I„©;…óÄבóÄ“Kç‰+ç‘óäÿs¶à=‰OtŽÚÂq£˜ÿA=é¸Ì§dO—‹ùÎô>tÛƒ¡¾îcÂc+BÖÑŸ~4¹dÏÿA»ytÚvçþ†ÛX=ÿŸ?=|ñ´4ÿŸ¾|þìçüÿÏV!}c~££ãè Ïü¶Ç(fWJ°Ê»Žcr’@p”V][çB®/\élmÁïeáh™ûqœÿü×z†£æ`>÷$÷`JCc&ÁQ¯‘nô’Ò<>Ç‹!f4Æ+;Ì)‰äàuP¯ÿ—†xÔëþ:‚öQg£w! Û­Q§×Ý…Nw8j7`Юκi›£œõš£6Bpœ:|ìÇsÖ &©ø´½Ùï¨-;3ò§í­B¦d,Ñ …$A`ÖŸ;¸áýx&tf9ƒ¦¾lxøi{ sÖÕ뺾ï¡.wÌqÑLjÆs&ô4Ó˜„R×7WJ\ÒD7/TÇBª«B÷Áë4O1=L˨ÝrÀŒÌëiéº)½N DˆdN}¼Âb5tS²®J.RïX_*Ö xâ§Iæ‹„Ô¡ u¼©3fZ„sš]õÕü+‰)¼Q+^%¼ìR°:Q%ë〗Ãmš›ò4‚½á°¨¹PÏ`É…XQ_¢€KÙtžž¢êp­¼” !¦IR'Ìpþi{‹ê÷YܾG®> #«–Òß;f*·»G›šÈ(1 ÓÐqžØ‰( ‹^£]Oé‰Tëå˜Y½AÝys“Å1`V!–oX¹„QúÔš»Yº¥f¿“:B:é½ OÌEVGO\Њ[sI=)€L‰"HB’ÈÙ_Ô·úh :H˯‚$sW¬ãtB ¾ÏtõqÖ]ˆ¸¥H×\¸qx=¦X¿8Øßß¿0ׂ˜›GA㽩*©ÓB•Ó çŽÄI(€M síHîèѱ/Yy§̈€#Þ K15f”w4§»ÂºÜ­ ìÇ—» Jªй߭¡”3sÃ&͹œ±›N@¯Èù“máEA1'B^¦H®Ôîào43ÁŒ/’ÏÁºÀ×ù'˜ Wæì¢ÉMF~$i]s–ŸQâc²?6§ çââÂI“r¦ö€ ì)í~Oò4¸ïEnZÕÀÌÏ-¢ÞWá,ó,yn­ßGùÅ×k”.>GÙÊZÎm}Ígí‚(Õrnᢨ±¤¿­lÞ©²é¨þeÔ¹°/ï­YE<Áýèè{‡Ì… ËÐYrÿð*Ì,t$™®ìC:dªPÒiÑÑ7#­I j 2œ[hjy>ç~F5Iúï[UÈ_.qY«tŒ´¬¨} fTÎô}§x«"¢ªX~oáoÁxqÑì¯% n¡‰ÑÁxC/& V’# +ht ¯ŽÙé@GÛ/«ÀÜâU%JÔ Mc¶t¬:j­ùZtl·©‹R…™îáž[¸¨?›Etr‡©EV6÷DÝÇÅ):ÇfÜék V^ÆÉdåe(•ÝÃ/R”ÒCO¾*)ôv®Fh‰Z!}ª?Jú܇ZîÄ^B­ûv˜^O¹% µÍa£t¦‹2ütäúDÎî¼)ltHŬ[hiM2KüšZ}Ìo_FÇNƒµÆ-\¾Y­{ÃÅÁ#©“¹ÏºúrJ׋’EPö ?'ŸÙ<™C«¶ýý•ÚÅÚèè‹.XgsEîtž¾:}à´/¡“úWÑÇFÇ”£þRú(ô`â,E§‚>Õè,£Ï-\¼xöPâht*L+fVg‚W.îÚ°ò»:Õ*òITªcÅ›Åì:Ö&ѤúT Ǩ:»…»åŠˆA§´S®. §$B]¬´ýήRç¾]úâKñêNXCBtìü(«äŽAG·õ1…‚MC4UåèÞ£ßhƒ ¦ —ԆѩääµX9'ÐÑ´;½î"::U¼bínÆzøŒ%ÝÆhƒèTròw`e0ñ8V>nõ ¥7†Î£yÇÂhè˜5ëü´Óô†½·£óV¯;jvºíÁEA9]ŽÑéðïDÇ"ÔÔ9ÂßA^¿ÝŽš­ßÖ¥ŽÂEA’x—#:ëPg—¿ Ker‡O¬Öó±à¿g°š­ö`”R‡ÈØB« %Œ“Ð7P%úlÖ»æ`tÞï Gço{ƒÓó·ö‰¾îB5ð–ÇsW!õgBãÄD(¡g–§M6ƒNÐ{¿ ›{щb~ECô¦ØuŽÚýÑ»ePn! Wj3£´w½#ÆSÄhž’IÄæÆ5è<ìx¢7gÇËÑÎø5øtœLaN… Óª-DVúћ⠓Þ1FõU¸…^ZBƉ'“˜úð)®Y˜Y¯ŒŽÚÏ?ÆdSGŸÐŸGÍQûmç¤=¼°8bO2ÕªÍ'èêü¹îo¦#óÊè|5uRtNÛ£A§5,kƒ):ý˜Ï©œÑDÀœÊ˜yÕÛ¾Q§ÙïT©î):$ï÷Pô)Ý#Û :Í““Þ‡óÞûöàà3j/êÊ¿6ÓÚ\£‚›ô1…˜Ö“(àħ~†ÎW–ƒgƒ“%Jct Äúpܕӿ•ó;þóÒPNÏFïΛÝ^÷÷ÓÞÙðü¸ ¦Ô!!á¸=RËBšI"g™ûÆF¨ceK®0Æ ÍÝÕxÉ‘gŒMQµ~ÔétÚÿôGÙÉ­f'ã¡!ҳɢÃÝ×¢³˜«¸zÝxi&{ÔžxÙÅ×-c¥¬¾KÏt*Šü<2ÅêÁC­ÌÐÙu.éRûw†Î%ÕJ¼\ŽŒö8*å^=ûC–WSØ®§f˜0ʹºôEÒý2ðñV¸…¡$’yy8ðºjåPÄšQâTdâ[ÜÂEÈh‰/âce¨4‰•O³S^®ÆþÑÜS̰œ{ª|ª‘úŠs“…{„–ŒV5:yíÍ£“ßU¡“ªK0I£à¿gQMn€o–{:Ç£p¹¨×…žá¯Ñ½ôãnöóÓxš B’;¶'î…‹nÝvp‡ã\\\`ˆœSðÙÕ×Ãü¦nßkYŸ/^­™ ô˜-ÄÊ [íÖÖTŇ8§ä’‚Hbªýší…^{~G4n@P™Dh­Ñ6ê‹ùM]ÖµáúÂA·á*=FB@‰ÐjrîÈkGm°mÍØ^§y ÚwTá‰nÀ_€š‰®5 ötÿàiý`¿~ð²¶«>)aA‚µ|Ô˜teµ!óU Ü4œ0!{ã?¨'ÖÃíÉ„z2+“hzR7÷1K\‡ D¤kæý§¬ÊÀø(h$äZ4Äa£Ñ°I£«Ýí.ÇÓàØœ=Ï#PI5¨¬°þtLeåû~’¾\Ïöž˜¾9Ÿœ;Íf¿ow˜Ã´£~Ágv¬xéŠÆ$€kr#Èš[p’ãã{»¤‚.ÔÌ$è8kGúD“¿ »²vóNЗȵÈ'ô…5{.t£"ñfÆGÜɸï´Kê”ƺÙTe¿;/œÖ5À&[U9m4nT}ÒÜ$¢®fUýÀ)ÞóÐü0ÑGèF\ý]Rl)«T…˜•²Ä;—¯„v¨ªëbLFTØû£˜_h|$±d^@?m§Á‹>÷„K®…«Y£;ÍÓ=LŽ!÷Ô~æ8a>ÝcþyÌ*ÎAÏÅ5“Þ CÑêÔ{êÎä<Øù?Ì ?SØQŽ3â:¨Ê§)WkAcR+“^ÌrÌáãe2¦O™çÃ]«ªñ{2¦toN”>»g0O!ì8Š*ÆU DÚ>õí®¸ÿ‡ÙÀ¸_6Ê™ªTeFæfŽ¨Ù¢¨ÈЀڗÂ8(¦¸«}†‚{¢A¿Fmšz"×›67¼ÖídéØNÚqxó¶« ¦ã».pµ%é§t'™ftqšé—²)ÀÚÞb¬Z ñ²u’ï‰]aH§* ¯9(Š9ñfT`¨§ Ÿ’ ÀHsðyø«„k¢½ }=.æê6tzcA@c:¡Ž ôˆ »0¦-á‘o´2Ÿßí"h1ãIMW’.€H9ã‚ÚÝiX Y ~3õ0Uíu­ëÍͧz}ó9Æ×¾.[^׿7u“?¢þì9=|î¿zNžÿÇ¡¹ø2 ! 8¤ c t ÁÅûæÉY{xÈøˆ;ÒG³ànRšî˜w,ÔñtfÎ,¤eÓmq&A½®ç@ï‹ÍH¯Ñ«µ|XüZ³ñnÕaÎk4-T)²ùkäòÈž÷»%£ÑëEÚkúéàN< ²£4tœ Lÿ¿ÝŽÚû}e0lû4¢!*c<4ù»2 gÒò{ohŽì'Š¿ÍNC½îzÿÓnŒ#×/Gín³[z—ì•‹é9cÁ<ê6;Ýbuón¡ìÙ°=(T/Ì,ìY‡è›œq‘ ³¥ÂCyqáXæ?ÜXò5µ²üÞ;èߥyfL#Ë©6¿!‰œ¹I ©åê©»¿—–ΈÞÀG7NiæšEOý51é×”œúkdü#¾÷´Z–ËÊN–d·’*–Vr7‰Tó!â>Ä÷òŒ‡ÀL–¥€ íI¨Wü]Õ×(¦WxÖ§”_“Ph1Äß]‡·-Ç ÂVßË[À<–¾…xxÅì/Ú€WÇÌ%›0'a‚f“9 É”úÖ…Kiº5dúÚ%¬3Ð.&*2vd8lmAg¢ÈËBêïbš”7cjì~Þ²±¹˜Ðð4íÔ˜'¨mAán¨†£¡·¬ó‰$x’O,â´x1ÐÚ¯þ+«aLÕ굪JµÚ.\Ϙ7_;’Šêl])´$4À¶¼†w2› ‡0 ‚]ùBc "ê3}Q3]î‚7ã<;uNù9k™Æ.Àö4zªx¶ùa¨z ÓÚ~Îñoí]Œ0úw\ƒ†JxíàG¤¿FC‘áû˜ô+“É9Néu=O •ö} nb¢M®«f©‡‰ž{i"j«»…TÕéD½|%\Æ÷*ï+ÝmoCòCU=ûNž®JA˜óI+ì»îÁS÷à°ñìù‹—»ù‹gø¢¦æ¹?gaÇoèé‹¡uý¹œÑÌöSúˆÙj)ÃàU <0f#ܳ8€G¨ ¾úßüY Ý dÙûìLt»@À§ëã,«Ÿžé1Ša5Á‹†‚:™Î˜*÷ñTÛwË<¦ß—x+3`˜¯zzdž ˜çM ˜1ÕÛ›F5Èz=Jâi%èïqõçóóùùü|~Œçÿÿÿ®ÈXÎoras-go-1.1.1/testdata/charts/chartmuseum/0000755000175000017500000000000014212212744020040 5ustar nileshnileshoras-go-1.1.1/testdata/charts/chartmuseum/templates/0000755000175000017500000000000014212212744022036 5ustar nileshnileshoras-go-1.1.1/testdata/charts/chartmuseum/templates/service.yaml0000755000175000017500000000200114212212744024356 0ustar nileshnileshapiVersion: v1 kind: Service metadata: {{- if .Values.service.servicename }} name: {{ .Values.service.servicename }} {{- else }} name: {{ include "chartmuseum.fullname" . }} {{- end }} labels: {{ include "chartmuseum.labels.standard" . | indent 4 }} {{- if .Values.service.labels }} {{ toYaml .Values.service.labels | indent 4 }} {{- end }} {{- if .Values.service.annotations }} annotations: {{ toYaml .Values.service.annotations | indent 4 }} {{- end }} spec: type: {{ .Values.service.type }} {{- if eq .Values.service.type "ClusterIP" }} {{- if .Values.service.clusterIP }} clusterIP: {{ .Values.service.clusterIP }} {{- end }} {{- end }} ports: - port: {{ .Values.service.externalPort }} {{- if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort))) }} nodePort: {{.Values.service.nodePort}} {{- else }} targetPort: http {{- end }} protocol: TCP name: http selector: app: {{ template "chartmuseum.name" . }} release: {{ .Release.Name | quote }} oras-go-1.1.1/testdata/charts/chartmuseum/templates/deployment.yaml0000755000175000017500000001046714212212744025115 0ustar nileshnileshapiVersion: extensions/v1beta1 kind: Deployment metadata: name: {{ include "chartmuseum.fullname" . }} annotations: {{ toYaml .Values.deployment.annotations | indent 4 }} labels: {{ include "chartmuseum.labels.standard" . | indent 4 }} {{- if .Values.deployment.labels }} {{ toYaml .Values.deployment.labels | indent 4 }} {{- end }} spec: replicas: {{ .Values.replicaCount }} strategy: {{ toYaml .Values.strategy | indent 4 }} revisionHistoryLimit: 10 {{- if .Values.deployment.matchlabes }} selector: matchLabels: {{ toYaml .Values.deployment.matchlabes | indent 6 }} {{- end }} template: metadata: name: {{ include "chartmuseum.fullname" . }} annotations: {{ toYaml .Values.replica.annotations | indent 8 }} labels: app: {{ template "chartmuseum.name" . }} release: {{ .Release.Name | quote }} {{- if .Values.deployment.labels }} {{ toYaml .Values.deployment.labels | indent 8 }} {{- end }} spec: containers: - name: {{ .Chart.Name }} image: {{ .Values.image.repository }}:{{ .Values.image.tag }} imagePullPolicy: {{ .Values.image.pullPolicy }} env: {{- range $name, $value := .Values.env.open }} {{- if not (empty $value) }} - name: {{ $name | quote }} value: {{ $value | quote }} {{- end }} {{- end }} {{- range $name, $value := .Values.env.field }} {{- if not ( empty $value) }} - name: {{ $name | quote }} valueFrom: fieldRef: fieldPath: {{ $value | quote }} {{- end }} {{- end }} {{- if .Values.gcp.secret.enabled }} - name: GOOGLE_APPLICATION_CREDENTIALS value: "/etc/secrets/google/credentials.json" {{- end }} {{- $secret_name := include "chartmuseum.fullname" . }} {{- range $name, $value := .Values.env.secret }} {{- if not ( empty $value) }} - name: {{ $name | quote }} valueFrom: secretKeyRef: name: {{ $secret_name }} key: {{ $name | quote }} {{- end }} {{- end }} args: - --port=8080 {{- if eq .Values.env.open.STORAGE "local" }} - --storage-local-rootdir=/storage {{- end }} ports: - name: http containerPort: 8080 livenessProbe: httpGet: path: {{ .Values.env.open.CONTEXT_PATH }}/health port: http {{ toYaml .Values.probes.liveness | indent 10 }} readinessProbe: httpGet: path: {{ .Values.env.open.CONTEXT_PATH }}/health port: http {{ toYaml .Values.probes.readiness | indent 10 }} {{- if eq .Values.env.open.STORAGE "local" }} volumeMounts: - mountPath: /storage name: storage-volume {{- end }} {{- if .Values.gcp.secret.enabled }} volumeMounts: - mountPath: /etc/secrets/google name: {{ include "chartmuseum.fullname" . }}-gcp {{- end }} {{- with .Values.resources }} resources: {{ toYaml . | indent 10 }} {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{ toYaml . | indent 8 }} {{- end }} {{- with .Values.affinity }} affinity: {{ toYaml . | indent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{ toYaml . | indent 8 }} {{- end }} {{- if .Values.serviceAccount.create }} serviceAccountName: {{ include "chartmuseum.fullname" . }} {{- else if .Values.serviceAccount.name }} serviceAccountName: {{ .Values.serviceAccount.name }} {{- end }} {{- with .Values.securityContext }} securityContext: {{ toYaml . | indent 8 }} {{- end }} volumes: - name: storage-volume {{- if .Values.persistence.enabled }} persistentVolumeClaim: claimName: {{ .Values.persistence.existingClaim | default (include "chartmuseum.fullname" .) }} {{- else }} emptyDir: {} {{- end -}} {{ if .Values.gcp.secret.enabled }} - name: {{ include "chartmuseum.fullname" . }}-gcp secret: {{ if .Values.env.secret.GOOGLE_CREDENTIALS_JSON }} secretName: {{ include "chartmuseum.fullname" . }} items: - key: GOOGLE_CREDENTIALS_JSON path: credentials.json {{ else }} secretName: {{ .Values.gcp.secret.name }} items: - key: {{ .Values.gcp.secret.key }} path: credentials.json {{ end }} {{ end }} oras-go-1.1.1/testdata/charts/chartmuseum/templates/NOTES.txt0000755000175000017500000000335314212212744023476 0ustar nileshnilesh** Please be patient while the chart is being deployed ** Get the ChartMuseum URL by running: {{- if contains "NodePort" .Values.service.type }} export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "chartmuseum.fullname" . }}) export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") echo http://$NODE_IP:$NODE_PORT{{ .Values.env.open.CONTEXT_PATH }}/ {{- else if contains "LoadBalancer" .Values.service.type }} ** Please ensure an external IP is associated to the {{ template "chartmuseum.fullname" . }} service before proceeding ** ** Watch the status using: kubectl get svc --namespace {{ .Release.Namespace }} -w {{ template "chartmuseum.fullname" . }} ** export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "chartmuseum.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') echo http://$SERVICE_IP:{{ .Values.service.externalPort }}{{ .Values.env.open.CONTEXT_PATH }}/ OR export SERVICE_HOST=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "chartmuseum.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') echo http://$SERVICE_HOST:{{ .Values.service.externalPort }}{{ .Values.env.open.CONTEXT_PATH }}/ {{- else if contains "ClusterIP" .Values.service.type }} export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "chartmuseum.name" . }}" -l "release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") echo http://127.0.0.1:8080{{ .Values.env.open.CONTEXT_PATH }}/ kubectl port-forward $POD_NAME 8080:8080 --namespace {{ .Release.Namespace }} {{- end }} oras-go-1.1.1/testdata/charts/chartmuseum/templates/_helpers.tpl0000755000175000017500000000536114212212744024370 0ustar nileshnilesh{{- /* name defines a template for the name of the chartmuseum chart. The prevailing wisdom is that names should only contain a-z, 0-9 plus dot (.) and dash (-), and should not exceed 63 characters. Parameters: - .Values.nameOverride: Replaces the computed name with this given name - .Values.namePrefix: Prefix - .Values.global.namePrefix: Global prefix - .Values.nameSuffix: Suffix - .Values.global.nameSuffix: Global suffix The applied order is: "global prefix + prefix + name + suffix + global suffix" Usage: 'name: "{{- template "chartmuseum.name" . -}}"' */ -}} {{- define "chartmuseum.name"}} {{- $global := default (dict) .Values.global -}} {{- $base := default .Chart.Name .Values.nameOverride -}} {{- $gpre := default "" $global.namePrefix -}} {{- $pre := default "" .Values.namePrefix -}} {{- $suf := default "" .Values.nameSuffix -}} {{- $gsuf := default "" $global.nameSuffix -}} {{- $name := print $gpre $pre $base $suf $gsuf -}} {{- $name | lower | trunc 54 | trimSuffix "-" -}} {{- end -}} {{- /* fullname defines a suitably unique name for a resource by combining the release name and the chartmuseum chart name. The prevailing wisdom is that names should only contain a-z, 0-9 plus dot (.) and dash (-), and should not exceed 63 characters. Parameters: - .Values.fullnameOverride: Replaces the computed name with this given name - .Values.fullnamePrefix: Prefix - .Values.global.fullnamePrefix: Global prefix - .Values.fullnameSuffix: Suffix - .Values.global.fullnameSuffix: Global suffix The applied order is: "global prefix + prefix + name + suffix + global suffix" Usage: 'name: "{{- template "chartmuseum.fullname" . -}}"' */ -}} {{- define "chartmuseum.fullname"}} {{- $global := default (dict) .Values.global -}} {{- $base := default (printf "%s-%s" .Release.Name .Chart.Name) .Values.fullnameOverride -}} {{- $gpre := default "" $global.fullnamePrefix -}} {{- $pre := default "" .Values.fullnamePrefix -}} {{- $suf := default "" .Values.fullnameSuffix -}} {{- $gsuf := default "" $global.fullnameSuffix -}} {{- $name := print $gpre $pre $base $suf $gsuf -}} {{- $name | lower | trunc 54 | trimSuffix "-" -}} {{- end -}} {{- /* chartmuseum.labels.standard prints the standard chartmuseum Helm labels. The standard labels are frequently used in metadata. */ -}} {{- define "chartmuseum.labels.standard" -}} app: {{ template "chartmuseum.name" . }} chart: {{ template "chartmuseum.chartref" . }} heritage: {{ .Release.Service | quote }} release: {{ .Release.Name | quote }} {{- end -}} {{- /* chartmuseum.chartref prints a chart name and version. It does minimal escaping for use in Kubernetes labels. Example output: chartmuseum-0.4.5 */ -}} {{- define "chartmuseum.chartref" -}} {{- replace "+" "_" .Chart.Version | printf "%s-%s" .Chart.Name -}} {{- end -}} oras-go-1.1.1/testdata/charts/chartmuseum/templates/serviceaccount.yaml0000755000175000017500000000033114212212744025737 0ustar nileshnilesh{{- if .Values.serviceAccount.create -}} --- apiVersion: v1 kind: ServiceAccount metadata: name: {{ include "chartmuseum.fullname" . }} labels: {{ include "chartmuseum.labels.standard" . | indent 4 }} {{- end -}} oras-go-1.1.1/testdata/charts/chartmuseum/templates/secret.yaml0000755000175000017500000000062614212212744024216 0ustar nileshnileshapiVersion: v1 kind: Secret metadata: name: {{ include "chartmuseum.fullname" . }} labels: {{ include "chartmuseum.labels.standard" . | indent 4 }} type: Opaque data: {{- range $name, $value := .Values.env.secret }} {{- if not (empty $value) }} {{- if eq $name "GOOGLE_CREDENTIALS_JSON" }} {{ $name }}: {{ $value }} {{- else }} {{ $name }}: {{ $value | b64enc }} {{- end }} {{- end }} {{- end }} oras-go-1.1.1/testdata/charts/chartmuseum/templates/pv.yaml0000755000175000017500000000114614212212744023354 0ustar nileshnilesh{{- if .Values.persistence.pv.enabled -}} apiVersion: v1 kind: PersistentVolume metadata: {{- if .Values.persistence.pv.pvname }} name: {{ .Values.persistence.pv.pvname }} {{- else }} name: {{ include "chartmuseum.fullname" . }} {{- end }} labels: app: {{ include "chartmuseum.fullname" . }} release: {{ .Release.Name | quote }} spec: capacity: storage: {{ .Values.persistence.pv.capacity.storage }} accessModes: - {{ .Values.persistence.pv.accessMode | quote }} nfs: server: {{ .Values.persistence.pv.nfs.server }} path: {{ .Values.persistence.pv.nfs.path | quote }} {{- end }}oras-go-1.1.1/testdata/charts/chartmuseum/templates/ingress.yaml0000755000175000017500000000155714212212744024407 0ustar nileshnilesh{{- $servicePort := .Values.service.externalPort -}} {{- $serviceName := include "chartmuseum.fullname" . -}} {{- if .Values.ingress.enabled }} --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: {{ include "chartmuseum.fullname" . }} annotations: {{ toYaml .Values.ingress.annotations | indent 4 }} labels: {{- if .Values.ingress.labels }} {{ toYaml .Values.ingress.labels | indent 4 }} {{- end }} {{ include "chartmuseum.labels.standard" . | indent 4 }} spec: rules: {{- range $host, $paths := .Values.ingress.hosts }} - host: {{ $host }} http: paths: {{- range $paths }} - path: {{ . }} backend: serviceName: {{ $serviceName }} servicePort: {{ $servicePort }} {{- end -}} {{- end -}} {{- if .Values.ingress.tls }} tls: {{ toYaml .Values.ingress.tls | indent 4 }} {{- end -}} {{- end -}} oras-go-1.1.1/testdata/charts/chartmuseum/templates/pvc.yaml0000755000175000017500000000157614212212744023526 0ustar nileshnilesh{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) -}} kind: PersistentVolumeClaim apiVersion: v1 metadata: name: {{ include "chartmuseum.fullname" . }} labels: app: {{ include "chartmuseum.fullname" . }} release: {{ .Release.Name | quote }} {{- if .Values.persistence.labels }} {{ toYaml .Values.persistence.labels | indent 4 }} {{- end }} spec: accessModes: - {{ .Values.persistence.accessMode | quote }} resources: requests: storage: {{ .Values.persistence.size | quote }} {{- if .Values.persistence.storageClass }} {{- if (eq "-" .Values.persistence.storageClass) }} storageClassName: "" {{- else }} storageClassName: "{{ .Values.persistence.storageClass }}" {{- end }} {{- else if and .Values.persistence.volumeName (.Values.persistence.pv.enabled) }} volumeName: "{{ .Values.persistence.volumeName }}" {{- end }} {{- end }} oras-go-1.1.1/testdata/charts/chartmuseum/.helmignore0000755000175000017500000000056114212212744022177 0ustar nileshnilesh# Patterns to ignore when building packages. # This supports shell glob matching, relative path matching, and # negation (prefixed with !). Only one pattern per line. .DS_Store # Common VCS dirs .git/ .gitignore .bzr/ .bzrignore .hg/ .hgignore .svn/ # Common backup files *.swp *.bak *.tmp *~ # Various IDEs .project .idea/ *.tmproj # OWNERS file for Kubernetes OWNERS oras-go-1.1.1/testdata/charts/chartmuseum/README.md0000755000175000017500000005236214212212744021332 0ustar nileshnilesh# ChartMuseum Helm Chart Deploy your own private ChartMuseum. Please also see https://github.com/kubernetes-helm/chartmuseum ## Table of Content - [Prerequisites](#prerequisites) - [Configuration](#configuration) - [Installation](#installation) - [Using with Amazon S3](#using-with-amazon-s3) - [permissions grant with access keys](#permissions-grant-with-access-keys) - [permissions grant with IAM instance profile](#permissions-grant-with-iam-instance-profile) - [permissions grant with IAM assumed role](#permissions-grant-with-iam-assumed-role) - [Using with Google Cloud Storage](#using-with-google-cloud-storage) - [Using with Microsoft Azure Blob Storage](#using-with-microsoft-azure-blob-storage) - [Using with Alibaba Cloud OSS Storage](#using-with-alibaba-cloud-oss-storage) - [Using with local filesystem storage](#using-with-local-filesystem-storage) - [Example storage class](#example-storage-class) - [Uninstall](#uninstall) ## Prerequisites * Kubernetes with extensions/v1beta1 available * [If enabled] A persistent storage resource and RW access to it * [If enabled] Kubernetes StorageClass for dynamic provisioning ## Configuration By default this chart will not have persistent storage, and the API service will be *DISABLED*. This protects against unauthorized access to the API with default configuration values. In addition, by default, pod `securityContext.fsGroup` is set to `1000`. This is the user/group that the ChartMuseum container runs as, and is used to enable local persitant storage. If your cluster has DenySecurityContext enabled, you can set `securityContext` to `{}` and still use this chart with one of the cloud storage options. For a more robust solution supply helm install with a custom values.yaml You are also required to create the StorageClass resource ahead of time: ``` kubectl create -f /path/to/storage_class.yaml ``` The following table lists common configurable parameters of the chart and their default values. See values.yaml for all available options. | Parameter | Description | Default | |----------------------------------------|---------------------------------------------|-----------------------------------------------------| | `image.pullPolicy` | Container pull policy | `IfNotPresent` | | `image.repository` | Container image to use | `chartmuseum/chartmuseum` | | `image.tag` | Container image tag to deploy | `v0.8.0` | | `persistence.accessMode` | Access mode to use for PVC | `ReadWriteOnce` | | `persistence.enabled` | Whether to use a PVC for persistent storage | `false` | | `persistence.size` | Amount of space to claim for PVC | `8Gi` | | `persistence.labels` | Additional labels for PVC | `{}` | | `persistence.storageClass` | Storage Class to use for PVC | `-` | | `persistence.volumeName` | Volume to use for PVC | `` | | `persistence.pv.enabled` | Whether to use a PV for persistent storage | `false` | | `persistence.pv.capacity.storage` | Storage size to use for PV | `8Gi` | | `persistence.pv.accessMode` | Access mode to use for PV | `ReadWriteOnce` | | `persistence.pv.nfs.server` | NFS server for PV | `` | | `persistence.pv.nfs.path` | Storage Path | `` | | `persistence.pv.pvname` | Custom name for private volume | `` | | `replicaCount` | k8s replicas | `1` | | `resources.limits.cpu` | Container maximum CPU | `100m` | | `resources.limits.memory` | Container maximum memory | `128Mi` | | `resources.requests.cpu` | Container requested CPU | `80m` | | `resources.requests.memory` | Container requested memory | `64Mi` | | `serviceAccount.create` | If true, create the service account | `false` | | `serviceAccount.name` | Name of the serviceAccount to create or use | `{{ chartmuseum.fullname }}` | | `securityContext` | Map of securityContext for the pod | `{ fsGroup: 1000 }` | | `nodeSelector` | Map of node labels for pod assignment | `{}` | | `tolerations` | List of node taints to tolerate | `[]` | | `affinity` | Map of node/pod affinities | `{}` | | `env.open.STORAGE` | Storage Backend to use | `local` | | `env.open.ALIBABA_BUCKET` | Bucket to store charts in for Alibaba | `` | | `env.open.ALIBABA_PREFIX` | Prefix to store charts under for Alibaba | `` | | `env.open.ALIBABA_ENDPOINT` | Alternative Alibaba endpoint | `` | | `env.open.ALIBABA_SSE` | Server side encryption algorithm to use | `` | | `env.open.AMAZON_BUCKET` | Bucket to store charts in for AWS | `` | | `env.open.AMAZON_ENDPOINT` | Alternative AWS endpoint | `` | | `env.open.AMAZON_PREFIX` | Prefix to store charts under for AWS | `` | | `env.open.AMAZON_REGION` | Region to use for bucket access for AWS | `` | | `env.open.AMAZON_SSE` | Server side encryption algorithm to use | `` | | `env.open.GOOGLE_BUCKET` | Bucket to store charts in for GCP | `` | | `env.open.GOOGLE_PREFIX` | Prefix to store charts under for GCP | `` | | `env.open.STORAGE_MICROSOFT_CONTAINER` | Container to store charts under for MS | `` | | `env.open.STORAGE_MICROSOFT_PREFIX` | Prefix to store charts under for MS | `` | | `env.open.STORAGE_OPENSTACK_CONTAINER` | Container to store charts for openstack | `` | | `env.open.STORAGE_OPENSTACK_PREFIX` | Prefix to store charts for openstack | `` | | `env.open.STORAGE_OPENSTACK_REGION` | Region of openstack container | `` | | `env.open.STORAGE_OPENSTACK_CACERT` | Path to a CA cert bundle for openstack | `` | | `env.open.CHART_POST_FORM_FIELD_NAME` | Form field to query for chart file content | `` | | `env.open.PROV_POST_FORM_FIELD_NAME` | Form field to query for chart provenance | `` | | `env.open.DEPTH` | levels of nested repos for multitenancy. | `0` | | `env.open.DEBUG` | Show debug messages | `false` | | `env.open.LOG_JSON` | Output structured logs in JSON | `true` | | `env.open.DISABLE_STATEFILES` | Disable use of index-cache.yaml | `false` | | `env.open.DISABLE_METRICS` | Disable Prometheus metrics | `true` | | `env.open.DISABLE_API` | Disable all routes prefixed with /api | `true` | | `env.open.ALLOW_OVERWRITE` | Allow chart versions to be re-uploaded | `false` | | `env.open.CHART_URL` | Absolute url for .tgzs in index.yaml | `` | | `env.open.AUTH_ANONYMOUS_GET` | Allow anon GET operations when auth is used | `false` | | `env.open.CONTEXT_PATH` | Set the base context path | `` | | `env.open.INDEX_LIMIT` | Parallel scan limit for the repo indexer | `` | | `env.open.CACHE` | Cache store, can be one of: redis | `` | | `env.open.CACHE_REDIS_ADDR` | Address of Redis service (host:port) | `` | | `env.open.CACHE_REDIS_DB` | Redis database to be selected after connect | `0` | | `env.field` | Expose pod information to containers through environment variables | `` | | `env.secret.BASIC_AUTH_USER` | Username for basic HTTP authentication | `` | | `env.secret.BASIC_AUTH_PASS` | Password for basic HTTP authentication | `` | | `env.secret.CACHE_REDIS_PASSWORD` | Redis requirepass server configuration | `` | | `gcp.secret.enabled` | Flag for the GCP service account | `false` | | `gcp.secret.name` | Secret name for the GCP json file | `` | | `gcp.secret.key` | Secret key for te GCP json file | `credentials.json` | | `service.type` | Kubernetes Service type | `ClusterIP` | | `service.clusterIP` | Static clusterIP or None for headless services| `nil` | | `service.servicename` | Custom name for service | `` | | `service.labels` | Additional labels for service | `{}` | | `deployment.labels` | Additional labels for deployment | `{}` | | `deployment.matchlabes` | Match labels for deployment selector | `{}` | Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. ## Installation ```shell helm install --name my-chartmuseum -f custom.yaml stable/chartmuseum ``` ### Using with Amazon S3 Make sure your environment is properly setup to access `my-s3-bucket` You need at least the following permissions inside your IAM Policy ```yaml { "Version": "2012-10-17", "Statement": [ { "Sid": "AllowListObjects", "Effect": "Allow", "Action": [ "s3:ListBucket" ], "Resource": "arn:aws:s3:::my-s3-bucket" }, { "Sid": "AllowObjectsCRUD", "Effect": "Allow", "Action": [ "s3:DeleteObject", "s3:GetObject", "s3:PutObject" ], "Resource": "arn:aws:s3:::my-s3-bucket/*" } ] } ``` You can grant it to `chartmuseum` by several ways: #### permissions grant with access keys Grant permissions to `special user` and us it's access keys for auth on aws Specify `custom.yaml` with such values ```yaml env: open: STORAGE: amazon STORAGE_AMAZON_BUCKET: my-s3-bucket STORAGE_AMAZON_PREFIX: STORAGE_AMAZON_REGION: us-east-1 secret: AWS_ACCESS_KEY_ID: "********" ## aws access key id value AWS_SECRET_ACCESS_KEY: "********" ## aws access key secret value ``` Run command to install ```shell helm install --name my-chartmuseum -f custom.yaml stable/chartmuseum ``` #### permissions grant with IAM instance profile You can grant permissions to k8s node IAM instance profile. For more information read this [article](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2.html) Specify `custom.yaml` with such values ```yaml env: open: STORAGE: amazon STORAGE_AMAZON_BUCKET: my-s3-bucket STORAGE_AMAZON_PREFIX: STORAGE_AMAZON_REGION: us-east-1 ``` Run command to install ```shell helm install --name my-chartmuseum -f custom.yaml stable/chartmuseum ``` #### permissions grant with IAM assumed role To provide access with assumed role you need to install [kube2iam](https://github.com/kubernetes/charts/tree/master/stable/kube2iam) and create role with granded permissions. Specify `custom.yaml` with such values ```yaml env: open: STORAGE: amazon STORAGE_AMAZON_BUCKET: my-s3-bucket STORAGE_AMAZON_PREFIX: STORAGE_AMAZON_REGION: us-east-1 replica: annotations: iam.amazonaws.com/role: "{assumed role name}" ``` Run command to install ```shell helm install --name my-chartmuseum -f custom.yaml stable/chartmuseum ``` ### Using with Google Cloud Storage Make sure your environment is properly setup to access `my-gcs-bucket` Specify `custom.yaml` with such values ```yaml env: open: STORAGE: google STORAGE_GOOGLE_BUCKET: my-gcs-bucket STORAGE_GOOGLE_PREFIX: ``` ### Using with Google Cloud Storage and a Google Service Account A Google service account credentials are stored in a json file. There are two approaches here. Ideally you don't want to send your secrets to tiller. In that case, before installing this chart, you should create a secret with those credentials: ```shell kubectl create secret generic chartmuseum-secret --from-file=credentials.json="my-project-45e35d85a593.json" ``` Then you can either use a `VALUES` yaml with your values or set those values in the command line: ```shell helm install stable/chartmuseum --debug --set gcp.secret.enabled=true,env.open.STORAGE=google,env.open.DISABLE_API=false,env.open.STORAGE_GOOGLE_BUCKET=my-gcp-chartmuseum,gcp.secret.name=chartmuseum-secret ``` If you prefer to use a yaml file: ```yaml env: open: STORAGE: google STORAGE_GOOGLE_BUCKET: my-gcs-bucket STORAGE_GOOGLE_PREFIX: gcp: secret: enabled: true name: chartmuseum-secret key: credentials.json ``` Run command to install ```shell helm install --name my-chartmuseum -f custom.yaml stable/chartmuseum ``` In case that you don't mind adding your secret to tiller (you shouldn't do it), this are the commands ```yaml env: open: STORAGE: google STORAGE_GOOGLE_BUCKET: my-gcs-bucket STORAGE_GOOGLE_PREFIX: secret: GOOGLE_CREDENTIALS_JSON: my-json-file-base64-encoded gcp: secret: enabled: true ``` Run command to install ```shell helm install --name my-chartmuseum -f custom.yaml stable/chartmuseum ``` To set the values directly in the command line, use the follosing command. Note that we have to base64 encode the json file because we cannot pass a multi-line text as a value. ```shell export JSONKEY=$(cat my-project-77e35d85a593.json | base64) helm install stable/chartmuseum --debug --set gcp.secret.enabled=true,env.secret.GOOGLE_CREDENTIALS_JSON=${JSONKEY},env.open.STORAGE=google,env.open.DISABLE_API=false,env.open.STORAGE_GOOGLE_BUCKET=my-gcp-chartmuseum ``` ### Using with Microsoft Azure Blob Storage Make sure your environment is properly setup to access `mycontainer`. To do so, you must set the following env vars: - `AZURE_STORAGE_ACCOUNT` - `AZURE_STORAGE_ACCESS_KEY` Specify `custom.yaml` with such values ```yaml env: open: STORAGE: microsoft STORAGE_MICROSOFT_CONTAINER: mycontainer # prefix to store charts for microsoft storage backend STORAGE_MICROSOFT_PREFIX: secret: AZURE_STORAGE_ACCOUNT: "********" ## azure storage account AZURE_STORAGE_ACCESS_KEY: "********" ## azure storage account access key ``` Run command to install ```shell helm install --name my-chartmuseum -f custom.yaml stable/chartmuseum ``` ### Using with Alibaba Cloud OSS Storage Make sure your environment is properly setup to access `my-oss-bucket`. To do so, you must set the following env vars: - `ALIBABA_CLOUD_ACCESS_KEY_ID` - `ALIBABA_CLOUD_ACCESS_KEY_SECRET` Specify `custom.yaml` with such values ```yaml env: open: STORAGE: alibaba STORAGE_ALIBABA_BUCKET: my-oss-bucket STORAGE_ALIBABA_PREFIX: STORAGE_ALIBABA_ENDPOINT: oss-cn-beijing.aliyuncs.com secret: ALIBABA_CLOUD_ACCESS_KEY_ID: "********" ## alibaba OSS access key id ALIBABA_CLOUD_ACCESS_KEY_SECRET: "********" ## alibaba OSS access key secret ``` Run command to install ```shell helm install --name my-chartmuseum -f custom.yaml stable/chartmuseum ``` ### Using with Openstack Object Storage Make sure your environment is properly setup to access `mycontainer`. To do so, you must set the following env vars (depending on your openstack version): - `OS_AUTH_URL` - either `OS_PROJECT_NAME` or `OS_TENANT_NAME` or `OS_PROJECT_ID` or `OS_TENANT_ID` - either `OS_DOMAIN_NAME` or `OS_DOMAIN_ID` - either `OS_USERNAME` or `OS_USERID` - `OS_PASSWORD` Specify `custom.yaml` with such values ```yaml env: open: STORAGE: openstack STORAGE_OPENSTACK_CONTAINER: mycontainer STORAGE_OPENSTACK_PREFIX: STORAGE_OPENSTACK_REGION: YOURREGION secret: OS_AUTH_URL: https://myauth.url.com/v2.0/ OS_TENANT_ID: yourtenantid OS_USERNAME: yourusername OS_PASSWORD: yourpassword ``` Run command to install ```shell helm install --name my-chartmuseum -f custom.yaml stable/chartmuseum ``` ### Using with local filesystem storage By default chartmuseum uses local filesystem storage. But on pod recreation it will lose all charts, to prevent that enable persistent storage. ```yaml env: open: STORAGE: local persistence: enabled: true accessMode: ReadWriteOnce size: 8Gi ## A manually managed Persistent Volume and Claim ## Requires persistence.enabled: true ## If defined, PVC must be created manually before volume will be bound # existingClaim: ## Chartmuseum data Persistent Volume Storage Class ## If defined, storageClassName: ## If set to "-", storageClassName: "", which disables dynamic provisioning ## If undefined (the default) or set to null, no storageClassName spec is ## set, choosing the default provisioner. (gp2 on AWS, standard on ## GKE, AWS & OpenStack) ## # storageClass: "-" ``` Run command to install ```shell helm install --name my-chartmuseum -f custom.yaml stable/chartmuseum ``` #### Example storage class Example storage-class.yaml provided here for use with a Ceph cluster. ``` kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: storage-volume provisioner: kubernetes.io/rbd parameters: monitors: "10.11.12.13:4567,10.11.12.14:4567" adminId: admin adminSecretName: thesecret adminSecretNamespace: default pool: chartstore userId: user userSecretName: thesecret ``` ## Uninstall By default, a deliberate uninstall will result in the persistent volume claim being deleted. ```shell helm delete my-chartmuseum ``` To delete the deployment and its history: ```shell helm delete --purge my-chartmuseum ``` oras-go-1.1.1/testdata/charts/chartmuseum/values.yaml0000755000175000017500000001404114212212744022226 0ustar nileshnileshreplicaCount: 1 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 image: repository: chartmuseum/chartmuseum tag: v0.8.0 pullPolicy: IfNotPresent env: open: # storage backend, can be one of: local, alibaba, amazon, google, microsoft STORAGE: local # oss bucket to store charts for alibaba storage backend STORAGE_ALIBABA_BUCKET: # prefix to store charts for alibaba storage backend STORAGE_ALIBABA_PREFIX: # oss endpoint to store charts for alibaba storage backend STORAGE_ALIBABA_ENDPOINT: # server side encryption algorithm for alibaba storage backend, can be one # of: AES256 or KMS STORAGE_ALIBABA_SSE: # s3 bucket to store charts for amazon storage backend STORAGE_AMAZON_BUCKET: # prefix to store charts for amazon storage backend STORAGE_AMAZON_PREFIX: # region of s3 bucket to store charts STORAGE_AMAZON_REGION: # alternative s3 endpoint STORAGE_AMAZON_ENDPOINT: # server side encryption algorithm STORAGE_AMAZON_SSE: # gcs bucket to store charts for google storage backend STORAGE_GOOGLE_BUCKET: # prefix to store charts for google storage backend STORAGE_GOOGLE_PREFIX: # container to store charts for microsoft storage backend STORAGE_MICROSOFT_CONTAINER: # prefix to store charts for microsoft storage backend STORAGE_MICROSOFT_PREFIX: # container to store charts for openstack storage backend STORAGE_OPENSTACK_CONTAINER: # prefix to store charts for openstack storage backend STORAGE_OPENSTACK_PREFIX: # region of openstack container STORAGE_OPENSTACK_REGION: # path to a CA cert bundle for your openstack endpoint STORAGE_OPENSTACK_CACERT: # form field which will be queried for the chart file content CHART_POST_FORM_FIELD_NAME: chart # form field which will be queried for the provenance file content PROV_POST_FORM_FIELD_NAME: prov # levels of nested repos for multitenancy. The default depth is 0 (singletenant server) DEPTH: 0 # show debug messages DEBUG: false # output structured logs as json LOG_JSON: true # disable use of index-cache.yaml DISABLE_STATEFILES: false # disable Prometheus metrics DISABLE_METRICS: true # disable all routes prefixed with /api DISABLE_API: true # allow chart versions to be re-uploaded ALLOW_OVERWRITE: false # absolute url for .tgzs in index.yaml CHART_URL: # allow anonymous GET operations when auth is used AUTH_ANONYMOUS_GET: false # sets the base context path CONTEXT_PATH: # parallel scan limit for the repo indexer INDEX_LIMIT: 0 # cache store, can be one of: redis (leave blank for inmemory cache) CACHE: # address of Redis service (host:port) CACHE_REDIS_ADDR: # Redis database to be selected after connect CACHE_REDIS_DB: 0 field: # POD_IP: status.podIP secret: # username for basic http authentication BASIC_AUTH_USER: # password for basic http authentication BASIC_AUTH_PASS: # GCP service account json file GOOGLE_CREDENTIALS_JSON: # Redis requirepass server configuration CACHE_REDIS_PASSWORD: deployment: ## Chartmuseum Deployment annotations annotations: {} # name: value labels: {} # name: value matchlabes: {} # name: value replica: ## Chartmuseum Replicas annotations annotations: {} ## Read more about kube2iam to provide access to s3 https://github.com/jtblin/kube2iam # iam.amazonaws.com/role: role-arn service: servicename: type: ClusterIP # clusterIP: None externalPort: 8080 nodePort: annotations: {} labels: {} resources: {} # limits: # cpu: 100m # memory: 128Mi # requests: # cpu: 80m # memory: 64Mi probes: liveness: initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 1 successThreshold: 1 failureThreshold: 3 readiness: initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 1 successThreshold: 1 failureThreshold: 3 serviceAccount: create: false # name: # UID/GID 1000 is the default user "chartmuseum" used in # the container image starting in v0.8.0 and above. This # is required for local persistant storage. If your cluster # does not allow this, try setting securityContext: {} securityContext: fsGroup: 1000 nodeSelector: {} tolerations: [] affinity: {} persistence: enabled: false accessMode: ReadWriteOnce size: 8Gi labels: {} # name: value ## A manually managed Persistent Volume and Claim ## Requires persistence.enabled: true ## If defined, PVC must be created manually before volume will be bound # existingClaim: ## Chartmuseum data Persistent Volume Storage Class ## If defined, storageClassName: ## If set to "-", storageClassName: "", which disables dynamic provisioning ## If undefined (the default) or set to null, no storageClassName spec is ## set, choosing the default provisioner. (gp2 on AWS, standard on ## GKE, AWS & OpenStack) ## # storageClass: "-" # volumeName: pv: enabled: false pvname: capacity: storage: 8Gi accessMode: ReadWriteOnce nfs: server: path: ## Ingress for load balancer ingress: enabled: false ## Chartmuseum Ingress labels ## # labels: # dns: "route53" ## Chartmuseum Ingress annotations ## # annotations: # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" ## Chartmuseum Ingress hostnames ## Must be provided if Ingress is enabled ## # hosts: # chartmuseum.domain.com: # - /charts # - /index.yaml ## Chartmuseum Ingress TLS configuration ## Secrets must be manually created in the namespace ## # tls: # - secretName: chartmuseum-server-tls # hosts: # - chartmuseum.domain.com # Adding secrets to tiller is not a great option, so If you want to use an existing # secret that contains the json file, you can use the following entries gcp: secret: enabled: false # Name of the secret that contains the encoded json name: # Secret key that holds the json value. key: credentials.json oras-go-1.1.1/testdata/charts/chartmuseum/Chart.yaml0000755000175000017500000000067314212212744021776 0ustar nileshnileshapiVersion: v1 appVersion: 0.8.0 description: Host your own Helm Chart Repository home: https://github.com/helm/chartmuseum icon: https://raw.githubusercontent.com/helm/chartmuseum/master/logo2.png keywords: - chartmuseum - helm - charts repo maintainers: - email: opensource@codefresh.io name: codefresh-io - email: hello@cloudposse.com name: cloudposse - email: chartmuseum@gmail.com name: chartmuseum name: chartmuseum version: 1.8.2 oras-go-1.1.1/scripts/0000755000175000017500000000000014212212744014075 5ustar nileshnileshoras-go-1.1.1/scripts/acceptance.sh0000755000175000017500000000365714212212744016535 0ustar nileshnilesh#!/bin/bash -ex # Copyright The ORAS Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd $DIR/../ LOCAL_REGISTRY_HOSTNAME="${LOCAL_REGISTRY_HOSTNAME:-localhost}" # Cleanup from previous runs rm -f hello.txt rm -f bin/oras-acceptance-* || true docker rm -f oras-acceptance-registry || true # Build the examples into binaries CGO_ENABLED=0 go build -v -o bin/oras-acceptance-simple ./examples/simple CGO_ENABLED=0 go build -v -o bin/oras-acceptance-advanced ./examples/advanced # Run a test registry and expose at localhost:5000 trap "docker rm -f oras-acceptance-registry" EXIT docker run -d -p 5000:5000 \ --name oras-acceptance-registry \ index.docker.io/registry # Wait for a connection to port 5000 (timeout after 1 minute) WAIT_TIME=0 while true; do if nc -w 1 -z "${LOCAL_REGISTRY_HOSTNAME}" 5000; then echo "Able to connect to ${LOCAL_REGISTRY_HOSTNAME} port 5000" break else if (( ${WAIT_TIME} >= 60 )); then echo "Timed out waiting for connection to ${LOCAL_REGISTRY_HOSTNAME} on port 5000. Exiting." exit 1 fi echo "Waiting to connect to ${LOCAL_REGISTRY_HOSTNAME} on port 5000. Sleeping 5 seconds.." sleep 5 WAIT_TIME=$((WAIT_TIME + 5)) fi done # Wait another 5 seconds for good measure sleep 5 # Run the example binary bin/oras-acceptance-simple # Ensure hello.txt exists and contains expected content grep '^Hello World!$' hello.txt oras-go-1.1.1/scripts/test.sh0000755000175000017500000000217014212212744015413 0ustar nileshnilesh#!/bin/bash -ex # Copyright The ORAS Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd $DIR/../ rm -rf .cover/ .test/ mkdir .cover/ .test/ trap "rm -rf .test/" EXIT export CGO_ENABLED=0 for pkg in `go list ./pkg/... | grep -v /vendor/`; do go test -v -covermode=atomic \ -coverprofile=".cover/$(echo $pkg | sed 's/\//_/g').cover.out" $pkg done echo "mode: set" > .cover/cover.out && cat .cover/*.cover.out | grep -v mode: | sort -r | \ awk '{if($1 != last) {print $0;last=$1}}' >> .cover/cover.out go tool cover -html=.cover/cover.out -o=.cover/coverage.html oras-go-1.1.1/go.mod0000644000175000017500000000637014212212744013522 0ustar nileshnileshmodule oras.land/oras-go go 1.17 require ( github.com/containerd/containerd v1.6.1 github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684 github.com/docker/cli v20.10.11+incompatible github.com/docker/docker v20.10.11+incompatible github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.0.2 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.2.1 github.com/stretchr/testify v1.7.0 golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c ) require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd // indirect github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b // indirect github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/distribution v2.7.1+incompatible // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.4.0 // indirect github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/gomodule/redigo v1.8.2 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/klauspost/compress v1.13.6 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.11.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.30.0 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 // indirect github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 // indirect github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f // indirect golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d // indirect golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect google.golang.org/grpc v1.43.0 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) oras-go-1.1.1/Makefile0000644000175000017500000000260514212212744014051 0ustar nileshnilesh# TODO: use this for fetch-dist TARGET_OBJS ?= todo1.txt todo2.txt .PHONY: test test: vendor check-encoding ./scripts/test.sh .PHONY: covhtml covhtml: open .cover/coverage.html .PHONY: acceptance acceptance: ./scripts/acceptance.sh .PHONY: clean clean: git status --ignored --short | grep '^!! ' | sed 's/!! //' | xargs rm -rf .PHONY: check-encoding check-encoding: ! find pkg examples -name "*.go" -type f -exec file "{}" ";" | grep CRLF ! find scripts -name "*.sh" -type f -exec file "{}" ";" | grep CRLF .PHONY: fix-encoding fix-encoding: find pkg examples -type f -name "*.go" -exec sed -i -e "s/\r//g" {} + find scripts -type f -name "*.sh" -exec sed -i -e "s/\r//g" {} + .PHONY: vendor vendor: GO111MODULE=on go mod vendor # TODO: use this .PHONY: fetch-dist fetch-dist: mkdir -p _dist cd _dist && \ for obj in ${TARGET_OBJS} ; do \ curl -sSL -o oras_${VERSION}_$${obj} https://github.com/oras-project/oras-go/releases/download/v${VERSION}/oras_${VERSION}_$${obj} ; \ done # 1. mkdir _dist/ # 2. manually download .zip / .tar.gz from release page # 3. move files into _dist/ # 4. make sign # 5. upload .asc files back to release page .PHONY: sign sign: for f in $$(ls _dist/*.{zip,tar.gz} 2>/dev/null) ; do \ gpg --armor --detach-sign $${f} ; \ done .PHONY: checksums checksums: cd _dist/ && \ for f in $$(ls *.{zip,tar.gz} 2>/dev/null) ; do \ shasum -a 256 $${f} ; \ done oras-go-1.1.1/pkg/0000755000175000017500000000000014212212744013167 5ustar nileshnileshoras-go-1.1.1/pkg/oras/0000755000175000017500000000000014212212744014133 5ustar nileshnileshoras-go-1.1.1/pkg/oras/store.go0000644000175000017500000001272214212212744015622 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package oras import ( "context" "io" "io/ioutil" "time" "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/remotes" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "golang.org/x/sync/errgroup" orascontent "oras.land/oras-go/pkg/content" ) type hybridStore struct { cache *orascontent.Memory cachedMediaTypes []string cacheOnly bool provider content.Provider ingester content.Ingester } func newHybridStoreFromPusher(pusher remotes.Pusher, cachedMediaTypes []string, cacheOnly bool) *hybridStore { // construct an ingester from a pusher ingester := pusherIngester{ pusher: pusher, } return &hybridStore{ cache: orascontent.NewMemory(), cachedMediaTypes: cachedMediaTypes, ingester: ingester, cacheOnly: cacheOnly, } } func (s *hybridStore) Set(desc ocispec.Descriptor, content []byte) { s.cache.Set(desc, content) } func (s *hybridStore) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) { reader, err := s.cache.Fetch(ctx, desc) if err == nil { return reader, err } if s.provider != nil { rat, err := s.provider.ReaderAt(ctx, desc) return ioutil.NopCloser(orascontent.NewReaderAtWrapper(rat)), err } return nil, err } func (s *hybridStore) Push(ctx context.Context, desc ocispec.Descriptor) (content.Writer, error) { return s.Writer(ctx, content.WithDescriptor(desc)) } // Writer begins or resumes the active writer identified by desc func (s *hybridStore) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) { var wOpts content.WriterOpts for _, opt := range opts { if err := opt(&wOpts); err != nil { return nil, err } } if isAllowedMediaType(wOpts.Desc.MediaType, s.cachedMediaTypes...) || s.ingester == nil { pusher, err := s.cache.Pusher(ctx, "") if err != nil { return nil, err } cacheWriter, err := pusher.Push(ctx, wOpts.Desc) if err != nil { return nil, err } // if we cache it only, do not pass it through if s.cacheOnly { return cacheWriter, nil } ingesterWriter, err := s.ingester.Writer(ctx, opts...) switch { case err == nil: return newTeeWriter(wOpts.Desc, cacheWriter, ingesterWriter), nil case errdefs.IsAlreadyExists(err): return cacheWriter, nil } return nil, err } return s.ingester.Writer(ctx, opts...) } // teeWriter tees the content to one or more content.Writer type teeWriter struct { writers []content.Writer digester digest.Digester status content.Status } func newTeeWriter(desc ocispec.Descriptor, writers ...content.Writer) *teeWriter { now := time.Now() return &teeWriter{ writers: writers, digester: digest.Canonical.Digester(), status: content.Status{ Total: desc.Size, StartedAt: now, UpdatedAt: now, }, } } func (t *teeWriter) Close() error { g := new(errgroup.Group) for _, w := range t.writers { w := w // closure issues, see https://golang.org/doc/faq#closures_and_goroutines g.Go(func() error { return w.Close() }) } return g.Wait() } func (t *teeWriter) Write(p []byte) (n int, err error) { g := new(errgroup.Group) for _, w := range t.writers { w := w // closure issues, see https://golang.org/doc/faq#closures_and_goroutines g.Go(func() error { n, err := w.Write(p[:]) if err != nil { return err } if n != len(p) { return io.ErrShortWrite } return nil }) } err = g.Wait() n = len(p) if err != nil { return n, err } _, _ = t.digester.Hash().Write(p[:n]) t.status.Offset += int64(len(p)) t.status.UpdatedAt = time.Now() return n, nil } // Digest may return empty digest or panics until committed. func (t *teeWriter) Digest() digest.Digest { return t.digester.Digest() } func (t *teeWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error { g := new(errgroup.Group) for _, w := range t.writers { w := w // closure issues, see https://golang.org/doc/faq#closures_and_goroutines g.Go(func() error { return w.Commit(ctx, size, expected, opts...) }) } return g.Wait() } // Status returns the current state of write func (t *teeWriter) Status() (content.Status, error) { return t.status, nil } // Truncate updates the size of the target blob func (t *teeWriter) Truncate(size int64) error { g := new(errgroup.Group) for _, w := range t.writers { w := w // closure issues, see https://golang.org/doc/faq#closures_and_goroutines g.Go(func() error { return w.Truncate(size) }) } return g.Wait() } // pusherIngester simple wrapper to get an ingester from a pusher type pusherIngester struct { pusher remotes.Pusher } func (p pusherIngester) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) { var wOpts content.WriterOpts for _, opt := range opts { if err := opt(&wOpts); err != nil { return nil, err } } return p.pusher.Push(ctx, wOpts.Desc) } oras-go-1.1.1/pkg/oras/errors.go0000644000175000017500000000265014212212744016001 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package oras import ( "errors" "fmt" ) // Common errors var ( ErrResolverUndefined = errors.New("resolver undefined") ErrFromResolverUndefined = errors.New("from target resolver undefined") ErrToResolverUndefined = errors.New("to target resolver undefined") ErrFromTargetUndefined = errors.New("from target undefined") ErrToTargetUndefined = errors.New("from target undefined") ) // Path validation related errors var ( ErrDirtyPath = errors.New("dirty path") ErrPathNotSlashSeparated = errors.New("path not slash separated") ErrAbsolutePathDisallowed = errors.New("absolute path disallowed") ErrPathTraversalDisallowed = errors.New("path traversal disallowed") ) // ErrStopProcessing is used to stop processing an oras operation. // This error only makes sense in sequential pulling operation. var ErrStopProcessing = fmt.Errorf("stop processing") oras-go-1.1.1/pkg/oras/copy.go0000644000175000017500000001520614212212744015440 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package oras import ( "bytes" "context" "fmt" "sync" "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/images" "github.com/containerd/containerd/log" "github.com/containerd/containerd/remotes" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/pkg/target" ) // Copy copy a ref from one target.Target to a ref in another target.Target. If toRef is blank, reuses fromRef // Returns the root // Descriptor of the copied item. Can use the root to retrieve child elements from target.Target. func Copy(ctx context.Context, from target.Target, fromRef string, to target.Target, toRef string, opts ...CopyOpt) (ocispec.Descriptor, error) { if from == nil { return ocispec.Descriptor{}, ErrFromTargetUndefined } if to == nil { return ocispec.Descriptor{}, ErrToTargetUndefined } // blank toRef if toRef == "" { toRef = fromRef } opt := copyOptsDefaults() for _, o := range opts { if err := o(opt); err != nil { return ocispec.Descriptor{}, err } } if from == nil { return ocispec.Descriptor{}, ErrFromResolverUndefined } if to == nil { return ocispec.Descriptor{}, ErrToResolverUndefined } // for the "from", we resolve the ref, then use resolver.Fetcher to fetch the various content blobs // for the "to", we simply use resolver.Pusher to push the various content blobs _, desc, err := from.Resolve(ctx, fromRef) if err != nil { return ocispec.Descriptor{}, err } fetcher, err := from.Fetcher(ctx, fromRef) if err != nil { return ocispec.Descriptor{}, err } // construct the reference we send to the pusher using the digest, so it knows what the root is pushRef := fmt.Sprintf("%s@%s", toRef, desc.Digest.String()) pusher, err := to.Pusher(ctx, pushRef) if err != nil { return ocispec.Descriptor{}, err } if err := transferContent(ctx, desc, fetcher, pusher, opt); err != nil { return ocispec.Descriptor{}, err } return desc, nil } func transferContent(ctx context.Context, desc ocispec.Descriptor, fetcher remotes.Fetcher, pusher remotes.Pusher, opts *copyOpts) error { var descriptors, manifests []ocispec.Descriptor lock := &sync.Mutex{} picker := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { if isAllowedMediaType(desc.MediaType, opts.allowedMediaTypes...) { if opts.filterName(desc) { lock.Lock() defer lock.Unlock() descriptors = append(descriptors, desc) } return nil, nil } return nil, nil }) // we use a hybrid store - a cache wrapping the underlying pusher - for two reasons: // 1. so that we can cache the manifests as pushing them, then retrieve them later to push in reverse order after the blobs // 2. so that we can retrieve them to analyze and find children in the Dispatch routine store := opts.contentProvideIngesterPusherFetcher if store == nil { store = newHybridStoreFromPusher(pusher, opts.cachedMediaTypes, true) } // fetchHandler pushes to the *store*, which may or may not cache it baseFetchHandler := func(p remotes.Pusher, f remotes.Fetcher) images.HandlerFunc { return images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { cw, err := p.Push(ctx, desc) if err != nil { if !errdefs.IsAlreadyExists(err) { return nil, err } return nil, nil } defer cw.Close() rc, err := f.Fetch(ctx, desc) if err != nil { return nil, err } defer rc.Close() return nil, content.Copy(ctx, cw, rc, desc.Size, desc.Digest) }) } // track all of our manifests that will be cached fetchHandler := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { if isAllowedMediaType(desc.MediaType, opts.cachedMediaTypes...) { lock.Lock() defer lock.Unlock() manifests = append(manifests, desc) } return baseFetchHandler(store, fetcher)(ctx, desc) }) handlers := []images.Handler{ filterHandler(opts, opts.allowedMediaTypes...), } handlers = append(handlers, opts.baseHandlers...) handlers = append(handlers, fetchHandler, picker, images.ChildrenHandler(&ProviderWrapper{Fetcher: store}), ) handlers = append(handlers, opts.callbackHandlers...) if err := opts.dispatch(ctx, images.Handlers(handlers...), nil, desc); err != nil { return err } // we cached all of the manifests, so push those out // Iterate in reverse order as seen, parent always uploaded after child for i := len(manifests) - 1; i >= 0; i-- { _, err := baseFetchHandler(pusher, store)(ctx, manifests[i]) if err != nil { return err } } // if the option to request the root manifest was passed, accommodate it if opts.saveManifest != nil && len(manifests) > 0 { rc, err := store.Fetch(ctx, manifests[0]) if err != nil { return fmt.Errorf("could not get root manifest to save based on CopyOpt: %v", err) } defer rc.Close() buf := new(bytes.Buffer) if _, err := buf.ReadFrom(rc); err != nil { return fmt.Errorf("unable to read data for root manifest to save based on CopyOpt: %v", err) } // get the root manifest from the store opts.saveManifest(buf.Bytes()) } // if the option to request the layers was passed, accommodate it if opts.saveLayers != nil && len(descriptors) > 0 { opts.saveLayers(descriptors) } return nil } func filterHandler(opts *copyOpts, allowedMediaTypes ...string) images.HandlerFunc { return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { switch { case isAllowedMediaType(desc.MediaType, ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageIndex): return nil, nil case isAllowedMediaType(desc.MediaType, allowedMediaTypes...): if opts.filterName(desc) { return nil, nil } log.G(ctx).Warnf("blob no name: %v", desc.Digest) default: log.G(ctx).Warnf("unknown type: %v", desc.MediaType) } return nil, images.ErrStopHandler } } func isAllowedMediaType(mediaType string, allowedMediaTypes ...string) bool { if len(allowedMediaTypes) == 0 { return true } for _, allowedMediaType := range allowedMediaTypes { if mediaType == allowedMediaType { return true } } return false } oras-go-1.1.1/pkg/oras/opts.go0000644000175000017500000001671214212212744015456 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package oras import ( "context" "fmt" "io" "path/filepath" "strings" "sync" "github.com/containerd/containerd/images" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "golang.org/x/sync/semaphore" orascontent "oras.land/oras-go/pkg/content" ) func copyOptsDefaults() *copyOpts { return ©Opts{ dispatch: images.Dispatch, filterName: filterName, cachedMediaTypes: []string{ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageIndex}, validateName: ValidateNameAsPath, } } type CopyOpt func(o *copyOpts) error type copyOpts struct { allowedMediaTypes []string dispatch func(context.Context, images.Handler, *semaphore.Weighted, ...ocispec.Descriptor) error baseHandlers []images.Handler callbackHandlers []images.Handler contentProvideIngesterPusherFetcher orascontent.Store filterName func(ocispec.Descriptor) bool cachedMediaTypes []string saveManifest func([]byte) saveLayers func([]ocispec.Descriptor) validateName func(desc ocispec.Descriptor) error userAgent string } // ValidateNameAsPath validates name in the descriptor as file path in order // to generate good packages intended to be pulled using the FileStore or // the oras cli. // For cross-platform considerations, only unix paths are accepted. func ValidateNameAsPath(desc ocispec.Descriptor) error { // no empty name path, ok := orascontent.ResolveName(desc) if !ok || path == "" { return orascontent.ErrNoName } // path should be clean if target := filepath.ToSlash(filepath.Clean(path)); target != path { return errors.Wrap(ErrDirtyPath, path) } // path should be slash-separated if strings.Contains(path, "\\") { return errors.Wrap(ErrPathNotSlashSeparated, path) } // disallow absolute path: covers unix and windows format if strings.HasPrefix(path, "/") { return errors.Wrap(ErrAbsolutePathDisallowed, path) } if len(path) > 2 { c := path[0] if path[1] == ':' && path[2] == '/' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') { return errors.Wrap(ErrAbsolutePathDisallowed, path) } } // disallow path traversal if strings.HasPrefix(path, "../") || path == ".." { return errors.Wrap(ErrPathTraversalDisallowed, path) } return nil } // dispatchBFS behaves the same as images.Dispatch() but in sequence with breath-first search. func dispatchBFS(ctx context.Context, handler images.Handler, weighted *semaphore.Weighted, descs ...ocispec.Descriptor) error { for i := 0; i < len(descs); i++ { desc := descs[i] children, err := handler.Handle(ctx, desc) if err != nil { switch err := errors.Cause(err); err { case images.ErrSkipDesc: continue // don't traverse the children. case ErrStopProcessing: return nil } return err } descs = append(descs, children...) } return nil } func filterName(desc ocispec.Descriptor) bool { // needs to be filled in return true } // WithAdditionalCachedMediaTypes adds media types normally cached in memory when pulling. // This does not replace the default media types, but appends to them func WithAdditionalCachedMediaTypes(cachedMediaTypes ...string) CopyOpt { return func(o *copyOpts) error { o.cachedMediaTypes = append(o.cachedMediaTypes, cachedMediaTypes...) return nil } } // WithAllowedMediaType sets the allowed media types func WithAllowedMediaType(allowedMediaTypes ...string) CopyOpt { return func(o *copyOpts) error { o.allowedMediaTypes = append(o.allowedMediaTypes, allowedMediaTypes...) return nil } } // WithAllowedMediaTypes sets the allowed media types func WithAllowedMediaTypes(allowedMediaTypes []string) CopyOpt { return func(o *copyOpts) error { o.allowedMediaTypes = append(o.allowedMediaTypes, allowedMediaTypes...) return nil } } // WithPullByBFS opt to pull in sequence with breath-first search func WithPullByBFS(o *copyOpts) error { o.dispatch = dispatchBFS return nil } // WithPullBaseHandler provides base handlers, which will be called before // any pull specific handlers. func WithPullBaseHandler(handlers ...images.Handler) CopyOpt { return func(o *copyOpts) error { o.baseHandlers = append(o.baseHandlers, handlers...) return nil } } // WithPullCallbackHandler provides callback handlers, which will be called after // any pull specific handlers. func WithPullCallbackHandler(handlers ...images.Handler) CopyOpt { return func(o *copyOpts) error { o.callbackHandlers = append(o.callbackHandlers, handlers...) return nil } } // WithContentProvideIngester opt to the provided Provider and Ingester // for file system I/O, including caches. func WithContentStore(store orascontent.Store) CopyOpt { return func(o *copyOpts) error { o.contentProvideIngesterPusherFetcher = store return nil } } // WithPullEmptyNameAllowed allows pulling blobs with empty name. func WithPullEmptyNameAllowed() CopyOpt { return func(o *copyOpts) error { o.filterName = func(ocispec.Descriptor) bool { return true } return nil } } // WithPullStatusTrack report results to stdout func WithPullStatusTrack(writer io.Writer) CopyOpt { return WithPullCallbackHandler(pullStatusTrack(writer)) } func pullStatusTrack(writer io.Writer) images.Handler { var printLock sync.Mutex return images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { if name, ok := orascontent.ResolveName(desc); ok { digestString := desc.Digest.String() if err := desc.Digest.Validate(); err == nil { if algo := desc.Digest.Algorithm(); algo == digest.SHA256 { digestString = desc.Digest.Encoded()[:12] } } printLock.Lock() defer printLock.Unlock() fmt.Fprintln(writer, "Downloaded", digestString, name) } return nil, nil }) } // WithNameValidation validates the image title in the descriptor. // Pass nil to disable name validation. func WithNameValidation(validate func(desc ocispec.Descriptor) error) CopyOpt { return func(o *copyOpts) error { o.validateName = validate return nil } } // WithUserAgent set the user agent string in http communications func WithUserAgent(agent string) CopyOpt { return func(o *copyOpts) error { o.userAgent = agent return nil } } // WithLayerDescriptors passes the slice of Descriptors for layers to the // provided func. If the passed parameter is nil, returns an error. func WithLayerDescriptors(save func([]ocispec.Descriptor)) CopyOpt { return func(o *copyOpts) error { if save == nil { return errors.New("layers save func must be non-nil") } o.saveLayers = save return nil } } // WithRootManifest passes the root manifest for the artifacts to the provided // func. If the passed parameter is nil, returns an error. func WithRootManifest(save func(b []byte)) CopyOpt { return func(o *copyOpts) error { if save == nil { return errors.New("manifest save func must be non-nil") } o.saveManifest = save return nil } } oras-go-1.1.1/pkg/oras/oras_test.go0000644000175000017500000004255214212212744016475 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package oras import ( "archive/tar" "bytes" "compress/gzip" "context" _ "crypto/sha256" "fmt" "io" "io/ioutil" "os" "path/filepath" "testing" "time" orascontent "oras.land/oras-go/pkg/content" "oras.land/oras-go/pkg/target" "github.com/containerd/containerd/images" "github.com/distribution/distribution/v3/configuration" "github.com/distribution/distribution/v3/registry" _ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/phayes/freeport" "github.com/stretchr/testify/suite" ) var ( testTarball = "../../testdata/charts/chartmuseum-1.8.2.tgz" testDir = "../../testdata/charts/chartmuseum" testDirFiles = []string{ "Chart.yaml", "values.yaml", "README.md", "templates/_helpers.tpl", "templates/NOTES.txt", "templates/service.yaml", ".helmignore", } ) type ORASTestSuite struct { suite.Suite DockerRegistryHost string } func newContext() context.Context { return context.Background() } func newResolver() target.Target { reg, _ := orascontent.NewRegistry(orascontent.RegistryOptions{}) return reg } // Start Docker registry func (suite *ORASTestSuite) SetupSuite() { config := &configuration.Configuration{} port, err := freeport.GetFreePort() if err != nil { suite.Nil(err, "no error finding free port for test registry") } suite.DockerRegistryHost = fmt.Sprintf("localhost:%d", port) config.HTTP.Addr = fmt.Sprintf(":%d", port) config.HTTP.DrainTimeout = time.Duration(10) * time.Second config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}} dockerRegistry, err := registry.NewRegistry(context.Background(), config) suite.Nil(err, "no error finding free port for test registry") go dockerRegistry.ListenAndServe() } // Push files to docker registry func (suite *ORASTestSuite) Test_0_Copy() { var ( err error ref string desc ocispec.Descriptor descriptors []ocispec.Descriptor store *orascontent.File memStore *orascontent.Memory ) _, err = Copy(newContext(), nil, ref, nil, ref) suite.NotNil(err, "error pushing with empty resolver") _, err = Copy(newContext(), orascontent.NewMemory(), ref, newResolver(), "") suite.NotNil(err, "error pushing when ref missing hostname") ref = fmt.Sprintf("%s/empty:test", suite.DockerRegistryHost) memStore = orascontent.NewMemory() config, configDesc, err := orascontent.GenerateConfig(nil) suite.Nil(err, "no error generating config") memStore.Set(configDesc, config) emptyManifest, emptyManifestDesc, err := orascontent.GenerateManifest(&configDesc, nil) suite.Nil(err, "no error creating manifest with empty descriptors") err = memStore.StoreManifest(ref, emptyManifestDesc, emptyManifest) suite.Nil(err, "no error pushing manifest with empty descriptors") _, err = Copy(newContext(), memStore, ref, newResolver(), "") suite.Nil(err, "no error pushing with empty descriptors") // Load descriptors with test chart tgz (as single layer) ref = fmt.Sprintf("%s/chart-tgz:test", suite.DockerRegistryHost) store = orascontent.NewFile("") err = store.Load(configDesc, config) suite.Nil(err, "no error loading config for test chart") basename := filepath.Base(testTarball) desc, err = store.Add(basename, "", testTarball) suite.Nil(err, "no error loading test chart") testChartManifest, testChartManifestDesc, err := orascontent.GenerateManifest(&configDesc, nil, desc) suite.Nil(err, "no error creating manifest with test chart descriptor") err = store.StoreManifest(ref, testChartManifestDesc, testChartManifest) suite.Nil(err, "no error pushing manifest with test chart descriptor") fmt.Printf("%s\n", testChartManifest) _, err = Copy(newContext(), store, ref, newResolver(), "") suite.Nil(err, "no error pushing test chart tgz (as single layer)") // Load descriptors with test chart dir (each file as layer) testDirAbs, err := filepath.Abs(testDir) suite.Nil(err, "no error parsing test directory") store = orascontent.NewFile(testDirAbs) err = store.Load(configDesc, config) suite.Nil(err, "no error saving config for test dir") descriptors = []ocispec.Descriptor{} var ff = func(pathX string, infoX os.FileInfo, errX error) error { if !infoX.IsDir() { filename := filepath.Join(filepath.Dir(pathX), infoX.Name()) name := filepath.ToSlash(filename) desc, err = store.Add(name, "", filename) if err != nil { return err } descriptors = append(descriptors, desc) } return nil } cwd, _ := os.Getwd() os.Chdir(testDir) filepath.Walk(".", ff) os.Chdir(cwd) ref = fmt.Sprintf("%s/chart-dir:test", suite.DockerRegistryHost) testChartDirManifest, testChartDirManifestDesc, err := orascontent.GenerateManifest(&configDesc, nil, descriptors...) suite.Nil(err, "no error creating manifest with test chart dir (each file as layer)") err = store.StoreManifest(ref, testChartDirManifestDesc, testChartDirManifest) suite.Nil(err, "no error pushing manifest with test chart dir (each file as layer)") _, err = Copy(newContext(), store, ref, newResolver(), "") suite.Nil(err, "no error pushing test chart dir (each file as layer)") } // Pull files and verify descriptors func (suite *ORASTestSuite) Test_1_Pull() { var ( err error ref string desc ocispec.Descriptor store *orascontent.Memory emptyDesc ocispec.Descriptor ) desc, err = Copy(newContext(), nil, ref, nil, ref) suite.NotNil(err, "error pulling with empty resolver") suite.Equal(desc, emptyDesc, "descriptor empty pulling with empty resolver") // Pull non-existent store = orascontent.NewMemory() ref = fmt.Sprintf("%s/nonexistent:test", suite.DockerRegistryHost) desc, err = Copy(newContext(), newResolver(), ref, store, ref) suite.NotNil(err, "error pulling non-existent ref") suite.Equal(desc, emptyDesc, "descriptor empty with error") // Pull chart-tgz store = orascontent.NewMemory() ref = fmt.Sprintf("%s/chart-tgz:test", suite.DockerRegistryHost) _, err = Copy(newContext(), newResolver(), ref, store, ref) suite.Nil(err, "no error pulling chart-tgz ref") // Verify the descriptors, single layer/file content, err := ioutil.ReadFile(testTarball) suite.Nil(err, "no error loading test chart") name := filepath.Base(testTarball) _, actualContent, ok := store.GetByName(name) suite.True(ok, "find in memory") suite.Equal(content, actualContent, ".tgz content matches on pull") // Pull chart-dir store = orascontent.NewMemory() ref = fmt.Sprintf("%s/chart-dir:test", suite.DockerRegistryHost) desc, err = Copy(newContext(), newResolver(), ref, store, ref) suite.Nil(err, "no error pulling chart-dir ref") // Verify the descriptors, multiple layers/files cwd, _ := os.Getwd() os.Chdir(testDir) for _, filename := range testDirFiles { content, err = ioutil.ReadFile(filename) suite.Nil(err, fmt.Sprintf("no error loading %s", filename)) _, actualContent, ok := store.GetByName(filename) suite.True(ok, "find in memory") suite.Equal(content, actualContent, fmt.Sprintf("%s content matches on pull", filename)) } os.Chdir(cwd) } // Push and pull with customized media types func (suite *ORASTestSuite) Test_2_MediaType() { var ( testData = [][]string{ {"hi.txt", "application/vnd.me.hi", "hi"}, {"bye.txt", "application/vnd.me.bye", "bye"}, } err error ref string descriptors []ocispec.Descriptor store *orascontent.Memory ) // Push content with customized media types store = orascontent.NewMemory() descriptors = nil for _, data := range testData { desc, _ := store.Add(data[0], data[1], []byte(data[2])) descriptors = append(descriptors, desc) } ref = fmt.Sprintf("%s/media-type:test", suite.DockerRegistryHost) config, configDesc, err := orascontent.GenerateConfig(nil) suite.Nil(err, "no error generating config") store.Set(configDesc, config) emptyManifest, emptyManifestDesc, err := orascontent.GenerateManifest(&configDesc, nil, descriptors...) suite.Nil(err, "no error creating manifest with empty descriptors") err = store.StoreManifest(ref, emptyManifestDesc, emptyManifest) suite.Nil(err, "no error pushing manifest with empty descriptors") _, err = Copy(newContext(), store, ref, newResolver(), ref) suite.Nil(err, "no error pushing test data with customized media type") // Pull with all media types store = orascontent.NewMemory() store.Set(configDesc, config) ref = fmt.Sprintf("%s/media-type:test", suite.DockerRegistryHost) _, err = Copy(newContext(), newResolver(), ref, store, ref) suite.Nil(err, "no error pulling media-type ref") for _, data := range testData { _, actualContent, ok := store.GetByName(data[0]) suite.True(ok, "find in memory") content := []byte(data[2]) suite.Equal(content, actualContent, "test content matches on pull") } // Pull with specified media type store = orascontent.NewMemory() store.Set(configDesc, config) ref = fmt.Sprintf("%s/media-type:test", suite.DockerRegistryHost) _, err = Copy(newContext(), newResolver(), ref, store, ref, WithAllowedMediaType(testData[0][1])) suite.Nil(err, "no error pulling media-type ref") for _, data := range testData[:1] { _, actualContent, ok := store.GetByName(data[0]) suite.True(ok, "find in memory") content := []byte(data[2]) suite.Equal(content, actualContent, "test content matches on pull") } // Pull with non-existing media type, so only should do root manifest store = orascontent.NewMemory() store.Set(configDesc, config) ref = fmt.Sprintf("%s/media-type:test", suite.DockerRegistryHost) _, err = Copy(newContext(), newResolver(), ref, store, ref, WithAllowedMediaType("non.existing.media.type")) suite.Nil(err, "no error pulling media-type ref") } // Pull with condition func (suite *ORASTestSuite) Test_3_Conditional_Pull() { var ( testData = [][]string{ {"version.txt", "edge"}, {"content.txt", "hello world"}, } err error ref string descriptors []ocispec.Descriptor store *orascontent.Memory stop bool ) // Push test content store = orascontent.NewMemory() descriptors = nil for _, data := range testData { desc, _ := store.Add(data[0], "", []byte(data[1])) descriptors = append(descriptors, desc) } ref = fmt.Sprintf("%s/conditional-pull:test", suite.DockerRegistryHost) config, configDesc, err := orascontent.GenerateConfig(nil) suite.Nil(err, "no error generating config") store.Set(configDesc, config) testManifest, testManifestDesc, err := orascontent.GenerateManifest(&configDesc, nil, descriptors...) suite.Nil(err, "no error creating manifest with test descriptors") err = store.StoreManifest(ref, testManifestDesc, testManifest) suite.Nil(err, "no error pushing manifest with test descriptors") _, err = Copy(newContext(), store, ref, newResolver(), ref) suite.Nil(err, "no error pushing test data") // Pull all contents in sequence store = orascontent.NewMemory() store.Set(configDesc, config) ref = fmt.Sprintf("%s/conditional-pull:test", suite.DockerRegistryHost) _, err = Copy(newContext(), newResolver(), ref, store, ref, WithPullByBFS) suite.Nil(err, "no error pulling ref") for i, data := range testData { _, actualContent, ok := store.GetByName(data[0]) suite.True(ok, "find in memory") content := []byte(data[1]) suite.Equal(content, actualContent, "test content matches on pull") name, _ := orascontent.ResolveName(descriptors[i]) suite.Equal(data[0], name, "content sequence matches on pull") } // Selective pull contents: stop at the very beginning store = orascontent.NewMemory() store.Set(configDesc, config) ref = fmt.Sprintf("%s/conditional-pull:test", suite.DockerRegistryHost) _, err = Copy(newContext(), newResolver(), ref, store, ref, WithPullByBFS, WithPullBaseHandler(images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { if name, ok := orascontent.ResolveName(desc); ok && name == testData[0][0] { return nil, ErrStopProcessing } return nil, nil }))) suite.Nil(err, "no error pulling ref") // Selective pull contents: stop in the middle store = orascontent.NewMemory() store.Set(configDesc, config) ref = fmt.Sprintf("%s/conditional-pull:test", suite.DockerRegistryHost) stop = false _, err = Copy(newContext(), newResolver(), ref, store, ref, WithPullByBFS, WithPullBaseHandler(images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { if stop { return nil, ErrStopProcessing } if name, ok := orascontent.ResolveName(desc); ok && name == testData[0][0] { stop = true } return nil, nil }))) suite.Nil(err, "no error pulling ref") for _, data := range testData[:1] { _, actualContent, ok := store.GetByName(data[0]) suite.True(ok, "find in memory") content := []byte(data[1]) suite.Equal(content, actualContent, "test content matches on pull") } } // Test for vulnerability GHSA-g5v4-5x39-vwhx func (suite *ORASTestSuite) Test_4_GHSA_g5v4_5x39_vwhx() { var testVulnerability = func(headers []tar.Header, tag string, expectedError string) { // Step 1: build malicious tar+gzip buf := bytes.NewBuffer(nil) digester := digest.Canonical.Digester() zw := gzip.NewWriter(io.MultiWriter(buf, digester.Hash())) tarDigester := digest.Canonical.Digester() tw := tar.NewWriter(io.MultiWriter(zw, tarDigester.Hash())) for _, header := range headers { err := tw.WriteHeader(&header) suite.Nil(err, "error writing header") } err := tw.Close() suite.Nil(err, "error closing tar") err = zw.Close() suite.Nil(err, "error closing gzip") // Step 2: construct malicious descriptor evilDesc := ocispec.Descriptor{ MediaType: ocispec.MediaTypeImageLayerGzip, Digest: digester.Digest(), Size: int64(buf.Len()), Annotations: map[string]string{ orascontent.AnnotationDigest: tarDigester.Digest().String(), orascontent.AnnotationUnpack: "true", ocispec.AnnotationTitle: "foo", }, } // Step 3: upload malicious artifact to registry memoryStore := orascontent.NewMemory() memoryStore.Set(evilDesc, buf.Bytes()) ref := fmt.Sprintf("%s/evil:%s", suite.DockerRegistryHost, tag) config, configDesc, err := orascontent.GenerateConfig(nil) suite.Nil(err, "no error generating config") memoryStore.Set(configDesc, config) testManifest, testManifestDesc, err := orascontent.GenerateManifest(&configDesc, nil, evilDesc) suite.Nil(err, "no error creating manifest with evil descriptors") err = memoryStore.StoreManifest(ref, testManifestDesc, testManifest) suite.Nil(err, "no error pushing manifest with evil descriptors") _, err = Copy(newContext(), memoryStore, ref, newResolver(), ref) suite.Nil(err, "no error pushing test data") // Step 4: pull malicious tar with oras filestore and ensure error tempDir, err := ioutil.TempDir("", "oras_test") if err != nil { suite.FailNow("error creating temp directory", err) } defer os.RemoveAll(tempDir) store := orascontent.NewFile(tempDir) defer store.Close() err = store.Load(configDesc, config) suite.Nil(err, "no error saving config") ref = fmt.Sprintf("%s/evil:%s", suite.DockerRegistryHost, tag) _, err = Copy(newContext(), newResolver(), ref, store, ref) suite.NotNil(err, "error expected pulling malicious tar") suite.Contains(err.Error(), expectedError, "did not get correct error message", ) } tests := []struct { name string headers []tar.Header tag string expectedError string }{ { name: "Test symbolic link path traversal", headers: []tar.Header{ { Typeflag: tar.TypeDir, Name: "foo/subdir/", Mode: 0755, }, { // Symbolic link to `foo` Typeflag: tar.TypeSymlink, Name: "foo/subdir/parent", Linkname: "..", Mode: 0755, }, { // Symbolic link to `../etc/passwd` Typeflag: tar.TypeSymlink, Name: "foo/subdir/parent/passwd", Linkname: "../../etc/passwd", Mode: 0644, }, { // Symbolic link to `../etc` Typeflag: tar.TypeSymlink, Name: "foo/subdir/parent/etc", Linkname: "../../etc", Mode: 0644, }, }, tag: "symlink_path", expectedError: "no symbolic link allowed", }, { name: "Test symbolic link pointing to outside", headers: []tar.Header{ { // Symbolic link to `/etc/passwd` Typeflag: tar.TypeSymlink, Name: "foo/passwd", Linkname: "../../../etc/passwd", Mode: 0644, }, }, tag: "symlink", expectedError: "is outside of", }, { name: "Test hard link pointing to outside", headers: []tar.Header{ { // Hard link to `/etc/passwd` Typeflag: tar.TypeLink, Name: "foo/passwd", Linkname: "../../../etc/passwd", Mode: 0644, }, }, tag: "hardlink", expectedError: "is outside of", }, } for _, test := range tests { suite.T().Log(test.name) testVulnerability(test.headers, test.tag, test.expectedError) } } func TestORASTestSuite(t *testing.T) { suite.Run(t, new(ORASTestSuite)) } oras-go-1.1.1/pkg/oras/opts_test.go0000644000175000017500000000447214212212744016515 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package oras import ( "testing" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/suite" ) type PushOptsSuite struct { suite.Suite } func (suite *PushOptsSuite) TestValidateNameAsPath() { var err error // valid path err = ValidateNameAsPath(descFromName("hello.txt")) suite.NoError(err, "valid path") err = ValidateNameAsPath(descFromName("foo/bar")) suite.NoError(err, "valid path with multiple sub-directories") // no empty name err = ValidateNameAsPath(descFromName("")) suite.Error(err, "empty path") // path should be clean err = ValidateNameAsPath(descFromName("./hello.txt")) suite.Error(err, "dirty path") err = ValidateNameAsPath(descFromName("foo/../bar")) suite.Error(err, "dirty path") // path should be slash-separated err = ValidateNameAsPath(descFromName("foo\\bar")) suite.Error(err, "path not slash separated") // disallow absolute path err = ValidateNameAsPath(descFromName("/foo/bar")) suite.Error(err, "unix: absolute path disallowed") err = ValidateNameAsPath(descFromName("C:\\foo\\bar")) suite.Error(err, "windows: absolute path disallowed") err = ValidateNameAsPath(descFromName("C:/foo/bar")) suite.Error(err, "windows: absolute path disallowed") // disallow path traversal err = ValidateNameAsPath(descFromName("..")) suite.Error(err, "path traversal disallowed") err = ValidateNameAsPath(descFromName("../bar")) suite.Error(err, "path traversal disallowed") err = ValidateNameAsPath(descFromName("foo/../../bar")) suite.Error(err, "path traversal disallowed") } func TestPushOptsSuite(t *testing.T) { suite.Run(t, new(PushOptsSuite)) } func descFromName(name string) ocispec.Descriptor { return ocispec.Descriptor{ Annotations: map[string]string{ ocispec.AnnotationTitle: name, }, } } oras-go-1.1.1/pkg/oras/provider.go0000644000175000017500000000351114212212744016314 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package oras import ( "context" "errors" "io" "github.com/containerd/containerd/content" "github.com/containerd/containerd/remotes" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // ProviderWrapper wraps a remote.Fetcher to make a content.Provider, which is useful for things type ProviderWrapper struct { Fetcher remotes.Fetcher } func (p *ProviderWrapper) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) { if p.Fetcher == nil { return nil, errors.New("no Fetcher provided") } return &fetcherReaderAt{ ctx: ctx, fetcher: p.Fetcher, desc: desc, offset: 0, }, nil } type fetcherReaderAt struct { ctx context.Context fetcher remotes.Fetcher desc ocispec.Descriptor rc io.ReadCloser offset int64 } func (f *fetcherReaderAt) Close() error { if f.rc == nil { return nil } return f.rc.Close() } func (f *fetcherReaderAt) Size() int64 { return f.desc.Size } func (f *fetcherReaderAt) ReadAt(p []byte, off int64) (n int, err error) { // if we do not have a readcloser, get it if f.rc == nil || f.offset != off { rc, err := f.fetcher.Fetch(f.ctx, f.desc) if err != nil { return 0, err } f.rc = rc } n, err = f.rc.Read(p) if err != nil { return n, err } f.offset += int64(n) return n, err } oras-go-1.1.1/pkg/auth/0000755000175000017500000000000014212212744014130 5ustar nileshnileshoras-go-1.1.1/pkg/auth/docker/0000755000175000017500000000000014212212744015377 5ustar nileshnileshoras-go-1.1.1/pkg/auth/docker/client_test.go0000644000175000017500000001216114212212744020244 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "context" "fmt" "io/ioutil" "net/http" "os" "path/filepath" "testing" "time" "github.com/distribution/distribution/v3/configuration" "github.com/distribution/distribution/v3/registry" _ "github.com/distribution/distribution/v3/registry/auth/htpasswd" _ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory" "github.com/phayes/freeport" "github.com/stretchr/testify/suite" "golang.org/x/crypto/bcrypt" iface "oras.land/oras-go/pkg/auth" ) var ( testConfig = "test.config" testHtpasswd = "test.htpasswd" testUsername = "alice" testPassword = "wonderland" ) type DockerClientTestSuite struct { suite.Suite DockerRegistryHost string Client *Client TempTestDir string } func newContext() context.Context { return context.Background() } func (suite *DockerClientTestSuite) SetupSuite() { tempDir, err := ioutil.TempDir("", "oras_auth_docker_test") suite.Nil(err, "no error creating temp directory for test") suite.TempTestDir = tempDir // Create client client, err := NewClient(filepath.Join(suite.TempTestDir, testConfig)) suite.Nil(err, "no error creating client") var ok bool suite.Client, ok = client.(*Client) suite.True(ok, "NewClient returns a *docker.Client inside") // Create htpasswd file with bcrypt secret, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost) suite.Nil(err, "no error generating bcrypt password for test htpasswd file") authRecord := fmt.Sprintf("%s:%s\n", testUsername, string(secret)) htpasswdPath := filepath.Join(suite.TempTestDir, testHtpasswd) err = ioutil.WriteFile(htpasswdPath, []byte(authRecord), 0644) suite.Nil(err, "no error creating test htpasswd file") // Registry config config := &configuration.Configuration{} port, err := freeport.GetFreePort() suite.Nil(err, "no error finding free port for test registry") suite.DockerRegistryHost = fmt.Sprintf("localhost:%d", port) config.HTTP.Addr = fmt.Sprintf(":%d", port) config.HTTP.DrainTimeout = time.Duration(10) * time.Second config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}} config.Auth = configuration.Auth{ "htpasswd": configuration.Parameters{ "realm": "localhost", "path": htpasswdPath, }, } dockerRegistry, err := registry.NewRegistry(context.Background(), config) suite.Nil(err, "no error finding free port for test registry") // Start Docker registry go dockerRegistry.ListenAndServe() ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() for { select { case <-ctx.Done(): suite.FailNow("docker registry timed out") default: } req, err := http.NewRequestWithContext( ctx, http.MethodGet, fmt.Sprintf("http://%s/v2/", suite.DockerRegistryHost), nil, ) suite.Nil(err, "no error in generate a /v2/ request") resp, err := http.DefaultClient.Do(req) if err == nil { resp.Body.Close() break } time.Sleep(time.Second) } } func (suite *DockerClientTestSuite) TearDownSuite() { os.RemoveAll(suite.TempTestDir) } func (suite *DockerClientTestSuite) Test_0_Login() { var err error err = suite.Client.Login(newContext(), suite.DockerRegistryHost, "oscar", "opponent", false) suite.NotNil(err, "error logging into registry with invalid credentials") err = suite.Client.Login(newContext(), suite.DockerRegistryHost, testUsername, testPassword, false) suite.Nil(err, "no error logging into registry with valid credentials") } func (suite *DockerClientTestSuite) Test_1_LoginWithOpts() { var err error opts := []iface.LoginOption{ iface.WithLoginContext(newContext()), iface.WithLoginHostname(suite.DockerRegistryHost), iface.WithLoginUsername("oscar"), iface.WithLoginSecret("opponent"), } err = suite.Client.LoginWithOpts(opts...) suite.NotNil(err, "error logging into registry with invalid credentials (LoginWithOpts)") opts = []iface.LoginOption{ iface.WithLoginContext(newContext()), iface.WithLoginHostname(suite.DockerRegistryHost), iface.WithLoginUsername(testUsername), iface.WithLoginSecret(testPassword), } err = suite.Client.LoginWithOpts(opts...) suite.Nil(err, "no error logging into registry with valid credentials (LoginWithOpts)") } func (suite *DockerClientTestSuite) Test_2_Logout() { var err error err = suite.Client.Logout(newContext(), "non-existing-host:42") suite.NotNil(err, "error logging out of registry that has no entry") err = suite.Client.Logout(newContext(), suite.DockerRegistryHost) suite.Nil(err, "no error logging out of registry") } func TestDockerClientTestSuite(t *testing.T) { suite.Run(t, new(DockerClientTestSuite)) } oras-go-1.1.1/pkg/auth/docker/login.go0000644000175000017500000000452414212212744017043 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "context" ctypes "github.com/docker/cli/cli/config/types" "github.com/docker/docker/api/types" "github.com/docker/docker/registry" iface "oras.land/oras-go/pkg/auth" ) // Login logs in to a docker registry identified by the hostname. // Deprecated: use LoginWithOpts func (c *Client) Login(ctx context.Context, hostname, username, secret string, insecure bool) error { settings := &iface.LoginSettings{ Context: ctx, Hostname: hostname, Username: username, Secret: secret, Insecure: insecure, } return c.login(settings) } // LoginWithOpts logs in to a docker registry identified by the hostname with custom options. func (c *Client) LoginWithOpts(options ...iface.LoginOption) error { settings := &iface.LoginSettings{} for _, option := range options { option(settings) } return c.login(settings) } func (c *Client) login(settings *iface.LoginSettings) error { hostname := resolveHostname(settings.Hostname) cred := types.AuthConfig{ Username: settings.Username, ServerAddress: hostname, } if settings.Username == "" { cred.IdentityToken = settings.Secret } else { cred.Password = settings.Secret } opts := registry.ServiceOptions{} if settings.Insecure { opts.InsecureRegistries = []string{hostname} } // Login to ensure valid credential remote, err := registry.NewService(opts) if err != nil { return err } ctx := settings.Context if ctx == nil { ctx = context.Background() } userAgent := settings.UserAgent if userAgent == "" { userAgent = "oras" } if _, token, err := remote.Auth(ctx, &cred, userAgent); err != nil { return err } else if token != "" { cred.Username = "" cred.Password = "" cred.IdentityToken = token } // Store credential return c.primaryCredentialsStore(hostname).Store(ctypes.AuthConfig(cred)) } oras-go-1.1.1/pkg/auth/docker/resolver.go0000644000175000017500000000465614212212744017602 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "context" "net/http" "github.com/containerd/containerd/remotes" "github.com/containerd/containerd/remotes/docker" ctypes "github.com/docker/cli/cli/config/types" "github.com/docker/docker/registry" iface "oras.land/oras-go/pkg/auth" ) // Resolver returns a new authenticated resolver. // Deprecated: use ResolverWithOpts func (c *Client) Resolver(_ context.Context, client *http.Client, plainHTTP bool) (remotes.Resolver, error) { return docker.NewResolver(docker.ResolverOptions{ Credentials: c.Credential, Client: client, PlainHTTP: plainHTTP, }), nil } // ResolverWithOpts returns a new authenticated resolver with custom options. func (c *Client) ResolverWithOpts(options ...iface.ResolverOption) (remotes.Resolver, error) { settings := &iface.ResolverSettings{} for _, option := range options { option(settings) } return docker.NewResolver(docker.ResolverOptions{ Credentials: c.Credential, Client: settings.Client, PlainHTTP: settings.PlainHTTP, Headers: settings.Headers, }), nil } // Credential returns the login credential of the request host. func (c *Client) Credential(hostname string) (string, string, error) { hostname = resolveHostname(hostname) var ( auth ctypes.AuthConfig err error ) for _, cfg := range c.configs { auth, err = cfg.GetAuthConfig(hostname) if err != nil { // fall back to next config continue } if auth.IdentityToken != "" { return "", auth.IdentityToken, nil } if auth.Username == "" && auth.Password == "" { // fall back to next config continue } return auth.Username, auth.Password, nil } return "", "", err } // resolveHostname resolves Docker specific hostnames func resolveHostname(hostname string) string { switch hostname { case registry.IndexHostname, registry.IndexName, registry.DefaultV2Registry.Host: return registry.IndexServer } return hostname } oras-go-1.1.1/pkg/auth/docker/client.go0000644000175000017500000000652414212212744017213 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "os" "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/config/credentials" "github.com/pkg/errors" "oras.land/oras-go/pkg/auth" ) // Client provides authentication operations for docker registries. type Client struct { configs []*configfile.ConfigFile } // NewClient creates a new auth client based on provided config paths. // If not config path is provided, the default path is used. // Credentials are read from the first config and fall backs to next. // All changes will only be written to the first config file. func NewClient(configPaths ...string) (auth.Client, error) { if len(configPaths) == 0 { cfg, err := config.Load(config.Dir()) if err != nil { return nil, err } if !cfg.ContainsAuth() { cfg.CredentialsStore = credentials.DetectDefaultStore(cfg.CredentialsStore) } return &Client{ configs: []*configfile.ConfigFile{cfg}, }, nil } var configs []*configfile.ConfigFile for _, path := range configPaths { cfg, err := loadConfigFile(path) if err != nil { return nil, errors.Wrap(err, path) } configs = append(configs, cfg) } return &Client{ configs: configs, }, nil } // NewClientWithDockerFallback creates a new auth client // which falls back on Docker's default config path. // This allows support for ~/.docker/config.json as a fallback, // as well as support for the DOCKER_CONFIG environment variable. func NewClientWithDockerFallback(configPaths ...string) (auth.Client, error) { if len(configPaths) == 0 { return NewClient() } var configs []*configfile.ConfigFile for _, path := range configPaths { cfg, err := loadConfigFile(path) if err != nil { return nil, errors.Wrap(err, path) } configs = append(configs, cfg) } // Add the Docker default config last dockerFallbackCfg, err := config.Load(config.Dir()) if err != nil { return nil, err } if !dockerFallbackCfg.ContainsAuth() { dockerFallbackCfg.CredentialsStore = credentials.DetectDefaultStore(dockerFallbackCfg.CredentialsStore) } configs = append(configs, dockerFallbackCfg) return &Client{ configs: configs, }, nil } func (c *Client) primaryCredentialsStore(hostname string) credentials.Store { return c.configs[0].GetCredentialsStore(hostname) } // loadConfigFile reads the configuration files from the given path. func loadConfigFile(path string) (*configfile.ConfigFile, error) { cfg := configfile.New(path) if _, err := os.Stat(path); err == nil { file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() if err := cfg.LoadFromReader(file); err != nil { return nil, err } } else if !os.IsNotExist(err) { return nil, err } if !cfg.ContainsAuth() { cfg.CredentialsStore = credentials.DetectDefaultStore(cfg.CredentialsStore) } return cfg, nil } oras-go-1.1.1/pkg/auth/docker/logout.go0000644000175000017500000000226314212212744017242 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "context" "github.com/docker/cli/cli/config/configfile" "oras.land/oras-go/pkg/auth" ) // Logout logs out from a docker registry identified by the hostname. func (c *Client) Logout(_ context.Context, hostname string) error { hostname = resolveHostname(hostname) var configs []*configfile.ConfigFile for _, config := range c.configs { if _, ok := config.AuthConfigs[hostname]; ok { configs = append(configs, config) } } if len(configs) == 0 { return auth.ErrNotLoggedIn } // Log out form the primary config only as backups are read-only. return c.primaryCredentialsStore(hostname).Erase(hostname) } oras-go-1.1.1/pkg/auth/client.go0000644000175000017500000000306414212212744015740 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package auth import ( "context" "errors" "net/http" "github.com/containerd/containerd/remotes" ) // Common errors var ( ErrNotLoggedIn = errors.New("not logged in") ) // Client provides authentication operations for remotes. type Client interface { // Login logs in to a remote server identified by the hostname. // Deprecated: use LoginWithOpts Login(ctx context.Context, hostname, username, secret string, insecure bool) error // LoginWithOpts logs in to a remote server identified by the hostname with custom options LoginWithOpts(options ...LoginOption) error // Logout logs out from a remote server identified by the hostname. Logout(ctx context.Context, hostname string) error // Resolver returns a new authenticated resolver. // Deprecated: use ResolverWithOpts Resolver(ctx context.Context, client *http.Client, plainHTTP bool) (remotes.Resolver, error) // ResolverWithOpts returns a new authenticated resolver with custom options. ResolverWithOpts(options ...ResolverOption) (remotes.Resolver, error) } oras-go-1.1.1/pkg/auth/client_opts.go0000644000175000017500000000607614212212744017013 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package auth import ( "context" "net/http" ) type ( // LoginOption allows specifying various settings on login. LoginOption func(*LoginSettings) // LoginSettings represent all the various settings on login. LoginSettings struct { Context context.Context Hostname string Username string Secret string Insecure bool UserAgent string } ) // WithLoginContext returns a function that sets the Context setting on login. func WithLoginContext(context context.Context) LoginOption { return func(settings *LoginSettings) { settings.Context = context } } // WithLoginHostname returns a function that sets the Hostname setting on login. func WithLoginHostname(hostname string) LoginOption { return func(settings *LoginSettings) { settings.Hostname = hostname } } // WithLoginUsername returns a function that sets the Username setting on login. func WithLoginUsername(username string) LoginOption { return func(settings *LoginSettings) { settings.Username = username } } // WithLoginSecret returns a function that sets the Secret setting on login. func WithLoginSecret(secret string) LoginOption { return func(settings *LoginSettings) { settings.Secret = secret } } // WithLoginInsecure returns a function that sets the Insecure setting to true on login. func WithLoginInsecure() LoginOption { return func(settings *LoginSettings) { settings.Insecure = true } } // WithLoginUserAgent returns a function that sets the UserAgent setting on login. func WithLoginUserAgent(userAgent string) LoginOption { return func(settings *LoginSettings) { settings.UserAgent = userAgent } } type ( // ResolverOption allows specifying various settings on the resolver. ResolverOption func(*ResolverSettings) // ResolverSettings represent all the various settings on a resolver. ResolverSettings struct { Client *http.Client PlainHTTP bool Headers http.Header } ) // WithResolverClient returns a function that sets the Client setting on resolver. func WithResolverClient(client *http.Client) ResolverOption { return func(settings *ResolverSettings) { settings.Client = client } } // WithResolverPlainHTTP returns a function that sets the PlainHTTP setting to true on resolver. func WithResolverPlainHTTP() ResolverOption { return func(settings *ResolverSettings) { settings.PlainHTTP = true } } // WithResolverHeaders returns a function that sets the Headers setting on resolver. func WithResolverHeaders(headers http.Header) ResolverOption { return func(settings *ResolverSettings) { settings.Headers = headers } } oras-go-1.1.1/pkg/auth/client_opts_test.go0000644000175000017500000000723314212212744020046 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package auth import ( "context" "net/http" "testing" "github.com/stretchr/testify/suite" ) type ClientOptsSuite struct { suite.Suite } func (suite *ClientOptsSuite) TestWithLoginContext() { settings := &LoginSettings{} suite.Nil(settings.Context, "settings.Context is nil by default") ctx := context.Background() opt := WithLoginContext(ctx) opt(settings) suite.Equal(ctx, settings.Context, "Able to override settings.Context") } func (suite *ClientOptsSuite) TestWithLoginHostname() { settings := &LoginSettings{} suite.Equal("", settings.Hostname, "settings.Hostname is empty string by default") hostname := "example.com" opt := WithLoginHostname(hostname) opt(settings) suite.Equal(hostname, settings.Hostname, "Able to override settings.Hostname") } func (suite *ClientOptsSuite) TestWithLoginUsername() { settings := &LoginSettings{} suite.Equal("", settings.Username, "settings.Username is empty string by default") username := "fran" opt := WithLoginUsername(username) opt(settings) suite.Equal(username, settings.Username, "Able to override settings.Username") } func (suite *ClientOptsSuite) TestWithLoginSecret() { settings := &LoginSettings{} suite.Equal("", settings.Secret, "settings.Secret is empty string by default") secret := "shhhhhhhhhh" opt := WithLoginSecret(secret) opt(settings) suite.Equal(secret, settings.Secret, "Able to override settings.Secret") } func (suite *ClientOptsSuite) TestWithLoginInsecure() { settings := &LoginSettings{} suite.Equal(false, settings.Insecure, "settings.Insecure is false by default") opt := WithLoginInsecure() opt(settings) suite.Equal(true, settings.Insecure, "Able to override settings.Insecure") } func (suite *ClientOptsSuite) TestWithLoginUserAgent() { settings := &LoginSettings{} suite.Equal("", settings.UserAgent, "settings.UserAgent is empty string by default") userAgent := "superclient" opt := WithLoginUserAgent(userAgent) opt(settings) suite.Equal(userAgent, settings.UserAgent, "Able to override settings.UserAgent") } func (suite *ClientOptsSuite) TestWithResolverClient() { settings := &ResolverSettings{} suite.Nil(settings.Client, "settings.Client is nil by default") defaultClient := http.DefaultClient opt := WithResolverClient(defaultClient) opt(settings) suite.Equal(defaultClient, settings.Client, "Able to override settings.Client") } func (suite *ClientOptsSuite) TestWithResolverPlainHTTP() { settings := &ResolverSettings{} suite.Equal(false, settings.PlainHTTP, "settings.PlainHTTP is false by default") plainHTTP := true opt := WithResolverPlainHTTP() opt(settings) suite.Equal(plainHTTP, settings.PlainHTTP, "Able to override settings.PlainHTTP") } func (suite *ClientOptsSuite) TestWithResolverHeaders() { settings := &ResolverSettings{} suite.Nil(settings.Headers, "settings.Headers is nil by default") key := "User-Agent" value := "oras-go/test" headers := http.Header{} headers.Set(key, value) opt := WithResolverHeaders(headers) opt(settings) suite.Equal(settings.Headers.Get(key), value, "Able to override settings.Headers") } func TestClientOptsSuite(t *testing.T) { suite.Run(t, new(ClientOptsSuite)) } oras-go-1.1.1/pkg/target/0000755000175000017500000000000014212212744014455 5ustar nileshnileshoras-go-1.1.1/pkg/target/target.go0000644000175000017500000000153414212212744016275 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package target import ( "github.com/containerd/containerd/remotes" ) // Target represents a place to which one can send/push or retrieve/pull artifacts. // Anything that implements the Target interface can be used as a place to send or // retrieve artifacts. type Target interface { remotes.Resolver } oras-go-1.1.1/pkg/content/0000755000175000017500000000000014212212744014641 5ustar nileshnileshoras-go-1.1.1/pkg/content/registry.go0000644000175000017500000000435114212212744017043 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import ( "context" "crypto/tls" "fmt" "net/http" "os" auth "oras.land/oras-go/pkg/auth/docker" "github.com/containerd/containerd/remotes" "github.com/containerd/containerd/remotes/docker" ) // RegistryOptions provide configuration options to a Registry type RegistryOptions struct { Configs []string Username string Password string Insecure bool PlainHTTP bool } // Registry provides content from a spec-compliant registry. Create an use a new one for each // registry with unique configuration of RegistryOptions. type Registry struct { remotes.Resolver } // NewRegistry creates a new Registry store func NewRegistry(opts RegistryOptions) (*Registry, error) { return &Registry{ Resolver: newResolver(opts.Username, opts.Password, opts.Insecure, opts.PlainHTTP, opts.Configs...), }, nil } func newResolver(username, password string, insecure bool, plainHTTP bool, configs ...string) remotes.Resolver { opts := docker.ResolverOptions{ PlainHTTP: plainHTTP, } client := http.DefaultClient if insecure { client.Transport = &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, } } opts.Client = client if username != "" || password != "" { opts.Credentials = func(hostName string) (string, string, error) { return username, password, nil } return docker.NewResolver(opts) } cli, err := auth.NewClient(configs...) if err != nil { fmt.Fprintf(os.Stderr, "WARNING: Error loading auth file: %v\n", err) } resolver, err := cli.Resolver(context.Background(), client, plainHTTP) if err != nil { fmt.Fprintf(os.Stderr, "WARNING: Error loading resolver: %v\n", err) resolver = docker.NewResolver(opts) } return resolver } oras-go-1.1.1/pkg/content/untar.go0000644000175000017500000001045014212212744016321 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import ( "archive/tar" "fmt" "io" "github.com/containerd/containerd/content" ) // NewUntarWriter wrap a writer with an untar, so that the stream is untarred // // By default, it calculates the hash when writing. If the option `skipHash` is true, // it will skip doing the hash. Skipping the hash is intended to be used only // if you are confident about the validity of the data being passed to the writer, // and wish to save on the hashing time. func NewUntarWriter(writer content.Writer, opts ...WriterOpt) content.Writer { // process opts for default wOpts := DefaultWriterOpts() for _, opt := range opts { if err := opt(&wOpts); err != nil { return nil } } return NewPassthroughWriter(writer, func(r io.Reader, w io.Writer, done chan<- error) { tr := tar.NewReader(r) var err error for { _, err := tr.Next() if err == io.EOF { // clear the error, since we do not pass an io.EOF err = nil break // End of archive } if err != nil { // pass the error on err = fmt.Errorf("UntarWriter tar file header read error: %v", err) break } // write out the untarred data // we can handle io.EOF, just go to the next file // any other errors should stop and get reported b := make([]byte, wOpts.Blocksize, wOpts.Blocksize) for { var n int n, err = tr.Read(b) if err != nil && err != io.EOF { err = fmt.Errorf("UntarWriter file data read error: %v\n", err) break } l := n if n > len(b) { l = len(b) } if _, err2 := w.Write(b[:l]); err2 != nil { err = fmt.Errorf("UntarWriter error writing to underlying writer: %v", err2) break } if err == io.EOF { // go to the next file break } } // did we break with a non-nil and non-EOF error? if err != nil && err != io.EOF { break } } done <- err }, opts...) } // NewUntarWriterByName wrap multiple writers with an untar, so that the stream is untarred and passed // to the appropriate writer, based on the filename. If a filename is not found, it is up to the called func // to determine how to process it. func NewUntarWriterByName(writers func(string) (content.Writer, error), opts ...WriterOpt) content.Writer { // process opts for default wOpts := DefaultWriterOpts() for _, opt := range opts { if err := opt(&wOpts); err != nil { return nil } } // need a PassthroughMultiWriter here return NewPassthroughMultiWriter(writers, func(r io.Reader, getwriter func(name string) io.Writer, done chan<- error) { tr := tar.NewReader(r) var err error for { header, err := tr.Next() if err == io.EOF { // clear the error, since we do not pass an io.EOF err = nil break // End of archive } if err != nil { // pass the error on err = fmt.Errorf("UntarWriter tar file header read error: %v", err) break } // get the filename filename := header.Name // get the writer for this filename w := getwriter(filename) if w == nil { continue } // write out the untarred data // we can handle io.EOF, just go to the next file // any other errors should stop and get reported b := make([]byte, wOpts.Blocksize, wOpts.Blocksize) for { var n int n, err = tr.Read(b) if err != nil && err != io.EOF { err = fmt.Errorf("UntarWriter file data read error: %v\n", err) break } l := n if n > len(b) { l = len(b) } if _, err2 := w.Write(b[:l]); err2 != nil { err = fmt.Errorf("UntarWriter error writing to underlying writer at for name '%s': %v", filename, err2) break } if err == io.EOF { // go to the next file break } } // did we break with a non-nil and non-EOF error? if err != nil && err != io.EOF { break } } done <- err }, opts...) } oras-go-1.1.1/pkg/content/errors.go0000644000175000017500000000202614212212744016504 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import "errors" // Common errors var ( ErrNotFound = errors.New("not_found") ErrNoName = errors.New("no_name") ErrUnsupportedSize = errors.New("unsupported_size") ErrUnsupportedVersion = errors.New("unsupported_version") ErrInvalidReference = errors.New("invalid_reference") ) // FileStore errors var ( ErrPathTraversalDisallowed = errors.New("path_traversal_disallowed") ErrOverwriteDisallowed = errors.New("overwrite_disallowed") ) oras-go-1.1.1/pkg/content/multireader.go0000644000175000017500000000324514212212744017511 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import ( "context" "fmt" "io" "github.com/containerd/containerd/remotes" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // MultiReader store to read content from multiple stores. It finds the content by asking each underlying // store to find the content, which it does based on the hash. // // Example: // fileStore := NewFileStore(rootPath) // memoryStore := NewMemoryStore() // // load up content in fileStore and memoryStore // multiStore := MultiReader([]content.Provider{fileStore, memoryStore}) // // You now can use multiStore anywhere that content.Provider is accepted type MultiReader struct { stores []remotes.Fetcher } // AddStore add a store to read from func (m *MultiReader) AddStore(store ...remotes.Fetcher) { m.stores = append(m.stores, store...) } // ReaderAt get a reader func (m MultiReader) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) { for _, store := range m.stores { r, err := store.Fetch(ctx, desc) if r != nil && err == nil { return r, nil } } // we did not find any return nil, fmt.Errorf("not found") } oras-go-1.1.1/pkg/content/file_test.go0000644000175000017500000000324314212212744017150 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content_test import ( "context" "io/ioutil" "os" "testing" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/pkg/content" ) func TestFileStoreNoName(t *testing.T) { testContent := []byte("Hello World!") descriptor := ocispec.Descriptor{ MediaType: ocispec.MediaTypeImageConfig, Digest: digest.FromBytes(testContent), Size: int64(len(testContent)), // do NOT add the AnnotationTitle here; it is the essence of the test } tests := []struct { opts []content.WriterOpt err error }{ {nil, nil}, {[]content.WriterOpt{content.WithErrorOnNoName()}, content.ErrNoName}, } for _, tt := range tests { rootPath, err := ioutil.TempDir("", "oras_filestore_test") if err != nil { t.Fatalf("error creating tempdir: %v", err) } defer os.RemoveAll(rootPath) fileStore := content.NewFile(rootPath, tt.opts...) ctx := context.Background() pusher, _ := fileStore.Pusher(ctx, "") if _, err := pusher.Push(ctx, descriptor); err != tt.err { t.Errorf("mismatched error, actual '%v', expected '%v'", err, tt.err) } } } oras-go-1.1.1/pkg/content/decompress.go0000644000175000017500000001167714212212744017350 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import ( "context" "errors" "strings" ctrcontent "github.com/containerd/containerd/content" "github.com/containerd/containerd/remotes" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // Decompress store to decompress content and extract from tar, if needed, wrapping // another store. By default, a FileStore will simply take each artifact and write it to // a file, as a MemoryStore will do into memory. If the artifact is gzipped or tarred, // you might want to store the actual object inside tar or gzip. Wrap your Store // with Decompress, and it will check the media-type and, if relevant, // gunzip and/or untar. // // For example: // // fileStore := NewFileStore(rootPath) // Decompress := store.NewDecompress(fileStore, WithBlocksize(blocksize)) // // The above example works if there is no tar, i.e. each artifact is just a single file, perhaps gzipped, // or if there is only one file in each tar archive. In other words, when each content.Writer has only one target output stream. // However, if you have multiple files in each tar archive, each archive of which is an artifact layer, then // you need a way to select how to handle each file in the tar archive. In other words, when each content.Writer has more than one // target output stream. In that case, use the following example: // // multiStore := NewMultiStore(rootPath) // some store that can handle different filenames // Decompress := store.NewDecompress(multiStore, WithBlocksize(blocksize), WithMultiWriterIngester()) // type Decompress struct { pusher remotes.Pusher blocksize int multiWriterIngester bool } func NewDecompress(pusher remotes.Pusher, opts ...WriterOpt) Decompress { // we have to reprocess the opts to find the blocksize var wOpts WriterOpts for _, opt := range opts { if err := opt(&wOpts); err != nil { // TODO: we probably should handle errors here continue } } return Decompress{pusher, wOpts.Blocksize, wOpts.MultiWriterIngester} } // Push get a content.Writer func (d Decompress) Push(ctx context.Context, desc ocispec.Descriptor) (ctrcontent.Writer, error) { // the logic is straightforward: // - if there is a desc in the opts, and the mediatype is tar or tar+gzip, then pass the correct decompress writer // - else, pass the regular writer var ( writer ctrcontent.Writer err error multiIngester MultiWriterPusher ok bool ) // check to see if we are supposed to use a MultiWriterIngester if d.multiWriterIngester { multiIngester, ok = d.pusher.(MultiWriterPusher) if !ok { return nil, errors.New("configured to use multiwriter ingester, but ingester does not implement multiwriter") } } // figure out if compression and/or archive exists // before we pass it down, we need to strip anything we are removing here // and possibly update the digest, since the store indexes things by digest hasGzip, hasTar, modifiedMediaType := checkCompression(desc.MediaType) desc.MediaType = modifiedMediaType // determine if we pass it blocksize, only if positive writerOpts := []WriterOpt{} if d.blocksize > 0 { writerOpts = append(writerOpts, WithBlocksize(d.blocksize)) } writer, err = d.pusher.Push(ctx, desc) if err != nil { return nil, err } // do we need to wrap with an untar writer? if hasTar { // if not multiingester, get a regular writer if multiIngester == nil { writer = NewUntarWriter(writer, writerOpts...) } else { writers, err := multiIngester.Pushers(ctx, desc) if err != nil { return nil, err } writer = NewUntarWriterByName(writers, writerOpts...) } } if hasGzip { if writer == nil { writer, err = d.pusher.Push(ctx, desc) if err != nil { return nil, err } } writer = NewGunzipWriter(writer, writerOpts...) } return writer, nil } // checkCompression check if the mediatype uses gzip compression or tar. // Returns if it has gzip and/or tar, as well as the base media type without // those suffixes. func checkCompression(mediaType string) (gzip, tar bool, mt string) { mt = mediaType gzipSuffix := "+gzip" gzipAltSuffix := ".gzip" tarSuffix := ".tar" switch { case strings.HasSuffix(mt, gzipSuffix): mt = mt[:len(mt)-len(gzipSuffix)] gzip = true case strings.HasSuffix(mt, gzipAltSuffix): mt = mt[:len(mt)-len(gzipAltSuffix)] gzip = true } if strings.HasSuffix(mt, tarSuffix) { mt = mt[:len(mt)-len(tarSuffix)] tar = true } return } oras-go-1.1.1/pkg/content/multireader_test.go0000644000175000017500000000456714212212744020560 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content_test import ( "context" "io/ioutil" "testing" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/pkg/content" ) var ( testContentA = []byte("Hello World!") testContentHashA = digest.FromBytes(testContentA) testContentB = []byte("So long and thanks for all the fish!") testContentHashB = digest.FromBytes(testContentB) testDescriptorA = ocispec.Descriptor{ MediaType: ocispec.MediaTypeImageConfig, Digest: testContentHashA, Size: int64(len(testContentA)), } testDescriptorB = ocispec.Descriptor{ MediaType: ocispec.MediaTypeImageConfig, Digest: testContentHashB, Size: int64(len(testContentB)), } ) func TestMultiReader(t *testing.T) { mem1, mem2 := content.NewMemory(), content.NewMemory() mem1.Add("a", ocispec.MediaTypeImageConfig, testContentA) mem2.Add("b", ocispec.MediaTypeImageConfig, testContentB) multiReader := content.MultiReader{} multiReader.AddStore(mem1, mem2) ctx := context.Background() contentA, err := multiReader.Fetch(ctx, testDescriptorA) if err != nil { t.Fatalf("failed to get a reader for descriptor A: %v", err) } outputA, err := ioutil.ReadAll(contentA) if err != nil { t.Fatalf("failed to read content for descriptor A: %v", err) } if string(outputA) != string(testContentA) { t.Errorf("mismatched content for A, actual '%s', expected '%s'", outputA, testContentA) } contentB, err := multiReader.Fetch(ctx, testDescriptorB) if err != nil { t.Fatalf("failed to get a reader for descriptor B: %v", err) } outputB, err := ioutil.ReadAll(contentB) if err != nil { t.Fatalf("failed to read content for descriptor B: %v", err) } if string(outputB) != string(testContentB) { t.Errorf("mismatched content for B, actual '%s', expected '%s'", outputB, testContentB) } } oras-go-1.1.1/pkg/content/consts.go0000644000175000017500000000354114212212744016504 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import ( "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) const ( // DefaultBlobMediaType specifies the default blob media type DefaultBlobMediaType = ocispec.MediaTypeImageLayer // DefaultBlobDirMediaType specifies the default blob directory media type DefaultBlobDirMediaType = ocispec.MediaTypeImageLayerGzip ) const ( // TempFilePattern specifies the pattern to create temporary files TempFilePattern = "oras" ) const ( // AnnotationDigest is the annotation key for the digest of the uncompressed content AnnotationDigest = "io.deis.oras.content.digest" // AnnotationUnpack is the annotation key for indication of unpacking AnnotationUnpack = "io.deis.oras.content.unpack" ) const ( // OCIImageIndexFile is the file name of the index from the OCI Image Layout Specification // Reference: https://github.com/opencontainers/image-spec/blob/master/image-layout.md#indexjson-file OCIImageIndexFile = "index.json" ) const ( // DefaultBlocksize default size of each slice of bytes read in each write through in gunzipand untar. // Simply uses the same size as io.Copy() DefaultBlocksize = 32768 ) const ( // what you get for a blank digest BlankHash = digest.Digest("sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") ) oras-go-1.1.1/pkg/content/readerat.go0000644000175000017500000000260014212212744016755 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import ( "io" "github.com/containerd/containerd/content" ) // ensure interface var ( _ content.ReaderAt = sizeReaderAt{} ) type readAtCloser interface { io.ReaderAt io.Closer } type sizeReaderAt struct { readAtCloser size int64 } func (ra sizeReaderAt) Size() int64 { return ra.size } func NopCloserAt(r io.ReaderAt) nopCloserAt { return nopCloserAt{r} } type nopCloserAt struct { io.ReaderAt } func (n nopCloserAt) Close() error { return nil } // readerAtWrapper wraps a ReaderAt to give a Reader type ReaderAtWrapper struct { offset int64 readerAt io.ReaderAt } func (r *ReaderAtWrapper) Read(p []byte) (n int, err error) { n, err = r.readerAt.ReadAt(p, r.offset) r.offset += int64(n) return } func NewReaderAtWrapper(readerAt io.ReaderAt) *ReaderAtWrapper { return &ReaderAtWrapper{readerAt: readerAt} } oras-go-1.1.1/pkg/content/passthrough_test.go0000644000175000017500000001371114212212744020601 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content_test import ( "bytes" "context" "fmt" "io" "math/rand" "testing" ctrcontent "github.com/containerd/containerd/content" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/pkg/content" ) var ( testRef = "abc123" testContent = []byte("Hello World!") testContentHash = digest.FromBytes(testContent) appendText = "1" modifiedContent = fmt.Sprintf("%s%s", testContent, appendText) modifiedContentHash = digest.FromBytes([]byte(modifiedContent)) testDescriptor = ocispec.Descriptor{ MediaType: ocispec.MediaTypeImageConfig, Digest: testContentHash, Size: int64(len(testContent)), Annotations: map[string]string{ ocispec.AnnotationTitle: testRef, }, } modifiedDescriptor = ocispec.Descriptor{ MediaType: ocispec.MediaTypeImageConfig, Digest: modifiedContentHash, Size: int64(len(modifiedContent)), Annotations: map[string]string{ ocispec.AnnotationTitle: testRef, }, } ) func TestPassthroughWriter(t *testing.T) { // simple pass through function that modifies the data just slightly f := func(r io.Reader, w io.Writer, done chan<- error) { var ( err error n int ) for { b := make([]byte, 1024) n, err = r.Read(b) if err != nil && err != io.EOF { t.Fatalf("data read error: %v", err) break } l := n if n > len(b) { l = len(b) } // we change it just slightly b = b[:l] if l > 0 { b = append(b, []byte(appendText)...) } if _, err := w.Write(b); err != nil { t.Fatalf("error writing to underlying writer: %v", err) break } if err == io.EOF { break } } done <- err } tests := []struct { opts []content.WriterOpt hash digest.Digest }{ {nil, testContentHash}, {[]content.WriterOpt{content.WithInputHash(testContentHash), content.WithOutputHash(modifiedContentHash)}, testContentHash}, } for _, tt := range tests { ctx := context.Background() mem := content.NewMemory() pusher, _ := mem.Pusher(ctx, "") memw, err := pusher.Push(ctx, modifiedDescriptor) if err != nil { t.Fatalf("unexpected error getting the memory store writer: %v", err) } writer := content.NewPassthroughWriter(memw, f, tt.opts...) n, err := writer.Write(testContent) if err != nil { t.Fatalf("unexpected error on Write: %v", err) } if n != len(testContent) { t.Fatalf("wrote %d bytes instead of %d", n, len(testContent)) } if err := writer.Commit(ctx, testDescriptor.Size, tt.hash); err != nil { t.Errorf("unexpected error on Commit: %v", err) } if digest := writer.Digest(); digest != tt.hash { t.Errorf("mismatched digest: actual %v, expected %v", digest, tt.hash) } // make sure the data is what we expected _, b, found := mem.Get(modifiedDescriptor) if !found { t.Fatalf("target descriptor not found in underlying memory store") } if len(b) != len(modifiedContent) { t.Errorf("unexpectedly got %d bytes instead of expected %d", len(b), len(modifiedContent)) } if string(b) != modifiedContent { t.Errorf("mismatched content, expected '%s', got '%s'", modifiedContent, string(b)) } } } func TestPassthroughMultiWriter(t *testing.T) { // pass through function that selects one of two outputs var ( b1, b2 bytes.Buffer name1, name2 = "I am name 01", "I am name 02" // each of these is 12 bytes data1, data2 = make([]byte, 500), make([]byte, 500) ) rand.Read(data1) rand.Read(data2) combined := append([]byte(name1), data1...) combined = append(combined, []byte(name2)...) combined = append(combined, data2...) f := func(r io.Reader, getwriter func(name string) io.Writer, done chan<- error) { var ( err error ) // test is done rather simply, with a single 1024 byte chunk, split into 2x512 data streams, each of which is // 12 bytes of name and 500 bytes of data b := make([]byte, 1024) _, err = r.Read(b) if err != nil && err != io.EOF { t.Fatalf("data read error: %v", err) } // get the names and data for each n1, n2 := string(b[0:12]), string(b[512+0:512+12]) w1, w2 := getwriter(n1), getwriter(n2) if _, err := w1.Write(b[12:512]); err != nil { t.Fatalf("w1 write error: %v", err) } if _, err := w2.Write(b[512+12 : 1024]); err != nil { t.Fatalf("w2 write error: %v", err) } done <- err } var ( opts = []content.WriterOpt{content.WithInputHash(testContentHash), content.WithOutputHash(modifiedContentHash)} hash = testContentHash ) ctx := context.Background() writers := func(name string) (ctrcontent.Writer, error) { switch name { case name1: return content.NewIoContentWriter(&b1), nil case name2: return content.NewIoContentWriter(&b2), nil } return nil, fmt.Errorf("unknown name %s", name) } writer := content.NewPassthroughMultiWriter(writers, f, opts...) n, err := writer.Write(combined) if err != nil { t.Fatalf("unexpected error on Write: %v", err) } if n != len(combined) { t.Fatalf("wrote %d bytes instead of %d", n, len(combined)) } if err := writer.Commit(ctx, testDescriptor.Size, hash); err != nil { t.Errorf("unexpected error on Commit: %v", err) } if digest := writer.Digest(); digest != hash { t.Errorf("mismatched digest: actual %v, expected %v", digest, hash) } // make sure the data is what we expected if !bytes.Equal(data1, b1.Bytes()) { t.Errorf("b1 data1 did not match") } if !bytes.Equal(data2, b2.Bytes()) { t.Errorf("b2 data2 did not match") } } oras-go-1.1.1/pkg/content/interface.go0000644000175000017500000000140014212212744017123 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import ( "github.com/containerd/containerd/remotes" ) // ProvideIngester is the interface that groups the basic Read and Write methods. type Store interface { remotes.Pusher remotes.Fetcher } oras-go-1.1.1/pkg/content/passthrough.go0000644000175000017500000001735414212212744017551 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import ( "context" "errors" "io" "time" "github.com/containerd/containerd/content" "github.com/opencontainers/go-digest" ) // PassthroughWriter takes an input stream and passes it through to an underlying writer, // while providing the ability to manipulate the stream before it gets passed through type PassthroughWriter struct { writer content.Writer pipew *io.PipeWriter digester digest.Digester size int64 underlyingWriter *underlyingWriter reader *io.PipeReader hash *digest.Digest done chan error } // NewPassthroughWriter creates a pass-through writer that allows for processing // the content via an arbitrary function. The function should do whatever processing it // wants, reading from the Reader to the Writer. When done, it must indicate via // sending an error or nil to the Done func NewPassthroughWriter(writer content.Writer, f func(r io.Reader, w io.Writer, done chan<- error), opts ...WriterOpt) content.Writer { // process opts for default wOpts := DefaultWriterOpts() for _, opt := range opts { if err := opt(&wOpts); err != nil { return nil } } r, w := io.Pipe() pw := &PassthroughWriter{ writer: writer, pipew: w, digester: digest.Canonical.Digester(), underlyingWriter: &underlyingWriter{ writer: writer, digester: digest.Canonical.Digester(), hash: wOpts.OutputHash, }, reader: r, hash: wOpts.InputHash, done: make(chan error, 1), } go f(r, pw.underlyingWriter, pw.done) return pw } func (pw *PassthroughWriter) Write(p []byte) (n int, err error) { n, err = pw.pipew.Write(p) if pw.hash == nil { pw.digester.Hash().Write(p[:n]) } pw.size += int64(n) return } func (pw *PassthroughWriter) Close() error { if pw.pipew != nil { pw.pipew.Close() } pw.writer.Close() return nil } // Digest may return empty digest or panics until committed. func (pw *PassthroughWriter) Digest() digest.Digest { if pw.hash != nil { return *pw.hash } return pw.digester.Digest() } // Commit commits the blob (but no roll-back is guaranteed on an error). // size and expected can be zero-value when unknown. // Commit always closes the writer, even on error. // ErrAlreadyExists aborts the writer. func (pw *PassthroughWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error { if pw.pipew != nil { pw.pipew.Close() } err := <-pw.done if pw.reader != nil { pw.reader.Close() } if err != nil && err != io.EOF { return err } // Some underlying writers will validate an expected digest, so we need the option to pass it // that digest. That is why we caluclate the digest of the underlying writer throughout the write process. return pw.writer.Commit(ctx, pw.underlyingWriter.size, pw.underlyingWriter.Digest(), opts...) } // Status returns the current state of write func (pw *PassthroughWriter) Status() (content.Status, error) { return pw.writer.Status() } // Truncate updates the size of the target blob func (pw *PassthroughWriter) Truncate(size int64) error { return pw.writer.Truncate(size) } // underlyingWriter implementation of io.Writer to write to the underlying // io.Writer type underlyingWriter struct { writer content.Writer digester digest.Digester size int64 hash *digest.Digest } // Write write to the underlying writer func (u *underlyingWriter) Write(p []byte) (int, error) { n, err := u.writer.Write(p) if err != nil { return 0, err } if u.hash == nil { u.digester.Hash().Write(p) } u.size += int64(len(p)) return n, nil } // Size get total size written func (u *underlyingWriter) Size() int64 { return u.size } // Digest may return empty digest or panics until committed. func (u *underlyingWriter) Digest() digest.Digest { if u.hash != nil { return *u.hash } return u.digester.Digest() } // PassthroughMultiWriter single writer that passes through to multiple writers, allowing the passthrough // function to select which writer to use. type PassthroughMultiWriter struct { writers []*PassthroughWriter pipew *io.PipeWriter digester digest.Digester size int64 reader *io.PipeReader hash *digest.Digest done chan error startedAt time.Time updatedAt time.Time } func NewPassthroughMultiWriter(writers func(name string) (content.Writer, error), f func(r io.Reader, getwriter func(name string) io.Writer, done chan<- error), opts ...WriterOpt) content.Writer { // process opts for default wOpts := DefaultWriterOpts() for _, opt := range opts { if err := opt(&wOpts); err != nil { return nil } } r, w := io.Pipe() pmw := &PassthroughMultiWriter{ startedAt: time.Now(), updatedAt: time.Now(), done: make(chan error, 1), digester: digest.Canonical.Digester(), hash: wOpts.InputHash, pipew: w, reader: r, } // get our output writers getwriter := func(name string) io.Writer { writer, err := writers(name) if err != nil || writer == nil { return nil } pw := &PassthroughWriter{ writer: writer, digester: digest.Canonical.Digester(), underlyingWriter: &underlyingWriter{ writer: writer, digester: digest.Canonical.Digester(), hash: wOpts.OutputHash, }, done: make(chan error, 1), } pmw.writers = append(pmw.writers, pw) return pw.underlyingWriter } go f(r, getwriter, pmw.done) return pmw } func (pmw *PassthroughMultiWriter) Write(p []byte) (n int, err error) { n, err = pmw.pipew.Write(p) if pmw.hash == nil { pmw.digester.Hash().Write(p[:n]) } pmw.size += int64(n) pmw.updatedAt = time.Now() return } func (pmw *PassthroughMultiWriter) Close() error { pmw.pipew.Close() for _, w := range pmw.writers { w.Close() } return nil } // Digest may return empty digest or panics until committed. func (pmw *PassthroughMultiWriter) Digest() digest.Digest { if pmw.hash != nil { return *pmw.hash } return pmw.digester.Digest() } // Commit commits the blob (but no roll-back is guaranteed on an error). // size and expected can be zero-value when unknown. // Commit always closes the writer, even on error. // ErrAlreadyExists aborts the writer. func (pmw *PassthroughMultiWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error { pmw.pipew.Close() err := <-pmw.done if pmw.reader != nil { pmw.reader.Close() } if err != nil && err != io.EOF { return err } // Some underlying writers will validate an expected digest, so we need the option to pass it // that digest. That is why we caluclate the digest of the underlying writer throughout the write process. for _, w := range pmw.writers { // maybe this should be Commit(ctx, pw.underlyingWriter.size, pw.underlyingWriter.Digest(), opts...) w.done <- err if err := w.Commit(ctx, size, expected, opts...); err != nil { return err } } return nil } // Status returns the current state of write func (pmw *PassthroughMultiWriter) Status() (content.Status, error) { return content.Status{ StartedAt: pmw.startedAt, UpdatedAt: pmw.updatedAt, Total: pmw.size, }, nil } // Truncate updates the size of the target blob, but cannot do anything with a multiwriter func (pmw *PassthroughMultiWriter) Truncate(size int64) error { return errors.New("truncate unavailable on multiwriter") } oras-go-1.1.1/pkg/content/iowriter.go0000644000175000017500000000646014212212744017042 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import ( "context" "io" "io/ioutil" "github.com/containerd/containerd/content" "github.com/opencontainers/go-digest" ) // IoContentWriter writer that wraps an io.Writer, so the results can be streamed to // an open io.Writer. For example, can be used to pull a layer and write it to a file, or device. type IoContentWriter struct { writer io.Writer digester digest.Digester size int64 hash *digest.Digest } // NewIoContentWriter create a new IoContentWriter. // // By default, it calculates the hash when writing. If the option `skipHash` is true, // it will skip doing the hash. Skipping the hash is intended to be used only // if you are confident about the validity of the data being passed to the writer, // and wish to save on the hashing time. func NewIoContentWriter(writer io.Writer, opts ...WriterOpt) content.Writer { w := writer if w == nil { w = ioutil.Discard } // process opts for default wOpts := DefaultWriterOpts() for _, opt := range opts { if err := opt(&wOpts); err != nil { return nil } } ioc := &IoContentWriter{ writer: w, digester: digest.Canonical.Digester(), // we take the OutputHash, since the InputHash goes to the passthrough writer, // which then passes the processed output to us hash: wOpts.OutputHash, } return NewPassthroughWriter(ioc, func(r io.Reader, w io.Writer, done chan<- error) { // write out the data to the io writer var ( err error ) // we could use io.Copy, but calling it with the default blocksize is identical to // io.CopyBuffer. Otherwise, we would need some way to let the user flag "I want to use // io.Copy", when it should not matter to them b := make([]byte, wOpts.Blocksize, wOpts.Blocksize) _, err = io.CopyBuffer(w, r, b) done <- err }, opts...) } func (w *IoContentWriter) Write(p []byte) (n int, err error) { n, err = w.writer.Write(p) if err != nil { return 0, err } w.size += int64(n) if w.hash == nil { w.digester.Hash().Write(p[:n]) } return } func (w *IoContentWriter) Close() error { return nil } // Digest may return empty digest or panics until committed. func (w *IoContentWriter) Digest() digest.Digest { return w.digester.Digest() } // Commit commits the blob (but no roll-back is guaranteed on an error). // size and expected can be zero-value when unknown. // Commit always closes the writer, even on error. // ErrAlreadyExists aborts the writer. func (w *IoContentWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error { return nil } // Status returns the current state of write func (w *IoContentWriter) Status() (content.Status, error) { return content.Status{}, nil } // Truncate updates the size of the target blob func (w *IoContentWriter) Truncate(size int64) error { return nil } oras-go-1.1.1/pkg/content/decompress_test.go0000644000175000017500000000505314212212744020376 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content_test import ( "bytes" "compress/gzip" "context" "fmt" "testing" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/pkg/content" ) func TestDecompressStore(t *testing.T) { rawContent := []byte("Hello World!") var buf bytes.Buffer gw := gzip.NewWriter(&buf) if _, err := gw.Write(rawContent); err != nil { t.Fatalf("unable to create gzip content for testing: %v", err) } if err := gw.Close(); err != nil { t.Fatalf("unable to close gzip writer creating content for testing: %v", err) } gzipContent := buf.Bytes() gzipContentHash := digest.FromBytes(gzipContent) extensions := []string{"+gzip", ".gzip"} for _, ext := range extensions { gzipDescriptor := ocispec.Descriptor{ MediaType: fmt.Sprintf("%s%s", ocispec.MediaTypeImageConfig, ext), Digest: gzipContentHash, Size: int64(len(gzipContent)), } memStore := content.NewMemory() ctx := context.Background() memPusher, _ := memStore.Pusher(ctx, "") decompressStore := content.NewDecompress(memPusher, content.WithBlocksize(0)) decompressWriter, err := decompressStore.Push(ctx, gzipDescriptor) if err != nil { t.Fatalf("unable to get a decompress writer: %v", err) } n, err := decompressWriter.Write(gzipContent) if err != nil { t.Fatalf("failed to write to decompress writer: %v", err) } if n != len(gzipContent) { t.Fatalf("wrote %d instead of expected %d bytes", n, len(gzipContent)) } if err := decompressWriter.Commit(ctx, int64(len(gzipContent)), gzipContentHash); err != nil { t.Fatalf("unexpected error committing decompress writer: %v", err) } // and now we should be able to get the decompressed data from the memory store _, b, found := memStore.Get(gzipDescriptor) if !found { t.Fatalf("failed to get data from underlying memory store: %v", err) } if string(b) != string(rawContent) { t.Errorf("mismatched data in underlying memory store, actual '%s', expected '%s'", b, rawContent) } } } oras-go-1.1.1/pkg/content/oci.go0000644000175000017500000002277414212212744015756 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import ( "context" "encoding/json" "errors" "fmt" "io" "io/ioutil" "os" "path/filepath" "strings" "github.com/containerd/containerd/content" "github.com/containerd/containerd/content/local" "github.com/containerd/containerd/remotes" "github.com/opencontainers/go-digest" specs "github.com/opencontainers/image-spec/specs-go" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // OCI provides content from the file system with the OCI-Image layout. // Reference: https://github.com/opencontainers/image-spec/blob/master/image-layout.md type OCI struct { content.Store root string index *ocispec.Index nameMap map[string]ocispec.Descriptor } // NewOCI creates a new OCI store func NewOCI(rootPath string) (*OCI, error) { fileStore, err := local.NewStore(rootPath) if err != nil { return nil, err } store := &OCI{ Store: fileStore, root: rootPath, } if err := store.validateOCILayoutFile(); err != nil { return nil, err } if err := store.LoadIndex(); err != nil { return nil, err } return store, nil } // LoadIndex reads the index.json from the file system func (s *OCI) LoadIndex() error { path := filepath.Join(s.root, OCIImageIndexFile) indexFile, err := os.Open(path) if err != nil { if !os.IsNotExist(err) { return err } s.index = &ocispec.Index{ Versioned: specs.Versioned{ SchemaVersion: 2, // historical value }, } s.nameMap = make(map[string]ocispec.Descriptor) return nil } defer indexFile.Close() if err := json.NewDecoder(indexFile).Decode(&s.index); err != nil { return err } s.nameMap = make(map[string]ocispec.Descriptor) for _, desc := range s.index.Manifests { if name := desc.Annotations[ocispec.AnnotationRefName]; name != "" { s.nameMap[name] = desc } } return nil } // SaveIndex writes the index.json to the file system func (s *OCI) SaveIndex() error { // first need to update the index var descs []ocispec.Descriptor for name, desc := range s.nameMap { if desc.Annotations == nil { desc.Annotations = map[string]string{} } desc.Annotations[ocispec.AnnotationRefName] = name descs = append(descs, desc) } s.index.Manifests = descs indexJSON, err := json.Marshal(s.index) if err != nil { return err } path := filepath.Join(s.root, OCIImageIndexFile) return ioutil.WriteFile(path, indexJSON, 0644) } func (s *OCI) Resolver() remotes.Resolver { return s } func (s *OCI) Resolve(ctx context.Context, ref string) (name string, desc ocispec.Descriptor, err error) { if err := s.LoadIndex(); err != nil { return "", ocispec.Descriptor{}, err } desc, ok := s.nameMap[ref] if !ok { return "", ocispec.Descriptor{}, fmt.Errorf("reference %s not in store", ref) } return ref, desc, nil } func (s *OCI) Fetcher(ctx context.Context, ref string) (remotes.Fetcher, error) { if err := s.LoadIndex(); err != nil { return nil, err } if _, ok := s.nameMap[ref]; !ok { return nil, fmt.Errorf("reference %s not in store", ref) } return s, nil } // Fetch get an io.ReadCloser for the specific content func (s *OCI) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) { readerAt, err := s.Store.ReaderAt(ctx, desc) if err != nil { return nil, err } // just wrap the ReaderAt with a Reader return ioutil.NopCloser(&ReaderAtWrapper{readerAt: readerAt}), nil } // Pusher get a remotes.Pusher for the given ref func (s *OCI) Pusher(ctx context.Context, ref string) (remotes.Pusher, error) { // separate the tag based ref from the hash var ( baseRef, hash string ) parts := strings.SplitN(ref, "@", 2) baseRef = parts[0] if len(parts) > 1 { hash = parts[1] } return &ociPusher{oci: s, ref: baseRef, digest: hash}, nil } // AddReference adds or updates an reference to index. func (s *OCI) AddReference(name string, desc ocispec.Descriptor) { if desc.Annotations == nil { desc.Annotations = map[string]string{ ocispec.AnnotationRefName: name, } } else { desc.Annotations[ocispec.AnnotationRefName] = name } if _, ok := s.nameMap[name]; ok { s.nameMap[name] = desc for i, ref := range s.index.Manifests { if name == ref.Annotations[ocispec.AnnotationRefName] { s.index.Manifests[i] = desc return } } // Process should not reach here. // Fallthrough to `Add` scenario and recover. s.index.Manifests = append(s.index.Manifests, desc) return } s.index.Manifests = append(s.index.Manifests, desc) s.nameMap[name] = desc } // DeleteReference deletes an reference from index. func (s *OCI) DeleteReference(name string) { if _, ok := s.nameMap[name]; !ok { return } delete(s.nameMap, name) for i, desc := range s.index.Manifests { if name == desc.Annotations[ocispec.AnnotationRefName] { s.index.Manifests[i] = s.index.Manifests[len(s.index.Manifests)-1] s.index.Manifests = s.index.Manifests[:len(s.index.Manifests)-1] return } } } // ListReferences lists all references in index. func (s *OCI) ListReferences() map[string]ocispec.Descriptor { return s.nameMap } // validateOCILayoutFile ensures the `oci-layout` file func (s *OCI) validateOCILayoutFile() error { layoutFilePath := filepath.Join(s.root, ocispec.ImageLayoutFile) layoutFile, err := os.Open(layoutFilePath) if err != nil { if !os.IsNotExist(err) { return err } layout := ocispec.ImageLayout{ Version: ocispec.ImageLayoutVersion, } layoutJSON, err := json.Marshal(layout) if err != nil { return err } return ioutil.WriteFile(layoutFilePath, layoutJSON, 0644) } defer layoutFile.Close() var layout *ocispec.ImageLayout err = json.NewDecoder(layoutFile).Decode(&layout) if err != nil { return err } if layout.Version != ocispec.ImageLayoutVersion { return ErrUnsupportedVersion } return nil } // TODO: implement (needed to create a content.Store) // TODO: do not return empty content.Info // Abort completely cancels the ingest operation targeted by ref. func (s *OCI) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) { return content.Info{}, nil } // TODO: implement (needed to create a content.Store) // Update updates mutable information related to content. // If one or more fieldpaths are provided, only those // fields will be updated. // Mutable fields: // labels.* func (s *OCI) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) { return content.Info{}, errors.New("not yet implemented: Update (content.Store interface)") } // TODO: implement (needed to create a content.Store) // Walk will call fn for each item in the content store which // match the provided filters. If no filters are given all // items will be walked. func (s *OCI) Walk(ctx context.Context, fn content.WalkFunc, filters ...string) error { return errors.New("not yet implemented: Walk (content.Store interface)") } // TODO: implement (needed to create a content.Store) // Delete removes the content from the store. func (s *OCI) Delete(ctx context.Context, dgst digest.Digest) error { return errors.New("not yet implemented: Delete (content.Store interface)") } // TODO: implement (needed to create a content.Store) func (s *OCI) Status(ctx context.Context, ref string) (content.Status, error) { // Status returns the status of the provided ref. return content.Status{}, errors.New("not yet implemented: Status (content.Store interface)") } // TODO: implement (needed to create a content.Store) // ListStatuses returns the status of any active ingestions whose ref match the // provided regular expression. If empty, all active ingestions will be // returned. func (s *OCI) ListStatuses(ctx context.Context, filters ...string) ([]content.Status, error) { return []content.Status{}, errors.New("not yet implemented: ListStatuses (content.Store interface)") } // TODO: implement (needed to create a content.Store) // Abort completely cancels the ingest operation targeted by ref. func (s *OCI) Abort(ctx context.Context, ref string) error { return errors.New("not yet implemented: Abort (content.Store interface)") } // ReaderAt provides contents func (s *OCI) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) { return s.Store.ReaderAt(ctx, desc) } // ociPusher to push content for a single referencem can handle multiple descriptors. // Needs to be able to recognize when a root manifest is being pushed and to create the tag // for it. type ociPusher struct { oci *OCI ref string digest string } // Push get a writer for a single Descriptor func (p *ociPusher) Push(ctx context.Context, desc ocispec.Descriptor) (content.Writer, error) { // do we need to create a tag? switch desc.MediaType { case ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageIndex: // if the hash of the content matches that which was provided as the hash for the root, mark it if p.digest != "" && p.digest == desc.Digest.String() { if err := p.oci.LoadIndex(); err != nil { return nil, err } p.oci.nameMap[p.ref] = desc if err := p.oci.SaveIndex(); err != nil { return nil, err } } } return p.oci.Store.Writer(ctx, content.WithDescriptor(desc), content.WithRef(p.ref)) } oras-go-1.1.1/pkg/content/opts.go0000644000175000017500000000673114212212744016164 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import ( "errors" "github.com/opencontainers/go-digest" ) type WriterOpts struct { InputHash *digest.Digest OutputHash *digest.Digest Blocksize int MultiWriterIngester bool IgnoreNoName bool } type WriterOpt func(*WriterOpts) error func DefaultWriterOpts() WriterOpts { return WriterOpts{ InputHash: nil, OutputHash: nil, Blocksize: DefaultBlocksize, IgnoreNoName: true, } } // WithInputHash provide the expected input hash to a writer. Writers // may suppress their own calculation of a hash on the stream, taking this // hash instead. If the Writer processes the data before passing it on to another // Writer layer, this is the hash of the *input* stream. // // To have a blank hash, use WithInputHash(BlankHash). func WithInputHash(hash digest.Digest) WriterOpt { return func(w *WriterOpts) error { w.InputHash = &hash return nil } } // WithOutputHash provide the expected output hash to a writer. Writers // may suppress their own calculation of a hash on the stream, taking this // hash instead. If the Writer processes the data before passing it on to another // Writer layer, this is the hash of the *output* stream. // // To have a blank hash, use WithInputHash(BlankHash). func WithOutputHash(hash digest.Digest) WriterOpt { return func(w *WriterOpts) error { w.OutputHash = &hash return nil } } // WithBlocksize set the blocksize used by the processor of data. // The default is DefaultBlocksize, which is the same as that used by io.Copy. // Includes a safety check to ensure the caller doesn't actively set it to <= 0. func WithBlocksize(blocksize int) WriterOpt { return func(w *WriterOpts) error { if blocksize <= 0 { return errors.New("blocksize must be greater than or equal to 0") } w.Blocksize = blocksize return nil } } // WithMultiWriterIngester the passed ingester also implements MultiWriter // and should be used as such. If this is set to true, but the ingester does not // implement MultiWriter, calling Writer should return an error. func WithMultiWriterIngester() WriterOpt { return func(w *WriterOpts) error { w.MultiWriterIngester = true return nil } } // WithErrorOnNoName some ingesters, when creating a Writer, do not return an error if // the descriptor does not have a valid name on the descriptor. Passing WithErrorOnNoName // tells the writer to return an error instead of passing the data to a nil writer. func WithErrorOnNoName() WriterOpt { return func(w *WriterOpts) error { w.IgnoreNoName = false return nil } } // WithIgnoreNoName some ingesters, when creating a Writer, return an error if // the descriptor does not have a valid name on the descriptor. Passing WithIgnoreNoName // tells the writer not to return an error, but rather to pass the data to a nil writer. // // Deprecated: Use WithErrorOnNoName func WithIgnoreNoName() WriterOpt { return func(w *WriterOpts) error { w.IgnoreNoName = true return nil } } oras-go-1.1.1/pkg/content/utils.go0000644000175000017500000001251414212212744016333 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import ( "archive/tar" "compress/gzip" "fmt" "io" "os" "path/filepath" "strings" "time" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) // ResolveName resolves name from descriptor func ResolveName(desc ocispec.Descriptor) (string, bool) { name, ok := desc.Annotations[ocispec.AnnotationTitle] return name, ok } // tarDirectory walks the directory specified by path, and tar those files with a new // path prefix. func tarDirectory(root, prefix string, w io.Writer, stripTimes bool) error { tw := tar.NewWriter(w) defer tw.Close() if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { return err } // Rename path name, err := filepath.Rel(root, path) if err != nil { return err } name = filepath.Join(prefix, name) name = filepath.ToSlash(name) // Generate header var link string mode := info.Mode() if mode&os.ModeSymlink != 0 { if link, err = os.Readlink(path); err != nil { return err } } header, err := tar.FileInfoHeader(info, link) if err != nil { return errors.Wrap(err, path) } header.Name = name header.Uid = 0 header.Gid = 0 header.Uname = "" header.Gname = "" if stripTimes { header.ModTime = time.Time{} header.AccessTime = time.Time{} header.ChangeTime = time.Time{} } // Write file if err := tw.WriteHeader(header); err != nil { return errors.Wrap(err, "tar") } if mode.IsRegular() { file, err := os.Open(path) if err != nil { return err } defer file.Close() if _, err := io.Copy(tw, file); err != nil { return errors.Wrap(err, path) } } return nil }); err != nil { return err } return nil } // extractTarDirectory extracts tar file to a directory specified by the `root` // parameter. The file name prefix is ensured to be the string specified by the // `prefix` parameter and is trimmed. func extractTarDirectory(root, prefix string, r io.Reader) error { tr := tar.NewReader(r) for { header, err := tr.Next() if err != nil { if err == io.EOF { return nil } return err } // Name check name := header.Name path, err := ensureBasePath(root, prefix, name) if err != nil { return err } path = filepath.Join(root, path) // Link check switch header.Typeflag { case tar.TypeLink, tar.TypeSymlink: link := header.Linkname if !filepath.IsAbs(link) { link = filepath.Join(filepath.Dir(name), link) } if _, err := ensureBasePath(root, prefix, link); err != nil { return err } } // Create content switch header.Typeflag { case tar.TypeReg: err = writeFile(path, tr, header.FileInfo().Mode()) case tar.TypeDir: err = os.MkdirAll(path, header.FileInfo().Mode()) case tar.TypeLink: err = os.Link(header.Linkname, path) case tar.TypeSymlink: err = os.Symlink(header.Linkname, path) default: continue // Non-regular files are skipped } if err != nil { return err } // Change access time and modification time if possible (error ignored) os.Chtimes(path, header.AccessTime, header.ModTime) } } // ensureBasePath ensures the target path is in the base path, // returning its relative path to the base path. func ensureBasePath(root, base, target string) (string, error) { path, err := filepath.Rel(base, target) if err != nil { return "", err } cleanPath := filepath.ToSlash(filepath.Clean(path)) if cleanPath == ".." || strings.HasPrefix(cleanPath, "../") { return "", fmt.Errorf("%q is outside of %q", target, base) } // No symbolic link allowed in the relative path dir := filepath.Dir(path) for dir != "." { if info, err := os.Lstat(filepath.Join(root, dir)); err != nil { if !os.IsNotExist(err) { return "", err } } else if info.Mode()&os.ModeSymlink != 0 { return "", fmt.Errorf("no symbolic link allowed between %q and %q", base, target) } dir = filepath.Dir(dir) } return path, nil } func writeFile(path string, r io.Reader, perm os.FileMode) error { file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) if err != nil { return err } defer file.Close() _, err = io.Copy(file, r) return err } func extractTarGzip(root, prefix, filename, checksum string) error { file, err := os.Open(filename) if err != nil { return err } defer file.Close() zr, err := gzip.NewReader(file) if err != nil { return err } defer zr.Close() var r io.Reader = zr var verifier digest.Verifier if checksum != "" { if digest, err := digest.Parse(checksum); err == nil { verifier = digest.Verifier() r = io.TeeReader(r, verifier) } } if err := extractTarDirectory(root, prefix, r); err != nil { return err } if verifier != nil && !verifier.Verified() { return errors.New("content digest mismatch") } return nil } oras-go-1.1.1/pkg/content/memory.go0000644000175000017500000001617614212212744016513 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import ( "bytes" "context" "fmt" "io" "io/ioutil" "strings" "sync" "time" "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/remotes" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) // Memory provides content from the memory type Memory struct { descriptor map[digest.Digest]ocispec.Descriptor content map[digest.Digest][]byte nameMap map[string]ocispec.Descriptor refMap map[string]ocispec.Descriptor lock *sync.Mutex } // NewMemory creats a new memory store func NewMemory() *Memory { return &Memory{ descriptor: make(map[digest.Digest]ocispec.Descriptor), content: make(map[digest.Digest][]byte), nameMap: make(map[string]ocispec.Descriptor), refMap: make(map[string]ocispec.Descriptor), lock: &sync.Mutex{}, } } func (s *Memory) Resolver() remotes.Resolver { return s } func (s *Memory) Resolve(ctx context.Context, ref string) (name string, desc ocispec.Descriptor, err error) { desc, ok := s.refMap[ref] if !ok { return "", ocispec.Descriptor{}, fmt.Errorf("unknown reference: %s", ref) } return ref, desc, nil } func (s *Memory) Fetcher(ctx context.Context, ref string) (remotes.Fetcher, error) { if _, ok := s.refMap[ref]; !ok { return nil, fmt.Errorf("unknown reference: %s", ref) } return s, nil } // Fetch get an io.ReadCloser for the specific content func (s *Memory) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) { _, content, ok := s.Get(desc) if !ok { return nil, ErrNotFound } return ioutil.NopCloser(bytes.NewReader(content)), nil } func (s *Memory) Pusher(ctx context.Context, ref string) (remotes.Pusher, error) { var tag, hash string parts := strings.SplitN(ref, "@", 2) if len(parts) > 0 { tag = parts[0] } if len(parts) > 1 { hash = parts[1] } return &memoryPusher{ store: s, ref: tag, hash: hash, }, nil } type memoryPusher struct { store *Memory ref string hash string } func (s *memoryPusher) Push(ctx context.Context, desc ocispec.Descriptor) (content.Writer, error) { name, _ := ResolveName(desc) now := time.Now() // is this the root? if desc.Digest.String() == s.hash { s.store.refMap[s.ref] = desc } return &memoryWriter{ store: s.store, buffer: bytes.NewBuffer(nil), desc: desc, digester: digest.Canonical.Digester(), status: content.Status{ Ref: name, Total: desc.Size, StartedAt: now, UpdatedAt: now, }, }, nil } // Add adds content, generating a descriptor and returning it. func (s *Memory) Add(name, mediaType string, content []byte) (ocispec.Descriptor, error) { var annotations map[string]string if name != "" { annotations = map[string]string{ ocispec.AnnotationTitle: name, } } if mediaType == "" { mediaType = DefaultBlobMediaType } desc := ocispec.Descriptor{ MediaType: mediaType, Digest: digest.FromBytes(content), Size: int64(len(content)), Annotations: annotations, } s.Set(desc, content) return desc, nil } // Set adds the content to the store func (s *Memory) Set(desc ocispec.Descriptor, content []byte) { s.lock.Lock() defer s.lock.Unlock() s.descriptor[desc.Digest] = desc s.content[desc.Digest] = content if name, ok := ResolveName(desc); ok && name != "" { s.nameMap[name] = desc } } // Get finds the content from the store func (s *Memory) Get(desc ocispec.Descriptor) (ocispec.Descriptor, []byte, bool) { s.lock.Lock() defer s.lock.Unlock() desc, ok := s.descriptor[desc.Digest] if !ok { return ocispec.Descriptor{}, nil, false } content, ok := s.content[desc.Digest] return desc, content, ok } // GetByName finds the content from the store by name (i.e. AnnotationTitle) func (s *Memory) GetByName(name string) (ocispec.Descriptor, []byte, bool) { s.lock.Lock() defer s.lock.Unlock() desc, ok := s.nameMap[name] if !ok { return ocispec.Descriptor{}, nil, false } content, ok := s.content[desc.Digest] return desc, content, ok } // StoreManifest stores a manifest linked to by the provided ref. The children of the // manifest, such as layers and config, should already exist in the file store, either // as files linked via Add(), or via Set(). If they do not exist, then a typical // Fetcher that walks the manifest will hit an unresolved hash. // // StoreManifest does *not* validate their presence. func (s *Memory) StoreManifest(ref string, desc ocispec.Descriptor, manifest []byte) error { s.refMap[ref] = desc s.Add("", desc.MediaType, manifest) return nil } func descFromBytes(b []byte, mediaType string) (ocispec.Descriptor, error) { digest, err := digest.FromReader(bytes.NewReader(b)) if err != nil { return ocispec.Descriptor{}, err } if mediaType == "" { mediaType = DefaultBlobMediaType } return ocispec.Descriptor{ MediaType: mediaType, Digest: digest, Size: int64(len(b)), }, nil } type memoryWriter struct { store *Memory buffer *bytes.Buffer desc ocispec.Descriptor digester digest.Digester status content.Status } func (w *memoryWriter) Status() (content.Status, error) { return w.status, nil } // Digest returns the current digest of the content, up to the current write. // // Cannot be called concurrently with `Write`. func (w *memoryWriter) Digest() digest.Digest { return w.digester.Digest() } // Write p to the transaction. func (w *memoryWriter) Write(p []byte) (n int, err error) { n, err = w.buffer.Write(p) w.digester.Hash().Write(p[:n]) w.status.Offset += int64(len(p)) w.status.UpdatedAt = time.Now() return n, err } func (w *memoryWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error { var base content.Info for _, opt := range opts { if err := opt(&base); err != nil { return err } } if w.buffer == nil { return errors.Wrap(errdefs.ErrFailedPrecondition, "cannot commit on closed writer") } content := w.buffer.Bytes() w.buffer = nil if size > 0 && size != int64(len(content)) { return errors.Wrapf(errdefs.ErrFailedPrecondition, "unexpected commit size %d, expected %d", len(content), size) } if dgst := w.digester.Digest(); expected != "" && expected != dgst { return errors.Wrapf(errdefs.ErrFailedPrecondition, "unexpected commit digest %s, expected %s", dgst, expected) } w.store.Set(w.desc, content) return nil } func (w *memoryWriter) Close() error { w.buffer = nil return nil } func (w *memoryWriter) Truncate(size int64) error { if size != 0 { return ErrUnsupportedSize } w.status.Offset = 0 w.digester.Hash().Reset() w.buffer.Truncate(0) return nil } oras-go-1.1.1/pkg/content/gunzip.go0000644000175000017500000000376114212212744016513 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import ( "compress/gzip" "fmt" "io" "github.com/containerd/containerd/content" ) // NewGunzipWriter wrap a writer with a gunzip, so that the stream is gunzipped // // By default, it calculates the hash when writing. If the option `skipHash` is true, // it will skip doing the hash. Skipping the hash is intended to be used only // if you are confident about the validity of the data being passed to the writer, // and wish to save on the hashing time. func NewGunzipWriter(writer content.Writer, opts ...WriterOpt) content.Writer { // process opts for default wOpts := DefaultWriterOpts() for _, opt := range opts { if err := opt(&wOpts); err != nil { return nil } } return NewPassthroughWriter(writer, func(r io.Reader, w io.Writer, done chan<- error) { gr, err := gzip.NewReader(r) if err != nil { done <- fmt.Errorf("error creating gzip reader: %v", err) return } // write out the uncompressed data b := make([]byte, wOpts.Blocksize, wOpts.Blocksize) for { var n int n, err = gr.Read(b) if err != nil && err != io.EOF { err = fmt.Errorf("GunzipWriter data read error: %v\n", err) break } l := n if n > len(b) { l = len(b) } if _, err2 := w.Write(b[:l]); err2 != nil { err = fmt.Errorf("GunzipWriter: error writing to underlying writer: %v", err2) break } if err == io.EOF { // clear the error err = nil break } } gr.Close() done <- err }, opts...) } oras-go-1.1.1/pkg/content/manifest.go0000644000175000017500000000675014212212744017006 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import ( "encoding/json" "sort" "github.com/opencontainers/go-digest" specs "github.com/opencontainers/image-spec/specs-go" ocispec "github.com/opencontainers/image-spec/specs-go/v1" artifact "oras.land/oras-go/pkg/artifact" ) // GenerateManifest generates a manifest. The manifest will include the provided config, // and descs as layers. Raw bytes will be returned. func GenerateManifest(config *ocispec.Descriptor, annotations map[string]string, descs ...ocispec.Descriptor) ([]byte, ocispec.Descriptor, error) { // Config - either it was set, or we have to set it if config == nil { _, configGen, err := GenerateConfig(nil) if err != nil { return nil, ocispec.Descriptor{}, err } config = &configGen } return pack(*config, annotations, descs) } // GenerateConfig generates a blank config with optional annotations. func GenerateConfig(annotations map[string]string) ([]byte, ocispec.Descriptor, error) { configBytes := []byte("{}") dig := digest.FromBytes(configBytes) config := ocispec.Descriptor{ MediaType: artifact.UnknownConfigMediaType, Digest: dig, Size: int64(len(configBytes)), Annotations: annotations, } return configBytes, config, nil } // GenerateManifestAndConfig generates a config and then a manifest. Raw bytes will be returned. func GenerateManifestAndConfig(manifestAnnotations map[string]string, configAnnotations map[string]string, descs ...ocispec.Descriptor) (manifest []byte, manifestDesc ocispec.Descriptor, config []byte, configDesc ocispec.Descriptor, err error) { config, configDesc, err = GenerateConfig(configAnnotations) if err != nil { return nil, ocispec.Descriptor{}, nil, ocispec.Descriptor{}, err } manifest, manifestDesc, err = GenerateManifest(&configDesc, manifestAnnotations, descs...) if err != nil { return nil, ocispec.Descriptor{}, nil, ocispec.Descriptor{}, err } return } // pack given a bunch of descriptors, create a manifest that references all of them func pack(config ocispec.Descriptor, annotations map[string]string, descriptors []ocispec.Descriptor) ([]byte, ocispec.Descriptor, error) { if descriptors == nil { descriptors = []ocispec.Descriptor{} // make it an empty array to prevent potential server-side bugs } // sort descriptors alphanumerically by sha hash so it always is consistent sort.Slice(descriptors, func(i, j int) bool { return descriptors[i].Digest < descriptors[j].Digest }) manifest := ocispec.Manifest{ Versioned: specs.Versioned{ SchemaVersion: 2, // historical value. does not pertain to OCI or docker version }, Config: config, Layers: descriptors, Annotations: annotations, } manifestBytes, err := json.Marshal(manifest) if err != nil { return nil, ocispec.Descriptor{}, err } manifestDescriptor := ocispec.Descriptor{ MediaType: ocispec.MediaTypeImageManifest, Digest: digest.FromBytes(manifestBytes), Size: int64(len(manifestBytes)), } return manifestBytes, manifestDescriptor, nil } oras-go-1.1.1/pkg/content/multiwriter.go0000644000175000017500000000333514212212744017563 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import ( "context" ctrcontent "github.com/containerd/containerd/content" "github.com/containerd/containerd/remotes" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // MultiWriterIngester an ingester that can provide a single writer or multiple writers for a single // descriptor. Useful when the target of a descriptor can have multiple items within it, e.g. a layer // that is a tar file with multiple files, each of which should go to a different stream, some of which // should not be handled at all. type MultiWriterIngester interface { ctrcontent.Ingester Writers(ctx context.Context, opts ...ctrcontent.WriterOpt) (func(string) (ctrcontent.Writer, error), error) } // MultiWriterPusher a pusher that can provide a single writer or multiple writers for a single // descriptor. Useful when the target of a descriptor can have multiple items within it, e.g. a layer // that is a tar file with multiple files, each of which should go to a different stream, some of which // should not be handled at all. type MultiWriterPusher interface { remotes.Pusher Pushers(ctx context.Context, desc ocispec.Descriptor) (func(string) (ctrcontent.Writer, error), error) } oras-go-1.1.1/pkg/content/file.go0000644000175000017500000003243014212212744016111 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import ( "bytes" "compress/gzip" "context" _ "crypto/sha256" "fmt" "io" "io/ioutil" "os" "path/filepath" "strings" "sync" "time" "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/remotes" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) // File provides content via files from the file system type File struct { DisableOverwrite bool AllowPathTraversalOnWrite bool // Reproducible enables stripping times from added files Reproducible bool root string descriptor *sync.Map // map[digest.Digest]ocispec.Descriptor pathMap *sync.Map // map[name string](file string) memoryMap *sync.Map // map[digest.Digest]([]byte) refMap *sync.Map // map[string]ocispec.Descriptor tmpFiles *sync.Map ignoreNoName bool } // NewFile creats a new file target. It represents a single root reference and all of its components. func NewFile(rootPath string, opts ...WriterOpt) *File { // we have to process the opts to find if they told us to change defaults wOpts := DefaultWriterOpts() for _, opt := range opts { if err := opt(&wOpts); err != nil { continue } } return &File{ root: rootPath, descriptor: &sync.Map{}, pathMap: &sync.Map{}, memoryMap: &sync.Map{}, refMap: &sync.Map{}, tmpFiles: &sync.Map{}, ignoreNoName: wOpts.IgnoreNoName, } } func (s *File) Resolver() remotes.Resolver { return s } func (s *File) Resolve(ctx context.Context, ref string) (name string, desc ocispec.Descriptor, err error) { desc, ok := s.getRef(ref) if !ok { return "", ocispec.Descriptor{}, fmt.Errorf("unknown reference: %s", ref) } return ref, desc, nil } func (s *File) Fetcher(ctx context.Context, ref string) (remotes.Fetcher, error) { if _, ok := s.refMap.Load(ref); !ok { return nil, fmt.Errorf("unknown reference: %s", ref) } return s, nil } // Fetch get an io.ReadCloser for the specific content func (s *File) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) { // first see if it is in the in-memory manifest map manifest, ok := s.getMemory(desc) if ok { return ioutil.NopCloser(bytes.NewReader(manifest)), nil } desc, ok = s.get(desc) if !ok { return nil, ErrNotFound } name, ok := ResolveName(desc) if !ok { return nil, ErrNoName } path := s.ResolvePath(name) return os.Open(path) } func (s *File) Pusher(ctx context.Context, ref string) (remotes.Pusher, error) { var tag, hash string parts := strings.SplitN(ref, "@", 2) if len(parts) > 0 { tag = parts[0] } if len(parts) > 1 { hash = parts[1] } return &filePusher{ store: s, ref: tag, hash: hash, }, nil } type filePusher struct { store *File ref string hash string } func (s *filePusher) Push(ctx context.Context, desc ocispec.Descriptor) (content.Writer, error) { name, ok := ResolveName(desc) now := time.Now() if !ok { // if we were not told to ignore NoName, then return an error if !s.store.ignoreNoName { return nil, ErrNoName } // just return a nil writer - we do not want to calculate the hash, so just use // whatever was passed in the descriptor return NewIoContentWriter(ioutil.Discard, WithOutputHash(desc.Digest)), nil } path, err := s.store.resolveWritePath(name) if err != nil { return nil, err } file, afterCommit, err := s.store.createWritePath(path, desc, name) if err != nil { return nil, err } return &fileWriter{ store: s.store, file: file, desc: desc, digester: digest.Canonical.Digester(), status: content.Status{ Ref: name, Total: desc.Size, StartedAt: now, UpdatedAt: now, }, afterCommit: afterCommit, }, nil } // Add adds a file reference from a path, either directory or single file, // and returns the reference descriptor. func (s *File) Add(name, mediaType, path string) (ocispec.Descriptor, error) { if path == "" { path = name } path = s.MapPath(name, path) fileInfo, err := os.Stat(path) if err != nil { return ocispec.Descriptor{}, err } var desc ocispec.Descriptor if fileInfo.IsDir() { desc, err = s.descFromDir(name, mediaType, path) } else { desc, err = s.descFromFile(fileInfo, mediaType, path) } if err != nil { return ocispec.Descriptor{}, err } if desc.Annotations == nil { desc.Annotations = make(map[string]string) } desc.Annotations[ocispec.AnnotationTitle] = name s.set(desc) return desc, nil } // Load is a lower-level memory-only version of Add. Rather than taking a path, // generating a descriptor and creating a reference, it takes raw data and a descriptor // that describes that data and stores it in memory. It will disappear at process // termination. // // It is especially useful for adding ephemeral data, such as config, that must // exist in order to walk a manifest. func (s *File) Load(desc ocispec.Descriptor, data []byte) error { s.memoryMap.Store(desc.Digest, data) return nil } // Ref gets a reference's descriptor and content func (s *File) Ref(ref string) (ocispec.Descriptor, []byte, error) { desc, ok := s.getRef(ref) if !ok { return ocispec.Descriptor{}, nil, ErrNotFound } // first see if it is in the in-memory manifest map manifest, ok := s.getMemory(desc) if !ok { return ocispec.Descriptor{}, nil, ErrNotFound } return desc, manifest, nil } func (s *File) descFromFile(info os.FileInfo, mediaType, path string) (ocispec.Descriptor, error) { file, err := os.Open(path) if err != nil { return ocispec.Descriptor{}, err } defer file.Close() digest, err := digest.FromReader(file) if err != nil { return ocispec.Descriptor{}, err } if mediaType == "" { mediaType = DefaultBlobMediaType } return ocispec.Descriptor{ MediaType: mediaType, Digest: digest, Size: info.Size(), }, nil } func (s *File) descFromDir(name, mediaType, root string) (ocispec.Descriptor, error) { // generate temp file file, err := s.tempFile() if err != nil { return ocispec.Descriptor{}, err } defer file.Close() s.MapPath(name, file.Name()) // compress directory digester := digest.Canonical.Digester() zw := gzip.NewWriter(io.MultiWriter(file, digester.Hash())) defer zw.Close() tarDigester := digest.Canonical.Digester() if err := tarDirectory(root, name, io.MultiWriter(zw, tarDigester.Hash()), s.Reproducible); err != nil { return ocispec.Descriptor{}, err } // flush all if err := zw.Close(); err != nil { return ocispec.Descriptor{}, err } if err := file.Sync(); err != nil { return ocispec.Descriptor{}, err } // generate descriptor if mediaType == "" { mediaType = DefaultBlobDirMediaType } info, err := file.Stat() if err != nil { return ocispec.Descriptor{}, err } return ocispec.Descriptor{ MediaType: mediaType, Digest: digester.Digest(), Size: info.Size(), Annotations: map[string]string{ AnnotationDigest: tarDigester.Digest().String(), AnnotationUnpack: "true", }, }, nil } func (s *File) tempFile() (*os.File, error) { file, err := ioutil.TempFile("", TempFilePattern) if err != nil { return nil, err } s.tmpFiles.Store(file.Name(), file) return file, nil } // Close frees up resources used by the file store func (s *File) Close() error { var errs []string s.tmpFiles.Range(func(name, _ interface{}) bool { if err := os.Remove(name.(string)); err != nil { errs = append(errs, err.Error()) } return true }) if len(errs) > 0 { return errors.New(strings.Join(errs, "; ")) } return nil } func (s *File) resolveWritePath(name string) (string, error) { path := s.ResolvePath(name) if !s.AllowPathTraversalOnWrite { base, err := filepath.Abs(s.root) if err != nil { return "", err } target, err := filepath.Abs(path) if err != nil { return "", err } rel, err := filepath.Rel(base, target) if err != nil { return "", ErrPathTraversalDisallowed } rel = filepath.ToSlash(rel) if strings.HasPrefix(rel, "../") || rel == ".." { return "", ErrPathTraversalDisallowed } } if s.DisableOverwrite { if _, err := os.Stat(path); err == nil { return "", ErrOverwriteDisallowed } else if !os.IsNotExist(err) { return "", err } } return path, nil } func (s *File) createWritePath(path string, desc ocispec.Descriptor, prefix string) (*os.File, func() error, error) { if value, ok := desc.Annotations[AnnotationUnpack]; !ok || value != "true" { if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { return nil, nil, err } file, err := os.Create(path) return file, nil, err } if err := os.MkdirAll(path, 0755); err != nil { return nil, nil, err } file, err := s.tempFile() checksum := desc.Annotations[AnnotationDigest] afterCommit := func() error { return extractTarGzip(path, prefix, file.Name(), checksum) } return file, afterCommit, err } // MapPath maps name to path func (s *File) MapPath(name, path string) string { path = s.resolvePath(path) s.pathMap.Store(name, path) return path } // ResolvePath returns the path by name func (s *File) ResolvePath(name string) string { if value, ok := s.pathMap.Load(name); ok { if path, ok := value.(string); ok { return path } } // using the name as a fallback solution return s.resolvePath(name) } func (s *File) resolvePath(path string) string { if filepath.IsAbs(path) { return path } return filepath.Join(s.root, path) } func (s *File) set(desc ocispec.Descriptor) { s.descriptor.Store(desc.Digest, desc) } func (s *File) get(desc ocispec.Descriptor) (ocispec.Descriptor, bool) { value, ok := s.descriptor.Load(desc.Digest) if !ok { return ocispec.Descriptor{}, false } desc, ok = value.(ocispec.Descriptor) return desc, ok } func (s *File) getMemory(desc ocispec.Descriptor) ([]byte, bool) { value, ok := s.memoryMap.Load(desc.Digest) if !ok { return nil, false } content, ok := value.([]byte) return content, ok } func (s *File) getRef(ref string) (ocispec.Descriptor, bool) { value, ok := s.refMap.Load(ref) if !ok { return ocispec.Descriptor{}, false } desc, ok := value.(ocispec.Descriptor) return desc, ok } // StoreManifest stores a manifest linked to by the provided ref. The children of the // manifest, such as layers and config, should already exist in the file store, either // as files linked via Add(), or via Load(). If they do not exist, then a typical // Fetcher that walks the manifest will hit an unresolved hash. // // StoreManifest does *not* validate their presence. func (s *File) StoreManifest(ref string, desc ocispec.Descriptor, manifest []byte) error { s.refMap.Store(ref, desc) s.memoryMap.Store(desc.Digest, manifest) return nil } type fileWriter struct { store *File file *os.File desc ocispec.Descriptor digester digest.Digester status content.Status afterCommit func() error } func (w *fileWriter) Status() (content.Status, error) { return w.status, nil } // Digest returns the current digest of the content, up to the current write. // // Cannot be called concurrently with `Write`. func (w *fileWriter) Digest() digest.Digest { return w.digester.Digest() } // Write p to the transaction. func (w *fileWriter) Write(p []byte) (n int, err error) { n, err = w.file.Write(p) w.digester.Hash().Write(p[:n]) w.status.Offset += int64(len(p)) w.status.UpdatedAt = time.Now() return n, err } func (w *fileWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error { var base content.Info for _, opt := range opts { if err := opt(&base); err != nil { return err } } if w.file == nil { return errors.Wrap(errdefs.ErrFailedPrecondition, "cannot commit on closed writer") } file := w.file w.file = nil if err := file.Sync(); err != nil { file.Close() return errors.Wrap(err, "sync failed") } fileInfo, err := file.Stat() if err != nil { file.Close() return errors.Wrap(err, "stat failed") } if err := file.Close(); err != nil { return errors.Wrap(err, "failed to close file") } if size > 0 && size != fileInfo.Size() { return errors.Wrapf(errdefs.ErrFailedPrecondition, "unexpected commit size %d, expected %d", fileInfo.Size(), size) } if dgst := w.digester.Digest(); expected != "" && expected != dgst { return errors.Wrapf(errdefs.ErrFailedPrecondition, "unexpected commit digest %s, expected %s", dgst, expected) } w.store.set(w.desc) if w.afterCommit != nil { return w.afterCommit() } return nil } // Close the writer, flushing any unwritten data and leaving the progress in // tact. func (w *fileWriter) Close() error { if w.file == nil { return nil } w.file.Sync() err := w.file.Close() w.file = nil return err } func (w *fileWriter) Truncate(size int64) error { if size != 0 { return ErrUnsupportedSize } w.status.Offset = 0 w.digester.Hash().Reset() if _, err := w.file.Seek(0, io.SeekStart); err != nil { return err } return w.file.Truncate(0) } oras-go-1.1.1/pkg/content/content_test.go0000644000175000017500000001513014212212744017701 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package content import ( "context" "fmt" "io/ioutil" "os" "path/filepath" "testing" "github.com/containerd/containerd/remotes" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/suite" ) type ContentTestSuite struct { suite.Suite TestMemoryStore *Memory TestFileStore *File } var ( testDirRoot, _ = filepath.Abs("../../.test") testFileName = filepath.Join(testDirRoot, "testfile") testRef = "abc123" testContent = []byte("Hello World!") testDescriptor = ocispec.Descriptor{ MediaType: ocispec.MediaTypeImageConfig, Digest: digest.FromBytes(testContent), Size: int64(len(testContent)), Annotations: map[string]string{ ocispec.AnnotationTitle: testRef, }, } testBadContent = []byte("doesnotexist") testBadDescriptor = ocispec.Descriptor{ MediaType: ocispec.MediaTypeImageConfig, Digest: digest.FromBytes(testBadContent), Size: int64(len(testBadContent)), } ) func (suite *ContentTestSuite) SetupSuite() { testMemoryStore := NewMemory() desc, err := testMemoryStore.Add(testRef, "", testContent) suite.Nil(err, "no error adding testContent to memory store") manifest, manifestDesc, config, configDesc, err := GenerateManifestAndConfig(nil, nil, desc) suite.Nil(err, "no error creating config and manifest") testMemoryStore.Set(configDesc, config) err = testMemoryStore.StoreManifest(testRef, manifestDesc, manifest) suite.Nil(err, "no error adding ref to memory store") suite.TestMemoryStore = testMemoryStore os.Remove(testFileName) err = ioutil.WriteFile(testFileName, testContent, 0644) suite.Nil(err, "no error creating test file on disk") testFileStore := NewFile(testDirRoot, WithErrorOnNoName()) desc, err = testFileStore.Add(testRef, "", testFileName) suite.Nil(err, "no error adding item to file store") err = testFileStore.Load(configDesc, config) suite.Nil(err, "no error adding config to file store") err = testFileStore.StoreManifest(testRef, manifestDesc, manifest) suite.Nil(err, "no error adding ref to file store") suite.TestFileStore = testFileStore } // Tests all Writers (Ingesters) func (suite *ContentTestSuite) Test_0_Ingesters() { ctx := context.Background() memPusher, _ := suite.TestMemoryStore.Pusher(ctx, "") filePusher, _ := suite.TestFileStore.Pusher(ctx, "") ingesters := map[string]remotes.Pusher{ "memory": memPusher, "file": filePusher, } for key, ingester := range ingesters { // Bad ref writer, err := ingester.Push(ctx, testBadDescriptor) if key == "file" { suite.NotNil(err, fmt.Sprintf("no error getting writer w bad ref for %s store", key)) } // Good ref ctx = context.Background() writer, err = ingester.Push(ctx, testDescriptor) suite.Nil(err, fmt.Sprintf("no error getting writer w good ref for %s store", key)) _, err = writer.Write(testContent) suite.Nil(err, fmt.Sprintf("no error using writer.Write w good ref for %s store", key)) err = writer.Commit(ctx, testDescriptor.Size, testDescriptor.Digest) suite.Nil(err, fmt.Sprintf("no error using writer.Commit w good ref for %s store", key)) digest := writer.Digest() suite.Equal(testDescriptor.Digest, digest, fmt.Sprintf("correct digest for %s store", key)) status, err := writer.Status() suite.Nil(err, fmt.Sprintf("no error retrieving writer status for %s store", key)) suite.Equal(testRef, status.Ref, fmt.Sprintf("correct status for %s store", key)) // close writer err = writer.Close() suite.Nil(err, fmt.Sprintf("no error closing writer w bad ref for %s store", key)) err = writer.Commit(ctx, testDescriptor.Size, testDescriptor.Digest) suite.NotNil(err, fmt.Sprintf("error using writer.Commit when closed w good ref for %s store", key)) // re-init writer after closing writer, _ = ingester.Push(ctx, testDescriptor) writer.Write(testContent) // invalid truncate size err = writer.Truncate(123456789) suite.NotNil(err, fmt.Sprintf("error using writer.Truncate w invalid size, good ref for %s store", key)) // valid truncate size err = writer.Truncate(0) suite.Nil(err, fmt.Sprintf("no error using writer.Truncate w valid size, good ref for %s store", key)) writer.Commit(ctx, testDescriptor.Size, testDescriptor.Digest) // bad size err = writer.Commit(ctx, 1, testDescriptor.Digest) fmt.Println(err) suite.NotNil(err, fmt.Sprintf("error using writer.Commit w bad size, good ref for %s store", key)) // bad digest err = writer.Commit(ctx, 0, testBadDescriptor.Digest) suite.NotNil(err, fmt.Sprintf("error using writer.Commit w bad digest, good ref for %s store", key)) } } // Tests all Readers (Providers) func (suite *ContentTestSuite) Test_1_Providers() { ctx := context.Background() memFetcher, _ := suite.TestMemoryStore.Fetcher(ctx, testRef) fileFetcher, _ := suite.TestFileStore.Fetcher(ctx, testRef) providers := map[string]remotes.Fetcher{ "memory": memFetcher, "file": fileFetcher, } // Readers (Providers) for key, provider := range providers { // Bad ref ctx := context.Background() _, err := provider.Fetch(ctx, testBadDescriptor) suite.NotNil(err, fmt.Sprintf("error with bad ref for %s store", key)) // Good ref ctx = context.Background() reader, err := provider.Fetch(ctx, testDescriptor) suite.Nil(err, fmt.Sprintf("no error with good ref for %s store", key)) // readerat Close() err = reader.Close() suite.Nil(err, fmt.Sprintf("no error closing reader for %s store", key)) // file missing if key == "file" { os.Remove(testFileName) ctx := context.Background() _, err := provider.Fetch(ctx, testDescriptor) suite.NotNil(err, fmt.Sprintf("error with good ref for %s store (file missing)", key)) } } } func (suite *ContentTestSuite) Test_2_GetByName() { // NotFound _, _, ok := suite.TestMemoryStore.GetByName("doesnotexist") suite.False(ok, "unable to find non-existant ref by name for memory store") // Found _, _, ok = suite.TestMemoryStore.GetByName(testRef) suite.True(ok, "able to find non-existant ref by name for memory store") } func TestContentTestSuite(t *testing.T) { suite.Run(t, new(ContentTestSuite)) } oras-go-1.1.1/pkg/registry/0000755000175000017500000000000014212212744015037 5ustar nileshnileshoras-go-1.1.1/pkg/registry/repository.go0000644000175000017500000000426014212212744017607 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package registry import ( "context" ) // Repository is an ORAS target and an union of the blob and the manifest CASs. // As specified by https://docs.docker.com/registry/spec/api/, it is natural to // assume that content.Resolver interface only works for manifests. Tagging a // blob may be resulted in an `ErrUnsupported` error. However, this interface // does not restrict tagging blobs. // Since a repository is an union of the blob and the manifest CASs, all // operations defined in the `BlobStore` are executed depending on the media // type of the given descriptor accordingly. // Furthurmore, this interface also provides the ability to enforce the // separation of the blob and the manifests CASs. type Repository interface { // Tags lists the tags available in the repository. // Since the returned tag list may be paginated by the underlying // implementation, a function should be passed in to process the paginated // tag list. // Note: When implemented by a remote registry, the tags API is called. // However, not all registries supports pagination or conforms the // specification. // References: // - https://github.com/opencontainers/distribution-spec/blob/main/spec.md#content-discovery // - https://docs.docker.com/registry/spec/api/#tags // See also `Tags()` in this package. Tags(ctx context.Context, fn func(tags []string) error) error } // Tags lists the tags available in the repository. func Tags(ctx context.Context, repo Repository) ([]string, error) { var res []string if err := repo.Tags(ctx, func(tags []string) error { res = append(res, tags...) return nil }); err != nil { return nil, err } return res, nil } oras-go-1.1.1/pkg/registry/reference.go0000644000175000017500000001205414212212744017326 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package registry import ( "fmt" "net/url" "regexp" "strings" "github.com/opencontainers/go-digest" errdef "oras.land/oras-go/pkg/content" ) // regular expressions for components. var ( // repositoryRegexp is adapted from the distribution implementation. // The repository name set under OCI distribution spec is a subset of the // the docker spec. For maximum compability, the docker spec is verified at // the client side. Further check is left to the server side. // References: // - https://github.com/distribution/distribution/blob/v2.7.1/reference/regexp.go#L53 // - https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pulling-manifests repositoryRegexp = regexp.MustCompile(`^[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9]+)*(?:/[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9]+)*)*$`) // tagRegexp checks the tag name. // The docker and OCI spec have the same regular expression. // Reference: https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pulling-manifests tagRegexp = regexp.MustCompile(`^[\w][\w.-]{0,127}$`) ) // Reference references to a descriptor in the registry. type Reference struct { // Registry is the name of the registry. // It is usually the domain name of the registry optionally with a port. Registry string // Repository is the name of the repository. Repository string // Reference is the reference of the object in the repository. // A reference can be a tag or a digest. Reference string } // ParseReference parses a string into a artifact reference. // If the reference contains both the tag and the digest, the tag will be // dropped. // Digest is recognized only if the corresponding algorithm is available. func ParseReference(raw string) (Reference, error) { parts := strings.SplitN(raw, "/", 2) if len(parts) == 1 { return Reference{}, fmt.Errorf("%w: missing repository", errdef.ErrInvalidReference) } registry, path := parts[0], parts[1] var repository string var reference string if index := strings.Index(path, "@"); index != -1 { // digest found repository = path[:index] reference = path[index+1:] // drop tag since the digest is present. if index := strings.Index(repository, ":"); index != -1 { repository = repository[:index] } } else if index := strings.Index(path, ":"); index != -1 { // tag found repository = path[:index] reference = path[index+1:] } else { // empty reference repository = path } res := Reference{ Registry: registry, Repository: repository, Reference: reference, } if err := res.Validate(); err != nil { return Reference{}, err } return res, nil } // Validate validates the entire reference. func (r Reference) Validate() error { err := r.ValidateRegistry() if err != nil { return err } err = r.ValidateRepository() if err != nil { return err } return r.ValidateReference() } // ValidateRegistry validates the registry. func (r Reference) ValidateRegistry() error { uri, err := url.ParseRequestURI("dummy://" + r.Registry) if err != nil || uri.Host != r.Registry { return fmt.Errorf("%w: invalid registry", errdef.ErrInvalidReference) } return nil } // ValidateRepository validates the repository. func (r Reference) ValidateRepository() error { if !repositoryRegexp.MatchString(r.Repository) { return fmt.Errorf("%w: invalid repository", errdef.ErrInvalidReference) } return nil } // ValidateReference validates the reference. func (r Reference) ValidateReference() error { if r.Reference == "" { return nil } if _, err := r.Digest(); err == nil { return nil } if !tagRegexp.MatchString(r.Reference) { return fmt.Errorf("%w: invalid tag", errdef.ErrInvalidReference) } return nil } // Host returns the host name of the registry. func (r Reference) Host() string { if r.Registry == "docker.io" { return "registry-1.docker.io" } return r.Registry } // ReferenceOrDefault returns the reference or the default reference if empty. func (r Reference) ReferenceOrDefault() string { if r.Reference == "" { return "latest" } return r.Reference } // Digest returns the reference as a digest. func (r Reference) Digest() (digest.Digest, error) { return digest.Parse(r.Reference) } // String implements `fmt.Stringer` and returns the reference string. // The resulted string is meaningful only if the reference is valid. func (r Reference) String() string { if r.Repository == "" { return r.Registry } ref := r.Registry + "/" + r.Repository if r.Reference == "" { return ref } if d, err := r.Digest(); err == nil { return ref + "@" + d.String() } return ref + ":" + r.Reference } oras-go-1.1.1/pkg/registry/remote/0000755000175000017500000000000014212212744016332 5ustar nileshnileshoras-go-1.1.1/pkg/registry/remote/repository.go0000644000175000017500000001243014212212744021100 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package remote import ( "context" "encoding/json" "fmt" "net/http" "strconv" errdef "oras.land/oras-go/pkg/content" "oras.land/oras-go/pkg/registry" "oras.land/oras-go/pkg/registry/remote/auth" "oras.land/oras-go/pkg/registry/remote/internal/errutil" ) // Client is an interface for a HTTP client. type Client interface { // Do sends an HTTP request and returns an HTTP response. // // Unlike http.RoundTripper, Client can attempt to interpret the response // and handle higher-level protocol details such as redirects and // authentication. // // Like http.RoundTripper, Client should not modify the request, and must // always close the request body. Do(*http.Request) (*http.Response, error) } // Repository is an HTTP client to a remote repository. type Repository struct { // Client is the underlying HTTP client used to access the remote registry. // If nil, auth.DefaultClient is used. Client Client // Reference references the remote repository. Reference registry.Reference // PlainHTTP signals the transport to access the remote repository via HTTP // instead of HTTPS. PlainHTTP bool // ManifestMediaTypes is used in `Accept` header for resolving manifests from // references. It is also used in identifying manifests and blobs from // descriptors. // If an empty list is present, default manifest media types are used. ManifestMediaTypes []string // TagListPageSize specifies the page size when invoking the tag list API. // If zero, the page size is determined by the remote registry. // Reference: https://docs.docker.com/registry/spec/api/#tags TagListPageSize int // ReferrerListPageSize specifies the page size when invoking the Referrers // API. // If zero, the page size is determined by the remote registry. // Reference: https://github.com/oras-project/artifacts-spec/blob/main/manifest-referrers-api.md ReferrerListPageSize int // MaxMetadataBytes specifies a limit on how many response bytes are allowed // in the server's response to the metadata APIs, such as catalog list, tag // list, and referrers list. // If zero, a default (currently 4MiB) is used. MaxMetadataBytes int64 } // NewRepository creates a client to the remote repository identified by a // reference. // Example: localhost:5000/hello-world func NewRepository(reference string) (*Repository, error) { ref, err := registry.ParseReference(reference) if err != nil { return nil, err } return &Repository{ Reference: ref, }, nil } // client returns an HTTP client used to access the remote repository. // A default HTTP client is return if the client is not configured. func (r *Repository) client() Client { if r.Client == nil { return auth.DefaultClient } return r.Client } // parseReference validates the reference. // Both simplified or fully qualified references are accepted as input. // A fully qualified reference is returned on success. func (r *Repository) parseReference(reference string) (registry.Reference, error) { ref, err := registry.ParseReference(reference) if err != nil { ref = registry.Reference{ Registry: r.Reference.Registry, Repository: r.Reference.Repository, Reference: reference, } if err = ref.ValidateReference(); err != nil { return registry.Reference{}, err } return ref, nil } if ref.Registry == r.Reference.Registry && ref.Repository == r.Reference.Repository { return ref, nil } return registry.Reference{}, fmt.Errorf("%w %q: expect %q", errdef.ErrInvalidReference, ref, r.Reference) } // Tags lists the tags available in the repository. func (r *Repository) Tags(ctx context.Context, fn func(tags []string) error) error { ctx = withScopeHint(ctx, r.Reference, auth.ActionPull) url := buildRepositoryTagListURL(r.PlainHTTP, r.Reference) var err error for err == nil { url, err = r.tags(ctx, fn, url) } if err != errNoLink { return err } return nil } // tags returns a single page of tag list with the next link. func (r *Repository) tags(ctx context.Context, fn func(tags []string) error, url string) (string, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return "", err } if r.TagListPageSize > 0 { q := req.URL.Query() q.Set("n", strconv.Itoa(r.TagListPageSize)) req.URL.RawQuery = q.Encode() } resp, err := r.client().Do(req) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", errutil.ParseErrorResponse(resp) } var page struct { Tags []string `json:"tags"` } lr := limitReader(resp.Body, r.MaxMetadataBytes) if err := json.NewDecoder(lr).Decode(&page); err != nil { return "", fmt.Errorf("%s %q: failed to decode response: %w", resp.Request.Method, resp.Request.URL, err) } if err := fn(page.Tags); err != nil { return "", err } return parseLink(resp) } oras-go-1.1.1/pkg/registry/remote/auth/0000755000175000017500000000000014212212744017273 5ustar nileshnileshoras-go-1.1.1/pkg/registry/remote/auth/scope_test.go0000644000175000017500000002054314212212744021776 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package auth import ( "context" "reflect" "testing" ) func TestScopeRepository(t *testing.T) { tests := []struct { name string repository string actions []string want string }{ { name: "empty repository", actions: []string{ "pull", }, }, { name: "nil actions", repository: "foo", }, { name: "empty actions", repository: "foo", actions: []string{}, }, { name: "empty actions list", repository: "foo", actions: []string{}, }, { name: "empty actions", repository: "foo", actions: []string{ "", }, }, { name: "single action", repository: "foo", actions: []string{ "pull", }, want: "repository:foo:pull", }, { name: "multiple actions", repository: "foo", actions: []string{ "pull", "push", }, want: "repository:foo:pull,push", }, { name: "unordered actions", repository: "foo", actions: []string{ "push", "pull", }, want: "repository:foo:pull,push", }, { name: "duplicated actions", repository: "foo", actions: []string{ "push", "pull", "pull", "delete", "push", }, want: "repository:foo:delete,pull,push", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := ScopeRepository(tt.repository, tt.actions...); got != tt.want { t.Errorf("ScopeRepository() = %v, want %v", got, tt.want) } }) } } func TestWithScopes(t *testing.T) { ctx := context.Background() // with single scope want := []string{ "repository:foo:pull", } ctx = WithScopes(ctx, want...) if got := GetScopes(ctx); !reflect.DeepEqual(got, want) { t.Errorf("GetScopes(WithScopes()) = %v, want %v", got, want) } // overwrite scopes want = []string{ "repository:bar:push", } ctx = WithScopes(ctx, want...) if got := GetScopes(ctx); !reflect.DeepEqual(got, want) { t.Errorf("GetScopes(WithScopes()) = %v, want %v", got, want) } // overwrite scopes with de-duplication scopes := []string{ "repository:hello-world:push", "repository:alpine:delete", "repository:hello-world:pull", "repository:alpine:delete", } want = []string{ "repository:alpine:delete", "repository:hello-world:pull,push", } ctx = WithScopes(ctx, scopes...) if got := GetScopes(ctx); !reflect.DeepEqual(got, want) { t.Errorf("GetScopes(WithScopes()) = %v, want %v", got, want) } // clean scopes want = nil ctx = WithScopes(ctx, want...) if got := GetScopes(ctx); !reflect.DeepEqual(got, want) { t.Errorf("GetScopes(WithScopes()) = %v, want %v", got, want) } } func TestAppendScopes(t *testing.T) { ctx := context.Background() // append single scope want := []string{ "repository:foo:pull", } ctx = AppendScopes(ctx, want...) if got := GetScopes(ctx); !reflect.DeepEqual(got, want) { t.Errorf("GetScopes(AppendScopes()) = %v, want %v", got, want) } // append scopes with de-duplication scopes := []string{ "repository:hello-world:push", "repository:alpine:delete", "repository:hello-world:pull", "repository:alpine:delete", } want = []string{ "repository:alpine:delete", "repository:foo:pull", "repository:hello-world:pull,push", } ctx = AppendScopes(ctx, scopes...) if got := GetScopes(ctx); !reflect.DeepEqual(got, want) { t.Errorf("GetScopes(AppendScopes()) = %v, want %v", got, want) } // append empty scopes ctx = AppendScopes(ctx) if got := GetScopes(ctx); !reflect.DeepEqual(got, want) { t.Errorf("GetScopes(AppendScopes()) = %v, want %v", got, want) } } func TestCleanScopes(t *testing.T) { tests := []struct { name string scopes []string want []string }{ { name: "nil scope", }, { name: "empty scope", scopes: []string{}, }, { name: "single scope", scopes: []string{ "repository:foo:pull", }, want: []string{ "repository:foo:pull", }, }, { name: "single scope with unordered actions", scopes: []string{ "repository:foo:push,pull,delete", }, want: []string{ "repository:foo:delete,pull,push", }, }, { name: "single scope with duplicated actions", scopes: []string{ "repository:foo:push,pull,push,pull,push,push,pull", }, want: []string{ "repository:foo:pull,push", }, }, { name: "single scope with wild cards", scopes: []string{ "repository:foo:pull,*,push", }, want: []string{ "repository:foo:*", }, }, { name: "single scope with no actions", scopes: []string{ "repository:foo:,", }, want: nil, }, { name: "multiple scopes", scopes: []string{ "repository:bar:push", "repository:foo:pull", }, want: []string{ "repository:bar:push", "repository:foo:pull", }, }, { name: "multiple unordered scopes", scopes: []string{ "repository:foo:pull", "repository:bar:push", }, want: []string{ "repository:bar:push", "repository:foo:pull", }, }, { name: "multiple scopes with duplicates", scopes: []string{ "repository:foo:pull", "repository:bar:push", "repository:foo:push", "repository:bar:push,delete,pull", "repository:bar:delete,pull", "repository:foo:pull", "registry:catalog:*", "registry:catalog:pull", }, want: []string{ "registry:catalog:*", "repository:bar:delete,pull,push", "repository:foo:pull,push", }, }, { name: "multiple scopes with no actions", scopes: []string{ "repository:foo:,", "repository:bar:,", }, want: nil, }, { name: "single unknown or invalid scope", scopes: []string{ "unknown", }, want: []string{ "unknown", }, }, { name: "multiple unknown or invalid scopes", scopes: []string{ "repository:foo:pull", "unknown", "invalid:scope", "no:actions:", "repository:foo:push", }, want: []string{ "invalid:scope", "repository:foo:pull,push", "unknown", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := CleanScopes(tt.scopes); !reflect.DeepEqual(got, tt.want) { t.Errorf("CleanScopes() = %v, want %v", got, tt.want) } }) } } func Test_cleanActions(t *testing.T) { tests := []struct { name string actions []string want []string }{ { name: "nil action", }, { name: "empty action", actions: []string{}, }, { name: "single action", actions: []string{ "pull", }, want: []string{ "pull", }, }, { name: "single empty action", actions: []string{ "", }, }, { name: "multiple actions", actions: []string{ "pull", "push", }, want: []string{ "pull", "push", }, }, { name: "multiple actions with empty action", actions: []string{ "pull", "", "push", }, want: []string{ "pull", "push", }, }, { name: "multiple actions with all empty action", actions: []string{ "", "", "", }, want: nil, }, { name: "unordered actions", actions: []string{ "push", "pull", "delete", }, want: []string{ "delete", "pull", "push", }, }, { name: "wildcard", actions: []string{ "*", }, want: []string{ "*", }, }, { name: "wildcard at the begining", actions: []string{ "*", "push", "pull", "delete", }, want: []string{ "*", }, }, { name: "wildcard in the middle", actions: []string{ "push", "pull", "*", "delete", }, want: []string{ "*", }, }, { name: "wildcard at the end", actions: []string{ "push", "pull", "delete", "*", }, want: []string{ "*", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := cleanActions(tt.actions); !reflect.DeepEqual(got, tt.want) { t.Errorf("cleanActions() = %v, want %v", got, tt.want) } }) } } oras-go-1.1.1/pkg/registry/remote/auth/client_test.go0000644000175000017500000020352614212212744022147 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package auth import ( "context" "encoding/base64" "fmt" "net/http" "net/http/httptest" "net/url" "reflect" "strings" "sync/atomic" "testing" ) func TestClient_SetUserAgent(t *testing.T) { wantUserAgent := "test agent" var requestCount, wantRequestCount int64 var successCount, wantSuccessCount int64 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt64(&requestCount, 1) if r.Method != http.MethodGet || r.URL.Path != "/" { t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) return } if userAgent := r.UserAgent(); userAgent != wantUserAgent { t.Errorf("unexpected User-Agent: %v, want %v", userAgent, wantUserAgent) return } atomic.AddInt64(&successCount, 1) })) defer ts.Close() var client Client client.SetUserAgent(wantUserAgent) req, err := http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err := client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount++; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } } func TestClient_Do_Basic_Auth(t *testing.T) { username := "test_user" password := "test_password" var requestCount, wantRequestCount int64 var successCount, wantSuccessCount int64 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt64(&requestCount, 1) if r.Method != http.MethodGet || r.URL.Path != "/" { t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) return } header := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password)) if auth := r.Header.Get("Authorization"); auth != header { w.Header().Set("Www-Authenticate", `Basic realm="Test Server"`) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&successCount, 1) })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } client := &Client{ Credential: func(ctx context.Context, reg string) (Credential, error) { if reg != uri.Host { err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) t.Error(err) return EmptyCredential, err } return Credential{ Username: username, Password: password, }, nil }, } // first request req, err := http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err := client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } // credential change username = "test_user2" password = "test_password2" req, err = http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err = client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } } func TestClient_Do_Basic_Auth_Cached(t *testing.T) { username := "test_user" password := "test_password" var requestCount, wantRequestCount int64 var successCount, wantSuccessCount int64 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt64(&requestCount, 1) if r.Method != http.MethodGet || r.URL.Path != "/" { t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) return } header := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password)) if auth := r.Header.Get("Authorization"); auth != header { w.Header().Set("Www-Authenticate", `Basic realm="Test Server"`) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&successCount, 1) })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } client := &Client{ Credential: func(ctx context.Context, reg string) (Credential, error) { if reg != uri.Host { err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) t.Error(err) return EmptyCredential, err } return Credential{ Username: username, Password: password, }, nil }, Cache: NewCache(), } // first request req, err := http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err := client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } // repeated request req, err = http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err = client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount++; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } // credential change username = "test_user2" password = "test_password2" req, err = http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err = client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } } func TestClient_Do_Bearer_AccessToken(t *testing.T) { accessToken := "test/access/token" var requestCount, wantRequestCount int64 var successCount, wantSuccessCount int64 as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Error("unexecuted attempt of authorization service") w.WriteHeader(http.StatusUnauthorized) })) defer as.Close() var service string scope := "repository:test:pull,push" ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt64(&requestCount, 1) if r.Method != http.MethodGet || r.URL.Path != "/" { t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) return } header := "Bearer " + accessToken if auth := r.Header.Get("Authorization"); auth != header { challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, scope) w.Header().Set("Www-Authenticate", challenge) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&successCount, 1) })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } service = uri.Host client := &Client{ Credential: func(ctx context.Context, reg string) (Credential, error) { if reg != uri.Host { err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) t.Error(err) return EmptyCredential, err } return Credential{ AccessToken: accessToken, }, nil }, } // first request req, err := http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err := client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } // credential change accessToken = "test/access/token/2" req, err = http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err = client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } } func TestClient_Do_Bearer_AccessToken_Cached(t *testing.T) { accessToken := "test/access/token" var requestCount, wantRequestCount int64 var successCount, wantSuccessCount int64 as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Error("unexecuted attempt of authorization service") w.WriteHeader(http.StatusUnauthorized) })) defer as.Close() var service string scope := "repository:test:pull,push" ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt64(&requestCount, 1) if r.Method != http.MethodGet || r.URL.Path != "/" { t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) return } header := "Bearer " + accessToken if auth := r.Header.Get("Authorization"); auth != header { challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, scope) w.Header().Set("Www-Authenticate", challenge) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&successCount, 1) })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } service = uri.Host client := &Client{ Credential: func(ctx context.Context, reg string) (Credential, error) { if reg != uri.Host { err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) t.Error(err) return EmptyCredential, err } return Credential{ AccessToken: accessToken, }, nil }, Cache: NewCache(), } // first request ctx := WithScopes(context.Background(), scope) req, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err := client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } // repeated request req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err = client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount++; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } // credential change accessToken = "test/access/token/2" req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err = client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } } func TestClient_Do_Bearer_Auth(t *testing.T) { username := "test_user" password := "test_password" accessToken := "test/access/token" var requestCount, wantRequestCount int64 var successCount, wantSuccessCount int64 var authCount, wantAuthCount int64 var service string scopes := []string{ "repository:dst:pull,push", "repository:src:pull", } as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet || r.URL.Path != "/" { t.Error("unexecuted attempt of authorization service") w.WriteHeader(http.StatusUnauthorized) return } header := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password)) if auth := r.Header.Get("Authorization"); auth != header { t.Errorf("unexpected auth: got %s, want %s", auth, header) w.WriteHeader(http.StatusUnauthorized) return } if got := r.URL.Query().Get("service"); got != service { t.Errorf("unexpected service: got %s, want %s", got, service) w.WriteHeader(http.StatusUnauthorized) return } if got := r.URL.Query()["scope"]; !reflect.DeepEqual(got, scopes) { t.Errorf("unexpected scope: got %s, want %s", got, scopes) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&authCount, 1) if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { t.Errorf("failed to write %q: %v", r.URL, err) } })) defer as.Close() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt64(&requestCount, 1) if r.Method != http.MethodGet || r.URL.Path != "/" { t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) return } header := "Bearer " + accessToken if auth := r.Header.Get("Authorization"); auth != header { challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, strings.Join(scopes, " ")) w.Header().Set("Www-Authenticate", challenge) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&successCount, 1) })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } service = uri.Host client := &Client{ Credential: func(ctx context.Context, reg string) (Credential, error) { if reg != uri.Host { err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) t.Error(err) return EmptyCredential, err } return Credential{ Username: username, Password: password, }, nil }, } // first request req, err := http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err := client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if wantAuthCount++; authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } // credential change username = "test_user2" password = "test_password2" accessToken = "test/access/token/2" req, err = http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err = client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if wantAuthCount++; authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } } func TestClient_Do_Bearer_Auth_Cached(t *testing.T) { username := "test_user" password := "test_password" accessToken := "test/access/token" var requestCount, wantRequestCount int64 var successCount, wantSuccessCount int64 var authCount, wantAuthCount int64 var service string scopes := []string{ "repository:dst:pull,push", "repository:src:pull", } as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet || r.URL.Path != "/" { t.Error("unexecuted attempt of authorization service") w.WriteHeader(http.StatusUnauthorized) return } header := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password)) if auth := r.Header.Get("Authorization"); auth != header { t.Errorf("unexpected auth: got %s, want %s", auth, header) w.WriteHeader(http.StatusUnauthorized) return } if got := r.URL.Query().Get("service"); got != service { t.Errorf("unexpected service: got %s, want %s", got, service) w.WriteHeader(http.StatusUnauthorized) return } if got := r.URL.Query()["scope"]; !reflect.DeepEqual(got, scopes) { t.Errorf("unexpected scope: got %s, want %s", got, scopes) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&authCount, 1) if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { t.Errorf("failed to write %q: %v", r.URL, err) } })) defer as.Close() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt64(&requestCount, 1) if r.Method != http.MethodGet || r.URL.Path != "/" { t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) return } header := "Bearer " + accessToken if auth := r.Header.Get("Authorization"); auth != header { challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, strings.Join(scopes, " ")) w.Header().Set("Www-Authenticate", challenge) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&successCount, 1) })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } service = uri.Host client := &Client{ Credential: func(ctx context.Context, reg string) (Credential, error) { if reg != uri.Host { err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) t.Error(err) return EmptyCredential, err } return Credential{ Username: username, Password: password, }, nil }, Cache: NewCache(), } // first request ctx := WithScopes(context.Background(), scopes...) req, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err := client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if wantAuthCount++; authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } // repeated request req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err = client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount++; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } // credential change username = "test_user2" password = "test_password2" accessToken = "test/access/token/2" req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err = client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if wantAuthCount++; authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } } func TestClient_Do_Bearer_OAuth2_Password(t *testing.T) { username := "test_user" password := "test_password" accessToken := "test/access/token" var requestCount, wantRequestCount int64 var successCount, wantSuccessCount int64 var authCount, wantAuthCount int64 var service string scopes := []string{ "repository:dst:pull,push", "repository:src:pull", } as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost || r.URL.Path != "/" { t.Error("unexecuted attempt of authorization service") w.WriteHeader(http.StatusUnauthorized) return } if err := r.ParseForm(); err != nil { t.Errorf("failed to parse form: %v", err) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("grant_type"); got != "password" { t.Errorf("unexpected grant type: %v, want %v", got, "password") w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("service"); got != service { t.Errorf("unexpected service: %v, want %v", got, service) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("client_id"); got != defaultClientID { t.Errorf("unexpected client id: %v, want %v", got, defaultClientID) w.WriteHeader(http.StatusUnauthorized) return } scope := strings.Join(scopes, " ") if got := r.PostForm.Get("scope"); got != scope { t.Errorf("unexpected scope: %v, want %v", got, scope) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("username"); got != username { t.Errorf("unexpected username: %v, want %v", got, username) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("password"); got != password { t.Errorf("unexpected password: %v, want %v", got, password) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&authCount, 1) if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { t.Errorf("failed to write %q: %v", r.URL, err) } })) defer as.Close() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt64(&requestCount, 1) if r.Method != http.MethodGet || r.URL.Path != "/" { t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) return } header := "Bearer " + accessToken if auth := r.Header.Get("Authorization"); auth != header { challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, strings.Join(scopes, " ")) w.Header().Set("Www-Authenticate", challenge) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&successCount, 1) })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } service = uri.Host client := &Client{ Credential: func(ctx context.Context, reg string) (Credential, error) { if reg != uri.Host { err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) t.Error(err) return EmptyCredential, err } return Credential{ Username: username, Password: password, }, nil }, ForceAttemptOAuth2: true, } // first request req, err := http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err := client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if wantAuthCount++; authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } // credential change username = "test_user2" password = "test_password2" accessToken = "test/access/token/2" req, err = http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err = client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if wantAuthCount++; authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } } func TestClient_Do_Bearer_OAuth2_Password_Cached(t *testing.T) { username := "test_user" password := "test_password" accessToken := "test/access/token" var requestCount, wantRequestCount int64 var successCount, wantSuccessCount int64 var authCount, wantAuthCount int64 var service string scopes := []string{ "repository:dst:pull,push", "repository:src:pull", } as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost || r.URL.Path != "/" { t.Error("unexecuted attempt of authorization service") w.WriteHeader(http.StatusUnauthorized) return } if err := r.ParseForm(); err != nil { t.Errorf("failed to parse form: %v", err) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("grant_type"); got != "password" { t.Errorf("unexpected grant type: %v, want %v", got, "password") w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("service"); got != service { t.Errorf("unexpected service: %v, want %v", got, service) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("client_id"); got != defaultClientID { t.Errorf("unexpected client id: %v, want %v", got, defaultClientID) w.WriteHeader(http.StatusUnauthorized) return } scope := strings.Join(scopes, " ") if got := r.PostForm.Get("scope"); got != scope { t.Errorf("unexpected scope: %v, want %v", got, scope) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("username"); got != username { t.Errorf("unexpected username: %v, want %v", got, username) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("password"); got != password { t.Errorf("unexpected password: %v, want %v", got, password) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&authCount, 1) if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { t.Errorf("failed to write %q: %v", r.URL, err) } })) defer as.Close() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt64(&requestCount, 1) if r.Method != http.MethodGet || r.URL.Path != "/" { t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) return } header := "Bearer " + accessToken if auth := r.Header.Get("Authorization"); auth != header { challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, strings.Join(scopes, " ")) w.Header().Set("Www-Authenticate", challenge) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&successCount, 1) })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } service = uri.Host client := &Client{ Credential: func(ctx context.Context, reg string) (Credential, error) { if reg != uri.Host { err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) t.Error(err) return EmptyCredential, err } return Credential{ Username: username, Password: password, }, nil }, ForceAttemptOAuth2: true, Cache: NewCache(), } // first request ctx := WithScopes(context.Background(), scopes...) req, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err := client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if wantAuthCount++; authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } // repeated request req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err = client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount++; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } // credential change username = "test_user2" password = "test_password2" accessToken = "test/access/token/2" req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err = client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if wantAuthCount++; authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } } func TestClient_Do_Bearer_OAuth2_RefreshToken(t *testing.T) { refreshToken := "test/refresh/token" accessToken := "test/access/token" var requestCount, wantRequestCount int64 var successCount, wantSuccessCount int64 var authCount, wantAuthCount int64 var service string scopes := []string{ "repository:dst:pull,push", "repository:src:pull", } as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost || r.URL.Path != "/" { t.Error("unexecuted attempt of authorization service") w.WriteHeader(http.StatusUnauthorized) return } if err := r.ParseForm(); err != nil { t.Errorf("failed to parse form: %v", err) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("grant_type"); got != "refresh_token" { t.Errorf("unexpected grant type: %v, want %v", got, "refresh_token") w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("service"); got != service { t.Errorf("unexpected service: %v, want %v", got, service) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("client_id"); got != defaultClientID { t.Errorf("unexpected client id: %v, want %v", got, defaultClientID) w.WriteHeader(http.StatusUnauthorized) return } scope := strings.Join(scopes, " ") if got := r.PostForm.Get("scope"); got != scope { t.Errorf("unexpected scope: %v, want %v", got, scope) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("refresh_token"); got != refreshToken { t.Errorf("unexpected refresh token: %v, want %v", got, refreshToken) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&authCount, 1) if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { t.Errorf("failed to write %q: %v", r.URL, err) } })) defer as.Close() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt64(&requestCount, 1) if r.Method != http.MethodGet || r.URL.Path != "/" { t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) return } header := "Bearer " + accessToken if auth := r.Header.Get("Authorization"); auth != header { challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, strings.Join(scopes, " ")) w.Header().Set("Www-Authenticate", challenge) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&successCount, 1) })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } service = uri.Host client := &Client{ Credential: func(ctx context.Context, reg string) (Credential, error) { if reg != uri.Host { err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) t.Error(err) return EmptyCredential, err } return Credential{ RefreshToken: refreshToken, }, nil }, } // first request req, err := http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err := client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if wantAuthCount++; authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } // credential change refreshToken = "test/refresh/token/2" accessToken = "test/access/token/2" req, err = http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err = client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if wantAuthCount++; authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } } func TestClient_Do_Bearer_OAuth2_RefreshToken_Cached(t *testing.T) { refreshToken := "test/refresh/token" accessToken := "test/access/token" var requestCount, wantRequestCount int64 var successCount, wantSuccessCount int64 var authCount, wantAuthCount int64 var service string scopes := []string{ "repository:dst:pull,push", "repository:src:pull", } as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost || r.URL.Path != "/" { t.Error("unexecuted attempt of authorization service") w.WriteHeader(http.StatusUnauthorized) return } if err := r.ParseForm(); err != nil { t.Errorf("failed to parse form: %v", err) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("grant_type"); got != "refresh_token" { t.Errorf("unexpected grant type: %v, want %v", got, "refresh_token") w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("service"); got != service { t.Errorf("unexpected service: %v, want %v", got, service) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("client_id"); got != defaultClientID { t.Errorf("unexpected client id: %v, want %v", got, defaultClientID) w.WriteHeader(http.StatusUnauthorized) return } scope := strings.Join(scopes, " ") if got := r.PostForm.Get("scope"); got != scope { t.Errorf("unexpected scope: %v, want %v", got, scope) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("refresh_token"); got != refreshToken { t.Errorf("unexpected refresh token: %v, want %v", got, refreshToken) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&authCount, 1) if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { t.Errorf("failed to write %q: %v", r.URL, err) } })) defer as.Close() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt64(&requestCount, 1) if r.Method != http.MethodGet || r.URL.Path != "/" { t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) return } header := "Bearer " + accessToken if auth := r.Header.Get("Authorization"); auth != header { challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, strings.Join(scopes, " ")) w.Header().Set("Www-Authenticate", challenge) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&successCount, 1) })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } service = uri.Host client := &Client{ Credential: func(ctx context.Context, reg string) (Credential, error) { if reg != uri.Host { err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) t.Error(err) return EmptyCredential, err } return Credential{ RefreshToken: refreshToken, }, nil }, Cache: NewCache(), } // first request ctx := WithScopes(context.Background(), scopes...) req, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err := client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if wantAuthCount++; authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } // repeated request req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err = client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount++; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } // credential change refreshToken = "test/refresh/token/2" accessToken = "test/access/token/2" req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err = client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if wantAuthCount++; authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } } func TestClient_Do_Token_Expire(t *testing.T) { refreshToken := "test/refresh/token" accessToken := "test/access/token" var requestCount, wantRequestCount int64 var successCount, wantSuccessCount int64 var authCount, wantAuthCount int64 var service string scopes := []string{ "repository:dst:pull,push", "repository:src:pull", } as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost || r.URL.Path != "/" { t.Error("unexecuted attempt of authorization service") w.WriteHeader(http.StatusUnauthorized) return } if err := r.ParseForm(); err != nil { t.Errorf("failed to parse form: %v", err) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("grant_type"); got != "refresh_token" { t.Errorf("unexpected grant type: %v, want %v", got, "refresh_token") w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("service"); got != service { t.Errorf("unexpected service: %v, want %v", got, service) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("client_id"); got != defaultClientID { t.Errorf("unexpected client id: %v, want %v", got, defaultClientID) w.WriteHeader(http.StatusUnauthorized) return } scope := strings.Join(scopes, " ") if got := r.PostForm.Get("scope"); got != scope { t.Errorf("unexpected scope: %v, want %v", got, scope) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("refresh_token"); got != refreshToken { t.Errorf("unexpected refresh token: %v, want %v", got, refreshToken) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&authCount, 1) if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { t.Errorf("failed to write %q: %v", r.URL, err) } })) defer as.Close() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt64(&requestCount, 1) if r.Method != http.MethodGet || r.URL.Path != "/" { t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) return } header := "Bearer " + accessToken if auth := r.Header.Get("Authorization"); auth != header { challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, strings.Join(scopes, " ")) w.Header().Set("Www-Authenticate", challenge) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&successCount, 1) })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } service = uri.Host client := &Client{ Credential: func(ctx context.Context, reg string) (Credential, error) { if reg != uri.Host { err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) t.Error(err) return EmptyCredential, err } return Credential{ RefreshToken: refreshToken, }, nil }, Cache: NewCache(), } // first request ctx := WithScopes(context.Background(), scopes...) req, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err := client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if wantAuthCount++; authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } // invalidate the access token and request again accessToken = "test/access/token/2" req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err = client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if wantAuthCount++; authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } } func TestClient_Do_Scope_Hint_Mismatch(t *testing.T) { username := "test_user" password := "test_password" accessToken := "test/access/token" var requestCount, wantRequestCount int64 var successCount, wantSuccessCount int64 var authCount, wantAuthCount int64 var service string scopes := []string{ "repository:dst:pull,push", "repository:src:pull", } scope := "repository:test:delete" as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost || r.URL.Path != "/" { t.Error("unexecuted attempt of authorization service") w.WriteHeader(http.StatusUnauthorized) return } if err := r.ParseForm(); err != nil { t.Errorf("failed to parse form: %v", err) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("grant_type"); got != "password" { t.Errorf("unexpected grant type: %v, want %v", got, "password") w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("service"); got != service { t.Errorf("unexpected service: %v, want %v", got, service) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("client_id"); got != defaultClientID { t.Errorf("unexpected client id: %v, want %v", got, defaultClientID) w.WriteHeader(http.StatusUnauthorized) return } scopes := CleanScopes(append([]string{scope}, scopes...)) scope := strings.Join(scopes, " ") if got := r.PostForm.Get("scope"); got != scope { t.Errorf("unexpected scope: %v, want %v", got, scope) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("username"); got != username { t.Errorf("unexpected username: %v, want %v", got, username) w.WriteHeader(http.StatusUnauthorized) return } if got := r.PostForm.Get("password"); got != password { t.Errorf("unexpected password: %v, want %v", got, password) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&authCount, 1) if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { t.Errorf("failed to write %q: %v", r.URL, err) } })) defer as.Close() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt64(&requestCount, 1) if r.Method != http.MethodGet || r.URL.Path != "/" { t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) return } header := "Bearer " + accessToken if auth := r.Header.Get("Authorization"); auth != header { challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, scope) w.Header().Set("Www-Authenticate", challenge) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&successCount, 1) })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } service = uri.Host client := &Client{ Credential: func(ctx context.Context, reg string) (Credential, error) { if reg != uri.Host { err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) t.Error(err) return EmptyCredential, err } return Credential{ Username: username, Password: password, }, nil }, ForceAttemptOAuth2: true, Cache: NewCache(), } // first request ctx := WithScopes(context.Background(), scopes...) req, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err := client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if wantAuthCount++; authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } // repeated request // although the actual scope does not match the hinted scopes, the client // with cache cannot avoid a request to obtain a challenge but can prevent // a repeated call to the authorization server. req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err = client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } } func TestClient_Do_Invalid_Credential_Basic(t *testing.T) { username := "test_user" password := "test_password" var requestCount, wantRequestCount int64 var successCount, wantSuccessCount int64 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt64(&requestCount, 1) if r.Method != http.MethodGet || r.URL.Path != "/" { t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) return } header := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password)) if auth := r.Header.Get("Authorization"); auth != header { w.Header().Set("Www-Authenticate", `Basic realm="Test Server"`) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&successCount, 1) t.Error("authentication should fail but succeeded") })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } client := &Client{ Credential: func(ctx context.Context, reg string) (Credential, error) { if reg != uri.Host { err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) t.Error(err) return EmptyCredential, err } return Credential{ Username: username, Password: "bad credential", }, nil }, } // request should fail req, err := http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err := client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusUnauthorized { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusUnauthorized) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } } func TestClient_Do_Invalid_Credential_Bearer(t *testing.T) { username := "test_user" password := "test_password" accessToken := "test/access/token" var requestCount, wantRequestCount int64 var successCount, wantSuccessCount int64 var authCount, wantAuthCount int64 var service string scopes := []string{ "repository:dst:pull,push", "repository:src:pull", } as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet || r.URL.Path != "/" { t.Error("unexecuted attempt of authorization service") w.WriteHeader(http.StatusUnauthorized) return } header := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password)) if auth := r.Header.Get("Authorization"); auth != header { atomic.AddInt64(&authCount, 1) w.WriteHeader(http.StatusUnauthorized) return } t.Error("authentication should fail but succeeded") })) defer as.Close() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt64(&requestCount, 1) if r.Method != http.MethodGet || r.URL.Path != "/" { t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) return } header := "Bearer " + accessToken if auth := r.Header.Get("Authorization"); auth != header { challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, strings.Join(scopes, " ")) w.Header().Set("Www-Authenticate", challenge) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&successCount, 1) t.Error("authentication should fail but succeeded") })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } service = uri.Host client := &Client{ Credential: func(ctx context.Context, reg string) (Credential, error) { if reg != uri.Host { err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) t.Error(err) return EmptyCredential, err } return Credential{ Username: username, Password: "bad credential", }, nil }, } // request should fail req, err := http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } _, err = client.Do(req) if err == nil { t.Fatalf("Client.Do() error = %v, wantErr %v", err, true) } if wantRequestCount++; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if wantAuthCount++; authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } } func TestClient_Do_Anonymous_Pull(t *testing.T) { accessToken := "test/access/token" var requestCount, wantRequestCount int64 var successCount, wantSuccessCount int64 var authCount, wantAuthCount int64 var service string scope := "repository:test:pull" as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet || r.URL.Path != "/" { t.Error("unexecuted attempt of authorization service") w.WriteHeader(http.StatusUnauthorized) return } if auth := r.Header.Get("Authorization"); auth != "" { t.Errorf("unexpected auth: got %s, want %s", auth, "") w.WriteHeader(http.StatusUnauthorized) return } if got := r.URL.Query().Get("service"); got != service { t.Errorf("unexpected service: got %s, want %s", got, service) w.WriteHeader(http.StatusUnauthorized) return } if got := r.URL.Query().Get("scope"); got != scope { t.Errorf("unexpected scope: got %s, want %s", got, scope) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&authCount, 1) if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { t.Errorf("failed to write %q: %v", r.URL, err) } })) defer as.Close() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt64(&requestCount, 1) if r.Method != http.MethodGet || r.URL.Path != "/" { t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) return } header := "Bearer " + accessToken if auth := r.Header.Get("Authorization"); auth != header { challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, scope) w.Header().Set("Www-Authenticate", challenge) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&successCount, 1) })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } service = uri.Host // request with the default client req, err := http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err := DefaultClient.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if wantAuthCount++; authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } } func TestClient_Do_Scheme_Change(t *testing.T) { username := "test_user" password := "test_password" accessToken := "test/access/token" var requestCount, wantRequestCount int64 var successCount, wantSuccessCount int64 var authCount, wantAuthCount int64 var service string scope := "repository:test:pull" challengeBearerAuth := true as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet || r.URL.Path != "/" { t.Error("unexecuted attempt of authorization service") w.WriteHeader(http.StatusUnauthorized) return } header := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password)) if auth := r.Header.Get("Authorization"); auth != header { t.Errorf("unexpected auth: got %s, want %s", auth, header) w.WriteHeader(http.StatusUnauthorized) return } if got := r.URL.Query().Get("service"); got != service { t.Errorf("unexpected service: got %s, want %s", got, service) w.WriteHeader(http.StatusUnauthorized) return } if got := r.URL.Query().Get("scope"); got != scope { t.Errorf("unexpected scope: got %s, want %s", got, scope) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&authCount, 1) if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { t.Errorf("failed to write %q: %v", r.URL, err) } })) defer as.Close() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt64(&requestCount, 1) if r.Method != http.MethodGet || r.URL.Path != "/" { t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) return } bearerHeader := "Bearer " + accessToken basicHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password)) header := r.Header.Get("Authorization") if (challengeBearerAuth && header != bearerHeader) || (!challengeBearerAuth && header != basicHeader) { var challenge string if challengeBearerAuth { challenge = fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, scope) } else { challenge = `Basic realm="Test Server"` } w.Header().Set("Www-Authenticate", challenge) w.WriteHeader(http.StatusUnauthorized) return } atomic.AddInt64(&successCount, 1) })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } service = uri.Host client := &Client{ Credential: func(ctx context.Context, reg string) (Credential, error) { if reg != uri.Host { err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) t.Error(err) return EmptyCredential, err } return Credential{ Username: username, Password: password, }, nil }, Cache: NewCache(), } // request with bearer auth req, err := http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err := client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if wantAuthCount++; authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } // change to basic auth challengeBearerAuth = false req, err = http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatalf("failed to create test request: %v", err) } resp, err = client.Do(req) if err != nil { t.Fatalf("Client.Do() error = %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) } if wantRequestCount += 2; requestCount != wantRequestCount { t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) } if wantSuccessCount++; successCount != wantSuccessCount { t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) } if authCount != wantAuthCount { t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) } } oras-go-1.1.1/pkg/registry/remote/auth/cache_test.go0000644000175000017500000003402414212212744021727 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package auth import ( "context" "errors" "strconv" "sync" "sync/atomic" "testing" "time" errdef "oras.land/oras-go/pkg/content" ) func Test_concurrentCache_GetScheme(t *testing.T) { cache := NewCache() // no entry in the cache ctx := context.Background() registry := "localhost:5000" got, err := cache.GetScheme(ctx, registry) if want := errdef.ErrNotFound; err != want { t.Fatalf("concurrentCache.GetScheme() error = %v, wantErr %v", err, want) } if got != SchemeUnknown { t.Errorf("concurrentCache.GetScheme() = %v, want %v", got, SchemeUnknown) } // set an cache entry scheme := SchemeBasic _, err = cache.Set(ctx, registry, scheme, "", func(c context.Context) (string, error) { return "foo", nil }) if err != nil { t.Fatalf("failed to set cache: %v", err) } // verify cache got, err = cache.GetScheme(ctx, registry) if err != nil { t.Fatalf("concurrentCache.GetScheme() error = %v", err) } if got != scheme { t.Errorf("concurrentCache.GetScheme() = %v, want %v", got, scheme) } // set cache entry again scheme = SchemeBearer _, err = cache.Set(ctx, registry, scheme, "", func(c context.Context) (string, error) { return "bar", nil }) if err != nil { t.Fatalf("failed to set cache: %v", err) } // verify cache got, err = cache.GetScheme(ctx, registry) if err != nil { t.Fatalf("concurrentCache.GetScheme() error = %v", err) } if got != scheme { t.Errorf("concurrentCache.GetScheme() = %v, want %v", got, scheme) } // test other registry registry = "localhost:5001" got, err = cache.GetScheme(ctx, registry) if want := errdef.ErrNotFound; err != want { t.Fatalf("concurrentCache.GetScheme() error = %v, wantErr %v", err, want) } if got != SchemeUnknown { t.Errorf("concurrentCache.GetScheme() = %v, want %v", got, SchemeUnknown) } } func Test_concurrentCache_GetToken(t *testing.T) { cache := NewCache() // no entry in the cache ctx := context.Background() registry := "localhost:5000" scheme := SchemeBearer key := "1st key" got, err := cache.GetToken(ctx, registry, scheme, key) if want := errdef.ErrNotFound; err != want { t.Fatalf("concurrentCache.GetToken() error = %v, wantErr %v", err, want) } if got != "" { t.Errorf("concurrentCache.GetToken() = %v, want %v", got, "") } // set an cache entry _, err = cache.Set(ctx, registry, scheme, key, func(c context.Context) (string, error) { return "foo", nil }) if err != nil { t.Fatalf("failed to set cache: %v", err) } // verify cache got, err = cache.GetToken(ctx, registry, scheme, key) if err != nil { t.Fatalf("concurrentCache.GetToken() error = %v", err) } if want := "foo"; got != want { t.Errorf("concurrentCache.GetToken() = %v, want %v", got, want) } // set cache entry again _, err = cache.Set(ctx, registry, scheme, key, func(c context.Context) (string, error) { return "bar", nil }) if err != nil { t.Fatalf("failed to set cache: %v", err) } // verify cache got, err = cache.GetToken(ctx, registry, scheme, key) if err != nil { t.Fatalf("concurrentCache.GetToken() error = %v", err) } if want := "bar"; got != want { t.Errorf("concurrentCache.GetToken() = %v, want %v", got, want) } // test other key key = "2nd key" got, err = cache.GetToken(ctx, registry, scheme, key) if want := errdef.ErrNotFound; err != want { t.Fatalf("concurrentCache.GetToken() error = %v, wantErr %v", err, want) } if got != "" { t.Errorf("concurrentCache.GetToken() = %v, want %v", got, "") } // set an cache entry _, err = cache.Set(ctx, registry, scheme, key, func(c context.Context) (string, error) { return "hello world", nil }) if err != nil { t.Fatalf("failed to set cache: %v", err) } // verify cache got, err = cache.GetToken(ctx, registry, scheme, key) if err != nil { t.Fatalf("concurrentCache.GetToken() error = %v", err) } if want := "hello world"; got != want { t.Errorf("concurrentCache.GetToken() = %v, want %v", got, want) } // verify cache of the previous key as keys should not interference each // other key = "1st key" got, err = cache.GetToken(ctx, registry, scheme, key) if err != nil { t.Fatalf("concurrentCache.GetToken() error = %v", err) } if want := "bar"; got != want { t.Errorf("concurrentCache.GetToken() = %v, want %v", got, want) } // test other registry registry = "localhost:5001" got, err = cache.GetToken(ctx, registry, scheme, key) if want := errdef.ErrNotFound; err != want { t.Fatalf("concurrentCache.GetToken() error = %v, wantErr %v", err, want) } if got != "" { t.Errorf("concurrentCache.GetToken() = %v, want %v", got, "") } // set an cache entry _, err = cache.Set(ctx, registry, scheme, key, func(c context.Context) (string, error) { return "foobar", nil }) if err != nil { t.Fatalf("failed to set cache: %v", err) } // verify cache got, err = cache.GetToken(ctx, registry, scheme, key) if err != nil { t.Fatalf("concurrentCache.GetToken() error = %v", err) } if want := "foobar"; got != want { t.Errorf("concurrentCache.GetToken() = %v, want %v", got, want) } // verify cache of the previous registry as registries should not // interference each other registry = "localhost:5000" got, err = cache.GetToken(ctx, registry, scheme, key) if err != nil { t.Fatalf("concurrentCache.GetToken() error = %v", err) } if want := "bar"; got != want { t.Errorf("concurrentCache.GetToken() = %v, want %v", got, want) } // test other scheme scheme = SchemeBasic got, err = cache.GetToken(ctx, registry, scheme, key) if want := errdef.ErrNotFound; err != want { t.Fatalf("concurrentCache.GetToken() error = %v, wantErr %v", err, want) } if got != "" { t.Errorf("concurrentCache.GetToken() = %v, want %v", got, "") } // set an cache entry _, err = cache.Set(ctx, registry, scheme, key, func(c context.Context) (string, error) { return "new scheme", nil }) if err != nil { t.Fatalf("failed to set cache: %v", err) } // verify cache got, err = cache.GetToken(ctx, registry, scheme, key) if err != nil { t.Fatalf("concurrentCache.GetToken() error = %v", err) } if want := "new scheme"; got != want { t.Errorf("concurrentCache.GetToken() = %v, want %v", got, want) } // cache of the previous scheme should be invalidated due to scheme change. got, err = cache.GetToken(ctx, registry, SchemeBearer, key) if want := errdef.ErrNotFound; err != want { t.Fatalf("concurrentCache.GetToken() error = %v, wantErr %v", err, want) } if got != "" { t.Errorf("concurrentCache.GetToken() = %v, want %v", got, "") } } func Test_concurrentCache_Set(t *testing.T) { registries := []string{ "localhost:5000", "localhost:5001", } scheme := SchemeBearer keys := []string{ "foo", "bar", } count := len(registries) * len(keys) ctx := context.Background() cache := NewCache() // first round of fetch fetch := func(i int) func(context.Context) (string, error) { return func(context.Context) (string, error) { return strconv.Itoa(i), nil } } var wg sync.WaitGroup for i := 0; i < 10; i++ { for j := 0; j < count; j++ { wg.Add(1) go func(i int) { defer wg.Done() registry := registries[i&1] key := keys[(i>>1)&1] got, err := cache.Set(ctx, registry, scheme, key, fetch(i)) if err != nil { t.Errorf("concurrentCache.Set() error = %v", err) } if want := strconv.Itoa(i); got != want { t.Errorf("concurrentCache.Set() = %v, want %v", got, want) } }(j) } } wg.Wait() for i := 0; i < count; i++ { registry := registries[i&1] key := keys[(i>>1)&1] gotScheme, err := cache.GetScheme(ctx, registry) if err != nil { t.Fatalf("concurrentCache.GetScheme() error = %v", err) } if want := scheme; gotScheme != want { t.Errorf("concurrentCache.GetScheme() = %v, want %v", gotScheme, want) } gotToken, err := cache.GetToken(ctx, registry, scheme, key) if err != nil { t.Fatalf("concurrentCache.GetToken() error = %v", err) } if want := strconv.Itoa(i); gotToken != want { t.Errorf("concurrentCache.GetToken() = %v, want %v", gotToken, want) } } // repeated fetch fetch = func(i int) func(context.Context) (string, error) { return func(context.Context) (string, error) { return strconv.Itoa(i) + " repeated", nil } } for i := 0; i < 10; i++ { for j := 0; j < count; j++ { wg.Add(1) go func(i int) { defer wg.Done() registry := registries[i&1] key := keys[(i>>1)&1] got, err := cache.Set(ctx, registry, scheme, key, fetch(i)) if err != nil { t.Errorf("concurrentCache.Set() error = %v", err) } if want := strconv.Itoa(i) + " repeated"; got != want { t.Errorf("concurrentCache.Set() = %v, want %v", got, want) } }(j) } } wg.Wait() for i := 0; i < count; i++ { registry := registries[i&1] key := keys[(i>>1)&1] gotScheme, err := cache.GetScheme(ctx, registry) if err != nil { t.Fatalf("concurrentCache.GetScheme() error = %v", err) } if want := scheme; gotScheme != want { t.Errorf("concurrentCache.GetScheme() = %v, want %v", gotScheme, want) } gotToken, err := cache.GetToken(ctx, registry, scheme, key) if err != nil { t.Fatalf("concurrentCache.GetToken() error = %v", err) } if want := strconv.Itoa(i) + " repeated"; gotToken != want { t.Errorf("concurrentCache.GetToken() = %v, want %v", gotToken, want) } } } func Test_concurrentCache_Set_Fetch_Once(t *testing.T) { registries := []string{ "localhost:5000", "localhost:5001", } schemes := []Scheme{ SchemeBasic, SchemeBearer, } keys := []string{ "foo", "bar", } count := make([]int64, len(registries)*len(schemes)*len(keys)) fetch := func(i int) func(context.Context) (string, error) { return func(context.Context) (string, error) { time.Sleep(500 * time.Millisecond) atomic.AddInt64(&count[i], 1) return strconv.Itoa(i), nil } } ctx := context.Background() cache := NewCache() // first round of fetch var wg sync.WaitGroup for i := 0; i < 10; i++ { for j := 0; j < len(count); j++ { wg.Add(1) go func(i int) { defer wg.Done() registry := registries[i&1] scheme := schemes[(i>>1)&1] key := keys[(i>>2)&1] got, err := cache.Set(ctx, registry, scheme, key, fetch(i)) if err != nil { t.Errorf("concurrentCache.Set() error = %v", err) } if want := strconv.Itoa(i); got != want { t.Errorf("concurrentCache.Set() = %v, want %v", got, want) } }(j) } } wg.Wait() for i := 0; i < len(count); i++ { if got := count[i]; got != 1 { t.Errorf("fetch is called more than once: %d", got) } } // repeated fetch for i := 0; i < 10; i++ { for j := 0; j < len(count); j++ { wg.Add(1) go func(i int) { defer wg.Done() registry := registries[i&1] scheme := schemes[(i>>1)&1] key := keys[(i>>2)&1] got, err := cache.Set(ctx, registry, scheme, key, fetch(i)) if err != nil { t.Errorf("concurrentCache.Set() error = %v", err) } if want := strconv.Itoa(i); got != want { t.Errorf("concurrentCache.Set() = %v, want %v", got, want) } }(j) } } wg.Wait() for i := 0; i < len(count); i++ { if got := count[i]; got != 2 { t.Errorf("fetch is called more than once: %d", got) } } } func Test_concurrentCache_Set_Fetch_Failure(t *testing.T) { registries := []string{ "localhost:5000", "localhost:5001", } scheme := SchemeBearer keys := []string{ "foo", "bar", } count := len(registries) * len(keys) ctx := context.Background() cache := NewCache() // first round of fetch fetch := func(i int) func(context.Context) (string, error) { return func(context.Context) (string, error) { return "", errors.New(strconv.Itoa(i)) } } var wg sync.WaitGroup for i := 0; i < 10; i++ { for j := 0; j < count; j++ { wg.Add(1) go func(i int) { defer wg.Done() registry := registries[i&1] key := keys[(i>>1)&1] _, err := cache.Set(ctx, registry, scheme, key, fetch(i)) if want := strconv.Itoa(i); err == nil || err.Error() != want { t.Errorf("concurrentCache.Set() error = %v, wantErr %v", err, want) } }(j) } } wg.Wait() for i := 0; i < count; i++ { registry := registries[i&1] key := keys[(i>>1)&1] _, err := cache.GetScheme(ctx, registry) if want := errdef.ErrNotFound; err != want { t.Fatalf("concurrentCache.GetScheme() error = %v, wantErr %v", err, want) } _, err = cache.GetToken(ctx, registry, scheme, key) if want := errdef.ErrNotFound; err != want { t.Errorf("concurrentCache.GetToken() error = %v, wantErr %v", err, want) } } // repeated fetch fetch = func(i int) func(context.Context) (string, error) { return func(context.Context) (string, error) { return strconv.Itoa(i), nil } } for i := 0; i < 10; i++ { for j := 0; j < count; j++ { wg.Add(1) go func(i int) { defer wg.Done() registry := registries[i&1] key := keys[(i>>1)&1] got, err := cache.Set(ctx, registry, scheme, key, fetch(i)) if err != nil { t.Errorf("concurrentCache.Set() error = %v", err) } if want := strconv.Itoa(i); got != want { t.Errorf("concurrentCache.Set() = %v, want %v", got, want) } }(j) } } wg.Wait() for i := 0; i < count; i++ { registry := registries[i&1] key := keys[(i>>1)&1] gotScheme, err := cache.GetScheme(ctx, registry) if err != nil { t.Fatalf("concurrentCache.GetScheme() error = %v", err) } if want := scheme; gotScheme != want { t.Errorf("concurrentCache.GetScheme() = %v, want %v", gotScheme, want) } gotToken, err := cache.GetToken(ctx, registry, scheme, key) if err != nil { t.Fatalf("concurrentCache.GetToken() error = %v", err) } if want := strconv.Itoa(i); gotToken != want { t.Errorf("concurrentCache.GetToken() = %v, want %v", gotToken, want) } } } oras-go-1.1.1/pkg/registry/remote/auth/challenge.go0000644000175000017500000001045314212212744021547 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package auth import ( "strconv" "strings" ) // Scheme define the authentication method. type Scheme byte const ( // SchemeUnknown represents unknown or unsupported schemes SchemeUnknown Scheme = iota // SchemeBasic represents the "Basic" HTTP authentication scheme. // Reference: https://tools.ietf.org/html/rfc7617 SchemeBasic // SchemeBearer represents the Bearer token in OAuth 2.0. // Reference: https://tools.ietf.org/html/rfc6750 SchemeBearer ) // parseScheme parse the authentication scheme from the given string // case-insensitively. func parseScheme(scheme string) Scheme { switch { case strings.EqualFold(scheme, "basic"): return SchemeBasic case strings.EqualFold(scheme, "bearer"): return SchemeBearer } return SchemeUnknown } // String return the string for the scheme. func (s Scheme) String() string { switch s { case SchemeBasic: return "Basic" case SchemeBearer: return "Bearer" } return "Unknown" } // parseChallenge parses the "WWW-Authenticate" header returned by the remote // registry, and extracts parameters if scheme is Bearer. // References: // - https://docs.docker.com/registry/spec/auth/token/#how-to-authenticate // - https://tools.ietf.org/html/rfc7235#section-2.1 func parseChallenge(header string) (scheme Scheme, params map[string]string) { // as defined in RFC 7235 section 2.1, we have // challenge = auth-scheme [ 1*SP ( token68 / #auth-param ) ] // auth-scheme = token // auth-param = token BWS "=" BWS ( token / quoted-string ) // // since we focus parameters only on Bearer, we have // challenge = auth-scheme [ 1*SP #auth-param ] schemeString, rest := parseToken(header) scheme = parseScheme(schemeString) // fast path for non bearer challenge if scheme != SchemeBearer { return } // parse params for bearer auth. // combining RFC 7235 section 2.1 with RFC 7230 section 7, we have // #auth-param => auth-param *( OWS "," OWS auth-param ) var key, value string for { key, rest = parseToken(skipSpace(rest)) if key == "" { return } rest = skipSpace(rest) if rest == "" || rest[0] != '=' { return } rest = skipSpace(rest[1:]) if rest == "" { return } if rest[0] == '"' { prefix, err := strconv.QuotedPrefix(rest) if err != nil { return } value, err = strconv.Unquote(prefix) if err != nil { return } rest = rest[len(prefix):] } else { value, rest = parseToken(rest) if value == "" { return } } if params == nil { params = map[string]string{ key: value, } } else { params[key] = value } rest = skipSpace(rest) if rest == "" || rest[0] != ',' { return } rest = rest[1:] } } // isNotTokenChar reports whether rune is not a `tchar` defined in RFC 7230 // section 3.2.6. func isNotTokenChar(r rune) bool { // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" // / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" // / DIGIT / ALPHA // ; any VCHAR, except delimiters return (r < 'A' || r > 'Z') && (r < 'a' || r > 'z') && (r < '0' || r > '9') && !strings.ContainsRune("!#$%&'*+-.^_`|~", r) } // parseToken finds the next token from the given string. If no token found, // an empty token is returned and the whole of the input is returned in rest. // Note: Since token = 1*tchar, empty string is not a valid token. func parseToken(s string) (token, rest string) { if i := strings.IndexFunc(s, isNotTokenChar); i != -1 { return s[:i], s[i:] } return s, "" } // skipSpace skips "bad" whitespace (BWS) defined in RFC 7230 section 3.2.3. func skipSpace(s string) string { // OWS = *( SP / HTAB ) // ; optional whitespace // BWS = OWS // ; "bad" whitespace if i := strings.IndexFunc(s, func(r rune) bool { return r != ' ' && r != '\t' }); i != -1 { return s[i:] } return s } oras-go-1.1.1/pkg/registry/remote/auth/scope.go0000644000175000017500000001467314212212744020746 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package auth import ( "context" "sort" "strings" ) // Actions used in scopes. // Reference: https://docs.docker.com/registry/spec/auth/scope/ const ( // ActionPull represents generic read access for resources of the repository // type. ActionPull = "pull" // ActionPush represents generic write access for resources of the // repository type. ActionPush = "push" // ActionDelete represents the delete permission for resources of the // repository type. ActionDelete = "delete" ) // ScopeRegistryCatalog is the scope for registry catalog access. const ScopeRegistryCatalog = "registry:catalog:*" // ScopeRepository returns a repository scope with given actions. // Reference: https://docs.docker.com/registry/spec/auth/scope/ func ScopeRepository(repository string, actions ...string) string { actions = cleanActions(actions) if repository == "" || len(actions) == 0 { return "" } return strings.Join([]string{ "repository", repository, strings.Join(actions, ","), }, ":") } // scopesContextKey is the context key for scopes. type scopesContextKey struct{} // WithScopes returns a context with scopes added. Scopes are de-duplicated. // Scopes are used as hints for the auth client to fetch bearer tokens with // larger scopes. // For example, uploading blob to the repository "hello-world" does HEAD request // first then POST and PUT. The HEAD request will return a challenge for scope // `repository:hello-world:pull`, and the auth client will fetch a token for // that challenge. Later, the POST request will return a challenge for scope // `repository:hello-world:push`, and the auth client will fetch a token for // that challenge again. By invoking `WithScopes()` with the scope // `repository:hello-world:pull,push`, the auth client with cache is hinted to // fetch a token via a single token fetch request for all the HEAD, POST, PUT // requests. // Passing an empty list of scopes will virtually remove the scope hints in the // context. // Reference: https://docs.docker.com/registry/spec/auth/scope/ func WithScopes(ctx context.Context, scopes ...string) context.Context { scopes = CleanScopes(scopes) return context.WithValue(ctx, scopesContextKey{}, scopes) } // AppendScopes appends additional scopes to the existing scopes in the context // and returns a new context. The resulted scopes are de-duplicated. // The append operation does modify the existing scope in the context passed in. func AppendScopes(ctx context.Context, scopes ...string) context.Context { if len(scopes) == 0 { return ctx } return WithScopes(ctx, append(GetScopes(ctx), scopes...)...) } // GetScopes returns the scopes in the context. func GetScopes(ctx context.Context) []string { if scopes, ok := ctx.Value(scopesContextKey{}).([]string); ok { return append([]string(nil), scopes...) } return nil } // CleanScopes merges and sort the actions in ascending order if the scopes have // the same resource type and name. The final scopes are sorted in ascending // order. In other words, the scopes passed in are de-duplicated and sorted. // Therefore, the output of this function is deterministic. // If there is a wildcard `*` in the action, other actions in the same resource // type and name are ignored. func CleanScopes(scopes []string) []string { // fast paths switch len(scopes) { case 0: return nil case 1: scope := scopes[0] i := strings.LastIndex(scope, ":") if i == -1 { return []string{scope} } actionList := strings.Split(scope[i+1:], ",") actionList = cleanActions(actionList) if len(actionList) == 0 { return nil } actions := strings.Join(actionList, ",") scope = scope[:i+1] + actions return []string{scope} } // slow path var result []string // merge recognizable scopes resourceTypes := make(map[string]map[string]map[string]struct{}) for _, scope := range scopes { // extract resource type i := strings.Index(scope, ":") if i == -1 { result = append(result, scope) continue } resourceType := scope[:i] // extract resource name and actions rest := scope[i+1:] i = strings.LastIndex(rest, ":") if i == -1 { result = append(result, scope) continue } resourceName := rest[:i] actions := rest[i+1:] if actions == "" { // drop scope since no action found continue } // add to the intermediate map for de-duplication namedActions := resourceTypes[resourceType] if namedActions == nil { namedActions = make(map[string]map[string]struct{}) resourceTypes[resourceType] = namedActions } actionSet := namedActions[resourceName] if actionSet == nil { actionSet = make(map[string]struct{}) namedActions[resourceName] = actionSet } for _, action := range strings.Split(actions, ",") { if action != "" { actionSet[action] = struct{}{} } } } // reconstruct scopes for resourceType, namedActions := range resourceTypes { for resourceName, actionSet := range namedActions { if len(actionSet) == 0 { continue } var actions []string for action := range actionSet { if action == "*" { actions = []string{"*"} break } actions = append(actions, action) } sort.Strings(actions) scope := resourceType + ":" + resourceName + ":" + strings.Join(actions, ",") result = append(result, scope) } } // sort and return sort.Strings(result) return result } // cleanActions removes the duplicated actions and sort in ascending order. // If there is a wildcard `*` in the action, other actions are ignored. func cleanActions(actions []string) []string { // fast paths switch len(actions) { case 0: return nil case 1: if actions[0] == "" { return nil } return actions } // slow path sort.Strings(actions) n := 0 for i := 0; i < len(actions); i++ { if actions[i] == "*" { return []string{"*"} } if actions[i] != actions[n] { n++ if n != i { actions[n] = actions[i] } } } n++ if actions[0] == "" { if n == 1 { return nil } return actions[1:n] } return actions[:n] } oras-go-1.1.1/pkg/registry/remote/auth/challenge_test.go0000644000175000017500000001154614212212744022612 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package auth import ( "reflect" "testing" ) func Test_parseChallenge(t *testing.T) { tests := []struct { name string header string wantScheme Scheme wantParams map[string]string }{ { name: "empty header", }, { name: "unknown scheme", header: "foo bar", wantScheme: SchemeUnknown, }, { name: "basic challenge", header: `Basic realm="Test Registry"`, wantScheme: SchemeBasic, }, { name: "basic challenge with no parameters", header: "Basic", wantScheme: SchemeBasic, }, { name: "basic challenge with no parameters but spaces", header: "Basic ", wantScheme: SchemeBasic, }, { name: "bearer challenge", header: `Bearer realm="https://auth.example.io/token",service="registry.example.io",scope="repository:library/hello-world:pull,push"`, wantScheme: SchemeBearer, wantParams: map[string]string{ "realm": "https://auth.example.io/token", "service": "registry.example.io", "scope": "repository:library/hello-world:pull,push", }, }, { name: "bearer challenge with multiple scopes", header: `Bearer realm="https://auth.example.io/token",service="registry.example.io",scope="repository:library/alpine:pull,push repository:ubuntu:pull"`, wantScheme: SchemeBearer, wantParams: map[string]string{ "realm": "https://auth.example.io/token", "service": "registry.example.io", "scope": "repository:library/alpine:pull,push repository:ubuntu:pull", }, }, { name: "bearer challenge with no parameters", header: "Bearer", wantScheme: SchemeBearer, }, { name: "bearer challenge with no parameters but spaces", header: "Bearer ", wantScheme: SchemeBearer, }, { name: "bearer challenge with white spaces", header: `Bearer realm = "https://auth.example.io/token" ,service=registry.example.io, scope ="repository:library/hello-world:pull,push" `, wantScheme: SchemeBearer, wantParams: map[string]string{ "realm": "https://auth.example.io/token", "service": "registry.example.io", "scope": "repository:library/hello-world:pull,push", }, }, { name: "bad bearer challenge (incomplete parameter with spaces)", header: `Bearer realm="https://auth.example.io/token",service`, wantScheme: SchemeBearer, wantParams: map[string]string{ "realm": "https://auth.example.io/token", }, }, { name: "bad bearer challenge (incomplete parameter with no value)", header: `Bearer realm="https://auth.example.io/token",service=`, wantScheme: SchemeBearer, wantParams: map[string]string{ "realm": "https://auth.example.io/token", }, }, { name: "bad bearer challenge (incomplete parameter with spaces)", header: `Bearer realm="https://auth.example.io/token",service= `, wantScheme: SchemeBearer, wantParams: map[string]string{ "realm": "https://auth.example.io/token", }, }, { name: "bad bearer challenge (incomplete quote)", header: `Bearer realm="https://auth.example.io/token",service="registry`, wantScheme: SchemeBearer, wantParams: map[string]string{ "realm": "https://auth.example.io/token", }, }, { name: "bearer challenge with empty parameter value", header: `Bearer realm="https://auth.example.io/token",empty="",service="registry.example.io",scope="repository:library/hello-world:pull,push"`, wantScheme: SchemeBearer, wantParams: map[string]string{ "realm": "https://auth.example.io/token", "empty": "", "service": "registry.example.io", "scope": "repository:library/hello-world:pull,push", }, }, { name: "bearer challenge with escaping parameter value", header: `Bearer foo="foo\"bar",hello="\"hello world\""`, wantScheme: SchemeBearer, wantParams: map[string]string{ "foo": `foo"bar`, "hello": `"hello world"`, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotScheme, gotParams := parseChallenge(tt.header) if gotScheme != tt.wantScheme { t.Errorf("parseChallenge() gotScheme = %v, want %v", gotScheme, tt.wantScheme) } if !reflect.DeepEqual(gotParams, tt.wantParams) { t.Errorf("parseChallenge() gotParams = %v, want %v", gotParams, tt.wantParams) } }) } } oras-go-1.1.1/pkg/registry/remote/auth/client.go0000644000175000017500000002662214212212744021110 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package auth import ( "context" "encoding/base64" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "strings" "oras.land/oras-go/pkg/registry/remote/internal/errutil" ) // DefaultClient is the default auth-decorated client. var DefaultClient = &Client{ Header: http.Header{ "User-Agent": {"oras-go"}, }, Cache: DefaultCache, } // maxResponseBytes specifies the default limit on how many response bytes are // allowed in the server's response from authorization service servers. // A typical response message from authorization service servers is around 1 to // 4 KiB. Since the size of a token must be smaller than the HTTP header size // limit, which is usually 16 KiB. As specified by the distribution, the // response may contain 2 identical tokens, that is, 16 x 2 = 32 KiB. // Hence, 128 KiB should be sufficient. // References: https://docs.docker.com/registry/spec/auth/token/ var maxResponseBytes int64 = 128 * 1024 // 128 KiB // defaultClientID specifies the default client ID used in OAuth2. // See also ClientID. var defaultClientID = "oras-go" // Client is an auth-decorated HTTP client. // Its zero value is a usable client that uses http.DefaultClient with no cache. type Client struct { // Client is the underlying HTTP client used to access the remote // server. // If nil, http.DefaultClient is used. Client *http.Client // Header contains the custom headers to be added to each request. Header http.Header // Credential specifies the function for resolving the credential for the // given registry (i.e. host:port). // `EmptyCredential` is a valid return value and should not be considered as // an error. // If nil, the credential is always resolved to `EmptyCredential`. Credential func(context.Context, string) (Credential, error) // Cache caches credentials for direct accessing the remote registry. // If nil, no cache is used. Cache Cache // ClientID used in fetching OAuth2 token as a required field. // If empty, a default client ID is used. // Reference: https://docs.docker.com/registry/spec/auth/oauth/#getting-a-token ClientID string // ForceAttemptOAuth2 controls whether to follow OAuth2 with password grant // instead the distribution spec when authenticating using username and // password. // References: // - https://docs.docker.com/registry/spec/auth/jwt/ // - https://docs.docker.com/registry/spec/auth/oauth/ ForceAttemptOAuth2 bool } // client returns an HTTP client used to access the remote registry. // http.DefaultClient is return if the client is not configured. func (c *Client) client() *http.Client { if c.Client == nil { return http.DefaultClient } return c.Client } // send adds headers to the request and sends the request to the remote server. func (c *Client) send(req *http.Request) (*http.Response, error) { for key, values := range c.Header { req.Header[key] = append(req.Header[key], values...) } return c.client().Do(req) } // credential resolves the credential for the given registry. func (c *Client) credential(ctx context.Context, reg string) (Credential, error) { if c.Credential == nil { return EmptyCredential, nil } return c.Credential(ctx, reg) } // cache resolves the cache. // noCache is return if the cache is not configured. func (c *Client) cache() Cache { if c.Cache == nil { return noCache{} } return c.Cache } // SetUserAgent sets the user agent for all out-going requests. func (c *Client) SetUserAgent(userAgent string) { if c.Header == nil { c.Header = http.Header{} } c.Header.Set("User-Agent", userAgent) } // Do sends the request to the remote server with resolving authentication // attempted. // On authentication failure due to bad credential, // - Do returns error if it fails to fetch token for bearer auth. // - Do returns the registry response without error for basic auth. func (c *Client) Do(originalReq *http.Request) (*http.Response, error) { ctx := originalReq.Context() req := originalReq.Clone(ctx) // attempt cached auth token var attemptedKey string cache := c.cache() registry := originalReq.Host scheme, err := cache.GetScheme(ctx, registry) if err == nil { switch scheme { case SchemeBasic: token, err := cache.GetToken(ctx, registry, SchemeBasic, "") if err == nil { req.Header.Set("Authorization", "Basic "+token) } case SchemeBearer: scopes := GetScopes(ctx) attemptedKey = strings.Join(scopes, " ") token, err := cache.GetToken(ctx, registry, SchemeBearer, attemptedKey) if err == nil { req.Header.Set("Authorization", "Bearer "+token) } } } resp, err := c.send(req) if err != nil { return nil, err } if resp.StatusCode != http.StatusUnauthorized { return resp, nil } // attempt again with credentials for recognized schemes challenge := resp.Header.Get("Www-Authenticate") scheme, params := parseChallenge(challenge) switch scheme { case SchemeBasic: resp.Body.Close() token, err := cache.Set(ctx, registry, SchemeBasic, "", func(ctx context.Context) (string, error) { return c.fetchBasicAuth(ctx, registry) }) if err != nil { return nil, fmt.Errorf("%s %q: %w", resp.Request.Method, resp.Request.URL, err) } req = originalReq.Clone(ctx) req.Header.Set("Authorization", "Basic "+token) case SchemeBearer: resp.Body.Close() // merge hinted scopes with challenged scopes scopes := GetScopes(ctx) if scope := params["scope"]; scope != "" { scopes = append(scopes, strings.Split(scope, " ")...) scopes = CleanScopes(scopes) } key := strings.Join(scopes, " ") // attempt the cache again if there is a scope change if key != attemptedKey { if token, err := cache.GetToken(ctx, registry, SchemeBearer, key); err == nil { req = originalReq.Clone(ctx) req.Header.Set("Authorization", "Bearer "+token) resp, err := c.send(req) if err != nil { return nil, err } if resp.StatusCode != http.StatusUnauthorized { return resp, nil } resp.Body.Close() } } // attempt with credentials realm := params["realm"] service := params["service"] token, err := cache.Set(ctx, registry, SchemeBearer, key, func(ctx context.Context) (string, error) { return c.fetchBearerToken(ctx, registry, realm, service, scopes) }) if err != nil { return nil, fmt.Errorf("%s %q: %w", resp.Request.Method, resp.Request.URL, err) } req = originalReq.Clone(ctx) req.Header.Set("Authorization", "Bearer "+token) default: return resp, nil } return c.send(req) } // fetchBasicAuth fetches a basic auth token for the basic challenge. func (c *Client) fetchBasicAuth(ctx context.Context, registry string) (string, error) { cred, err := c.credential(ctx, registry) if err != nil { return "", fmt.Errorf("failed to resolve credential: %w", err) } if cred == EmptyCredential { return "", errors.New("credential required for basic auth") } if cred.Username == "" || cred.Password == "" { return "", errors.New("missing username or password for basic auth") } auth := cred.Username + ":" + cred.Password return base64.StdEncoding.EncodeToString([]byte(auth)), nil } // fetchBearerToken fetches an access token for the bearer challenge. func (c *Client) fetchBearerToken(ctx context.Context, registry, realm, service string, scopes []string) (string, error) { cred, err := c.credential(ctx, registry) if err != nil { return "", err } if cred.AccessToken != "" { return cred.AccessToken, nil } if cred == EmptyCredential || (cred.RefreshToken == "" && !c.ForceAttemptOAuth2) { return c.fetchDistributionToken(ctx, realm, service, scopes, cred.Username, cred.Password) } return c.fetchOAuth2Token(ctx, realm, service, scopes, cred) } // fetchDistributionToken fetches an access token as defined by the distribution // specification. // It fetches anonymous tokens if no credential is provided. // References: // - https://docs.docker.com/registry/spec/auth/jwt/ // - https://docs.docker.com/registry/spec/auth/token/ func (c *Client) fetchDistributionToken(ctx context.Context, realm, service string, scopes []string, username, password string) (string, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, realm, nil) if err != nil { return "", err } if username != "" || password != "" { req.SetBasicAuth(username, password) } q := req.URL.Query() if service != "" { q.Set("service", service) } for _, scope := range scopes { q.Add("scope", scope) } req.URL.RawQuery = q.Encode() resp, err := c.send(req) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", errutil.ParseErrorResponse(resp) } // As specified in https://docs.docker.com/registry/spec/auth/token/ section // "Token Response Fields", the token is either in `token` or // `access_token`. If both present, they are identical. var result struct { Token string `json:"token"` AccessToken string `json:"access_token"` } lr := io.LimitReader(resp.Body, maxResponseBytes) if err := json.NewDecoder(lr).Decode(&result); err != nil { return "", fmt.Errorf("%s %q: failed to decode response: %w", resp.Request.Method, resp.Request.URL, err) } if result.AccessToken != "" { return result.AccessToken, nil } if result.Token != "" { return result.Token, nil } return "", fmt.Errorf("%s %q: empty token returned", resp.Request.Method, resp.Request.URL) } // fetchOAuth2Token fetches an OAuth2 access token. // Reference: https://docs.docker.com/registry/spec/auth/oauth/ func (c *Client) fetchOAuth2Token(ctx context.Context, realm, service string, scopes []string, cred Credential) (string, error) { form := url.Values{} if cred.RefreshToken != "" { form.Set("grant_type", "refresh_token") form.Set("refresh_token", cred.RefreshToken) } else if cred.Username != "" && cred.Password != "" { form.Set("grant_type", "password") form.Set("username", cred.Username) form.Set("password", cred.Password) } else { return "", errors.New("missing username or password for bearer auth") } form.Set("service", service) clientID := c.ClientID if clientID == "" { clientID = defaultClientID } form.Set("client_id", clientID) if len(scopes) != 0 { form.Set("scope", strings.Join(scopes, " ")) } body := strings.NewReader(form.Encode()) req, err := http.NewRequestWithContext(ctx, http.MethodPost, realm, body) if err != nil { return "", err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := c.send(req) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", errutil.ParseErrorResponse(resp) } var result struct { AccessToken string `json:"access_token"` } lr := io.LimitReader(resp.Body, maxResponseBytes) if err := json.NewDecoder(lr).Decode(&result); err != nil { return "", fmt.Errorf("%s %q: failed to decode response: %w", resp.Request.Method, resp.Request.URL, err) } if result.AccessToken != "" { return result.AccessToken, nil } return "", fmt.Errorf("%s %q: empty token returned", resp.Request.Method, resp.Request.URL) } oras-go-1.1.1/pkg/registry/remote/auth/credential.go0000644000175000017500000000254414212212744021741 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package auth // EmptyCredential represents an empty credential. var EmptyCredential Credential // Credential contains authentication credentials used to access remote // registries. type Credential struct { // Username is the name of the user for the remote registry. Username string // Password is the secret associated with the username. Password string // RefreshToken is a bearer token to be sent to the authorization service // for fetching access tokens. // A refresh token is often referred as an identity token. // Reference: https://docs.docker.com/registry/spec/auth/oauth/ RefreshToken string // AccessToken is a bearer token to be sent to the registry. // An access token is often referred as a registry token. // Reference: https://docs.docker.com/registry/spec/auth/token/ AccessToken string } oras-go-1.1.1/pkg/registry/remote/auth/cache.go0000644000175000017500000001256514212212744020676 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package auth import ( "context" "strings" "sync" errdef "oras.land/oras-go/pkg/content" "oras.land/oras-go/pkg/registry/remote/internal/syncutil" ) // DefaultCache is the sharable cache used by DefaultClient. var DefaultCache Cache = NewCache() // Cache caches the auth-scheme and auth-token for the "Authorization" header in // accessing the remote registry. // Precisely, the header is `Authorization: auth-scheme auth-token`. // The `auth-token` is a generic term as `token68` in RFC 7235 section 2.1. type Cache interface { // GetScheme returns the auth-scheme part cached for the given registry. // A single registry is assumed to have a consistent scheme. // If a registry has different schemes per path, the auth client is still // workable. However, the cache may not be effective as the cache cannot // correctly guess the scheme. GetScheme(ctx context.Context, registry string) (Scheme, error) // GetToken returns the auth-token part cached for the given registry of a // given scheme. // The underlying implementation MAY cache the token for all schemes for the // given registry. GetToken(ctx context.Context, registry string, scheme Scheme, key string) (string, error) // Set fetches the token using the given fetch function and caches the token // for the given scheme with the given key for the given registry. // The return values of the fetch function is returned by this function. // The underlying implementation MAY combine the fetch operation if the Set // function is invoked multiple times at the same time. Set(ctx context.Context, registry string, scheme Scheme, key string, fetch func(context.Context) (string, error)) (string, error) } // cacheEntry is a cache entry for a single registry. type cacheEntry struct { scheme Scheme tokens sync.Map // map[string]string } // concurrentCache is a cache suitable for concurrent invocation. type concurrentCache struct { status sync.Map // map[string]*syncutil.Once cache sync.Map // map[string]*cacheEntry } // NewCache creates a new go-routine safe cache instance. func NewCache() Cache { return &concurrentCache{} } // GetScheme returns the auth-scheme part cached for the given registry. func (cc *concurrentCache) GetScheme(ctx context.Context, registry string) (Scheme, error) { entry, ok := cc.cache.Load(registry) if !ok { return SchemeUnknown, errdef.ErrNotFound } return entry.(*cacheEntry).scheme, nil } // GetToken returns the auth-token part cached for the given registry of a given // scheme. func (cc *concurrentCache) GetToken(ctx context.Context, registry string, scheme Scheme, key string) (string, error) { entryValue, ok := cc.cache.Load(registry) if !ok { return "", errdef.ErrNotFound } entry := entryValue.(*cacheEntry) if entry.scheme != scheme { return "", errdef.ErrNotFound } if token, ok := entry.tokens.Load(key); ok { return token.(string), nil } return "", errdef.ErrNotFound } // Set fetches the token using the given fetch function and caches the token // for the given scheme with the given key for the given registry. // Set combines the fetch operation if the Set is invoked multiple times at the // same time. func (cc *concurrentCache) Set(ctx context.Context, registry string, scheme Scheme, key string, fetch func(context.Context) (string, error)) (string, error) { // fetch token statusKey := strings.Join([]string{ registry, scheme.String(), key, }, " ") statusValue, _ := cc.status.LoadOrStore(statusKey, syncutil.NewOnce()) fetchOnce := statusValue.(*syncutil.Once) fetchedFirst, result, err := fetchOnce.Do(ctx, func() (interface{}, error) { return fetch(ctx) }) if fetchedFirst { cc.status.Delete(statusKey) } if err != nil { return "", err } token := result.(string) if !fetchedFirst { return token, nil } // cache token newEntry := &cacheEntry{ scheme: scheme, } entryValue, exists := cc.cache.LoadOrStore(registry, newEntry) entry := entryValue.(*cacheEntry) if exists && entry.scheme != scheme { // there is a scheme change, which is not expected in most scenarios. // force invalidating all previous cache. entry = newEntry cc.cache.Store(registry, entry) } entry.tokens.Store(key, token) return token, nil } // noCache is a cache implementation that does not do cache at all. type noCache struct{} // GetScheme always returns not found error as it has no cache. func (noCache) GetScheme(ctx context.Context, registry string) (Scheme, error) { return SchemeUnknown, errdef.ErrNotFound } // GetToken always returns not found error as it has no cache. func (noCache) GetToken(ctx context.Context, registry string, scheme Scheme, key string) (string, error) { return "", errdef.ErrNotFound } // Set calls fetch directly without caching. func (noCache) Set(ctx context.Context, registry string, scheme Scheme, key string, fetch func(context.Context) (string, error)) (string, error) { return fetch(ctx) } oras-go-1.1.1/pkg/registry/remote/internal/0000755000175000017500000000000014212212744020146 5ustar nileshnileshoras-go-1.1.1/pkg/registry/remote/internal/errutil/0000755000175000017500000000000014212212744021634 5ustar nileshnileshoras-go-1.1.1/pkg/registry/remote/internal/errutil/errors.go0000644000175000017500000000433714212212744023506 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package errutil import ( "encoding/json" "fmt" "io" "net/http" "strings" "unicode" ) // maxErrorBytes specifies the default limit on how many response bytes are // allowed in the server's error response. // A typical error message is around 200 bytes. Hence, 8 KiB should be // sufficient. var maxErrorBytes int64 = 8 * 1024 // 8 KiB // requestError contains a single error. type requestError struct { Code string `json:"code"` Message string `json:"message"` } // Error returns a error string describing the error. func (e requestError) Error() string { code := strings.Map(func(r rune) rune { if r == '_' { return ' ' } return unicode.ToLower(r) }, e.Code) if e.Message == "" { return code } return fmt.Sprintf("%s: %s", code, e.Message) } // requestErrors is a bundle of requestError. type requestErrors []requestError // Error returns a error string describing the error. func (errs requestErrors) Error() string { switch len(errs) { case 0: return "" case 1: return errs[0].Error() } var errmsgs []string for _, err := range errs { errmsgs = append(errmsgs, err.Error()) } return strings.Join(errmsgs, "; ") } // ParseErrorResponse parses the error returned by the remote registry. func ParseErrorResponse(resp *http.Response) error { var errmsg string var body struct { Errors requestErrors `json:"errors"` } lr := io.LimitReader(resp.Body, maxErrorBytes) if err := json.NewDecoder(lr).Decode(&body); err == nil && len(body.Errors) > 0 { errmsg = body.Errors.Error() } else { errmsg = http.StatusText(resp.StatusCode) } return fmt.Errorf("%s %q: unexpected status code %d: %s", resp.Request.Method, resp.Request.URL, resp.StatusCode, errmsg) } oras-go-1.1.1/pkg/registry/remote/internal/errutil/errors_test.go0000644000175000017500000000475214212212744024546 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package errutil import ( "net/http" "net/http/httptest" "strings" "testing" ) func Test_ParseErrorResponse(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { msg := `{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"repository","Class":"","Name":"library/hello-world","Action":"pull"}]}]}` w.WriteHeader(http.StatusUnauthorized) if _, err := w.Write([]byte(msg)); err != nil { t.Errorf("failed to write %q: %v", r.URL, err) } })) defer ts.Close() resp, err := http.Get(ts.URL) if err != nil { t.Fatalf("failed to do request: %v", err) } err = ParseErrorResponse(resp) if err == nil { t.Errorf("ParseErrorResponse() error = %v, wantErr %v", err, true) } errmsg := err.Error() if want := "401"; !strings.Contains(errmsg, want) { t.Errorf("ParseErrorResponse() error = %v, want err message %v", err, want) } if want := "unauthorized"; !strings.Contains(errmsg, want) { t.Errorf("ParseErrorResponse() error = %v, want err message %v", err, want) } if want := "authentication required"; !strings.Contains(errmsg, want) { t.Errorf("ParseErrorResponse() error = %v, want err message %v", err, want) } } func Test_ParseErrorResponse_plain(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnauthorized) })) defer ts.Close() resp, err := http.Get(ts.URL) if err != nil { t.Fatalf("failed to do request: %v", err) } err = ParseErrorResponse(resp) if err == nil { t.Errorf("ParseErrorResponse() error = %v, wantErr %v", err, true) } errmsg := err.Error() if want := "401"; !strings.Contains(errmsg, want) { t.Errorf("ParseErrorResponse() error = %v, want err message %v", err, want) } if want := http.StatusText(http.StatusUnauthorized); !strings.Contains(errmsg, want) { t.Errorf("ParseErrorResponse() error = %v, want err message %v", err, want) } } oras-go-1.1.1/pkg/registry/remote/internal/syncutil/0000755000175000017500000000000014212212744022020 5ustar nileshnileshoras-go-1.1.1/pkg/registry/remote/internal/syncutil/once.go0000644000175000017500000000401014212212744023266 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package syncutil import "context" // Once is an object that will perform exactly one action. // Unlike sync.Once, this Once allowes the action to have return values. type Once struct { result interface{} err error status chan bool } // NewOnce creates a new Once instance. func NewOnce() *Once { status := make(chan bool, 1) status <- true return &Once{ status: status, } } // Do calls the function f if and only if Do is being called first time or all // previous function calls are cancelled, deadline exceeded, or panicking. // When `once.Do(ctx, f)` is called multiple times, the return value of the // first call of the function f is stored, and is directly returned for other // calls. // Besides the return value of the function f, including the error, Do returns // true if the function f passed is called first and is not cancelled, deadline // exceeded, or panicking. Otherwise, returns false. func (o *Once) Do(ctx context.Context, f func() (interface{}, error)) (bool, interface{}, error) { defer func() { if r := recover(); r != nil { o.status <- true panic(r) } }() for { select { case inProgress := <-o.status: if !inProgress { return false, o.result, o.err } result, err := f() if err == context.Canceled || err == context.DeadlineExceeded { o.status <- true return false, nil, err } o.result, o.err = result, err close(o.status) return true, result, err case <-ctx.Done(): return false, nil, ctx.Err() } } } oras-go-1.1.1/pkg/registry/remote/internal/syncutil/once_test.go0000644000175000017500000001173514212212744024341 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package syncutil import ( "context" "errors" "io" "reflect" "strconv" "sync" "testing" "time" ) func TestOnce_Do(t *testing.T) { var f []func() (interface{}, error) for i := 0; i < 100; i++ { f = append(f, func(i int) func() (interface{}, error) { return func() (interface{}, error) { return i + 1, errors.New(strconv.Itoa(i)) } }(i)) } once := NewOnce() first := make([]bool, len(f)) result := make([]interface{}, len(f)) err := make([]error, len(f)) var wg sync.WaitGroup for i := 0; i < len(f); i++ { wg.Add(1) go func(i int) { defer wg.Done() ctx := context.Background() first[i], result[i], err[i] = once.Do(ctx, f[i]) }(i) } wg.Wait() target := 0 for i := 0; i < len(f); i++ { if first[i] { target = i break } } targetErr := err[target] if targetErr == nil || targetErr.Error() != strconv.Itoa(target) { t.Errorf("Once.Do(%d) error = %v, wantErr %v", target, targetErr, strconv.Itoa(target)) } wantResult := target + 1 wantErr := targetErr for i := 0; i < len(f); i++ { wantFirst := false if i == target { wantFirst = true } if first[i] != wantFirst { t.Errorf("Once.Do(%d) first = %v, want %v", i, first[i], wantFirst) } if err[i] != wantErr { t.Errorf("Once.Do(%d) error = %v, wantErr %v", i, err[i], wantErr) } if !reflect.DeepEqual(result[i], wantResult) { t.Errorf("Once.Do(%d) result = %v, want %v", i, result[i], wantResult) } } } func TestOnce_Do_Cancel_Context(t *testing.T) { once := NewOnce() var wg sync.WaitGroup var ( first bool result interface{} err error ) wg.Add(1) go func() { defer wg.Done() ctx := context.Background() first, result, err = once.Do(ctx, func() (interface{}, error) { time.Sleep(200 * time.Millisecond) return "foo", io.EOF }) }() time.Sleep(100 * time.Millisecond) ctx := context.Background() ctx, cancel := context.WithCancel(ctx) cancel() first2, result2, err2 := once.Do(ctx, func() (interface{}, error) { return "bar", nil }) wg.Wait() if wantFirst := true; first != wantFirst { t.Fatalf("Once.Do() first = %v, want %v", first, wantFirst) } if wantErr := io.EOF; err != wantErr { t.Fatalf("Once.Do() error = %v, wantErr %v", err, wantErr) } if wantResult := "foo"; !reflect.DeepEqual(result, wantResult) { t.Fatalf("Once.Do() result = %v, want %v", result, wantResult) } if wantFirst := false; first2 != wantFirst { t.Fatalf("Once.Do() first = %v, want %v", first2, wantFirst) } if wantErr := context.Canceled; err2 != wantErr { t.Fatalf("Once.Do() error = %v, wantErr %v", err2, wantErr) } if wantResult := interface{}(nil); !reflect.DeepEqual(result2, wantResult) { t.Fatalf("Once.Do() result = %v, want %v", result2, wantResult) } } func TestOnce_Do_Cancel_Function(t *testing.T) { ctx := context.Background() once := NewOnce() first, result, err := once.Do(ctx, func() (interface{}, error) { return "foo", context.Canceled }) if wantFirst := false; first != wantFirst { t.Fatalf("Once.Do() first = %v, want %v", first, wantFirst) } if wantErr := context.Canceled; err != wantErr { t.Fatalf("Once.Do() error = %v, wantErr %v", err, wantErr) } if wantResult := interface{}(nil); !reflect.DeepEqual(result, wantResult) { t.Fatalf("Once.Do() result = %v, want %v", result, wantResult) } first, result, err = once.Do(ctx, func() (interface{}, error) { return "bar", io.EOF }) if wantFirst := true; first != wantFirst { t.Fatalf("Once.Do() first = %v, want %v", first, wantFirst) } if wantErr := io.EOF; err != wantErr { t.Fatalf("Once.Do() error = %v, wantErr %v", err, wantErr) } if wantResult := "bar"; !reflect.DeepEqual(result, wantResult) { t.Fatalf("Once.Do() result = %v, want %v", result, wantResult) } } func TestOnce_Do_Cancel_Panic(t *testing.T) { ctx := context.Background() once := NewOnce() func() { defer func() { got := recover() want := "foo" if got != want { t.Fatalf("Once.Do() panic = %v, want %v", got, want) } }() once.Do(ctx, func() (interface{}, error) { panic("foo") }) }() first, result, err := once.Do(ctx, func() (interface{}, error) { return "bar", io.EOF }) if wantFirst := true; first != wantFirst { t.Fatalf("Once.Do() first = %v, want %v", first, wantFirst) } if wantErr := io.EOF; err != wantErr { t.Fatalf("Once.Do() error = %v, wantErr %v", err, wantErr) } if wantResult := "bar"; !reflect.DeepEqual(result, wantResult) { t.Fatalf("Once.Do() result = %v, want %v", result, wantResult) } } oras-go-1.1.1/pkg/registry/remote/utils_test.go0000644000175000017500000000414314212212744021062 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package remote import ( "net/http" "net/url" "testing" ) func Test_parseLink(t *testing.T) { tests := []struct { name string url string header string want string wantErr bool }{ { name: "catalog", url: "https://localhost:5000/v2/_catalog", header: `; rel="next"`, want: "https://localhost:5000/v2/_catalog?last=alpine&n=1", }, { name: "list tag", url: "https://localhost:5000/v2/hello-world/tags/list", header: `; rel="next"`, want: "https://localhost:5000/v2/hello-world/tags/list?last=latest&n=1", }, { name: "other domain", url: "https://localhost:5000/v2/_catalog", header: `; rel="next"`, want: "https://localhost:5001/v2/_catalog?last=alpine&n=1", }, { name: "invalid header", url: "https://localhost:5000/v2/_catalog", header: `'); i == -1 { return "", fmt.Errorf("invalid next link %q: missing '>'", link) } else { link = link[1:i] } linkURL, err := resp.Request.URL.Parse(link) if err != nil { return "", err } return linkURL.String(), nil } // limitReader returns a Reader that reads from r but stops with EOF after n // bytes. If n is zero, defaultMaxMetadataBytes is used. func limitReader(r io.Reader, n int64) io.Reader { if n == 0 { n = defaultMaxMetadataBytes } return io.LimitReader(r, n) } // withScopeHint adds a hinted scope to the context. func withScopeHint(ctx context.Context, ref registry.Reference, actions ...string) context.Context { scope := auth.ScopeRepository(ref.Repository, actions...) return auth.AppendScopes(ctx, scope) } oras-go-1.1.1/pkg/registry/remote/url.go0000644000175000017500000000262314212212744017466 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package remote import ( "fmt" "oras.land/oras-go/pkg/registry" ) // buildScheme returns HTTP scheme used to access the remote registry. func buildScheme(plainHTTP bool) string { if plainHTTP { return "http" } return "https" } // buildRepositoryBaseURL builds the base endpoint of the remote repository. // Format: :///v2/ func buildRepositoryBaseURL(plainHTTP bool, ref registry.Reference) string { return fmt.Sprintf("%s://%s/v2/%s", buildScheme(plainHTTP), ref.Host(), ref.Repository) } // buildRepositoryTagListURL builds the URL for accessing the tag list API. // Format: :///v2//tags/list // Reference: https://docs.docker.com/registry/spec/api/#tags func buildRepositoryTagListURL(plainHTTP bool, ref registry.Reference) string { return buildRepositoryBaseURL(plainHTTP, ref) + "/tags/list" } oras-go-1.1.1/pkg/context/0000755000175000017500000000000014212212744014653 5ustar nileshnileshoras-go-1.1.1/pkg/context/context.go0000644000175000017500000000136114212212744016667 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package context import "context" // Background returns a default context with logger discarded. func Background() context.Context { ctx := context.Background() return WithLoggerDiscarded(ctx) } oras-go-1.1.1/pkg/context/logger.go0000644000175000017500000000314514212212744016464 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package context import ( "context" "io" "io/ioutil" "github.com/containerd/containerd/log" "github.com/sirupsen/logrus" ) // WithLogger returns a new context with the provided logger. // This method wraps github.com/containerd/containerd/log.WithLogger() func WithLogger(ctx context.Context, logger *logrus.Entry) context.Context { return log.WithLogger(ctx, logger) } // WithLoggerFromWriter returns a new context with the logger, writting to the provided logger. func WithLoggerFromWriter(ctx context.Context, writer io.Writer) context.Context { logger := logrus.New() logger.Out = writer entry := logrus.NewEntry(logger) return WithLogger(ctx, entry) } // WithLoggerDiscarded returns a new context with the logger, writting to nothing. func WithLoggerDiscarded(ctx context.Context) context.Context { return WithLoggerFromWriter(ctx, ioutil.Discard) } // GetLogger retrieves the current logger from the context. // This method wraps github.com/containerd/containerd/log.GetLogger() func GetLogger(ctx context.Context) *logrus.Entry { return log.GetLogger(ctx) } oras-go-1.1.1/pkg/artifact/0000755000175000017500000000000014212212744014764 5ustar nileshnileshoras-go-1.1.1/pkg/artifact/consts.go0000644000175000017500000000135714212212744016632 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package artifact const ( // UnknownConfigMediaType is the default mediaType used when no // config media type is specified. UnknownConfigMediaType = "application/vnd.unknown.config.v1+json" ) oras-go-1.1.1/KEYS0000644000175000017500000002636014212212744013113 0ustar nileshnileshThis file contains the PGP keys of developers who have signed releases of ORAS. For your convenience, commands are provided for those who use pgp and gpg. For users to import keys: pgp < KEYS or gpg --import KEYS Developers to add their keys: pgp -kxa and append it to this file. or (pgpk -ll && pgpk -xa ) >> KEYS or (gpg --list-sigs && gpg --armor --export ) >> KEYS pub rsa4096 2021-01-14 [C] [expires: 2031-01-12] E97F9DA5AE2E39CF48A142B7852A7470A39FB81D uid [ unknown] Josh Dolitsky sig 3 852A7470A39FB81D 2021-01-14 Josh Dolitsky sub rsa4096 2021-01-14 [E] [expires: 2031-01-12] sig 852A7470A39FB81D 2021-01-14 Josh Dolitsky sub rsa4096 2021-01-14 [S] [expires: 2031-01-12] sig 852A7470A39FB81D 2021-01-14 Josh Dolitsky sub rsa4096 2021-01-14 [A] [expires: 2031-01-12] sig 852A7470A39FB81D 2021-01-14 Josh Dolitsky -----BEGIN PGP PUBLIC KEY BLOCK----- mQINBGAA1BEBEADQj7kE8cu7jXwdsYqTN35MJzhahwjKH211TU3+YkbWmBunGKCD T2xpgfTJZJFMYcNIp8t8UZ15PVi1dgLe+E4bL3n1YnzcC2BWZZKPYWPXr9IlnTqX COEk7kTpcHp2kKXjKIuh+kexzahtjHcgGSzORgY98bU6wiNF3CtRRGtHYJO4WdO6 OiuO4h997D9MxXR5zvr7JdpdsBAZpF5e+o9Syyguon4bN1CYSwborqW4PBM2bt8N SWQ+kTv1Az7udAMjLyGq5fT7FouOpFXo1qxUv12vR1gXOoppDTbilVaewPGW0Jla BdgQZM5tHlnfa63N5fLbPz6HvdusoEGNpaq6lsE80imcyiWkyu1xZ1sgHqovSEeW 1NP0CZtV/CvPl5FSUcNrPcNkA+UoM+uSEaz9De6W4w8A0x240NneVCE/WtlZBi0t /NWYVQpNJ+IjgYeee5jj1wo5cxubHotCoIunQwO54h42qWHhnOb3yCo+iYOFa179 bBciq0m/k2ZSYeQizrT1IDQlSGS7YDpNSu5AZboxkC0fVZG65Dxea/pP/TrRmNZK e0uIEqgKm3mItmyuOiLxJT/HI5D/HF51IMbMz/+lqI5kmUMfhWH2v4EPRI4NWj+B WKOaRp3j/TiuAoh1LsVT8MlvU6fu+gakIuN/srUp4abOBjN/xfLwlp8pGQARAQAB tB5Kb3NoIERvbGl0c2t5IDxqb3NoQGRvbGl0LnNraT6JAlQEEwEIAD4WIQTpf52l ri45z0ihQreFKnRwo5+4HQUCYADUEQIbAQUJEswDAAULCQgHAgYVCgkICwIEFgID AQIeAQIXgAAKCRCFKnRwo5+4HSJFD/4q8xgdpz8QW9RM9DjUqwUvcYQgyF1uKxQr WXwvSD+Yfny76mQSIK5TjoTGYBWCAH+ANCveyeLPdLtxh+OsiRpyAW3ek3JtuCgz kTUO6zryq4yxsQA/xXb1jsUqyx9NZ7mZlJf1OZOgEVsRbEMAvA6XtH+UpcWY44yc hdiBSonCbVpteudFlskE/Ljp1zaQNirmPt87jo8lGBC3vFjoFmSisByDoJwzdkDF pCmDYXOKYA+yXSMTnKrpJhC6wnD9pOROM1zRQ/JVxxymH9NAVKTX0U5fvm/3AFKF QBSomqkEgVTYWmezeWW7z6hBcYILw0iXdf7Wf/lcaBHSLOpA9f6mQqM0NvUGFUPy JE6gUs/v6cHe2w/vsWn3ytcxelOrjlRCAMrS4hnKrJ2kJ2bfQdNXGGbw/ULWPqGj 4w5XpH6rPSMYm/vEEXEBqCdo/ASf+pukoegv7UaqZF+a6B7sHU7wsyk1m6T2TIcA gKESt+qWXxvXYtvo03tGTw7ap7RXEw5ob3e2jSQzZd+WeMVB33orWJcaRyfZ3x+5 yUhA6oBEP2pQEM0SRHhe/5X1tL96heeUoaJpHO5lQuNje0cJZhq6DPboT44iM1Ut yQsvMpoRkctCfi7brGoAHOJAxoQALZixyCwYyStN5wzjdb64UFIwtDckLHgp+tGe VhboNwQd/bkCDQRgANTcARAAvnpSk5lXtXoNoH3qDTyorqu+BG2WA8zGCLx1D0C/ UKwr5TO0Hju5LN278Wzfl/kJYanDGutDVmy9I2EsWN0mgzYtVwF+MtZqCs87i5EU F0pX4fwUylDJfjvbuPEE6+BBZ7CMszzJrdgZ6xwJruWYDl4JOOUIfy//SXL3ff6J B32r8qTpC7/62sR3FH6EvdO65khgU4KLxyTR/WGNGcPmBRAyMPLqFkmKFwtCJpFg fH00+eM2foQmHYhBjjLihT25m1YWGy1xeCjTzoCWzU0QCQVCyDpAwWD6XsRT6Qo5 M3j80abNm6JSjV2eWVloArlqh45MqeoDoNfd1Sj7BmhBXPLldz0H5jtdpaCOtBbV pdPovSa2vVJhh2B4LeTy11kHi6rQRjMtWdypHe8oSGfx/j3ysY9FJXduCRGZajw6 SjhEMaefYEIAV5Pl4SM0dMrl37WLCJZy/RH6YIrxw9Z+W6srjR6jT0MLXJFP/iAg evGXWNWnhmFBIKUAp2H3H0BUaFjYg74IFY+4G2TSya53CWiQ4PRYZW+9LzzYROLe WW3t0xE+I8jx74uNrDP9IeLrch2PateN4Izn1cuAmoFFRjH+11IOXbGMJcE4VDaP i4p9x8gm8q0xWjG2K+lLUpvAf1CFD7KV4wkHq3xpVJyGBWEmuWjQAqsKAehNt2D5 tfEAEQEAAYkCPAQYAQgAJhYhBOl/naWuLjnPSKFCt4UqdHCjn7gdBQJgANTcAhsM BQkSzAMAAAoJEIUqdHCjn7gdvQQP/2Q1P+Ir/D8wLgggEdGacDpDCWcXF2SsFcdP 27YKf/t5SPsIBRiEs4smDX44lWQDOjN3kAIi/qgSNLdos8H2K86BN1mGkL76Ryo4 eomJjALOeJKDG2fGR0aT1Dc4Nv/IkxumrcWF8UgNSxyzjTCginJWcbHGCGTxrwj3 k/gqNunjgrUBw69lC7H5TiJH1CYRVYCgtnkrGK+26/fyzCcF7W5d7Vfy9z5iZcTa ElF4Lbs41XtF36l2Q3+OJmW38PYx8bGSzEG0rGnZklLZUJhQJenbOUjMO/5dNBt0 n2c+TwTrwbY3GiB+wUVMtAWioGeT7FR05mz/qmfbdWShSJVvGqibcqpf6uhZ1h4g XjRmPiAuT2JDZioFB53xu/Kd66WYcbvWhzfhkoHHTotPK1jTdmixTEnpmGghG8X1 8nj/dkuMoTpXefdMLJlNxXnRCV7filgl2xGKn8ekweo6dYz/DtLUW/b7UGUHN6oM x/zeWzzZZx8glB6y9CYOvLbkkU8AB73I+lehCXoo+nGLFfGZWjyqLWWIbTTt+116 PuNzKlAJxko/QNLn3s7kvBWFjU2pAlY9e+ecV3yqPj2rQ/wMtOGwDks2ETc8JHvs lInKYqwP3leUG1E9rc4jI5kJt1WkMvLyrkAFhcCqImMxpi80V6tDuecLc02gP0o8 yFnrUyXauQINBGAA1a8BEADu6Ru60JLvE84Xfgd4NQe7RDuEif22H8OxjK2RTniP Y7DZxebD0HqClcatMrBK6UUqDJkI8Y1iKQ84yElOlmLkFrLeow16s8vta8mCUgSY QgYDDVjVYheqKKrizjD4xBWPiVSWhELtN/rbeF1POB3zrJPe7z3Fi9pgVCd7OP3a O8EELOzxGoS1Hjw7xvyQQXp4CUDRUV7pLi217OYFx54nrmqzES+IZrIC2l4a0tOK BEh3Vp2ij0RPZUJrw4Z5+XfH2wiONzGA29dKCEUpQQ+bw+yya2Borjq7go7/frXB caUlVNSsxkhl9NTbenovxZZ3oxr32PUKz3N/kAf8eN+KFIBoBXsLrWaIa5UV7jnr NhD3WlEdtJs+nJ7zZ6PAE2P/AkChYmOP7S58XbhatH295Mea8fwo4UsY2H7X07lg Cjfl68VMQwIRzsTn7Zj8DNFpFVm+YXiRL14iNepWFpyONlDmkdt1uSrFxeau2mw7 rdMyaSJaLhssTlv/ucQo8RT+xlU15mC3/B+3hD50vPcLaEnpmnUc6alKsFY3LYtZ mI8q+CPgo/30lbJ2c/vlSkZgbc95GPqaN+rCyTyVjmKGn+W5kwvSmCoF9jALcrdP EDveV+Jx5V9v/kNU8UyMAlXPs5A2HxY+XocQ7GlysnrUZ5Teojx2Ot565WSIT6tR 3wARAQABiQRyBBgBCAAmFiEE6X+dpa4uOc9IoUK3hSp0cKOfuB0FAmAA1a8CGwIF CRLMAwACQAkQhSp0cKOfuB3BdCAEGQEIAB0WIQQ6ZPpjpSRSehAOwKmyuTZzJDpl +wUCYADVrwAKCRCyuTZzJDpl+8rwD/0YcT4Zc2XNeZfNFLXpKzdhZNvbc+j9NLi2 h3IdDU+Jh1QHyIZUPKjN+xpmuP+8NIzab8JDnoVPrhOx4vyFRlXEeQU492Y+UJ2z YeHirFJNO6P6jFnMCCnn4bOY1aiRqkFB8zWxGEfdPfVz1HMAX9AbCFjDx6FCLw82 tlXbBH01sfsbC590RZh5K79GOQEcFYf10TEwDNOaft3d5/80RpOAa0kHV5SyMwPr s/+EWImfm6KkoMI7DiGCjR6n8UGQM6kmt4sMiDGa90ANcNN9ZTdClY1BS6bmAyyJ S0jqWv4i8mTBgNRHaSlLf/ECUJN4Fepsc6BB/BNyn1fIfl/g6kwxfqRuHURXUXLA MOGvB1/UaxoFiWeH0igJ1G8xPDVJv749UMKxw4epHtVRs7r8BGvCV7Jcih5kicp6 XGcGnsSp4ckefel63VfsfCdXg9ZanyfFS0A4Lx0MoRBIKuKz1/vfIGuRpwl5ynHM i8hwo8P1D/QlKXgnJGLemtKhNdpKt7IiknG2S9HxJU4mO8zN/DoUShgBFzLFlm4q RAY9tLUt7NiKGzuY4JAqKO1ghI7I+BtbXV4r39QFHzZD6RF7Cy5hG4RAm6qNyWz7 qydMGrMGZ+8332AqSsh2fZyI2v+iofbJEUrka1B661UVs5hXl6dC7h7z+pALhqgd JP2FDe8nw61zD/9DHHG3tMAcpOk8Thnz4ym39Z1dUsDtFeI+S5kUDee4n42PdKY9 WHxZovQmiPNKwyDM/JME1fngnRCTa/Ovclfm0/RoYBqDVJE4cyXSlZcAnlsHl3K1 mun6wEXONzgZrv1R6Vz+vz1Xbhd2g+NajW9reDOGjv4Clmbgqmo3xGXw2bZArFr5 ixYCGmMLc2dx1FqfaHewyuUWClK6YwOxLZjnLyMD4IoxrMlslQfDXJT9xPNuWcSW Bp7ShYysf8Rg0/wkdhtP2rI2aI2F3HlqSSpfgSXuqCTx+BxA14kCH90ZN9/2/UZH LoloPEhfa2RkHHhpTG8ePJpZMsbJXbs6E60tmdrE7nWd/ALNIXEWAxnXB+ihjytc nwismGaUY7YVGfVH4u3G8WW+dPF3dJdxsPMTWYK06CVDBN31+XMG6UiKNSf1ueYn pDUCXKEs2NefO66oOYrWKaSXzBUt/tFjoGBbBQ3rLCCrvAQSLgtzpu3nRYdTZzMt yb57U3ybVc2ctqwOz4V5QIBZNDAqyVMyAOUPDbkaagGnbBmOYmTxyUg+hlXFLDV5 yUwtJSekMpD8rqqAYn6qmMT+Q6xLmnsUuhuubivWOqBmoCFXldCMX1mVPjNOCNmk H5nQNUUoWCHM/BiQ3y7UUGF+63uYGPsmMBX/n0P0WxDkr4rL+GcwTrbixLkCDQRg ANXzARAA01L9vOMqENPb3LLe04G9LraTLxLI6Oq75yw0GpyZaeSllMPDx+EW4x3U 6RMiZHsmD9xWMBffJuDtiUDFcxvJSgPcVSeMgWeV+6kd9mk5A70KsrPrS17/uizG BjH5MGyIpwJbSBdZD2Mk2eoURPhyR9OYPw1ho1lASVH2J3vN4c1rcVs9Gt7KXNfp S6/9vW/H1c9xCHv2Nv5o5+4kqEYDl54d8kFNUuOywUSw2V2b9ay33uCSqdxm/CHp 1jM2fM/v4AY6+87Nvveo2G805PZ4WKyn9OqP1w+0m54mLT32jsMJ1eJmk+k/dBq+ uZzgngXXcvBoP2r+ukGZjfXeDUfoLg5TssiFGr0sBl72ICEv5vNrMnvFg+MiDW/J yIi2zeZ2k8Fpk97V3mkj7DPNKOP32ng+cDigViwTmUaSjrd5yRrYOP2owsG/jdvb 1TDLB2l3DT0lqBgyR/BMtQiy5upUZ5oLuKz5Nl6havMkQYHfwMxQHGR4mo5hrnH4 CNj9zM4iNcGXmxCfOwSSUyOJZyTXggvic15KjFrDlE23e7sEh9yhuIScHrKm+q+6 euDLp6JKYkAcuPBKAh4htMImHHnVY4l+vXIpONtpz/WWCkhEfS8GA/N7f9yMtLdS V3Ylsc7DrPpevv1SgAKqHuHOokOHlU4jOjAHoKXlfOvh3y2dyM0AEQEAAYkCPAQY AQgAJhYhBOl/naWuLjnPSKFCt4UqdHCjn7gdBQJgANXzAhsgBQkSzAMAAAoJEIUq dHCjn7gdXb4P/3ZBg4ngwgQWCKV/TGdWoNmJGQbyPzzO2vFGlz37VODyeenJOSHU sZzTuf2mKHaDtl77Mu8dpFALZVbmfp0a3teezF/Tpy2EtEQKIlDnzLQb8USOzQOK RA5Q8PG4m6hFcwCylIYL+M6YN9tGDEBZsh/wG5Blwuzp7Y3hd4UMEj6vTZOQ5vQv wrZ/jK8EUrqBJbB4pFZQxPWRNrZy0usPS24kU8EwegaV9qjDdjHIPfVwb9LPkNan 9DxGe/2rdQdeFtiEyRR781BzFtL1QhWm7CpJyOWe4PioYUeN+dBgELi9w0FX5JaO 7TpDUUYZj45+1Rr9lxhZQ/Y/kK46R1Ur4+01+YtgU9fu9ABPKPSeDmXtCDHapSzn dkyHZ5Ia1KSSxQegHDqXKxluUUxr+/XlFp128ZMJvOcRvnMzovL5Cahe8uVMKY0N Y++t8FEM/pPROP1VM4QORzStbT0UyriHsTDmIzOWd2hs7izs4nNDBUHaTm/iN8q2 RfjfEI/WPhGnuW/Fx2FxaNWeXJYocSxHK/Koy3imrkNvCrWp+RfXCSCdWpnN+vCi qZRI3XlFaYW8sNx1l7nUUBgLCQuIaA6ooyGRI/sMm7esn9eoCrwqW3n/VrEi/vot ltoSftjahxmK/stXWFa0lmnRez7jcK4DV/wjtVuNt7cQa51Vfz/RMfze =uijW -----END PGP PUBLIC KEY BLOCK----- pub rsa4096 2021-02-17 [SC] [expires: 2031-02-15] 94948D3F3D39CC3BEB012010ABA9CEB7DABDA437 uid [ultimate] Avi Deitcher sig 3 ABA9CEB7DABDA437 2021-02-17 Avi Deitcher sub rsa4096 2021-02-17 [E] [expires: 2031-02-15] sig ABA9CEB7DABDA437 2021-02-17 Avi Deitcher -----BEGIN PGP PUBLIC KEY BLOCK----- mQINBGAtazMBEADR+aQhjppC/WsUpBxY2esQg12CQ/3Upb9M2kIAQXTnLFzgxPNi CrmhJwOyUt/A65knbzUgazzh5/DHG0iI8JnAaax+Yis5wYHmuqCSKCGEzEeskv5V ziqFEZgV1z3Z9jOjhaBE+wW9LV5DNM8vJH7bsblwek7HOSdLIqQZM7wAuv/6nLxF J/ykGNY50bsj5eYt8/J6zvLvFgb8r8kJfGsgUr0VZ7Pg62W7ltc9UZXZt888sEDu z46BQElePVobc++izbqovvQYceRQYCik2IOFDyWBjEQd5LpbajHd+ZJ8C/81gTnX 3853Ky6jC+i+EpEGvQO+4ku8s+3F2VlBlYk3roGmOnUok6i+JVP4VlWm9Q9Tp0jz lrqtbGhIh9F2Aj2NWSnMdZlI96255efvy77EdSj4qOOqoqQmZ+xP7FEFjL1e4aj1 fINtHWw+ElhEnqEku74psPrDSr0xdLCHN5amF+wn11QlaHOx2NdWi4EDq1qtWLxT Qnz+Zf//gKkxWvD04jyAudryfHkay5ZTAAMNdET0/U8UT9YJpJKIlwNRz7UybSgK rjzrr+nYThODIeDD49iY0DZw6appJMYvfj+/0MXxUv7jyQbkmN9wenZie7m9ZGPl +Iz6WI0A8sGv0lWvN0ZQmW9vU3Dej1JOhPq3Rw1Kqq/ojq4SJpIbniPVSwARAQAB tB9BdmkgRGVpdGNoZXIgPGF2aUBkZWl0Y2hlci5uZXQ+iQJUBBMBCAA+FiEElJSN Pz05zDvrASAQq6nOt9q9pDcFAmAtazMCGwMFCRLMAwAFCwkIBwIGFQgJCgsCBBYC AwECHgECF4AACgkQq6nOt9q9pDecpg/+Oi7uDLPxDhXm409abemV6jipCsSjeibr qc/MxcjQLdadYOaagTE7jRFfGHBjB6aJ9cqueHSrwGFN2P+nTGwNgLiy90AOPF/W Rr/SE75cmXwK6Dm+ljjkhZGInXppgCnADPaNdaYTMDaYSJXQdpcI8Jz+Ghr5XxP+ wBzDFqYMy18Tr9iTlb8FXOWj/3CH9bG9IOeWjEIToqKo/pq8gFCDTpVTwXytGH3i phVds6Oe5wGmZLKK4y+QZU4KnkcqoCQDUWpExIqZXb2Zn7U3LQbK+hWUssBMWpK8 uumGUFbf9FR7ddOb9rI6gdb66IGJkHGd/2NKuTY/pUtQstyMsjXhR9GfAPziyLXk n1dHu60/0IGURDAaLctXStJdjU5eMQRJM3M1f1r2b1W9lA4o64IVYi1xZ4dRedfv WSQmK6bX4oxYOrsJDcE4CHErE3vTi59AMagxPkrmpjpUBAYj8IkZf3f4NhBXEkqk clyK/qfhCZ3T3kFueet7QLlXFu4uRy0s/BBC9vSdw4h+J3USGovC/93JHY4EsDTH g1p8LVudzhnXHVzo2cR5S2yKnEALXzRsrI1BEm6jqXg18JfIFnX/aoGxw7t46cZf Zks9V4gw1exXkY0MbSy12/1TWzsFMz/c62xalPEFpmQtxuqV58B5h4BFdpRsPmtw NW4aUBr1pBu5Ag0EYC1rMwEQAMfej6/X99iirhpYuOpAPj89/zDF6lTcxJyqakpG fx51Twjg455H8re4FmP39Nb6fL8OZHq9U+P8P91ixD+lgNxq9eb7SBLyXt0kxGUN Y1T6MXChidWsxLgDLODRvnoyBVUMjwSMud9az/gbNoqHfdy4JshsfxIig+9e8cFW /zHvjxy7wf8PQlZj9zkobDNSqZVGjXo8X876kBdWvYsuKj/Zv5NxU7ISei+mtp+x YL1GPTIo1gFuVomcNmpVnQY2PW3Lbt7a+wjWw5OY3HyXS08Z5/2XMNL0eutkLajH lhFbEQaiPR10Z1Wu/D+DJ1S7qdiJeXjgKH8YIhSw2HBVm2WvvODe+dwY1nMGDaTf HruuAZsTHs44bWis0PNrRAXw7pz0Qx9arCNiADEjFUwciQRGDXhH2uT1xZJvr91v N81lYWJQGw3ICEXHy6c+pps1WQUEadJ0IDQJksB6dqcLUjQ6vS0zQlfoIACEIVrN p/BkXJwyn7NRA4B4fEAXR8JWUMOOYbKEgPuxzah3uFFbKGenQFY9mIo8VgtATYIO H2p57h8dCvSnNy5UeDlnRVjm5ga1DbgrLh7Row4BnchfqiGBTWO5jOlXhFZGEXqw NKaK01aLPDZ9H/KwR65o4eiRRbP11BPx0FYHoTM8UFW9mqw7Qnfs31oB3wPPOPQi LuqpABEBAAGJAjwEGAEIACYWIQSUlI0/PTnMO+sBIBCrqc632r2kNwUCYC1rMwIb DAUJEswDAAAKCRCrqc632r2kN7c1EACFQQxZn1rwTLwYN8lMrhPDtkMUNAoBqrRm wVU8edNZ62XCEPcbr8ZONzmBelV29GPSxvroV1HpTd1AbLJ+suAyDbnJEIEcVdaS TravPqHvcuZCGVy1djgGjtlKm98LdqTAFrshajGG77EGo0oQkJS2F3CrJ0bK8Rv9 dWYWJiVuLnhWFrSFR1nZGvODrU6TAPyWD669ap3RnewXCZENYYfEO2SPisHv1rmm hSj2eXJe/fbuYkqySbqS1lnh16QzLtRd9ATXY4VPQH4/QF7Og9EglgG/jOiNJdat SwGvN1VwVPYu21l7jpsBjSdPPrFqjL4+DuD4a3yIrFX1euE8qk9PNdiG43rrmjem LDEjl3TPULnEngr7MiP3eOIlCSDNzPL7E+vbB7mXEqhhaR92dr97+eEpmMQcoXg5 qxiLaSZe2H9LA8sQ8jxHTMEBRpjQTMjK9UHcd8RZQCnG7emsPkIs7jNqoSz9CBeT RGX1h+7edEaRQSx5kWzRzeHi8RNtKYYhfno8lCRwDzL5pqrF1+RIMv/kxYwtaQ6S iEaWz4Gb0xat8kpARDtOfwsIid0hSBm4lxGxgF6P3Ddg9dSIJoAJmaHQBy2YqdND MFjEThTeRjNZ/fOwGE+e7+h2UVreYTUWSIy+N/LFo52IgbsSVHvE5EUD8YWcK7Sj EcROYUZZYQ== =hio0 -----END PGP PUBLIC KEY BLOCK----- oras-go-1.1.1/.gitignore0000644000175000017500000000046214212212744014400 0ustar nileshnilesh# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # VS Code .vscode debug # Jetbrains .idea # Custom .cover/ .test/ hello.txt bin/ dist/ *.tar.gz vendor/ _dist/ oras-go-1.1.1/LICENSE0000644000175000017500000002611714212212744013422 0ustar nileshnilesh Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2021 ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. oras-go-1.1.1/OWNERS.md0000644000175000017500000000025114212212744013743 0ustar nileshnilesh# Owners Owners: - Avi Deitcher (@deitch) - Josh Dolitsky (@jdolitsky) - Sajay Antony (@sajayantony) - Shiwei Zhang (@shizhMSFT) - Steve Lasker (@stevelasker)oras-go-1.1.1/.github/0000755000175000017500000000000014212212744013746 5ustar nileshnileshoras-go-1.1.1/.github/dependabot.yml0000644000175000017500000000015514212212744016577 0ustar nileshnileshversion: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" oras-go-1.1.1/.github/workflows/0000755000175000017500000000000014212212744016003 5ustar nileshnileshoras-go-1.1.1/.github/workflows/build-pr.yml0000644000175000017500000000125714212212744020251 0ustar nileshnileshname: build-pr on: pull_request: branches: - v1 jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: setup go environment uses: actions/setup-go@v1 with: go-version: '1.17.3' - name: run unit tests run: make test - name: run acceptance tests run: | export LOCAL_REGISTRY_HOSTNAME="$(hostname -I | awk '{print $1}')" make acceptance - name: upload coverage report uses: actions/upload-artifact@master with: name: oras-coverage-report-${{ github.sha }} path: .cover/ if: always() oras-go-1.1.1/.github/workflows/build.yml0000644000175000017500000000123414212212744017625 0ustar nileshnileshname: build on: push: branches: v1 jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: setup go environment uses: actions/setup-go@v1 with: go-version: '1.17.3' - name: run unit tests run: make test - name: run acceptance tests run: | export LOCAL_REGISTRY_HOSTNAME="$(hostname -I | awk '{print $1}')" make acceptance - name: upload coverage report uses: actions/upload-artifact@master with: name: oras-coverage-report-${{ github.sha }} path: .cover/ if: always() oras-go-1.1.1/CODE_OF_CONDUCT.md0000644000175000017500000000023114212212744015201 0ustar nileshnilesh# Code of Conduct OCI Registry As Storage (ORAS) follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). oras-go-1.1.1/examples/0000755000175000017500000000000014212212744014224 5ustar nileshnileshoras-go-1.1.1/examples/simple/0000755000175000017500000000000014212212744015515 5ustar nileshnileshoras-go-1.1.1/examples/simple/simple_push_pull.go0000644000175000017500000000425614212212744021437 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import ( "context" "fmt" "os" "oras.land/oras-go/pkg/content" "oras.land/oras-go/pkg/oras" ) func check(e error) { if e != nil { panic(e) } } func getLocalRegistryHostname() string { hostname := "localhost" if v := os.Getenv("LOCAL_REGISTRY_HOSTNAME"); v != "" { hostname = v } return hostname } func main() { ref := fmt.Sprintf("%s:5000/oras:test", getLocalRegistryHostname()) fileName := "hello.txt" fileContent := []byte("Hello World!\n") customMediaType := "my.custom.media.type" ctx := context.Background() // Push file(s) w custom mediatype to registry memoryStore := content.NewMemory() desc, err := memoryStore.Add(fileName, customMediaType, fileContent) check(err) manifest, manifestDesc, config, configDesc, err := content.GenerateManifestAndConfig(nil, nil, desc) check(err) memoryStore.Set(configDesc, config) err = memoryStore.StoreManifest(ref, manifestDesc, manifest) check(err) registry, err := content.NewRegistry(content.RegistryOptions{PlainHTTP: true}) fmt.Printf("Pushing %s to %s...\n", fileName, ref) desc, err = oras.Copy(ctx, memoryStore, ref, registry, "") check(err) fmt.Printf("Pushed to %s with digest %s\n", ref, desc.Digest) // Pull file(s) from registry and save to disk fmt.Printf("Pulling from %s and saving to %s...\n", ref, fileName) fileStore := content.NewFile("") defer fileStore.Close() allowedMediaTypes := []string{customMediaType} desc, err = oras.Copy(ctx, registry, ref, fileStore, "", oras.WithAllowedMediaTypes(allowedMediaTypes)) check(err) fmt.Printf("Pulled from %s with digest %s\n", ref, desc.Digest) fmt.Printf("Try running 'cat %s'\n", fileName) } oras-go-1.1.1/examples/advanced/0000755000175000017500000000000014212212744015771 5ustar nileshnileshoras-go-1.1.1/examples/advanced/advanced.go0000644000175000017500000001770314212212744020075 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import ( "context" "fmt" "os" "path/filepath" "strings" ocispec "github.com/opencontainers/image-spec/specs-go/v1" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "oras.land/oras-go/pkg/artifact" "oras.land/oras-go/pkg/content" "oras.land/oras-go/pkg/oras" "oras.land/oras-go/pkg/target" ) func main() { var verbose int cmd := &cobra.Command{ Use: fmt.Sprintf("%s [command]", os.Args[0]), SilenceUsage: true, PersistentPreRun: func(cmd *cobra.Command, args []string) { log.SetLevel(log.InfoLevel) if verbose > 1 { log.SetLevel(log.DebugLevel) } }, } cmd.AddCommand(copyCmd()) cmd.PersistentFlags().IntVarP(&verbose, "verbose", "v", 1, "set log level") if err := cmd.Execute(); err != nil { os.Exit(1) } } func copyCmd() *cobra.Command { var ( fromStr, toStr string manifestConfig string manifestAnnotations map[string]string configAnnotations map[string]string showRootManifest, showLayers bool opts content.RegistryOptions ) cmd := &cobra.Command{ Use: "copy ", Short: "Copy artifacts from one location to another", Long: `Copy artifacts from one location to another Example - Copy artifacts from local files to local files: oras copy foo/bar:v1 --from files --to files:path/to/save file1 file2 ... filen Example - Copy artifacts from registry to local files: oras copy foo/bar:v1 --from registry --to files:path/to/save Example - Copy artifacts from registry to oci: oras copy foo/bar:v1 --from registry --to oci:path/to/oci Example - Copy artifacts from local files to registry: oras copy foo/bar:v1 --from files --to registry file1 file2 ... filen When the source (--from) is "files", the config by default will be "{}" and of media type application/vnd.unknown.config.v1+json. You can override it by setting the path, for example: oras copy foo/bar:v1 --from files --manifest-config path/to/config:application/vnd.oci.image.config.v1+json --to files:path/to/save file1 file2 ... filen `, Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { var ( ref = args[0] err error from, to target.Target configDesc ocispec.Descriptor ) // get the fromStr; it might also have a ':' to add options fromParts := strings.SplitN(fromStr, ":", 2) toParts := strings.SplitN(toStr, ":", 2) switch fromParts[0] { case "files": fromFile := content.NewFile("") descs, err := loadFiles(fromFile, args[1:]...) if err != nil { return fmt.Errorf("unable to load files: %w", err) } // parse the manifest config if manifestConfig != "" { manifestConfigPath, manifestConfigMediaType := parseFileRef(manifestConfig, artifact.UnknownConfigMediaType) configDesc, err = fromFile.Add("", manifestConfigMediaType, manifestConfigPath) if err != nil { return fmt.Errorf("unable to load manifest config: %w", err) } } else { var config []byte config, configDesc, err = content.GenerateConfig(configAnnotations) if err != nil { return fmt.Errorf("unable to create new manifest config: %w", err) } if err := fromFile.Load(configDesc, config); err != nil { return fmt.Errorf("unable to load new manifest config: %w", err) } } manifest, manifestDesc, err := content.GenerateManifest(&configDesc, manifestAnnotations, descs...) if err != nil { return fmt.Errorf("unable to create manifest: %w", err) } if err := fromFile.StoreManifest(ref, manifestDesc, manifest); err != nil { return fmt.Errorf("unable to generate root manifest: %w", err) } rootDesc, rootManifest, err := fromFile.Ref(ref) if err != nil { return err } log.Debugf("root manifest: %s %v %s", ref, rootDesc, rootManifest) from = fromFile case "registry": from, err = content.NewRegistry(opts) if err != nil { return fmt.Errorf("could not create registry target: %w", err) } case "oci": from, err = content.NewOCI(fromParts[1]) if err != nil { return fmt.Errorf("could not read OCI layout at %s: %w", fromParts[1], err) } default: return fmt.Errorf("unknown from argyment: %s", from) } switch toParts[0] { case "files": to = content.NewFile(toParts[1]) case "registry": to, err = content.NewRegistry(opts) if err != nil { return fmt.Errorf("could not create registry target: %w", err) } case "oci": to, err = content.NewOCI(toParts[1]) if err != nil { return fmt.Errorf("could not read OCI layout at %s: %v", toParts[1], err) } default: return fmt.Errorf("unknown from argyment: %s", from) } if manifestConfig != "" && fromParts[0] != "files" { return fmt.Errorf("only specify --manifest-config when using --from files") } var copyOpts []oras.CopyOpt if showRootManifest { copyOpts = append(copyOpts, oras.WithRootManifest(func(b []byte) { fmt.Printf("root: %s\n", b) })) } if showLayers { copyOpts = append(copyOpts, oras.WithLayerDescriptors(func(layers []ocispec.Descriptor) { fmt.Printf("%#v\n", layers) })) } return runCopy(ref, from, to, copyOpts...) }, } cmd.Flags().StringVar(&fromStr, "from", "", "source type and possible options") cmd.MarkFlagRequired("from") cmd.Flags().StringVar(&toStr, "to", "", "destination type and possible options") cmd.MarkFlagRequired("to") cmd.Flags().StringArrayVarP(&opts.Configs, "config", "c", nil, "auth config path") cmd.Flags().StringVarP(&opts.Username, "username", "u", "", "registry username") cmd.Flags().StringVarP(&opts.Password, "password", "p", "", "registry password") cmd.Flags().BoolVarP(&opts.Insecure, "insecure", "", false, "allow connections to SSL registry without certs") cmd.Flags().BoolVarP(&opts.PlainHTTP, "plain-http", "", false, "use plain http and not https") cmd.Flags().StringVar(&manifestConfig, "manifest-config", "", "path to manifest config and its media type, e.g. path/to/file.json:application/vnd.oci.image.config.v1+json") cmd.Flags().StringToStringVar(&manifestAnnotations, "manifest-annotations", nil, "key-value pairs of annotations to set on the manifest, e.g. 'annotation=foo,other=bar'") cmd.Flags().StringToStringVar(&configAnnotations, "config-annotations", nil, "key-value pairs of annotations to set on the config, only if config is not passed explicitly, e.g. 'annotation=foo,other=bar'") cmd.Flags().BoolVarP(&showRootManifest, "show-manifest", "", false, "when copying, show the root manifest") cmd.Flags().BoolVarP(&showLayers, "show-layers", "", false, "when copying, show the descriptors for the layers") return cmd } func runCopy(ref string, from, to target.Target, copyOpts ...oras.CopyOpt) error { desc, err := oras.Copy(context.Background(), from, ref, to, "", copyOpts...) if err != nil { fmt.Fprintf(os.Stderr, "error: %v", err) os.Exit(1) } fmt.Printf("%#v\n", desc) return nil } func loadFiles(store *content.File, files ...string) ([]ocispec.Descriptor, error) { var descs []ocispec.Descriptor for _, fileRef := range files { filename, mediaType := parseFileRef(fileRef, "") name := filepath.Clean(filename) if !filepath.IsAbs(name) { // convert to slash-separated path unless it is absolute path name = filepath.ToSlash(name) } desc, err := store.Add(name, mediaType, filename) if err != nil { return nil, err } descs = append(descs, desc) } return descs, nil } oras-go-1.1.1/examples/advanced/file_windows.go0000644000175000017500000000210114212212744021003 0ustar nileshnilesh/* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import ( "strings" "unicode" ) // parseFileRef parse file reference on windows. // Windows systems does not allow ':' in the file path except for drive letter. func parseFileRef(ref string, mediaType string) (string, string) { i := strings.Index(ref, ":") if i < 0 { return ref, mediaType } // In case it is C:\ if i == 1 && len(ref) > 2 && ref[2] == '\\' && unicode.IsLetter(rune(ref[0])) { i = strings.Index(ref[3:], ":") if i < 0 { return ref, mediaType } i += 3 } return ref[:i], ref[i+1:] } oras-go-1.1.1/examples/advanced/file_unix.go0000644000175000017500000000143414212212744020304 0ustar nileshnilesh//go:build !windows // +build !windows /* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import "strings" func parseFileRef(ref string, mediaType string) (string, string) { i := strings.LastIndex(ref, ":") if i < 0 { return ref, mediaType } return ref[:i], ref[i+1:] }