mirror of https://github.com/docker/buildx.git
				
				
				
			Merge pull request #1948 from thaJeztah/buildkit_0.12
This commit is contained in:
		
						commit
						e98e8f6ac9
					
				
							
								
								
									
										9
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										9
									
								
								go.mod
								
								
								
								
							|  | @ -23,7 +23,7 @@ require ( | |||
| 	github.com/google/uuid v1.3.0 | ||||
| 	github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840 | ||||
| 	github.com/hashicorp/hcl/v2 v2.8.2 | ||||
| 	github.com/moby/buildkit v0.11.0-rc3.0.20230620112432-2d91ddcceedc | ||||
| 	github.com/moby/buildkit v0.12.1-0.20230717122532-faa0cc7da353 // v0.12.1-dev | ||||
| 	github.com/moby/sys/mountinfo v0.6.2 | ||||
| 	github.com/moby/sys/signal v0.7.0 | ||||
| 	github.com/morikuni/aec v1.0.0 | ||||
|  | @ -76,6 +76,7 @@ require ( | |||
| 	github.com/cenkalti/backoff/v4 v4.2.0 // indirect | ||||
| 	github.com/cespare/xxhash/v2 v2.2.0 // indirect | ||||
| 	github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e // indirect | ||||
| 	github.com/containerd/ttrpc v1.2.2 // indirect | ||||
| 	github.com/cyphar/filepath-securejoin v0.2.3 // indirect | ||||
| 	github.com/davecgh/go-spew v1.1.1 // indirect | ||||
| 	github.com/distribution/distribution/v3 v3.0.0-20230214150026-36d8c594d7aa // indirect | ||||
|  | @ -138,10 +139,10 @@ require ( | |||
| 	github.com/shibumi/go-pathspec v1.3.0 // indirect | ||||
| 	github.com/spf13/viper v1.14.0 // indirect | ||||
| 	github.com/theupdateframework/notary v0.6.1 // indirect | ||||
| 	github.com/tonistiigi/fsutil v0.0.0-20230407161946-9e7a6df48576 // indirect | ||||
| 	github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb // indirect | ||||
| 	github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect | ||||
| 	github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect | ||||
| 	github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect | ||||
| 	github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531 // indirect | ||||
| 	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect | ||||
| 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect | ||||
| 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect | ||||
| 	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.40.0 // indirect | ||||
|  |  | |||
							
								
								
									
										22
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										22
									
								
								go.sum
								
								
								
								
							|  | @ -136,6 +136,7 @@ github.com/containerd/nydus-snapshotter v0.8.2 h1:7SOrMU2YmLzfbsr5J7liMZJlNi5WT6 | |||
| github.com/containerd/stargz-snapshotter v0.14.3 h1:OTUVZoPSPs8mGgmQUE1dqw3WX/3nrsmsurW7UPLWl1U= | ||||
| github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= | ||||
| github.com/containerd/ttrpc v1.2.2 h1:9vqZr0pxwOF5koz6N0N3kJ0zDHokrcPxIR/ZR2YFtOs= | ||||
| github.com/containerd/ttrpc v1.2.2/go.mod h1:sIT6l32Ph/H9cvnJsfXM5drIVzTr5A2flTf1G5tYZak= | ||||
| github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= | ||||
| github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= | ||||
| github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | ||||
|  | @ -269,6 +270,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ | |||
| 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.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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||
|  | @ -370,8 +372,8 @@ github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzC | |||
| github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= | ||||
| github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= | ||||
| github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= | ||||
| github.com/moby/buildkit v0.11.0-rc3.0.20230620112432-2d91ddcceedc h1:79EnLqFEkPn6sTBXDHE546AHmYeb/QzXgGyIpli8w34= | ||||
| github.com/moby/buildkit v0.11.0-rc3.0.20230620112432-2d91ddcceedc/go.mod h1:6Y1HYDrxg3sY5gBY2FVaEvQpswBj3g/ck7aKYCjOkk0= | ||||
| github.com/moby/buildkit v0.12.1-0.20230717122532-faa0cc7da353 h1:/ZIwqvOF3QKObJbjX96xVvAKtnWdw/AuEqysbbujaZA= | ||||
| github.com/moby/buildkit v0.12.1-0.20230717122532-faa0cc7da353/go.mod h1:+n9GmkxwBCjVz4u7wmiyh+oqvjIjQM+1zk3iJrWfdos= | ||||
| 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/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo= | ||||
|  | @ -436,6 +438,7 @@ github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr | |||
| github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= | ||||
| 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.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= | ||||
| github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= | ||||
| github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= | ||||
| github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= | ||||
|  | @ -453,6 +456,7 @@ github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh | |||
| github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= | ||||
| github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= | ||||
| github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= | ||||
| github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= | ||||
| github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= | ||||
| github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= | ||||
| github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= | ||||
|  | @ -481,18 +485,19 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl | |||
| github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= | ||||
| github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz+cjWoom6l0= | ||||
| github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY= | ||||
| github.com/tonistiigi/fsutil v0.0.0-20230407161946-9e7a6df48576 h1:fZXPQDVh5fm2x7pA0CH1TtH80tiZ0L7i834kZqZN8Pw= | ||||
| github.com/tonistiigi/fsutil v0.0.0-20230407161946-9e7a6df48576/go.mod h1:q1CxMSzcAbjUkVGHoZeQUcCaALnaE4XdWk+zJcgMYFw= | ||||
| github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb h1:uUe8rNyVXM8moActoBol6Xf6xX2GMr7SosR2EywMvGg= | ||||
| github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb/go.mod h1:SxX/oNQ/ag6Vaoli547ipFK9J7BZn5JqJG0JE8lf8bA= | ||||
| github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0= | ||||
| github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk= | ||||
| github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f h1:DLpt6B5oaaS8jyXHa9VA4rrZloBVPVXeCtrOsrFauxc= | ||||
| github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc= | ||||
| github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531 h1:Y/M5lygoNPKwVNLMPXgVfsRT40CSFKXCxuU8LoHySjs= | ||||
| github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc= | ||||
| github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= | ||||
| github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= | ||||
| github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= | ||||
| github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= | ||||
| github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= | ||||
| github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= | ||||
| github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= | ||||
| github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= | ||||
| github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= | ||||
| github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= | ||||
| github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= | ||||
|  | @ -632,6 +637,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ | |||
| 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.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= | ||||
| golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
|  | @ -649,6 +655,7 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w | |||
| 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-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
|  | @ -673,6 +680,7 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc | |||
| golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= | ||||
| golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
|  |  | |||
|  | @ -0,0 +1,73 @@ | |||
| /* | ||||
|    Copyright The containerd 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 identifiers provides common validation for identifiers and keys
 | ||||
| // across containerd.
 | ||||
| //
 | ||||
| // Identifiers in containerd must be a alphanumeric, allowing limited
 | ||||
| // underscores, dashes and dots.
 | ||||
| //
 | ||||
| // While the character set may be expanded in the future, identifiers
 | ||||
| // are guaranteed to be safely used as filesystem path components.
 | ||||
| package identifiers | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"regexp" | ||||
| 
 | ||||
| 	"github.com/containerd/containerd/errdefs" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	maxLength  = 76 | ||||
| 	alphanum   = `[A-Za-z0-9]+` | ||||
| 	separators = `[._-]` | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	// identifierRe defines the pattern for valid identifiers.
 | ||||
| 	identifierRe = regexp.MustCompile(reAnchor(alphanum + reGroup(separators+reGroup(alphanum)) + "*")) | ||||
| ) | ||||
| 
 | ||||
| // Validate returns nil if the string s is a valid identifier.
 | ||||
| //
 | ||||
| // identifiers are similar to the domain name rules according to RFC 1035, section 2.3.1. However
 | ||||
| // rules in this package are relaxed to allow numerals to follow period (".") and mixed case is
 | ||||
| // allowed.
 | ||||
| //
 | ||||
| // In general identifiers that pass this validation should be safe for use as filesystem path components.
 | ||||
| func Validate(s string) error { | ||||
| 	if len(s) == 0 { | ||||
| 		return fmt.Errorf("identifier must not be empty: %w", errdefs.ErrInvalidArgument) | ||||
| 	} | ||||
| 
 | ||||
| 	if len(s) > maxLength { | ||||
| 		return fmt.Errorf("identifier %q greater than maximum length (%d characters): %w", s, maxLength, errdefs.ErrInvalidArgument) | ||||
| 	} | ||||
| 
 | ||||
| 	if !identifierRe.MatchString(s) { | ||||
| 		return fmt.Errorf("identifier %q must match %v: %w", s, identifierRe, errdefs.ErrInvalidArgument) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func reGroup(s string) string { | ||||
| 	return `(?:` + s + `)` | ||||
| } | ||||
| 
 | ||||
| func reAnchor(s string) string { | ||||
| 	return `^` + s + `$` | ||||
| } | ||||
|  | @ -0,0 +1,40 @@ | |||
| /* | ||||
|    Copyright The containerd 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 leases | ||||
| 
 | ||||
| import "context" | ||||
| 
 | ||||
| type leaseKey struct{} | ||||
| 
 | ||||
| // WithLease sets a given lease on the context
 | ||||
| func WithLease(ctx context.Context, lid string) context.Context { | ||||
| 	ctx = context.WithValue(ctx, leaseKey{}, lid) | ||||
| 
 | ||||
| 	// also store on the grpc headers so it gets picked up by any clients that
 | ||||
| 	// are using this.
 | ||||
| 	return withGRPCLeaseHeader(ctx, lid) | ||||
| } | ||||
| 
 | ||||
| // FromContext returns the lease from the context.
 | ||||
| func FromContext(ctx context.Context) (string, bool) { | ||||
| 	lid, ok := ctx.Value(leaseKey{}).(string) | ||||
| 	if !ok { | ||||
| 		return fromGRPCHeader(ctx) | ||||
| 	} | ||||
| 
 | ||||
| 	return lid, ok | ||||
| } | ||||
|  | @ -0,0 +1,58 @@ | |||
| /* | ||||
|    Copyright The containerd 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 leases | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 
 | ||||
| 	"google.golang.org/grpc/metadata" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// GRPCHeader defines the header name for specifying a containerd lease.
 | ||||
| 	GRPCHeader = "containerd-lease" | ||||
| ) | ||||
| 
 | ||||
| func withGRPCLeaseHeader(ctx context.Context, lid string) context.Context { | ||||
| 	// also store on the grpc headers so it gets picked up by any clients
 | ||||
| 	// that are using this.
 | ||||
| 	txheader := metadata.Pairs(GRPCHeader, lid) | ||||
| 	md, ok := metadata.FromOutgoingContext(ctx) // merge with outgoing context.
 | ||||
| 	if !ok { | ||||
| 		md = txheader | ||||
| 	} else { | ||||
| 		// order ensures the latest is first in this list.
 | ||||
| 		md = metadata.Join(txheader, md) | ||||
| 	} | ||||
| 
 | ||||
| 	return metadata.NewOutgoingContext(ctx, md) | ||||
| } | ||||
| 
 | ||||
| func fromGRPCHeader(ctx context.Context) (string, bool) { | ||||
| 	// try to extract for use in grpc servers.
 | ||||
| 	md, ok := metadata.FromIncomingContext(ctx) | ||||
| 	if !ok { | ||||
| 		return "", false | ||||
| 	} | ||||
| 
 | ||||
| 	values := md[GRPCHeader] | ||||
| 	if len(values) == 0 { | ||||
| 		return "", false | ||||
| 	} | ||||
| 
 | ||||
| 	return values[0], true | ||||
| } | ||||
|  | @ -0,0 +1,43 @@ | |||
| /* | ||||
|    Copyright The containerd 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 leases | ||||
| 
 | ||||
| import ( | ||||
| 	"crypto/rand" | ||||
| 	"encoding/base64" | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| // WithRandomID sets the lease ID to a random unique value
 | ||||
| func WithRandomID() Opt { | ||||
| 	return func(l *Lease) error { | ||||
| 		t := time.Now() | ||||
| 		var b [3]byte | ||||
| 		rand.Read(b[:]) | ||||
| 		l.ID = fmt.Sprintf("%d-%s", t.Nanosecond(), base64.URLEncoding.EncodeToString(b[:])) | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // WithID sets the ID for the lease
 | ||||
| func WithID(id string) Opt { | ||||
| 	return func(l *Lease) error { | ||||
| 		l.ID = id | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,91 @@ | |||
| /* | ||||
|    Copyright The containerd 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 leases | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| // Opt is used to set options on a lease
 | ||||
| type Opt func(*Lease) error | ||||
| 
 | ||||
| // DeleteOpt allows configuring a delete operation
 | ||||
| type DeleteOpt func(context.Context, *DeleteOptions) error | ||||
| 
 | ||||
| // Manager is used to create, list, and remove leases
 | ||||
| type Manager interface { | ||||
| 	Create(context.Context, ...Opt) (Lease, error) | ||||
| 	Delete(context.Context, Lease, ...DeleteOpt) error | ||||
| 	List(context.Context, ...string) ([]Lease, error) | ||||
| 	AddResource(context.Context, Lease, Resource) error | ||||
| 	DeleteResource(context.Context, Lease, Resource) error | ||||
| 	ListResources(context.Context, Lease) ([]Resource, error) | ||||
| } | ||||
| 
 | ||||
| // Lease retains resources to prevent cleanup before
 | ||||
| // the resources can be fully referenced.
 | ||||
| type Lease struct { | ||||
| 	ID        string | ||||
| 	CreatedAt time.Time | ||||
| 	Labels    map[string]string | ||||
| } | ||||
| 
 | ||||
| // Resource represents low level resource of image, like content, ingest and
 | ||||
| // snapshotter.
 | ||||
| type Resource struct { | ||||
| 	ID   string | ||||
| 	Type string | ||||
| } | ||||
| 
 | ||||
| // DeleteOptions provide options on image delete
 | ||||
| type DeleteOptions struct { | ||||
| 	Synchronous bool | ||||
| } | ||||
| 
 | ||||
| // SynchronousDelete is used to indicate that a lease deletion and removal of
 | ||||
| // any unreferenced resources should occur synchronously before returning the
 | ||||
| // result.
 | ||||
| func SynchronousDelete(ctx context.Context, o *DeleteOptions) error { | ||||
| 	o.Synchronous = true | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // WithLabels merges labels on a lease
 | ||||
| func WithLabels(labels map[string]string) Opt { | ||||
| 	return func(l *Lease) error { | ||||
| 		if l.Labels == nil { | ||||
| 			l.Labels = map[string]string{} | ||||
| 		} | ||||
| 		for k, v := range labels { | ||||
| 			l.Labels[k] = v | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // WithExpiration sets an expiration on the lease
 | ||||
| func WithExpiration(d time.Duration) Opt { | ||||
| 	return func(l *Lease) error { | ||||
| 		if l.Labels == nil { | ||||
| 			l.Labels = map[string]string{} | ||||
| 		} | ||||
| 		l.Labels["containerd.io/gc.expire"] = time.Now().Add(d).Format(time.RFC3339) | ||||
| 
 | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,78 @@ | |||
| /* | ||||
|    Copyright The containerd 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 namespaces | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 
 | ||||
| 	"github.com/containerd/containerd/errdefs" | ||||
| 	"github.com/containerd/containerd/identifiers" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// NamespaceEnvVar is the environment variable key name
 | ||||
| 	NamespaceEnvVar = "CONTAINERD_NAMESPACE" | ||||
| 	// Default is the name of the default namespace
 | ||||
| 	Default = "default" | ||||
| ) | ||||
| 
 | ||||
| type namespaceKey struct{} | ||||
| 
 | ||||
| // WithNamespace sets a given namespace on the context
 | ||||
| func WithNamespace(ctx context.Context, namespace string) context.Context { | ||||
| 	ctx = context.WithValue(ctx, namespaceKey{}, namespace) // set our key for namespace
 | ||||
| 	// also store on the grpc and ttrpc headers so it gets picked up by any clients that
 | ||||
| 	// are using this.
 | ||||
| 	return withTTRPCNamespaceHeader(withGRPCNamespaceHeader(ctx, namespace), namespace) | ||||
| } | ||||
| 
 | ||||
| // NamespaceFromEnv uses the namespace defined in CONTAINERD_NAMESPACE or
 | ||||
| // default
 | ||||
| func NamespaceFromEnv(ctx context.Context) context.Context { | ||||
| 	namespace := os.Getenv(NamespaceEnvVar) | ||||
| 	if namespace == "" { | ||||
| 		namespace = Default | ||||
| 	} | ||||
| 	return WithNamespace(ctx, namespace) | ||||
| } | ||||
| 
 | ||||
| // Namespace returns the namespace from the context.
 | ||||
| //
 | ||||
| // The namespace is not guaranteed to be valid.
 | ||||
| func Namespace(ctx context.Context) (string, bool) { | ||||
| 	namespace, ok := ctx.Value(namespaceKey{}).(string) | ||||
| 	if !ok { | ||||
| 		if namespace, ok = fromGRPCHeader(ctx); !ok { | ||||
| 			return fromTTRPCHeader(ctx) | ||||
| 		} | ||||
| 	} | ||||
| 	return namespace, ok | ||||
| } | ||||
| 
 | ||||
| // NamespaceRequired returns the valid namespace from the context or an error.
 | ||||
| func NamespaceRequired(ctx context.Context) (string, error) { | ||||
| 	namespace, ok := Namespace(ctx) | ||||
| 	if !ok || namespace == "" { | ||||
| 		return "", fmt.Errorf("namespace is required: %w", errdefs.ErrFailedPrecondition) | ||||
| 	} | ||||
| 	if err := identifiers.Validate(namespace); err != nil { | ||||
| 		return "", fmt.Errorf("namespace validation: %w", err) | ||||
| 	} | ||||
| 	return namespace, nil | ||||
| } | ||||
|  | @ -0,0 +1,61 @@ | |||
| /* | ||||
|    Copyright The containerd 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 namespaces | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 
 | ||||
| 	"google.golang.org/grpc/metadata" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// GRPCHeader defines the header name for specifying a containerd namespace.
 | ||||
| 	GRPCHeader = "containerd-namespace" | ||||
| ) | ||||
| 
 | ||||
| // NOTE(stevvooe): We can stub this file out if we don't want a grpc dependency here.
 | ||||
| 
 | ||||
| func withGRPCNamespaceHeader(ctx context.Context, namespace string) context.Context { | ||||
| 	// also store on the grpc headers so it gets picked up by any clients that
 | ||||
| 	// are using this.
 | ||||
| 	nsheader := metadata.Pairs(GRPCHeader, namespace) | ||||
| 	md, ok := metadata.FromOutgoingContext(ctx) // merge with outgoing context.
 | ||||
| 	if !ok { | ||||
| 		md = nsheader | ||||
| 	} else { | ||||
| 		// order ensures the latest is first in this list.
 | ||||
| 		md = metadata.Join(nsheader, md) | ||||
| 	} | ||||
| 
 | ||||
| 	return metadata.NewOutgoingContext(ctx, md) | ||||
| } | ||||
| 
 | ||||
| func fromGRPCHeader(ctx context.Context) (string, bool) { | ||||
| 	// try to extract for use in grpc servers.
 | ||||
| 	md, ok := metadata.FromIncomingContext(ctx) | ||||
| 	if !ok { | ||||
| 		// TODO(stevvooe): Check outgoing context?
 | ||||
| 		return "", false | ||||
| 	} | ||||
| 
 | ||||
| 	values := md[GRPCHeader] | ||||
| 	if len(values) == 0 { | ||||
| 		return "", false | ||||
| 	} | ||||
| 
 | ||||
| 	return values[0], true | ||||
| } | ||||
|  | @ -0,0 +1,44 @@ | |||
| /* | ||||
|    Copyright The containerd 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 namespaces | ||||
| 
 | ||||
| import "context" | ||||
| 
 | ||||
| // Store provides introspection about namespaces.
 | ||||
| //
 | ||||
| // Note that these are slightly different than other objects, which are record
 | ||||
| // oriented. A namespace is really just a name and a set of labels. Objects
 | ||||
| // that belong to a namespace are returned when the namespace is assigned to a
 | ||||
| // given context.
 | ||||
| type Store interface { | ||||
| 	Create(ctx context.Context, namespace string, labels map[string]string) error | ||||
| 	Labels(ctx context.Context, namespace string) (map[string]string, error) | ||||
| 	SetLabel(ctx context.Context, namespace, key, value string) error | ||||
| 	List(ctx context.Context) ([]string, error) | ||||
| 
 | ||||
| 	// Delete removes the namespace. The namespace must be empty to be deleted.
 | ||||
| 	Delete(ctx context.Context, namespace string, opts ...DeleteOpts) error | ||||
| } | ||||
| 
 | ||||
| // DeleteInfo specifies information for the deletion of a namespace
 | ||||
| type DeleteInfo struct { | ||||
| 	// Name of the namespace
 | ||||
| 	Name string | ||||
| } | ||||
| 
 | ||||
| // DeleteOpts allows the caller to set options for namespace deletion
 | ||||
| type DeleteOpts func(context.Context, *DeleteInfo) error | ||||
|  | @ -0,0 +1,51 @@ | |||
| /* | ||||
|    Copyright The containerd 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 namespaces | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 
 | ||||
| 	"github.com/containerd/ttrpc" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// TTRPCHeader defines the header name for specifying a containerd namespace
 | ||||
| 	TTRPCHeader = "containerd-namespace-ttrpc" | ||||
| ) | ||||
| 
 | ||||
| func copyMetadata(src ttrpc.MD) ttrpc.MD { | ||||
| 	md := ttrpc.MD{} | ||||
| 	for k, v := range src { | ||||
| 		md[k] = append(md[k], v...) | ||||
| 	} | ||||
| 	return md | ||||
| } | ||||
| 
 | ||||
| func withTTRPCNamespaceHeader(ctx context.Context, namespace string) context.Context { | ||||
| 	md, ok := ttrpc.GetMetadata(ctx) | ||||
| 	if !ok { | ||||
| 		md = ttrpc.MD{} | ||||
| 	} else { | ||||
| 		md = copyMetadata(md) | ||||
| 	} | ||||
| 	md.Set(TTRPCHeader, namespace) | ||||
| 	return ttrpc.WithMetadata(ctx, md) | ||||
| } | ||||
| 
 | ||||
| func fromTTRPCHeader(ctx context.Context) (string, bool) { | ||||
| 	return ttrpc.GetMetadataValue(ctx, TTRPCHeader) | ||||
| } | ||||
|  | @ -0,0 +1 @@ | |||
| *.go text eol=lf | ||||
|  | @ -0,0 +1,13 @@ | |||
| # Binaries for programs and plugins | ||||
| /bin/ | ||||
| *.exe | ||||
| *.dll | ||||
| *.so | ||||
| *.dylib | ||||
| 
 | ||||
| # Test binary, build with `go test -c` | ||||
| *.test | ||||
| 
 | ||||
| # Output of the go coverage tool, specifically when used with LiteIDE | ||||
| *.out | ||||
| coverage.txt | ||||
|  | @ -0,0 +1,52 @@ | |||
| linters: | ||||
|   enable: | ||||
|     - staticcheck | ||||
|     - unconvert | ||||
|     - gofmt | ||||
|     - goimports | ||||
|     - revive | ||||
|     - ineffassign | ||||
|     - vet | ||||
|     - unused | ||||
|     - misspell | ||||
|   disable: | ||||
|     - errcheck | ||||
| 
 | ||||
| linters-settings: | ||||
|   revive: | ||||
|     ignore-generated-headers: true | ||||
|     rules: | ||||
|       - name: blank-imports | ||||
|       - name: context-as-argument | ||||
|       - name: context-keys-type | ||||
|       - name: dot-imports | ||||
|       - name: error-return | ||||
|       - name: error-strings | ||||
|       - name: error-naming | ||||
|       - name: exported | ||||
|       - name: if-return | ||||
|       - name: increment-decrement | ||||
|       - name: var-naming | ||||
|         arguments: [["UID", "GID"], []] | ||||
|       - name: var-declaration | ||||
|       - name: package-comments | ||||
|       - name: range | ||||
|       - name: receiver-naming | ||||
|       - name: time-naming | ||||
|       - name: unexported-return | ||||
|       - name: indent-error-flow | ||||
|       - name: errorf | ||||
|       - name: empty-block | ||||
|       - name: superfluous-else | ||||
|       - name: unused-parameter | ||||
|       - name: unreachable-code | ||||
|       - name: redefines-builtin-id | ||||
| 
 | ||||
| issues: | ||||
|   include: | ||||
|     - EXC0002 | ||||
| 
 | ||||
| run: | ||||
|   timeout: 8m | ||||
|   skip-dirs: | ||||
|     - example | ||||
|  | @ -0,0 +1,201 @@ | |||
|                                  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 [yyyy] [name of copyright owner] | ||||
| 
 | ||||
|    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. | ||||
|  | @ -0,0 +1,180 @@ | |||
| #   Copyright The containerd 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.
 | ||||
| 
 | ||||
| 
 | ||||
| # Go command to use for build
 | ||||
| GO ?= go | ||||
| INSTALL ?= install | ||||
| 
 | ||||
| # Root directory of the project (absolute path).
 | ||||
| ROOTDIR=$(dir $(abspath $(lastword $(MAKEFILE_LIST)))) | ||||
| 
 | ||||
| WHALE = "🇩" | ||||
| ONI = "👹" | ||||
| 
 | ||||
| # Project binaries.
 | ||||
| COMMANDS=protoc-gen-go-ttrpc protoc-gen-gogottrpc | ||||
| 
 | ||||
| ifdef BUILDTAGS | ||||
|     GO_BUILDTAGS = ${BUILDTAGS} | ||||
| endif | ||||
| GO_BUILDTAGS ?= | ||||
| GO_TAGS=$(if $(GO_BUILDTAGS),-tags "$(strip $(GO_BUILDTAGS))",) | ||||
| 
 | ||||
| # Project packages.
 | ||||
| PACKAGES=$(shell $(GO) list ${GO_TAGS} ./... | grep -v /example) | ||||
| TESTPACKAGES=$(shell $(GO) list ${GO_TAGS} ./... | grep -v /cmd | grep -v /integration | grep -v /example) | ||||
| BINPACKAGES=$(addprefix ./cmd/,$(COMMANDS)) | ||||
| 
 | ||||
| #Replaces ":" (*nix), ";" (windows) with newline for easy parsing
 | ||||
| GOPATHS=$(shell echo ${GOPATH} | tr ":" "\n" | tr ";" "\n") | ||||
| 
 | ||||
| TESTFLAGS_RACE= | ||||
| GO_BUILD_FLAGS= | ||||
| # See Golang issue re: '-trimpath': https://github.com/golang/go/issues/13809
 | ||||
| GO_GCFLAGS=$(shell				\
 | ||||
| 	set -- ${GOPATHS};			\
 | ||||
| 	echo "-gcflags=-trimpath=$${1}/src";	\
 | ||||
| 	) | ||||
| 
 | ||||
| BINARIES=$(addprefix bin/,$(COMMANDS)) | ||||
| 
 | ||||
| # Flags passed to `go test`
 | ||||
| TESTFLAGS ?= $(TESTFLAGS_RACE) $(EXTRA_TESTFLAGS) | ||||
| TESTFLAGS_PARALLEL ?= 8 | ||||
| 
 | ||||
| # Use this to replace `go test` with, for instance, `gotestsum`
 | ||||
| GOTEST ?= $(GO) test | ||||
| 
 | ||||
| .PHONY: clean all AUTHORS build binaries test integration generate protos check-protos coverage ci check help install vendor install-protobuf install-protobuild | ||||
| .DEFAULT: default | ||||
| 
 | ||||
| # Forcibly set the default goal to all, in case an include above brought in a rule definition.
 | ||||
| .DEFAULT_GOAL := all | ||||
| 
 | ||||
| all: binaries | ||||
| 
 | ||||
| check: proto-fmt ## run all linters
 | ||||
| 	@echo "$(WHALE) $@" | ||||
| 	GOGC=75 golangci-lint run | ||||
| 
 | ||||
| ci: check binaries check-protos coverage # coverage-integration ## to be used by the CI
 | ||||
| 
 | ||||
| AUTHORS: .mailmap .git/HEAD | ||||
| 	git log --format='%aN <%aE>' | sort -fu > $@ | ||||
| 
 | ||||
| generate: protos | ||||
| 	@echo "$(WHALE) $@" | ||||
| 	@PATH="${ROOTDIR}/bin:${PATH}" $(GO) generate -x ${PACKAGES} | ||||
| 
 | ||||
| protos: bin/protoc-gen-gogottrpc bin/protoc-gen-go-ttrpc ## generate protobuf
 | ||||
| 	@echo "$(WHALE) $@" | ||||
| 	@(PATH="${ROOTDIR}/bin:${PATH}" protobuild --quiet ${PACKAGES}) | ||||
| 
 | ||||
| check-protos: protos ## check if protobufs needs to be generated again
 | ||||
| 	@echo "$(WHALE) $@" | ||||
| 	@test -z "$$(git status --short | grep ".pb.go" | tee /dev/stderr)" || \
 | ||||
| 		((git diff | cat) && \
 | ||||
| 		(echo "$(ONI) please run 'make protos' when making changes to proto files" && false)) | ||||
| 
 | ||||
| check-api-descriptors: protos ## check that protobuf changes aren't present.
 | ||||
| 	@echo "$(WHALE) $@" | ||||
| 	@test -z "$$(git status --short | grep ".pb.txt" | tee /dev/stderr)" || \
 | ||||
| 		((git diff $$(find . -name '*.pb.txt') | cat) && \
 | ||||
| 		(echo "$(ONI) please run 'make protos' when making changes to proto files and check-in the generated descriptor file changes" && false)) | ||||
| 
 | ||||
| proto-fmt: ## check format of proto files
 | ||||
| 	@echo "$(WHALE) $@" | ||||
| 	@test -z "$$(find . -name '*.proto' -type f -exec grep -Hn -e "^ " {} \; | tee /dev/stderr)" || \
 | ||||
| 		(echo "$(ONI) please indent proto files with tabs only" && false) | ||||
| 	@test -z "$$(find . -name '*.proto' -type f -exec grep -Hn "Meta meta = " {} \; | grep -v '(gogoproto.nullable) = false' | tee /dev/stderr)" || \
 | ||||
| 		(echo "$(ONI) meta fields in proto files must have option (gogoproto.nullable) = false" && false) | ||||
| 
 | ||||
| build: ## build the go packages
 | ||||
| 	@echo "$(WHALE) $@" | ||||
| 	@$(GO) build ${DEBUG_GO_GCFLAGS} ${GO_GCFLAGS} ${GO_BUILD_FLAGS} ${EXTRA_FLAGS} ${PACKAGES} | ||||
| 
 | ||||
| test: ## run tests, except integration tests and tests that require root
 | ||||
| 	@echo "$(WHALE) $@" | ||||
| 	@$(GOTEST) ${TESTFLAGS} ${TESTPACKAGES} | ||||
| 
 | ||||
| integration: ## run integration tests
 | ||||
| 	@echo "$(WHALE) $@" | ||||
| 	@cd "${ROOTDIR}/integration" && $(GOTEST) -v ${TESTFLAGS}  -parallel ${TESTFLAGS_PARALLEL} . | ||||
| 
 | ||||
| benchmark: ## run benchmarks tests
 | ||||
| 	@echo "$(WHALE) $@" | ||||
| 	@$(GO) test ${TESTFLAGS} -bench . -run Benchmark | ||||
| 
 | ||||
| FORCE: | ||||
| 
 | ||||
| define BUILD_BINARY | ||||
| @echo "$(WHALE) $@" | ||||
| @$(GO) build ${DEBUG_GO_GCFLAGS} ${GO_GCFLAGS} ${GO_BUILD_FLAGS} -o $@ ${GO_TAGS}  ./$< | ||||
| endef | ||||
| 
 | ||||
| # Build a binary from a cmd.
 | ||||
| bin/%: cmd/% FORCE | ||||
| 	$(call BUILD_BINARY) | ||||
| 
 | ||||
| binaries: $(BINARIES) ## build binaries
 | ||||
| 	@echo "$(WHALE) $@" | ||||
| 
 | ||||
| clean: ## clean up binaries
 | ||||
| 	@echo "$(WHALE) $@" | ||||
| 	@rm -f $(BINARIES) | ||||
| 
 | ||||
| install: ## install binaries
 | ||||
| 	@echo "$(WHALE) $@ $(BINPACKAGES)" | ||||
| 	@$(GO) install $(BINPACKAGES) | ||||
| 
 | ||||
| install-protobuf: | ||||
| 	@echo "$(WHALE) $@" | ||||
| 	@script/install-protobuf | ||||
| 
 | ||||
| install-protobuild: | ||||
| 	@echo "$(WHALE) $@" | ||||
| 	@$(GO) install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1 | ||||
| 	@$(GO) install github.com/containerd/protobuild@14832ccc41429f5c4f81028e5af08aa233a219cf | ||||
| 
 | ||||
| coverage: ## generate coverprofiles from the unit tests, except tests that require root
 | ||||
| 	@echo "$(WHALE) $@" | ||||
| 	@rm -f coverage.txt | ||||
| 	@$(GO) test ${TESTFLAGS} ${TESTPACKAGES} 2> /dev/null | ||||
| 	@( for pkg in ${PACKAGES}; do \
 | ||||
| 		$(GO) test ${TESTFLAGS} \
 | ||||
| 			-cover \
 | ||||
| 			-coverprofile=profile.out \
 | ||||
| 			-covermode=atomic $$pkg || exit; \
 | ||||
| 		if [ -f profile.out ]; then \
 | ||||
| 			cat profile.out >> coverage.txt; \
 | ||||
| 			rm profile.out; \
 | ||||
| 		fi; \
 | ||||
| 	done ) | ||||
| 
 | ||||
| vendor: ## ensure all the go.mod/go.sum files are up-to-date
 | ||||
| 	@echo "$(WHALE) $@" | ||||
| 	@$(GO) mod tidy | ||||
| 	@$(GO) mod verify | ||||
| 
 | ||||
| verify-vendor: ## verify if all the go.mod/go.sum files are up-to-date
 | ||||
| 	@echo "$(WHALE) $@" | ||||
| 	@$(GO) mod tidy | ||||
| 	@$(GO) mod verify | ||||
| 	@test -z "$$(git status --short | grep "go.sum" | tee /dev/stderr)" || \
 | ||||
| 		((git diff | cat) && \
 | ||||
| 		(echo "$(ONI) make sure to checkin changes after go mod tidy" && false)) | ||||
| 
 | ||||
| help: ## this help
 | ||||
| 	@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort | ||||
|  | @ -0,0 +1,240 @@ | |||
| # Protocol Specification | ||||
| 
 | ||||
| The ttrpc protocol is client/server protocol to support multiple request streams | ||||
| over a single connection with lightweight framing. The client represents the | ||||
| process which initiated the underlying connection and the server is the process | ||||
| which accepted the connection. The protocol is currently defined as | ||||
| asymmetrical, with clients sending requests and servers sending responses. Both | ||||
| clients and servers are able to send stream data. The roles are also used in | ||||
| determining the stream identifiers, with client initiated streams using odd | ||||
| number identifiers and server initiated using even number. The protocol may be | ||||
| extended in the future to support server initiated streams, that is not | ||||
| supported in the latest version. | ||||
| 
 | ||||
| ## Purpose | ||||
| 
 | ||||
| The ttrpc protocol is designed to be lightweight and optimized for low latency | ||||
| and reliable connections between processes on the same host. The protocol does | ||||
| not include features for handling unreliable connections such as handshakes, | ||||
| resets, pings, or flow control. The protocol is designed to make low-overhead | ||||
| implementations as simple as possible. It is not intended as a suitable | ||||
| replacement for HTTP2/3 over the network. | ||||
| 
 | ||||
| ## Message Frame | ||||
| 
 | ||||
| Each Message Frame consists of a 10-byte message header followed | ||||
| by message data. The data length and stream ID are both big-endian | ||||
| 4-byte unsigned integers. The message type is an unsigned 1-byte | ||||
| integer. The flags are also an unsigned 1-byte integer and | ||||
| use is defined by the message type. | ||||
| 
 | ||||
|     +---------------------------------------------------------------+ | ||||
|     |                       Data Length (32)                        | | ||||
|     +---------------------------------------------------------------+ | ||||
|     |                        Stream ID (32)                         | | ||||
|     +---------------+-----------------------------------------------+ | ||||
|     | Msg Type (8)  | | ||||
|     +---------------+ | ||||
|     |   Flags (8)   | | ||||
|     +---------------+-----------------------------------------------+ | ||||
|     |                           Data (*)                            | | ||||
|     +---------------------------------------------------------------+ | ||||
| 
 | ||||
| The Data Length field represents the number of bytes in the Data field. The | ||||
| total frame size will always be Data Length + 10 bytes. The maximum data length | ||||
| is 4MB and any larger size should be rejected. Due to the maximum data size | ||||
| being less than 16MB, the first frame byte should always be zero. This first | ||||
| byte should be considered reserved for future use. | ||||
| 
 | ||||
| The Stream ID must be odd for client initiated streams and even for server | ||||
| initiated streams. Server initiated streams are not currently supported. | ||||
| 
 | ||||
| ## Mesage Types | ||||
| 
 | ||||
| | Message Type | Name     | Description                      | | ||||
| |--------------|----------|----------------------------------| | ||||
| | 0x01         | Request  | Initiates stream                 | | ||||
| | 0x02         | Response | Final stream data and terminates | | ||||
| | 0x03         | Data     | Stream data                      | | ||||
| 
 | ||||
| ### Request | ||||
| 
 | ||||
| The request message is used to initiate stream and send along request data for | ||||
| properly routing and handling the stream. The stream may indicate unary without | ||||
| any inbound or outbound stream data with only a response is expected on the | ||||
| stream. The request may also indicate the stream is still open for more data and | ||||
| no response is expected until data is finished. If the remote indicates the | ||||
| stream is closed, the request may be considered non-unary but without anymore | ||||
| stream data sent. In the case of `remote closed`, the remote still expects to | ||||
| receive a response or stream data. For compatibility with non streaming clients, | ||||
| a request with empty flags indicates a unary request. | ||||
| 
 | ||||
| #### Request Flags | ||||
| 
 | ||||
| | Flag | Name            | Description                                      | | ||||
| |------|-----------------|--------------------------------------------------| | ||||
| | 0x01 | `remote closed` | Non-unary, but no more data expected from remote | | ||||
| | 0x02 | `remote open`   | Non-unary, remote is still sending data          | | ||||
| 
 | ||||
| ### Response | ||||
| 
 | ||||
| The response message is used to end a stream with data, an empty response, or | ||||
| an error. A response message is the only expected message after a unary request. | ||||
| A non-unary request does not require a response message if the server is sending | ||||
| back stream data. A non-unary stream may return a single response message but no | ||||
| other stream data may follow. | ||||
| 
 | ||||
| #### Response Flags | ||||
| 
 | ||||
| No response flags are defined at this time, flags should be empty. | ||||
| 
 | ||||
| ### Data | ||||
| 
 | ||||
| The data message is used to send data on an already initialized stream. Either | ||||
| client or server may send data. A data message is not allowed on a unary stream. | ||||
| A data message should not be sent after indicating `remote closed` to the peer. | ||||
| The last data message on a stream must set the `remote closed` flag. | ||||
| 
 | ||||
| The `no data` flag is used to indicate that the data message does not include | ||||
| any data. This is normally used with the `remote closed` flag to indicate the | ||||
| stream is now closed without transmitting any data. Since ttrpc normally | ||||
| transmits a single object per message, a zero length data message may be | ||||
| interpreted as an empty object. For example, transmitting the number zero as a | ||||
| protobuf message ends up with a data length of zero, but the message is still | ||||
| considered data and should be processed. | ||||
| 
 | ||||
| #### Data Flags | ||||
| 
 | ||||
| | Flag | Name            | Description                       | | ||||
| |------|-----------------|-----------------------------------| | ||||
| | 0x01 | `remote closed` | No more data expected from remote | | ||||
| | 0x04 | `no data`       | This message does not have data   | | ||||
| 
 | ||||
| ## Streaming | ||||
| 
 | ||||
| All ttrpc requests use streams to transfer data. Unary streams will only have | ||||
| two messages sent per stream, a request from a client and a response from the | ||||
| server. Non-unary streams, however, may send any numbers of messages from the | ||||
| client and the server. This makes stream management more complicated than unary | ||||
| streams since both client and server need to track additional state. To keep | ||||
| this management as simple as possible, ttrpc minimizes the number of states and | ||||
| uses two flags instead of control frames. Each stream has two states while a | ||||
| stream is still alive: `local closed` and `remote closed`. Each peer considers | ||||
| local and remote from their own perspective and sets flags from the other peer's | ||||
| perspective. For example, if a client sends a data frame with the | ||||
| `remote closed` flag, that is indicating that the client is now `local closed` | ||||
| and the server will be `remote closed`. A unary operation does not need to send | ||||
| these flags since each received message always indicates `remote closed`. Once a | ||||
| peer is both `local closed` and `remote closed`, the stream is considered | ||||
| finished and may be cleaned up. | ||||
| 
 | ||||
| Due to the asymmetric nature of the current protocol, a client should | ||||
| always be in the `local closed` state before `remote closed` and a server should | ||||
| always be in the `remote closed` state before `local closed`. This happens | ||||
| because the client is always initiating requests and a client always expects a | ||||
| final response back from a server to indicate the initiated request has been | ||||
| fulfilled. This may mean server sends a final empty response to finish a stream | ||||
| even after it has already completed sending data before the client. | ||||
| 
 | ||||
| ### Unary State Diagram | ||||
| 
 | ||||
|          +--------+                                    +--------+ | ||||
|          | Client |                                    | Server | | ||||
|          +---+----+                                    +----+---+ | ||||
|              |               +---------+                    | | ||||
|       local  >---------------+ Request +--------------------> remote | ||||
|       closed |               +---------+                    | closed | ||||
|              |                                              | | ||||
|              |              +----------+                    | | ||||
|     finished <--------------+ Response +--------------------< finished | ||||
|              |              +----------+                    | | ||||
|              |                                              | | ||||
| 
 | ||||
| ### Non-Unary State Diagrams | ||||
| 
 | ||||
| RC: `remote closed` flag | ||||
| RO: `remote open` flag | ||||
| 
 | ||||
|          +--------+                                    +--------+ | ||||
|          | Client |                                    | Server | | ||||
|          +---+----+                                    +----+---+ | ||||
|              |             +--------------+                 | | ||||
|              >-------------+ Request [RO] +-----------------> | ||||
|              |             +--------------+                 | | ||||
|              |                                              | | ||||
|              |                 +------+                     | | ||||
|              >-----------------+ Data +---------------------> | ||||
|              |                 +------+                     | | ||||
|              |                                              | | ||||
|              |               +-----------+                  | | ||||
|       local  >---------------+ Data [RC] +------------------> remote | ||||
|       closed |               +-----------+                  | closed | ||||
|              |                                              | | ||||
|              |              +----------+                    | | ||||
|     finished <--------------+ Response +--------------------< finished | ||||
|              |              +----------+                    | | ||||
|              |                                              | | ||||
| 
 | ||||
|          +--------+                                    +--------+ | ||||
|          | Client |                                    | Server | | ||||
|          +---+----+                                    +----+---+ | ||||
|              |             +--------------+                 | | ||||
|       local  >-------------+ Request [RC] +-----------------> remote | ||||
|       closed |             +--------------+                 | closed | ||||
|              |                                              | | ||||
|              |                 +------+                     | | ||||
|              <-----------------+ Data +---------------------< | ||||
|              |                 +------+                     | | ||||
|              |                                              | | ||||
|              |               +-----------+                  | | ||||
|     finished <---------------+ Data [RC] +------------------< finished | ||||
|              |               +-----------+                  | | ||||
|              |                                              | | ||||
| 
 | ||||
|          +--------+                                    +--------+ | ||||
|          | Client |                                    | Server | | ||||
|          +---+----+                                    +----+---+ | ||||
|              |             +--------------+                 | | ||||
|              >-------------+ Request [RO] +-----------------> | ||||
|              |             +--------------+                 | | ||||
|              |                                              | | ||||
|              |                 +------+                     | | ||||
|              >-----------------+ Data +---------------------> | ||||
|              |                 +------+                     | | ||||
|              |                                              | | ||||
|              |                 +------+                     | | ||||
|              <-----------------+ Data +---------------------< | ||||
|              |                 +------+                     | | ||||
|              |                                              | | ||||
|              |                 +------+                     | | ||||
|              >-----------------+ Data +---------------------> | ||||
|              |                 +------+                     | | ||||
|              |                                              | | ||||
|              |               +-----------+                  | | ||||
|       local  >---------------+ Data [RC] +------------------> remote | ||||
|       closed |               +-----------+                  | closed | ||||
|              |                                              | | ||||
|              |                 +------+                     | | ||||
|              <-----------------+ Data +---------------------< | ||||
|              |                 +------+                     | | ||||
|              |                                              | | ||||
|              |               +-----------+                  | | ||||
|     finished <---------------+ Data [RC] +------------------< finished | ||||
|              |               +-----------+                  | | ||||
|              |                                              | | ||||
| 
 | ||||
| ## RPC | ||||
| 
 | ||||
| While this protocol is defined primarily to support Remote Procedure Calls, the | ||||
| protocol does not define the request and response types beyond the messages | ||||
| defined in the protocol. The implementation provides a default protobuf | ||||
| definition of request and response which may be used for cross language rpc. | ||||
| All implementations should at least define a request type which support | ||||
| routing by procedure name and a response type which supports call status. | ||||
| 
 | ||||
| ## Version History | ||||
| 
 | ||||
| | Version | Features            | | ||||
| |---------|---------------------| | ||||
| | 1.0     | Unary requests only | | ||||
| | 1.2     | Streaming support   | | ||||
|  | @ -0,0 +1,28 @@ | |||
| version = "2" | ||||
| generators = ["go"] | ||||
| 
 | ||||
| # Control protoc include paths. Below are usually some good defaults, but feel | ||||
| # free to try it without them if it works for your project. | ||||
| [includes] | ||||
|   # Include paths that will be added before all others. Typically, you want to | ||||
|   # treat the root of the project as an include, but this may not be necessary. | ||||
|   before = ["."] | ||||
| 
 | ||||
|   # Paths that will be added untouched to the end of the includes. We use | ||||
|   # `/usr/local/include` to pickup the common install location of protobuf. | ||||
|   # This is the default. | ||||
|   after = ["/usr/local/include"] | ||||
| 
 | ||||
| # This section maps protobuf imports to Go packages. These will become | ||||
| # `-M` directives in the call to the go protobuf generator. | ||||
| [packages] | ||||
|   "google/protobuf/any.proto" = "github.com/gogo/protobuf/types" | ||||
|   "proto/status.proto" = "google.golang.org/genproto/googleapis/rpc/status" | ||||
| 
 | ||||
| [[overrides]] | ||||
| # enable ttrpc and disable fieldpath and grpc for the shim | ||||
| prefixes = ["github.com/containerd/ttrpc/integration/streaming"] | ||||
| generators = ["go", "go-ttrpc"] | ||||
| 
 | ||||
| [overrides.parameters.go-ttrpc] | ||||
| prefix = "TTRPC" | ||||
|  | @ -0,0 +1,59 @@ | |||
| # ttrpc | ||||
| 
 | ||||
| [](https://github.com/containerd/ttrpc/actions?query=workflow%3ACI) | ||||
| 
 | ||||
| GRPC for low-memory environments. | ||||
| 
 | ||||
| The existing grpc-go project requires a lot of memory overhead for importing | ||||
| packages and at runtime. While this is great for many services with low density | ||||
| requirements, this can be a problem when running a large number of services on | ||||
| a single machine or on a machine with a small amount of memory. | ||||
| 
 | ||||
| Using the same GRPC definitions, this project reduces the binary size and | ||||
| protocol overhead required. We do this by eliding the `net/http`, `net/http2` | ||||
| and `grpc` package used by grpc replacing it with a lightweight framing | ||||
| protocol. The result are smaller binaries that use less resident memory with | ||||
| the same ease of use as GRPC. | ||||
| 
 | ||||
| Please note that while this project supports generating either end of the | ||||
| protocol, the generated service definitions will be incompatible with regular | ||||
| GRPC services, as they do not speak the same protocol. | ||||
| 
 | ||||
| # Protocol | ||||
| 
 | ||||
| See the [protocol specification](./PROTOCOL.md). | ||||
| 
 | ||||
| # Usage | ||||
| 
 | ||||
| Create a gogo vanity binary (see | ||||
| [`cmd/protoc-gen-gogottrpc/main.go`](cmd/protoc-gen-gogottrpc/main.go) for an | ||||
| example with the ttrpc plugin enabled. | ||||
| 
 | ||||
| It's recommended to use [`protobuild`](https://github.com/containerd/protobuild) | ||||
| to build the protobufs for this project, but this will work with protoc | ||||
| directly, if required. | ||||
| 
 | ||||
| # Differences from GRPC | ||||
| 
 | ||||
| - The protocol stack has been replaced with a lighter protocol that doesn't | ||||
|   require http, http2 and tls. | ||||
| - The client and server interface are identical whereas in GRPC there is a | ||||
|   client and server interface that are different. | ||||
| - The Go stdlib context package is used instead. | ||||
| 
 | ||||
| # Status | ||||
| 
 | ||||
| TODO: | ||||
| 
 | ||||
| - [ ] Add testing under concurrent load to ensure | ||||
| - [ ] Verify connection error handling | ||||
| 
 | ||||
| # Project details | ||||
| 
 | ||||
| ttrpc is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE). | ||||
| As a containerd sub-project, you will find the: | ||||
|  * [Project governance](https://github.com/containerd/project/blob/main/GOVERNANCE.md), | ||||
|  * [Maintainers](https://github.com/containerd/project/blob/main/MAINTAINERS), | ||||
|  * and [Contributing guidelines](https://github.com/containerd/project/blob/main/CONTRIBUTING.md) | ||||
| 
 | ||||
| information in our [`containerd/project`](https://github.com/containerd/project) repository. | ||||
|  | @ -0,0 +1,182 @@ | |||
| /* | ||||
|    Copyright The containerd 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 ttrpc | ||||
| 
 | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"encoding/binary" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"google.golang.org/grpc/codes" | ||||
| 	"google.golang.org/grpc/status" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	messageHeaderLength = 10 | ||||
| 	messageLengthMax    = 4 << 20 | ||||
| ) | ||||
| 
 | ||||
| type messageType uint8 | ||||
| 
 | ||||
| const ( | ||||
| 	messageTypeRequest  messageType = 0x1 | ||||
| 	messageTypeResponse messageType = 0x2 | ||||
| 	messageTypeData     messageType = 0x3 | ||||
| ) | ||||
| 
 | ||||
| func (mt messageType) String() string { | ||||
| 	switch mt { | ||||
| 	case messageTypeRequest: | ||||
| 		return "request" | ||||
| 	case messageTypeResponse: | ||||
| 		return "response" | ||||
| 	case messageTypeData: | ||||
| 		return "data" | ||||
| 	default: | ||||
| 		return "unknown" | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	flagRemoteClosed uint8 = 0x1 | ||||
| 	flagRemoteOpen   uint8 = 0x2 | ||||
| 	flagNoData       uint8 = 0x4 | ||||
| ) | ||||
| 
 | ||||
| // messageHeader represents the fixed-length message header of 10 bytes sent
 | ||||
| // with every request.
 | ||||
| type messageHeader struct { | ||||
| 	Length   uint32      // length excluding this header. b[:4]
 | ||||
| 	StreamID uint32      // identifies which request stream message is a part of. b[4:8]
 | ||||
| 	Type     messageType // message type b[8]
 | ||||
| 	Flags    uint8       // type specific flags b[9]
 | ||||
| } | ||||
| 
 | ||||
| func readMessageHeader(p []byte, r io.Reader) (messageHeader, error) { | ||||
| 	_, err := io.ReadFull(r, p[:messageHeaderLength]) | ||||
| 	if err != nil { | ||||
| 		return messageHeader{}, err | ||||
| 	} | ||||
| 
 | ||||
| 	return messageHeader{ | ||||
| 		Length:   binary.BigEndian.Uint32(p[:4]), | ||||
| 		StreamID: binary.BigEndian.Uint32(p[4:8]), | ||||
| 		Type:     messageType(p[8]), | ||||
| 		Flags:    p[9], | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func writeMessageHeader(w io.Writer, p []byte, mh messageHeader) error { | ||||
| 	binary.BigEndian.PutUint32(p[:4], mh.Length) | ||||
| 	binary.BigEndian.PutUint32(p[4:8], mh.StreamID) | ||||
| 	p[8] = byte(mh.Type) | ||||
| 	p[9] = mh.Flags | ||||
| 
 | ||||
| 	_, err := w.Write(p[:]) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| var buffers sync.Pool | ||||
| 
 | ||||
| type channel struct { | ||||
| 	conn  net.Conn | ||||
| 	bw    *bufio.Writer | ||||
| 	br    *bufio.Reader | ||||
| 	hrbuf [messageHeaderLength]byte // avoid alloc when reading header
 | ||||
| 	hwbuf [messageHeaderLength]byte | ||||
| } | ||||
| 
 | ||||
| func newChannel(conn net.Conn) *channel { | ||||
| 	return &channel{ | ||||
| 		conn: conn, | ||||
| 		bw:   bufio.NewWriter(conn), | ||||
| 		br:   bufio.NewReader(conn), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // recv a message from the channel. The returned buffer contains the message.
 | ||||
| //
 | ||||
| // If a valid grpc status is returned, the message header
 | ||||
| // returned will be valid and caller should send that along to
 | ||||
| // the correct consumer. The bytes on the underlying channel
 | ||||
| // will be discarded.
 | ||||
| func (ch *channel) recv() (messageHeader, []byte, error) { | ||||
| 	mh, err := readMessageHeader(ch.hrbuf[:], ch.br) | ||||
| 	if err != nil { | ||||
| 		return messageHeader{}, nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if mh.Length > uint32(messageLengthMax) { | ||||
| 		if _, err := ch.br.Discard(int(mh.Length)); err != nil { | ||||
| 			return mh, nil, fmt.Errorf("failed to discard after receiving oversized message: %w", err) | ||||
| 		} | ||||
| 
 | ||||
| 		return mh, nil, status.Errorf(codes.ResourceExhausted, "message length %v exceed maximum message size of %v", mh.Length, messageLengthMax) | ||||
| 	} | ||||
| 
 | ||||
| 	var p []byte | ||||
| 	if mh.Length > 0 { | ||||
| 		p = ch.getmbuf(int(mh.Length)) | ||||
| 		if _, err := io.ReadFull(ch.br, p); err != nil { | ||||
| 			return messageHeader{}, nil, fmt.Errorf("failed reading message: %w", err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return mh, p, nil | ||||
| } | ||||
| 
 | ||||
| func (ch *channel) send(streamID uint32, t messageType, flags uint8, p []byte) error { | ||||
| 	// TODO: Error on send rather than on recv
 | ||||
| 	//if len(p) > messageLengthMax {
 | ||||
| 	//	return status.Errorf(codes.InvalidArgument, "refusing to send, message length %v exceed maximum message size of %v", len(p), messageLengthMax)
 | ||||
| 	//}
 | ||||
| 	if err := writeMessageHeader(ch.bw, ch.hwbuf[:], messageHeader{Length: uint32(len(p)), StreamID: streamID, Type: t, Flags: flags}); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if len(p) > 0 { | ||||
| 		_, err := ch.bw.Write(p) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return ch.bw.Flush() | ||||
| } | ||||
| 
 | ||||
| func (ch *channel) getmbuf(size int) []byte { | ||||
| 	// we can't use the standard New method on pool because we want to allocate
 | ||||
| 	// based on size.
 | ||||
| 	b, ok := buffers.Get().(*[]byte) | ||||
| 	if !ok || cap(*b) < size { | ||||
| 		// TODO(stevvooe): It may be better to allocate these in fixed length
 | ||||
| 		// buckets to reduce fragmentation but its not clear that would help
 | ||||
| 		// with performance. An ilogb approach or similar would work well.
 | ||||
| 		bb := make([]byte, size) | ||||
| 		b = &bb | ||||
| 	} else { | ||||
| 		*b = (*b)[:size] | ||||
| 	} | ||||
| 	return *b | ||||
| } | ||||
| 
 | ||||
| func (ch *channel) putmbuf(p []byte) { | ||||
| 	buffers.Put(&p) | ||||
| } | ||||
|  | @ -0,0 +1,512 @@ | |||
| /* | ||||
|    Copyright The containerd 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 ttrpc | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"syscall" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"google.golang.org/grpc/codes" | ||||
| 	"google.golang.org/grpc/status" | ||||
| 	"google.golang.org/protobuf/proto" | ||||
| ) | ||||
| 
 | ||||
| // Client for a ttrpc server
 | ||||
| type Client struct { | ||||
| 	codec   codec | ||||
| 	conn    net.Conn | ||||
| 	channel *channel | ||||
| 
 | ||||
| 	streamLock   sync.RWMutex | ||||
| 	streams      map[streamID]*stream | ||||
| 	nextStreamID streamID | ||||
| 	sendLock     sync.Mutex | ||||
| 
 | ||||
| 	ctx    context.Context | ||||
| 	closed func() | ||||
| 
 | ||||
| 	closeOnce       sync.Once | ||||
| 	userCloseFunc   func() | ||||
| 	userCloseWaitCh chan struct{} | ||||
| 
 | ||||
| 	interceptor UnaryClientInterceptor | ||||
| } | ||||
| 
 | ||||
| // ClientOpts configures a client
 | ||||
| type ClientOpts func(c *Client) | ||||
| 
 | ||||
| // WithOnClose sets the close func whenever the client's Close() method is called
 | ||||
| func WithOnClose(onClose func()) ClientOpts { | ||||
| 	return func(c *Client) { | ||||
| 		c.userCloseFunc = onClose | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // WithUnaryClientInterceptor sets the provided client interceptor
 | ||||
| func WithUnaryClientInterceptor(i UnaryClientInterceptor) ClientOpts { | ||||
| 	return func(c *Client) { | ||||
| 		c.interceptor = i | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // NewClient creates a new ttrpc client using the given connection
 | ||||
| func NewClient(conn net.Conn, opts ...ClientOpts) *Client { | ||||
| 	ctx, cancel := context.WithCancel(context.Background()) | ||||
| 	channel := newChannel(conn) | ||||
| 	c := &Client{ | ||||
| 		codec:           codec{}, | ||||
| 		conn:            conn, | ||||
| 		channel:         channel, | ||||
| 		streams:         make(map[streamID]*stream), | ||||
| 		nextStreamID:    1, | ||||
| 		closed:          cancel, | ||||
| 		ctx:             ctx, | ||||
| 		userCloseFunc:   func() {}, | ||||
| 		userCloseWaitCh: make(chan struct{}), | ||||
| 		interceptor:     defaultClientInterceptor, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, o := range opts { | ||||
| 		o(c) | ||||
| 	} | ||||
| 
 | ||||
| 	go c.run() | ||||
| 	return c | ||||
| } | ||||
| 
 | ||||
| func (c *Client) send(sid uint32, mt messageType, flags uint8, b []byte) error { | ||||
| 	c.sendLock.Lock() | ||||
| 	defer c.sendLock.Unlock() | ||||
| 	return c.channel.send(sid, mt, flags, b) | ||||
| } | ||||
| 
 | ||||
| // Call makes a unary request and returns with response
 | ||||
| func (c *Client) Call(ctx context.Context, service, method string, req, resp interface{}) error { | ||||
| 	payload, err := c.codec.Marshal(req) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	var ( | ||||
| 		creq = &Request{ | ||||
| 			Service: service, | ||||
| 			Method:  method, | ||||
| 			Payload: payload, | ||||
| 			// TODO: metadata from context
 | ||||
| 		} | ||||
| 
 | ||||
| 		cresp = &Response{} | ||||
| 	) | ||||
| 
 | ||||
| 	if metadata, ok := GetMetadata(ctx); ok { | ||||
| 		metadata.setRequest(creq) | ||||
| 	} | ||||
| 
 | ||||
| 	if dl, ok := ctx.Deadline(); ok { | ||||
| 		creq.TimeoutNano = time.Until(dl).Nanoseconds() | ||||
| 	} | ||||
| 
 | ||||
| 	info := &UnaryClientInfo{ | ||||
| 		FullMethod: fullPath(service, method), | ||||
| 	} | ||||
| 	if err := c.interceptor(ctx, creq, cresp, info, c.dispatch); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if err := c.codec.Unmarshal(cresp.Payload, resp); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if cresp.Status != nil && cresp.Status.Code != int32(codes.OK) { | ||||
| 		return status.ErrorProto(cresp.Status) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // StreamDesc describes the stream properties, whether the stream has
 | ||||
| // a streaming client, a streaming server, or both
 | ||||
| type StreamDesc struct { | ||||
| 	StreamingClient bool | ||||
| 	StreamingServer bool | ||||
| } | ||||
| 
 | ||||
| // ClientStream is used to send or recv messages on the underlying stream
 | ||||
| type ClientStream interface { | ||||
| 	CloseSend() error | ||||
| 	SendMsg(m interface{}) error | ||||
| 	RecvMsg(m interface{}) error | ||||
| } | ||||
| 
 | ||||
| type clientStream struct { | ||||
| 	ctx          context.Context | ||||
| 	s            *stream | ||||
| 	c            *Client | ||||
| 	desc         *StreamDesc | ||||
| 	localClosed  bool | ||||
| 	remoteClosed bool | ||||
| } | ||||
| 
 | ||||
| func (cs *clientStream) CloseSend() error { | ||||
| 	if !cs.desc.StreamingClient { | ||||
| 		return fmt.Errorf("%w: cannot close non-streaming client", ErrProtocol) | ||||
| 	} | ||||
| 	if cs.localClosed { | ||||
| 		return ErrStreamClosed | ||||
| 	} | ||||
| 	err := cs.s.send(messageTypeData, flagRemoteClosed|flagNoData, nil) | ||||
| 	if err != nil { | ||||
| 		return filterCloseErr(err) | ||||
| 	} | ||||
| 	cs.localClosed = true | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (cs *clientStream) SendMsg(m interface{}) error { | ||||
| 	if !cs.desc.StreamingClient { | ||||
| 		return fmt.Errorf("%w: cannot send data from non-streaming client", ErrProtocol) | ||||
| 	} | ||||
| 	if cs.localClosed { | ||||
| 		return ErrStreamClosed | ||||
| 	} | ||||
| 
 | ||||
| 	var ( | ||||
| 		payload []byte | ||||
| 		err     error | ||||
| 	) | ||||
| 	if m != nil { | ||||
| 		payload, err = cs.c.codec.Marshal(m) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	err = cs.s.send(messageTypeData, 0, payload) | ||||
| 	if err != nil { | ||||
| 		return filterCloseErr(err) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (cs *clientStream) RecvMsg(m interface{}) error { | ||||
| 	if cs.remoteClosed { | ||||
| 		return io.EOF | ||||
| 	} | ||||
| 
 | ||||
| 	var msg *streamMessage | ||||
| 	select { | ||||
| 	case <-cs.ctx.Done(): | ||||
| 		return cs.ctx.Err() | ||||
| 	case <-cs.s.recvClose: | ||||
| 		// If recv has a pending message, process that first
 | ||||
| 		select { | ||||
| 		case msg = <-cs.s.recv: | ||||
| 		default: | ||||
| 			return cs.s.recvErr | ||||
| 		} | ||||
| 	case msg = <-cs.s.recv: | ||||
| 	} | ||||
| 
 | ||||
| 	if msg.header.Type == messageTypeResponse { | ||||
| 		resp := &Response{} | ||||
| 		err := proto.Unmarshal(msg.payload[:msg.header.Length], resp) | ||||
| 		// return the payload buffer for reuse
 | ||||
| 		cs.c.channel.putmbuf(msg.payload) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		if err := cs.c.codec.Unmarshal(resp.Payload, m); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		if resp.Status != nil && resp.Status.Code != int32(codes.OK) { | ||||
| 			return status.ErrorProto(resp.Status) | ||||
| 		} | ||||
| 
 | ||||
| 		cs.c.deleteStream(cs.s) | ||||
| 		cs.remoteClosed = true | ||||
| 
 | ||||
| 		return nil | ||||
| 	} else if msg.header.Type == messageTypeData { | ||||
| 		if !cs.desc.StreamingServer { | ||||
| 			cs.c.deleteStream(cs.s) | ||||
| 			cs.remoteClosed = true | ||||
| 			return fmt.Errorf("received data from non-streaming server: %w", ErrProtocol) | ||||
| 		} | ||||
| 		if msg.header.Flags&flagRemoteClosed == flagRemoteClosed { | ||||
| 			cs.c.deleteStream(cs.s) | ||||
| 			cs.remoteClosed = true | ||||
| 
 | ||||
| 			if msg.header.Flags&flagNoData == flagNoData { | ||||
| 				return io.EOF | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		err := cs.c.codec.Unmarshal(msg.payload[:msg.header.Length], m) | ||||
| 		cs.c.channel.putmbuf(msg.payload) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	return fmt.Errorf("unexpected %q message received: %w", msg.header.Type, ErrProtocol) | ||||
| } | ||||
| 
 | ||||
| // Close closes the ttrpc connection and underlying connection
 | ||||
| func (c *Client) Close() error { | ||||
| 	c.closeOnce.Do(func() { | ||||
| 		c.closed() | ||||
| 
 | ||||
| 		c.conn.Close() | ||||
| 	}) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // UserOnCloseWait is used to blocks untils the user's on-close callback
 | ||||
| // finishes.
 | ||||
| func (c *Client) UserOnCloseWait(ctx context.Context) error { | ||||
| 	select { | ||||
| 	case <-c.userCloseWaitCh: | ||||
| 		return nil | ||||
| 	case <-ctx.Done(): | ||||
| 		return ctx.Err() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (c *Client) run() { | ||||
| 	err := c.receiveLoop() | ||||
| 	c.Close() | ||||
| 	c.cleanupStreams(err) | ||||
| 
 | ||||
| 	c.userCloseFunc() | ||||
| 	close(c.userCloseWaitCh) | ||||
| } | ||||
| 
 | ||||
| func (c *Client) receiveLoop() error { | ||||
| 	for { | ||||
| 		select { | ||||
| 		case <-c.ctx.Done(): | ||||
| 			return ErrClosed | ||||
| 		default: | ||||
| 			var ( | ||||
| 				msg = &streamMessage{} | ||||
| 				err error | ||||
| 			) | ||||
| 
 | ||||
| 			msg.header, msg.payload, err = c.channel.recv() | ||||
| 			if err != nil { | ||||
| 				_, ok := status.FromError(err) | ||||
| 				if !ok { | ||||
| 					// treat all errors that are not an rpc status as terminal.
 | ||||
| 					// all others poison the connection.
 | ||||
| 					return filterCloseErr(err) | ||||
| 				} | ||||
| 			} | ||||
| 			sid := streamID(msg.header.StreamID) | ||||
| 			s := c.getStream(sid) | ||||
| 			if s == nil { | ||||
| 				logrus.WithField("stream", sid).Errorf("ttrpc: received message on inactive stream") | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			if err != nil { | ||||
| 				s.closeWithError(err) | ||||
| 			} else { | ||||
| 				if err := s.receive(c.ctx, msg); err != nil { | ||||
| 					logrus.WithError(err).WithField("stream", sid).Errorf("ttrpc: failed to handle message") | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // createStream creates a new stream and registers it with the client
 | ||||
| // Introduce stream types for multiple or single response
 | ||||
| func (c *Client) createStream(flags uint8, b []byte) (*stream, error) { | ||||
| 	c.streamLock.Lock() | ||||
| 
 | ||||
| 	// Check if closed since lock acquired to prevent adding
 | ||||
| 	// anything after cleanup completes
 | ||||
| 	select { | ||||
| 	case <-c.ctx.Done(): | ||||
| 		c.streamLock.Unlock() | ||||
| 		return nil, ErrClosed | ||||
| 	default: | ||||
| 	} | ||||
| 
 | ||||
| 	// Stream ID should be allocated at same time
 | ||||
| 	s := newStream(c.nextStreamID, c) | ||||
| 	c.streams[s.id] = s | ||||
| 	c.nextStreamID = c.nextStreamID + 2 | ||||
| 
 | ||||
| 	c.sendLock.Lock() | ||||
| 	defer c.sendLock.Unlock() | ||||
| 	c.streamLock.Unlock() | ||||
| 
 | ||||
| 	if err := c.channel.send(uint32(s.id), messageTypeRequest, flags, b); err != nil { | ||||
| 		return s, filterCloseErr(err) | ||||
| 	} | ||||
| 
 | ||||
| 	return s, nil | ||||
| } | ||||
| 
 | ||||
| func (c *Client) deleteStream(s *stream) { | ||||
| 	c.streamLock.Lock() | ||||
| 	delete(c.streams, s.id) | ||||
| 	c.streamLock.Unlock() | ||||
| 	s.closeWithError(nil) | ||||
| } | ||||
| 
 | ||||
| func (c *Client) getStream(sid streamID) *stream { | ||||
| 	c.streamLock.RLock() | ||||
| 	s := c.streams[sid] | ||||
| 	c.streamLock.RUnlock() | ||||
| 	return s | ||||
| } | ||||
| 
 | ||||
| func (c *Client) cleanupStreams(err error) { | ||||
| 	c.streamLock.Lock() | ||||
| 	defer c.streamLock.Unlock() | ||||
| 
 | ||||
| 	for sid, s := range c.streams { | ||||
| 		s.closeWithError(err) | ||||
| 		delete(c.streams, sid) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // filterCloseErr rewrites EOF and EPIPE errors to ErrClosed. Use when
 | ||||
| // returning from call or handling errors from main read loop.
 | ||||
| //
 | ||||
| // This purposely ignores errors with a wrapped cause.
 | ||||
| func filterCloseErr(err error) error { | ||||
| 	switch { | ||||
| 	case err == nil: | ||||
| 		return nil | ||||
| 	case err == io.EOF: | ||||
| 		return ErrClosed | ||||
| 	case errors.Is(err, io.ErrClosedPipe): | ||||
| 		return ErrClosed | ||||
| 	case errors.Is(err, io.EOF): | ||||
| 		return ErrClosed | ||||
| 	case strings.Contains(err.Error(), "use of closed network connection"): | ||||
| 		return ErrClosed | ||||
| 	default: | ||||
| 		// if we have an epipe on a write or econnreset on a read , we cast to errclosed
 | ||||
| 		var oerr *net.OpError | ||||
| 		if errors.As(err, &oerr) { | ||||
| 			if (oerr.Op == "write" && errors.Is(err, syscall.EPIPE)) || | ||||
| 				(oerr.Op == "read" && errors.Is(err, syscall.ECONNRESET)) { | ||||
| 				return ErrClosed | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // NewStream creates a new stream with the given stream descriptor to the
 | ||||
| // specified service and method. If not a streaming client, the request object
 | ||||
| // may be provided.
 | ||||
| func (c *Client) NewStream(ctx context.Context, desc *StreamDesc, service, method string, req interface{}) (ClientStream, error) { | ||||
| 	var payload []byte | ||||
| 	if req != nil { | ||||
| 		var err error | ||||
| 		payload, err = c.codec.Marshal(req) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	request := &Request{ | ||||
| 		Service: service, | ||||
| 		Method:  method, | ||||
| 		Payload: payload, | ||||
| 		// TODO: metadata from context
 | ||||
| 	} | ||||
| 	p, err := c.codec.Marshal(request) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	var flags uint8 | ||||
| 	if desc.StreamingClient { | ||||
| 		flags = flagRemoteOpen | ||||
| 	} else { | ||||
| 		flags = flagRemoteClosed | ||||
| 	} | ||||
| 	s, err := c.createStream(flags, p) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return &clientStream{ | ||||
| 		ctx:  ctx, | ||||
| 		s:    s, | ||||
| 		c:    c, | ||||
| 		desc: desc, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func (c *Client) dispatch(ctx context.Context, req *Request, resp *Response) error { | ||||
| 	p, err := c.codec.Marshal(req) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	s, err := c.createStream(0, p) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer c.deleteStream(s) | ||||
| 
 | ||||
| 	var msg *streamMessage | ||||
| 	select { | ||||
| 	case <-ctx.Done(): | ||||
| 		return ctx.Err() | ||||
| 	case <-c.ctx.Done(): | ||||
| 		return ErrClosed | ||||
| 	case <-s.recvClose: | ||||
| 		// If recv has a pending message, process that first
 | ||||
| 		select { | ||||
| 		case msg = <-s.recv: | ||||
| 		default: | ||||
| 			return s.recvErr | ||||
| 		} | ||||
| 	case msg = <-s.recv: | ||||
| 	} | ||||
| 
 | ||||
| 	if msg.header.Type == messageTypeResponse { | ||||
| 		err = proto.Unmarshal(msg.payload[:msg.header.Length], resp) | ||||
| 	} else { | ||||
| 		err = fmt.Errorf("unexpected %q message received: %w", msg.header.Type, ErrProtocol) | ||||
| 	} | ||||
| 
 | ||||
| 	// return the payload buffer for reuse
 | ||||
| 	c.channel.putmbuf(msg.payload) | ||||
| 
 | ||||
| 	return err | ||||
| } | ||||
|  | @ -0,0 +1,43 @@ | |||
| /* | ||||
|    Copyright The containerd 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 ttrpc | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"google.golang.org/protobuf/proto" | ||||
| ) | ||||
| 
 | ||||
| type codec struct{} | ||||
| 
 | ||||
| func (c codec) Marshal(msg interface{}) ([]byte, error) { | ||||
| 	switch v := msg.(type) { | ||||
| 	case proto.Message: | ||||
| 		return proto.Marshal(v) | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("ttrpc: cannot marshal unknown type: %T", msg) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (c codec) Unmarshal(p []byte, msg interface{}) error { | ||||
| 	switch v := msg.(type) { | ||||
| 	case proto.Message: | ||||
| 		return proto.Unmarshal(p, v) | ||||
| 	default: | ||||
| 		return fmt.Errorf("ttrpc: cannot unmarshal into unknown type: %T", msg) | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,52 @@ | |||
| /* | ||||
|    Copyright The containerd 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 ttrpc | ||||
| 
 | ||||
| import "errors" | ||||
| 
 | ||||
| type serverConfig struct { | ||||
| 	handshaker  Handshaker | ||||
| 	interceptor UnaryServerInterceptor | ||||
| } | ||||
| 
 | ||||
| // ServerOpt for configuring a ttrpc server
 | ||||
| type ServerOpt func(*serverConfig) error | ||||
| 
 | ||||
| // WithServerHandshaker can be passed to NewServer to ensure that the
 | ||||
| // handshaker is called before every connection attempt.
 | ||||
| //
 | ||||
| // Only one handshaker is allowed per server.
 | ||||
| func WithServerHandshaker(handshaker Handshaker) ServerOpt { | ||||
| 	return func(c *serverConfig) error { | ||||
| 		if c.handshaker != nil { | ||||
| 			return errors.New("only one handshaker allowed per server") | ||||
| 		} | ||||
| 		c.handshaker = handshaker | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // WithUnaryServerInterceptor sets the provided interceptor on the server
 | ||||
| func WithUnaryServerInterceptor(i UnaryServerInterceptor) ServerOpt { | ||||
| 	return func(c *serverConfig) error { | ||||
| 		if c.interceptor != nil { | ||||
| 			return errors.New("only one interceptor allowed per server") | ||||
| 		} | ||||
| 		c.interceptor = i | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,23 @@ | |||
| /* | ||||
|    Copyright The containerd 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 ttrpc defines and implements a low level simple transfer protocol | ||||
| optimized for low latency and reliable connections between processes on the same | ||||
| host. The protocol uses simple framing for sending requests, responses, and data | ||||
| using multiple streams. | ||||
| */ | ||||
| package ttrpc | ||||
|  | @ -0,0 +1,34 @@ | |||
| /* | ||||
|    Copyright The containerd 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 ttrpc | ||||
| 
 | ||||
| import "errors" | ||||
| 
 | ||||
| var ( | ||||
| 	// ErrProtocol is a general error in the handling the protocol.
 | ||||
| 	ErrProtocol = errors.New("protocol error") | ||||
| 
 | ||||
| 	// ErrClosed is returned by client methods when the underlying connection is
 | ||||
| 	// closed.
 | ||||
| 	ErrClosed = errors.New("ttrpc: closed") | ||||
| 
 | ||||
| 	// ErrServerClosed is returned when the Server has closed its connection.
 | ||||
| 	ErrServerClosed = errors.New("ttrpc: server closed") | ||||
| 
 | ||||
| 	// ErrStreamClosed is when the streaming connection is closed.
 | ||||
| 	ErrStreamClosed = errors.New("ttrpc: stream closed") | ||||
| ) | ||||
|  | @ -0,0 +1,50 @@ | |||
| /* | ||||
|    Copyright The containerd 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 ttrpc | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"net" | ||||
| ) | ||||
| 
 | ||||
| // Handshaker defines the interface for connection handshakes performed on the
 | ||||
| // server or client when first connecting.
 | ||||
| type Handshaker interface { | ||||
| 	// Handshake should confirm or decorate a connection that may be incoming
 | ||||
| 	// to a server or outgoing from a client.
 | ||||
| 	//
 | ||||
| 	// If this returns without an error, the caller should use the connection
 | ||||
| 	// in place of the original connection.
 | ||||
| 	//
 | ||||
| 	// The second return value can contain credential specific data, such as
 | ||||
| 	// unix socket credentials or TLS information.
 | ||||
| 	//
 | ||||
| 	// While we currently only have implementations on the server-side, this
 | ||||
| 	// interface should be sufficient to implement similar handshakes on the
 | ||||
| 	// client-side.
 | ||||
| 	Handshake(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error) | ||||
| } | ||||
| 
 | ||||
| type handshakerFunc func(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error) | ||||
| 
 | ||||
| func (fn handshakerFunc) Handshake(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error) { | ||||
| 	return fn(ctx, conn) | ||||
| } | ||||
| 
 | ||||
| func noopHandshake(_ context.Context, conn net.Conn) (net.Conn, interface{}, error) { | ||||
| 	return conn, nil, nil | ||||
| } | ||||
|  | @ -0,0 +1,65 @@ | |||
| /* | ||||
|    Copyright The containerd 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 ttrpc | ||||
| 
 | ||||
| import "context" | ||||
| 
 | ||||
| // UnaryServerInfo provides information about the server request
 | ||||
| type UnaryServerInfo struct { | ||||
| 	FullMethod string | ||||
| } | ||||
| 
 | ||||
| // UnaryClientInfo provides information about the client request
 | ||||
| type UnaryClientInfo struct { | ||||
| 	FullMethod string | ||||
| } | ||||
| 
 | ||||
| // StreamServerInfo provides information about the server request
 | ||||
| type StreamServerInfo struct { | ||||
| 	FullMethod      string | ||||
| 	StreamingClient bool | ||||
| 	StreamingServer bool | ||||
| } | ||||
| 
 | ||||
| // Unmarshaler contains the server request data and allows it to be unmarshaled
 | ||||
| // into a concrete type
 | ||||
| type Unmarshaler func(interface{}) error | ||||
| 
 | ||||
| // Invoker invokes the client's request and response from the ttrpc server
 | ||||
| type Invoker func(context.Context, *Request, *Response) error | ||||
| 
 | ||||
| // UnaryServerInterceptor specifies the interceptor function for server request/response
 | ||||
| type UnaryServerInterceptor func(context.Context, Unmarshaler, *UnaryServerInfo, Method) (interface{}, error) | ||||
| 
 | ||||
| // UnaryClientInterceptor specifies the interceptor function for client request/response
 | ||||
| type UnaryClientInterceptor func(context.Context, *Request, *Response, *UnaryClientInfo, Invoker) error | ||||
| 
 | ||||
| func defaultServerInterceptor(ctx context.Context, unmarshal Unmarshaler, _ *UnaryServerInfo, method Method) (interface{}, error) { | ||||
| 	return method(ctx, unmarshal) | ||||
| } | ||||
| 
 | ||||
| func defaultClientInterceptor(ctx context.Context, req *Request, resp *Response, _ *UnaryClientInfo, invoker Invoker) error { | ||||
| 	return invoker(ctx, req, resp) | ||||
| } | ||||
| 
 | ||||
| type StreamServerInterceptor func(context.Context, StreamServer, *StreamServerInfo, StreamHandler) (interface{}, error) | ||||
| 
 | ||||
| func defaultStreamServerInterceptor(ctx context.Context, ss StreamServer, _ *StreamServerInfo, stream StreamHandler) (interface{}, error) { | ||||
| 	return stream(ctx, ss) | ||||
| } | ||||
| 
 | ||||
| type StreamClientInterceptor func(context.Context) | ||||
|  | @ -0,0 +1,107 @@ | |||
| /* | ||||
|    Copyright The containerd 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 ttrpc | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| // MD is the user type for ttrpc metadata
 | ||||
| type MD map[string][]string | ||||
| 
 | ||||
| // Get returns the metadata for a given key when they exist.
 | ||||
| // If there is no metadata, a nil slice and false are returned.
 | ||||
| func (m MD) Get(key string) ([]string, bool) { | ||||
| 	key = strings.ToLower(key) | ||||
| 	list, ok := m[key] | ||||
| 	if !ok || len(list) == 0 { | ||||
| 		return nil, false | ||||
| 	} | ||||
| 
 | ||||
| 	return list, true | ||||
| } | ||||
| 
 | ||||
| // Set sets the provided values for a given key.
 | ||||
| // The values will overwrite any existing values.
 | ||||
| // If no values provided, a key will be deleted.
 | ||||
| func (m MD) Set(key string, values ...string) { | ||||
| 	key = strings.ToLower(key) | ||||
| 	if len(values) == 0 { | ||||
| 		delete(m, key) | ||||
| 		return | ||||
| 	} | ||||
| 	m[key] = values | ||||
| } | ||||
| 
 | ||||
| // Append appends additional values to the given key.
 | ||||
| func (m MD) Append(key string, values ...string) { | ||||
| 	key = strings.ToLower(key) | ||||
| 	if len(values) == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	current, ok := m[key] | ||||
| 	if ok { | ||||
| 		m.Set(key, append(current, values...)...) | ||||
| 	} else { | ||||
| 		m.Set(key, values...) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (m MD) setRequest(r *Request) { | ||||
| 	for k, values := range m { | ||||
| 		for _, v := range values { | ||||
| 			r.Metadata = append(r.Metadata, &KeyValue{ | ||||
| 				Key:   k, | ||||
| 				Value: v, | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (m MD) fromRequest(r *Request) { | ||||
| 	for _, kv := range r.Metadata { | ||||
| 		m[kv.Key] = append(m[kv.Key], kv.Value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type metadataKey struct{} | ||||
| 
 | ||||
| // GetMetadata retrieves metadata from context.Context (previously attached with WithMetadata)
 | ||||
| func GetMetadata(ctx context.Context) (MD, bool) { | ||||
| 	metadata, ok := ctx.Value(metadataKey{}).(MD) | ||||
| 	return metadata, ok | ||||
| } | ||||
| 
 | ||||
| // GetMetadataValue gets a specific metadata value by name from context.Context
 | ||||
| func GetMetadataValue(ctx context.Context, name string) (string, bool) { | ||||
| 	metadata, ok := GetMetadata(ctx) | ||||
| 	if !ok { | ||||
| 		return "", false | ||||
| 	} | ||||
| 
 | ||||
| 	if list, ok := metadata.Get(name); ok { | ||||
| 		return list[0], true | ||||
| 	} | ||||
| 
 | ||||
| 	return "", false | ||||
| } | ||||
| 
 | ||||
| // WithMetadata attaches metadata map to a context.Context
 | ||||
| func WithMetadata(ctx context.Context, md MD) context.Context { | ||||
| 	return context.WithValue(ctx, metadataKey{}, md) | ||||
| } | ||||
|  | @ -0,0 +1,396 @@ | |||
| // Code generated by protoc-gen-go. DO NOT EDIT.
 | ||||
| // versions:
 | ||||
| // 	protoc-gen-go v1.28.1
 | ||||
| // 	protoc        v3.20.1
 | ||||
| // source: github.com/containerd/ttrpc/request.proto
 | ||||
| 
 | ||||
| package ttrpc | ||||
| 
 | ||||
| import ( | ||||
| 	status "google.golang.org/genproto/googleapis/rpc/status" | ||||
| 	protoreflect "google.golang.org/protobuf/reflect/protoreflect" | ||||
| 	protoimpl "google.golang.org/protobuf/runtime/protoimpl" | ||||
| 	reflect "reflect" | ||||
| 	sync "sync" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// Verify that this generated code is sufficiently up-to-date.
 | ||||
| 	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) | ||||
| 	// Verify that runtime/protoimpl is sufficiently up-to-date.
 | ||||
| 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) | ||||
| ) | ||||
| 
 | ||||
| type Request struct { | ||||
| 	state         protoimpl.MessageState | ||||
| 	sizeCache     protoimpl.SizeCache | ||||
| 	unknownFields protoimpl.UnknownFields | ||||
| 
 | ||||
| 	Service     string      `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` | ||||
| 	Method      string      `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"` | ||||
| 	Payload     []byte      `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` | ||||
| 	TimeoutNano int64       `protobuf:"varint,4,opt,name=timeout_nano,json=timeoutNano,proto3" json:"timeout_nano,omitempty"` | ||||
| 	Metadata    []*KeyValue `protobuf:"bytes,5,rep,name=metadata,proto3" json:"metadata,omitempty"` | ||||
| } | ||||
| 
 | ||||
| func (x *Request) Reset() { | ||||
| 	*x = Request{} | ||||
| 	if protoimpl.UnsafeEnabled { | ||||
| 		mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[0] | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		ms.StoreMessageInfo(mi) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (x *Request) String() string { | ||||
| 	return protoimpl.X.MessageStringOf(x) | ||||
| } | ||||
| 
 | ||||
| func (*Request) ProtoMessage() {} | ||||
| 
 | ||||
| func (x *Request) ProtoReflect() protoreflect.Message { | ||||
| 	mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[0] | ||||
| 	if protoimpl.UnsafeEnabled && x != nil { | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		if ms.LoadMessageInfo() == nil { | ||||
| 			ms.StoreMessageInfo(mi) | ||||
| 		} | ||||
| 		return ms | ||||
| 	} | ||||
| 	return mi.MessageOf(x) | ||||
| } | ||||
| 
 | ||||
| // Deprecated: Use Request.ProtoReflect.Descriptor instead.
 | ||||
| func (*Request) Descriptor() ([]byte, []int) { | ||||
| 	return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{0} | ||||
| } | ||||
| 
 | ||||
| func (x *Request) GetService() string { | ||||
| 	if x != nil { | ||||
| 		return x.Service | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
| 
 | ||||
| func (x *Request) GetMethod() string { | ||||
| 	if x != nil { | ||||
| 		return x.Method | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
| 
 | ||||
| func (x *Request) GetPayload() []byte { | ||||
| 	if x != nil { | ||||
| 		return x.Payload | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (x *Request) GetTimeoutNano() int64 { | ||||
| 	if x != nil { | ||||
| 		return x.TimeoutNano | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
| 
 | ||||
| func (x *Request) GetMetadata() []*KeyValue { | ||||
| 	if x != nil { | ||||
| 		return x.Metadata | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| type Response struct { | ||||
| 	state         protoimpl.MessageState | ||||
| 	sizeCache     protoimpl.SizeCache | ||||
| 	unknownFields protoimpl.UnknownFields | ||||
| 
 | ||||
| 	Status  *status.Status `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` | ||||
| 	Payload []byte         `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` | ||||
| } | ||||
| 
 | ||||
| func (x *Response) Reset() { | ||||
| 	*x = Response{} | ||||
| 	if protoimpl.UnsafeEnabled { | ||||
| 		mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[1] | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		ms.StoreMessageInfo(mi) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (x *Response) String() string { | ||||
| 	return protoimpl.X.MessageStringOf(x) | ||||
| } | ||||
| 
 | ||||
| func (*Response) ProtoMessage() {} | ||||
| 
 | ||||
| func (x *Response) ProtoReflect() protoreflect.Message { | ||||
| 	mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[1] | ||||
| 	if protoimpl.UnsafeEnabled && x != nil { | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		if ms.LoadMessageInfo() == nil { | ||||
| 			ms.StoreMessageInfo(mi) | ||||
| 		} | ||||
| 		return ms | ||||
| 	} | ||||
| 	return mi.MessageOf(x) | ||||
| } | ||||
| 
 | ||||
| // Deprecated: Use Response.ProtoReflect.Descriptor instead.
 | ||||
| func (*Response) Descriptor() ([]byte, []int) { | ||||
| 	return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{1} | ||||
| } | ||||
| 
 | ||||
| func (x *Response) GetStatus() *status.Status { | ||||
| 	if x != nil { | ||||
| 		return x.Status | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (x *Response) GetPayload() []byte { | ||||
| 	if x != nil { | ||||
| 		return x.Payload | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| type StringList struct { | ||||
| 	state         protoimpl.MessageState | ||||
| 	sizeCache     protoimpl.SizeCache | ||||
| 	unknownFields protoimpl.UnknownFields | ||||
| 
 | ||||
| 	List []string `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"` | ||||
| } | ||||
| 
 | ||||
| func (x *StringList) Reset() { | ||||
| 	*x = StringList{} | ||||
| 	if protoimpl.UnsafeEnabled { | ||||
| 		mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[2] | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		ms.StoreMessageInfo(mi) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (x *StringList) String() string { | ||||
| 	return protoimpl.X.MessageStringOf(x) | ||||
| } | ||||
| 
 | ||||
| func (*StringList) ProtoMessage() {} | ||||
| 
 | ||||
| func (x *StringList) ProtoReflect() protoreflect.Message { | ||||
| 	mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[2] | ||||
| 	if protoimpl.UnsafeEnabled && x != nil { | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		if ms.LoadMessageInfo() == nil { | ||||
| 			ms.StoreMessageInfo(mi) | ||||
| 		} | ||||
| 		return ms | ||||
| 	} | ||||
| 	return mi.MessageOf(x) | ||||
| } | ||||
| 
 | ||||
| // Deprecated: Use StringList.ProtoReflect.Descriptor instead.
 | ||||
| func (*StringList) Descriptor() ([]byte, []int) { | ||||
| 	return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{2} | ||||
| } | ||||
| 
 | ||||
| func (x *StringList) GetList() []string { | ||||
| 	if x != nil { | ||||
| 		return x.List | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| type KeyValue struct { | ||||
| 	state         protoimpl.MessageState | ||||
| 	sizeCache     protoimpl.SizeCache | ||||
| 	unknownFields protoimpl.UnknownFields | ||||
| 
 | ||||
| 	Key   string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` | ||||
| 	Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` | ||||
| } | ||||
| 
 | ||||
| func (x *KeyValue) Reset() { | ||||
| 	*x = KeyValue{} | ||||
| 	if protoimpl.UnsafeEnabled { | ||||
| 		mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[3] | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		ms.StoreMessageInfo(mi) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (x *KeyValue) String() string { | ||||
| 	return protoimpl.X.MessageStringOf(x) | ||||
| } | ||||
| 
 | ||||
| func (*KeyValue) ProtoMessage() {} | ||||
| 
 | ||||
| func (x *KeyValue) ProtoReflect() protoreflect.Message { | ||||
| 	mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[3] | ||||
| 	if protoimpl.UnsafeEnabled && x != nil { | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		if ms.LoadMessageInfo() == nil { | ||||
| 			ms.StoreMessageInfo(mi) | ||||
| 		} | ||||
| 		return ms | ||||
| 	} | ||||
| 	return mi.MessageOf(x) | ||||
| } | ||||
| 
 | ||||
| // Deprecated: Use KeyValue.ProtoReflect.Descriptor instead.
 | ||||
| func (*KeyValue) Descriptor() ([]byte, []int) { | ||||
| 	return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{3} | ||||
| } | ||||
| 
 | ||||
| func (x *KeyValue) GetKey() string { | ||||
| 	if x != nil { | ||||
| 		return x.Key | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
| 
 | ||||
| func (x *KeyValue) GetValue() string { | ||||
| 	if x != nil { | ||||
| 		return x.Value | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
| 
 | ||||
| var File_github_com_containerd_ttrpc_request_proto protoreflect.FileDescriptor | ||||
| 
 | ||||
| var file_github_com_containerd_ttrpc_request_proto_rawDesc = []byte{ | ||||
| 	0x0a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, | ||||
| 	0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x65, | ||||
| 	0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x74, 0x72, | ||||
| 	0x70, 0x63, 0x1a, 0x12, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, | ||||
| 	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa5, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, | ||||
| 	0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, | ||||
| 	0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, | ||||
| 	0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, | ||||
| 	0x74, 0x68, 0x6f, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, | ||||
| 	0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x21, | ||||
| 	0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x04, | ||||
| 	0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4e, 0x61, 0x6e, | ||||
| 	0x6f, 0x12, 0x2b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, | ||||
| 	0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x56, | ||||
| 	0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x45, | ||||
| 	0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x06, 0x73, 0x74, | ||||
| 	0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x53, 0x74, 0x61, | ||||
| 	0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, | ||||
| 	0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, | ||||
| 	0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x20, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, | ||||
| 	0x69, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, | ||||
| 	0x09, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0x32, 0x0a, 0x08, 0x4b, 0x65, 0x79, 0x56, 0x61, | ||||
| 	0x6c, 0x75, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, | ||||
| 	0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, | ||||
| 	0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x1d, 0x5a, 0x1b, 0x67, | ||||
| 	0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, | ||||
| 	0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, | ||||
| 	0x6f, 0x33, | ||||
| } | ||||
| 
 | ||||
| var ( | ||||
| 	file_github_com_containerd_ttrpc_request_proto_rawDescOnce sync.Once | ||||
| 	file_github_com_containerd_ttrpc_request_proto_rawDescData = file_github_com_containerd_ttrpc_request_proto_rawDesc | ||||
| ) | ||||
| 
 | ||||
| func file_github_com_containerd_ttrpc_request_proto_rawDescGZIP() []byte { | ||||
| 	file_github_com_containerd_ttrpc_request_proto_rawDescOnce.Do(func() { | ||||
| 		file_github_com_containerd_ttrpc_request_proto_rawDescData = protoimpl.X.CompressGZIP(file_github_com_containerd_ttrpc_request_proto_rawDescData) | ||||
| 	}) | ||||
| 	return file_github_com_containerd_ttrpc_request_proto_rawDescData | ||||
| } | ||||
| 
 | ||||
| var file_github_com_containerd_ttrpc_request_proto_msgTypes = make([]protoimpl.MessageInfo, 4) | ||||
| var file_github_com_containerd_ttrpc_request_proto_goTypes = []interface{}{ | ||||
| 	(*Request)(nil),       // 0: ttrpc.Request
 | ||||
| 	(*Response)(nil),      // 1: ttrpc.Response
 | ||||
| 	(*StringList)(nil),    // 2: ttrpc.StringList
 | ||||
| 	(*KeyValue)(nil),      // 3: ttrpc.KeyValue
 | ||||
| 	(*status.Status)(nil), // 4: Status
 | ||||
| } | ||||
| var file_github_com_containerd_ttrpc_request_proto_depIdxs = []int32{ | ||||
| 	3, // 0: ttrpc.Request.metadata:type_name -> ttrpc.KeyValue
 | ||||
| 	4, // 1: ttrpc.Response.status:type_name -> Status
 | ||||
| 	2, // [2:2] is the sub-list for method output_type
 | ||||
| 	2, // [2:2] is the sub-list for method input_type
 | ||||
| 	2, // [2:2] is the sub-list for extension type_name
 | ||||
| 	2, // [2:2] is the sub-list for extension extendee
 | ||||
| 	0, // [0:2] is the sub-list for field type_name
 | ||||
| } | ||||
| 
 | ||||
| func init() { file_github_com_containerd_ttrpc_request_proto_init() } | ||||
| func file_github_com_containerd_ttrpc_request_proto_init() { | ||||
| 	if File_github_com_containerd_ttrpc_request_proto != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if !protoimpl.UnsafeEnabled { | ||||
| 		file_github_com_containerd_ttrpc_request_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { | ||||
| 			switch v := v.(*Request); i { | ||||
| 			case 0: | ||||
| 				return &v.state | ||||
| 			case 1: | ||||
| 				return &v.sizeCache | ||||
| 			case 2: | ||||
| 				return &v.unknownFields | ||||
| 			default: | ||||
| 				return nil | ||||
| 			} | ||||
| 		} | ||||
| 		file_github_com_containerd_ttrpc_request_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { | ||||
| 			switch v := v.(*Response); i { | ||||
| 			case 0: | ||||
| 				return &v.state | ||||
| 			case 1: | ||||
| 				return &v.sizeCache | ||||
| 			case 2: | ||||
| 				return &v.unknownFields | ||||
| 			default: | ||||
| 				return nil | ||||
| 			} | ||||
| 		} | ||||
| 		file_github_com_containerd_ttrpc_request_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { | ||||
| 			switch v := v.(*StringList); i { | ||||
| 			case 0: | ||||
| 				return &v.state | ||||
| 			case 1: | ||||
| 				return &v.sizeCache | ||||
| 			case 2: | ||||
| 				return &v.unknownFields | ||||
| 			default: | ||||
| 				return nil | ||||
| 			} | ||||
| 		} | ||||
| 		file_github_com_containerd_ttrpc_request_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { | ||||
| 			switch v := v.(*KeyValue); i { | ||||
| 			case 0: | ||||
| 				return &v.state | ||||
| 			case 1: | ||||
| 				return &v.sizeCache | ||||
| 			case 2: | ||||
| 				return &v.unknownFields | ||||
| 			default: | ||||
| 				return nil | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	type x struct{} | ||||
| 	out := protoimpl.TypeBuilder{ | ||||
| 		File: protoimpl.DescBuilder{ | ||||
| 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | ||||
| 			RawDescriptor: file_github_com_containerd_ttrpc_request_proto_rawDesc, | ||||
| 			NumEnums:      0, | ||||
| 			NumMessages:   4, | ||||
| 			NumExtensions: 0, | ||||
| 			NumServices:   0, | ||||
| 		}, | ||||
| 		GoTypes:           file_github_com_containerd_ttrpc_request_proto_goTypes, | ||||
| 		DependencyIndexes: file_github_com_containerd_ttrpc_request_proto_depIdxs, | ||||
| 		MessageInfos:      file_github_com_containerd_ttrpc_request_proto_msgTypes, | ||||
| 	}.Build() | ||||
| 	File_github_com_containerd_ttrpc_request_proto = out.File | ||||
| 	file_github_com_containerd_ttrpc_request_proto_rawDesc = nil | ||||
| 	file_github_com_containerd_ttrpc_request_proto_goTypes = nil | ||||
| 	file_github_com_containerd_ttrpc_request_proto_depIdxs = nil | ||||
| } | ||||
|  | @ -0,0 +1,29 @@ | |||
| syntax = "proto3"; | ||||
| 
 | ||||
| package ttrpc; | ||||
| 
 | ||||
| import "proto/status.proto"; | ||||
| 
 | ||||
| option go_package = "github.com/containerd/ttrpc"; | ||||
| 
 | ||||
| message Request { | ||||
| 	string service = 1; | ||||
| 	string method = 2; | ||||
| 	bytes payload = 3; | ||||
| 	int64 timeout_nano = 4; | ||||
| 	repeated KeyValue metadata = 5; | ||||
| } | ||||
| 
 | ||||
| message Response { | ||||
| 	Status status = 1; | ||||
| 	bytes payload = 2; | ||||
| } | ||||
| 
 | ||||
| message StringList { | ||||
| 	repeated string list = 1; | ||||
| } | ||||
| 
 | ||||
| message KeyValue { | ||||
| 	string key = 1; | ||||
| 	string value = 2; | ||||
| } | ||||
|  | @ -0,0 +1,579 @@ | |||
| /* | ||||
|    Copyright The containerd 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 ttrpc | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 	"math/rand" | ||||
| 	"net" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 	"syscall" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"google.golang.org/grpc/codes" | ||||
| 	"google.golang.org/grpc/status" | ||||
| ) | ||||
| 
 | ||||
| type Server struct { | ||||
| 	config   *serverConfig | ||||
| 	services *serviceSet | ||||
| 	codec    codec | ||||
| 
 | ||||
| 	mu          sync.Mutex | ||||
| 	listeners   map[net.Listener]struct{} | ||||
| 	connections map[*serverConn]struct{} // all connections to current state
 | ||||
| 	done        chan struct{}            // marks point at which we stop serving requests
 | ||||
| } | ||||
| 
 | ||||
| func NewServer(opts ...ServerOpt) (*Server, error) { | ||||
| 	config := &serverConfig{} | ||||
| 	for _, opt := range opts { | ||||
| 		if err := opt(config); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	if config.interceptor == nil { | ||||
| 		config.interceptor = defaultServerInterceptor | ||||
| 	} | ||||
| 
 | ||||
| 	return &Server{ | ||||
| 		config:      config, | ||||
| 		services:    newServiceSet(config.interceptor), | ||||
| 		done:        make(chan struct{}), | ||||
| 		listeners:   make(map[net.Listener]struct{}), | ||||
| 		connections: make(map[*serverConn]struct{}), | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| // Register registers a map of methods to method handlers
 | ||||
| // TODO: Remove in 2.0, does not support streams
 | ||||
| func (s *Server) Register(name string, methods map[string]Method) { | ||||
| 	s.services.register(name, &ServiceDesc{Methods: methods}) | ||||
| } | ||||
| 
 | ||||
| func (s *Server) RegisterService(name string, desc *ServiceDesc) { | ||||
| 	s.services.register(name, desc) | ||||
| } | ||||
| 
 | ||||
| func (s *Server) Serve(ctx context.Context, l net.Listener) error { | ||||
| 	s.addListener(l) | ||||
| 	defer s.closeListener(l) | ||||
| 
 | ||||
| 	var ( | ||||
| 		backoff    time.Duration | ||||
| 		handshaker = s.config.handshaker | ||||
| 	) | ||||
| 
 | ||||
| 	if handshaker == nil { | ||||
| 		handshaker = handshakerFunc(noopHandshake) | ||||
| 	} | ||||
| 
 | ||||
| 	for { | ||||
| 		conn, err := l.Accept() | ||||
| 		if err != nil { | ||||
| 			select { | ||||
| 			case <-s.done: | ||||
| 				return ErrServerClosed | ||||
| 			default: | ||||
| 			} | ||||
| 
 | ||||
| 			if terr, ok := err.(interface { | ||||
| 				Temporary() bool | ||||
| 			}); ok && terr.Temporary() { | ||||
| 				if backoff == 0 { | ||||
| 					backoff = time.Millisecond | ||||
| 				} else { | ||||
| 					backoff *= 2 | ||||
| 				} | ||||
| 
 | ||||
| 				if max := time.Second; backoff > max { | ||||
| 					backoff = max | ||||
| 				} | ||||
| 
 | ||||
| 				sleep := time.Duration(rand.Int63n(int64(backoff))) | ||||
| 				logrus.WithError(err).Errorf("ttrpc: failed accept; backoff %v", sleep) | ||||
| 				time.Sleep(sleep) | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		backoff = 0 | ||||
| 
 | ||||
| 		approved, handshake, err := handshaker.Handshake(ctx, conn) | ||||
| 		if err != nil { | ||||
| 			logrus.WithError(err).Error("ttrpc: refusing connection after handshake") | ||||
| 			conn.Close() | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		sc, err := s.newConn(approved, handshake) | ||||
| 		if err != nil { | ||||
| 			logrus.WithError(err).Error("ttrpc: create connection failed") | ||||
| 			conn.Close() | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		go sc.run(ctx) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *Server) Shutdown(ctx context.Context) error { | ||||
| 	s.mu.Lock() | ||||
| 	select { | ||||
| 	case <-s.done: | ||||
| 	default: | ||||
| 		// protected by mutex
 | ||||
| 		close(s.done) | ||||
| 	} | ||||
| 	lnerr := s.closeListeners() | ||||
| 	s.mu.Unlock() | ||||
| 
 | ||||
| 	ticker := time.NewTicker(200 * time.Millisecond) | ||||
| 	defer ticker.Stop() | ||||
| 	for { | ||||
| 		s.closeIdleConns() | ||||
| 
 | ||||
| 		if s.countConnection() == 0 { | ||||
| 			break | ||||
| 		} | ||||
| 
 | ||||
| 		select { | ||||
| 		case <-ctx.Done(): | ||||
| 			return ctx.Err() | ||||
| 		case <-ticker.C: | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return lnerr | ||||
| } | ||||
| 
 | ||||
| // Close the server without waiting for active connections.
 | ||||
| func (s *Server) Close() error { | ||||
| 	s.mu.Lock() | ||||
| 	defer s.mu.Unlock() | ||||
| 
 | ||||
| 	select { | ||||
| 	case <-s.done: | ||||
| 	default: | ||||
| 		// protected by mutex
 | ||||
| 		close(s.done) | ||||
| 	} | ||||
| 
 | ||||
| 	err := s.closeListeners() | ||||
| 	for c := range s.connections { | ||||
| 		c.close() | ||||
| 		delete(s.connections, c) | ||||
| 	} | ||||
| 
 | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (s *Server) addListener(l net.Listener) { | ||||
| 	s.mu.Lock() | ||||
| 	defer s.mu.Unlock() | ||||
| 	s.listeners[l] = struct{}{} | ||||
| } | ||||
| 
 | ||||
| func (s *Server) closeListener(l net.Listener) error { | ||||
| 	s.mu.Lock() | ||||
| 	defer s.mu.Unlock() | ||||
| 
 | ||||
| 	return s.closeListenerLocked(l) | ||||
| } | ||||
| 
 | ||||
| func (s *Server) closeListenerLocked(l net.Listener) error { | ||||
| 	defer delete(s.listeners, l) | ||||
| 	return l.Close() | ||||
| } | ||||
| 
 | ||||
| func (s *Server) closeListeners() error { | ||||
| 	var err error | ||||
| 	for l := range s.listeners { | ||||
| 		if cerr := s.closeListenerLocked(l); cerr != nil && err == nil { | ||||
| 			err = cerr | ||||
| 		} | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (s *Server) addConnection(c *serverConn) error { | ||||
| 	s.mu.Lock() | ||||
| 	defer s.mu.Unlock() | ||||
| 
 | ||||
| 	select { | ||||
| 	case <-s.done: | ||||
| 		return ErrServerClosed | ||||
| 	default: | ||||
| 	} | ||||
| 
 | ||||
| 	s.connections[c] = struct{}{} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (s *Server) delConnection(c *serverConn) { | ||||
| 	s.mu.Lock() | ||||
| 	defer s.mu.Unlock() | ||||
| 
 | ||||
| 	delete(s.connections, c) | ||||
| } | ||||
| 
 | ||||
| func (s *Server) countConnection() int { | ||||
| 	s.mu.Lock() | ||||
| 	defer s.mu.Unlock() | ||||
| 
 | ||||
| 	return len(s.connections) | ||||
| } | ||||
| 
 | ||||
| func (s *Server) closeIdleConns() { | ||||
| 	s.mu.Lock() | ||||
| 	defer s.mu.Unlock() | ||||
| 
 | ||||
| 	for c := range s.connections { | ||||
| 		if st, ok := c.getState(); !ok || st == connStateActive { | ||||
| 			continue | ||||
| 		} | ||||
| 		c.close() | ||||
| 		delete(s.connections, c) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type connState int | ||||
| 
 | ||||
| const ( | ||||
| 	connStateActive = iota + 1 // outstanding requests
 | ||||
| 	connStateIdle              // no requests
 | ||||
| 	connStateClosed            // closed connection
 | ||||
| ) | ||||
| 
 | ||||
| func (cs connState) String() string { | ||||
| 	switch cs { | ||||
| 	case connStateActive: | ||||
| 		return "active" | ||||
| 	case connStateIdle: | ||||
| 		return "idle" | ||||
| 	case connStateClosed: | ||||
| 		return "closed" | ||||
| 	default: | ||||
| 		return "unknown" | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *Server) newConn(conn net.Conn, handshake interface{}) (*serverConn, error) { | ||||
| 	c := &serverConn{ | ||||
| 		server:    s, | ||||
| 		conn:      conn, | ||||
| 		handshake: handshake, | ||||
| 		shutdown:  make(chan struct{}), | ||||
| 	} | ||||
| 	c.setState(connStateIdle) | ||||
| 	if err := s.addConnection(c); err != nil { | ||||
| 		c.close() | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return c, nil | ||||
| } | ||||
| 
 | ||||
| type serverConn struct { | ||||
| 	server    *Server | ||||
| 	conn      net.Conn | ||||
| 	handshake interface{} // data from handshake, not used for now
 | ||||
| 	state     atomic.Value | ||||
| 
 | ||||
| 	shutdownOnce sync.Once | ||||
| 	shutdown     chan struct{} // forced shutdown, used by close
 | ||||
| } | ||||
| 
 | ||||
| func (c *serverConn) getState() (connState, bool) { | ||||
| 	cs, ok := c.state.Load().(connState) | ||||
| 	return cs, ok | ||||
| } | ||||
| 
 | ||||
| func (c *serverConn) setState(newstate connState) { | ||||
| 	c.state.Store(newstate) | ||||
| } | ||||
| 
 | ||||
| func (c *serverConn) close() error { | ||||
| 	c.shutdownOnce.Do(func() { | ||||
| 		close(c.shutdown) | ||||
| 	}) | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (c *serverConn) run(sctx context.Context) { | ||||
| 	type ( | ||||
| 		response struct { | ||||
| 			id          uint32 | ||||
| 			status      *status.Status | ||||
| 			data        []byte | ||||
| 			closeStream bool | ||||
| 			streaming   bool | ||||
| 		} | ||||
| 	) | ||||
| 
 | ||||
| 	var ( | ||||
| 		ch                     = newChannel(c.conn) | ||||
| 		ctx, cancel            = context.WithCancel(sctx) | ||||
| 		state        connState = connStateIdle | ||||
| 		responses              = make(chan response) | ||||
| 		recvErr                = make(chan error, 1) | ||||
| 		done                   = make(chan struct{}) | ||||
| 		streams                = sync.Map{} | ||||
| 		active       int32 | ||||
| 		lastStreamID uint32 | ||||
| 	) | ||||
| 
 | ||||
| 	defer c.conn.Close() | ||||
| 	defer cancel() | ||||
| 	defer close(done) | ||||
| 	defer c.server.delConnection(c) | ||||
| 
 | ||||
| 	sendStatus := func(id uint32, st *status.Status) bool { | ||||
| 		select { | ||||
| 		case responses <- response{ | ||||
| 			// even though we've had an invalid stream id, we send it
 | ||||
| 			// back on the same stream id so the client knows which
 | ||||
| 			// stream id was bad.
 | ||||
| 			id:          id, | ||||
| 			status:      st, | ||||
| 			closeStream: true, | ||||
| 		}: | ||||
| 			return true | ||||
| 		case <-c.shutdown: | ||||
| 			return false | ||||
| 		case <-done: | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	go func(recvErr chan error) { | ||||
| 		defer close(recvErr) | ||||
| 		for { | ||||
| 			select { | ||||
| 			case <-c.shutdown: | ||||
| 				return | ||||
| 			case <-done: | ||||
| 				return | ||||
| 			default: // proceed
 | ||||
| 			} | ||||
| 
 | ||||
| 			mh, p, err := ch.recv() | ||||
| 			if err != nil { | ||||
| 				status, ok := status.FromError(err) | ||||
| 				if !ok { | ||||
| 					recvErr <- err | ||||
| 					return | ||||
| 				} | ||||
| 
 | ||||
| 				// in this case, we send an error for that particular message
 | ||||
| 				// when the status is defined.
 | ||||
| 				if !sendStatus(mh.StreamID, status) { | ||||
| 					return | ||||
| 				} | ||||
| 
 | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			if mh.StreamID%2 != 1 { | ||||
| 				// enforce odd client initiated identifiers.
 | ||||
| 				if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "StreamID must be odd for client initiated streams")) { | ||||
| 					return | ||||
| 				} | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			if mh.Type == messageTypeData { | ||||
| 				i, ok := streams.Load(mh.StreamID) | ||||
| 				if !ok { | ||||
| 					if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "StreamID is no longer active")) { | ||||
| 						return | ||||
| 					} | ||||
| 				} | ||||
| 				sh := i.(*streamHandler) | ||||
| 				if mh.Flags&flagNoData != flagNoData { | ||||
| 					unmarshal := func(obj interface{}) error { | ||||
| 						err := protoUnmarshal(p, obj) | ||||
| 						ch.putmbuf(p) | ||||
| 						return err | ||||
| 					} | ||||
| 
 | ||||
| 					if err := sh.data(unmarshal); err != nil { | ||||
| 						if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "data handling error: %v", err)) { | ||||
| 							return | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				if mh.Flags&flagRemoteClosed == flagRemoteClosed { | ||||
| 					sh.closeSend() | ||||
| 					if len(p) > 0 { | ||||
| 						if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "data close message cannot include data")) { | ||||
| 							return | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} else if mh.Type == messageTypeRequest { | ||||
| 				if mh.StreamID <= lastStreamID { | ||||
| 					// enforce odd client initiated identifiers.
 | ||||
| 					if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "StreamID cannot be re-used and must increment")) { | ||||
| 						return | ||||
| 					} | ||||
| 					continue | ||||
| 
 | ||||
| 				} | ||||
| 				lastStreamID = mh.StreamID | ||||
| 
 | ||||
| 				// TODO: Make request type configurable
 | ||||
| 				// Unmarshaller which takes in a byte array and returns an interface?
 | ||||
| 				var req Request | ||||
| 				if err := c.server.codec.Unmarshal(p, &req); err != nil { | ||||
| 					ch.putmbuf(p) | ||||
| 					if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "unmarshal request error: %v", err)) { | ||||
| 						return | ||||
| 					} | ||||
| 					continue | ||||
| 				} | ||||
| 				ch.putmbuf(p) | ||||
| 
 | ||||
| 				id := mh.StreamID | ||||
| 				respond := func(status *status.Status, data []byte, streaming, closeStream bool) error { | ||||
| 					select { | ||||
| 					case responses <- response{ | ||||
| 						id:          id, | ||||
| 						status:      status, | ||||
| 						data:        data, | ||||
| 						closeStream: closeStream, | ||||
| 						streaming:   streaming, | ||||
| 					}: | ||||
| 					case <-done: | ||||
| 						return ErrClosed | ||||
| 					} | ||||
| 					return nil | ||||
| 				} | ||||
| 				sh, err := c.server.services.handle(ctx, &req, respond) | ||||
| 				if err != nil { | ||||
| 					status, _ := status.FromError(err) | ||||
| 					if !sendStatus(mh.StreamID, status) { | ||||
| 						return | ||||
| 					} | ||||
| 					continue | ||||
| 				} | ||||
| 
 | ||||
| 				streams.Store(id, sh) | ||||
| 				atomic.AddInt32(&active, 1) | ||||
| 			} | ||||
| 			// TODO: else we must ignore this for future compat. log this?
 | ||||
| 		} | ||||
| 	}(recvErr) | ||||
| 
 | ||||
| 	for { | ||||
| 		var ( | ||||
| 			newstate connState | ||||
| 			shutdown chan struct{} | ||||
| 		) | ||||
| 
 | ||||
| 		activeN := atomic.LoadInt32(&active) | ||||
| 		if activeN > 0 { | ||||
| 			newstate = connStateActive | ||||
| 			shutdown = nil | ||||
| 		} else { | ||||
| 			newstate = connStateIdle | ||||
| 			shutdown = c.shutdown // only enable this branch in idle mode
 | ||||
| 		} | ||||
| 		if newstate != state { | ||||
| 			c.setState(newstate) | ||||
| 			state = newstate | ||||
| 		} | ||||
| 
 | ||||
| 		select { | ||||
| 		case response := <-responses: | ||||
| 			if !response.streaming || response.status.Code() != codes.OK { | ||||
| 				p, err := c.server.codec.Marshal(&Response{ | ||||
| 					Status:  response.status.Proto(), | ||||
| 					Payload: response.data, | ||||
| 				}) | ||||
| 				if err != nil { | ||||
| 					logrus.WithError(err).Error("failed marshaling response") | ||||
| 					return | ||||
| 				} | ||||
| 
 | ||||
| 				if err := ch.send(response.id, messageTypeResponse, 0, p); err != nil { | ||||
| 					logrus.WithError(err).Error("failed sending message on channel") | ||||
| 					return | ||||
| 				} | ||||
| 			} else { | ||||
| 				var flags uint8 | ||||
| 				if response.closeStream { | ||||
| 					flags = flagRemoteClosed | ||||
| 				} | ||||
| 				if response.data == nil { | ||||
| 					flags = flags | flagNoData | ||||
| 				} | ||||
| 				if err := ch.send(response.id, messageTypeData, flags, response.data); err != nil { | ||||
| 					logrus.WithError(err).Error("failed sending message on channel") | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if response.closeStream { | ||||
| 				// The ttrpc protocol currently does not support the case where
 | ||||
| 				// the server is localClosed but not remoteClosed. Once the server
 | ||||
| 				// is closing, the whole stream may be considered finished
 | ||||
| 				streams.Delete(response.id) | ||||
| 				atomic.AddInt32(&active, -1) | ||||
| 			} | ||||
| 		case err := <-recvErr: | ||||
| 			// TODO(stevvooe): Not wildly clear what we should do in this
 | ||||
| 			// branch. Basically, it means that we are no longer receiving
 | ||||
| 			// requests due to a terminal error.
 | ||||
| 			recvErr = nil // connection is now "closing"
 | ||||
| 			if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, syscall.ECONNRESET) { | ||||
| 				// The client went away and we should stop processing
 | ||||
| 				// requests, so that the client connection is closed
 | ||||
| 				return | ||||
| 			} | ||||
| 			logrus.WithError(err).Error("error receiving message") | ||||
| 			// else, initiate shutdown
 | ||||
| 		case <-shutdown: | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| var noopFunc = func() {} | ||||
| 
 | ||||
| func getRequestContext(ctx context.Context, req *Request) (retCtx context.Context, cancel func()) { | ||||
| 	if len(req.Metadata) > 0 { | ||||
| 		md := MD{} | ||||
| 		md.fromRequest(req) | ||||
| 		ctx = WithMetadata(ctx, md) | ||||
| 	} | ||||
| 
 | ||||
| 	cancel = noopFunc | ||||
| 	if req.TimeoutNano == 0 { | ||||
| 		return ctx, cancel | ||||
| 	} | ||||
| 
 | ||||
| 	ctx, cancel = context.WithTimeout(ctx, time.Duration(req.TimeoutNano)) | ||||
| 	return ctx, cancel | ||||
| } | ||||
|  | @ -0,0 +1,275 @@ | |||
| /* | ||||
|    Copyright The containerd 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 ttrpc | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"unsafe" | ||||
| 
 | ||||
| 	"google.golang.org/grpc/codes" | ||||
| 	"google.golang.org/grpc/status" | ||||
| 	"google.golang.org/protobuf/proto" | ||||
| ) | ||||
| 
 | ||||
| type Method func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) | ||||
| 
 | ||||
| type StreamHandler func(context.Context, StreamServer) (interface{}, error) | ||||
| 
 | ||||
| type Stream struct { | ||||
| 	Handler         StreamHandler | ||||
| 	StreamingClient bool | ||||
| 	StreamingServer bool | ||||
| } | ||||
| 
 | ||||
| type ServiceDesc struct { | ||||
| 	Methods map[string]Method | ||||
| 	Streams map[string]Stream | ||||
| } | ||||
| 
 | ||||
| type serviceSet struct { | ||||
| 	services          map[string]*ServiceDesc | ||||
| 	unaryInterceptor  UnaryServerInterceptor | ||||
| 	streamInterceptor StreamServerInterceptor | ||||
| } | ||||
| 
 | ||||
| func newServiceSet(interceptor UnaryServerInterceptor) *serviceSet { | ||||
| 	return &serviceSet{ | ||||
| 		services:          make(map[string]*ServiceDesc), | ||||
| 		unaryInterceptor:  interceptor, | ||||
| 		streamInterceptor: defaultStreamServerInterceptor, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *serviceSet) register(name string, desc *ServiceDesc) { | ||||
| 	if _, ok := s.services[name]; ok { | ||||
| 		panic(fmt.Errorf("duplicate service %v registered", name)) | ||||
| 	} | ||||
| 
 | ||||
| 	s.services[name] = desc | ||||
| } | ||||
| 
 | ||||
| func (s *serviceSet) unaryCall(ctx context.Context, method Method, info *UnaryServerInfo, data []byte) (p []byte, st *status.Status) { | ||||
| 	unmarshal := func(obj interface{}) error { | ||||
| 		return protoUnmarshal(data, obj) | ||||
| 	} | ||||
| 
 | ||||
| 	resp, err := s.unaryInterceptor(ctx, unmarshal, info, method) | ||||
| 	if err == nil { | ||||
| 		if isNil(resp) { | ||||
| 			err = errors.New("ttrpc: marshal called with nil") | ||||
| 		} else { | ||||
| 			p, err = protoMarshal(resp) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	st, ok := status.FromError(err) | ||||
| 	if !ok { | ||||
| 		st = status.New(convertCode(err), err.Error()) | ||||
| 	} | ||||
| 
 | ||||
| 	return p, st | ||||
| } | ||||
| 
 | ||||
| func (s *serviceSet) streamCall(ctx context.Context, stream StreamHandler, info *StreamServerInfo, ss StreamServer) (p []byte, st *status.Status) { | ||||
| 	resp, err := s.streamInterceptor(ctx, ss, info, stream) | ||||
| 	if err == nil { | ||||
| 		p, err = protoMarshal(resp) | ||||
| 	} | ||||
| 	st, ok := status.FromError(err) | ||||
| 	if !ok { | ||||
| 		st = status.New(convertCode(err), err.Error()) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (s *serviceSet) handle(ctx context.Context, req *Request, respond func(*status.Status, []byte, bool, bool) error) (*streamHandler, error) { | ||||
| 	srv, ok := s.services[req.Service] | ||||
| 	if !ok { | ||||
| 		return nil, status.Errorf(codes.Unimplemented, "service %v", req.Service) | ||||
| 	} | ||||
| 
 | ||||
| 	if method, ok := srv.Methods[req.Method]; ok { | ||||
| 		go func() { | ||||
| 			ctx, cancel := getRequestContext(ctx, req) | ||||
| 			defer cancel() | ||||
| 
 | ||||
| 			info := &UnaryServerInfo{ | ||||
| 				FullMethod: fullPath(req.Service, req.Method), | ||||
| 			} | ||||
| 			p, st := s.unaryCall(ctx, method, info, req.Payload) | ||||
| 
 | ||||
| 			respond(st, p, false, true) | ||||
| 		}() | ||||
| 		return nil, nil | ||||
| 	} | ||||
| 	if stream, ok := srv.Streams[req.Method]; ok { | ||||
| 		ctx, cancel := getRequestContext(ctx, req) | ||||
| 		info := &StreamServerInfo{ | ||||
| 			FullMethod:      fullPath(req.Service, req.Method), | ||||
| 			StreamingClient: stream.StreamingClient, | ||||
| 			StreamingServer: stream.StreamingServer, | ||||
| 		} | ||||
| 		sh := &streamHandler{ | ||||
| 			ctx:     ctx, | ||||
| 			respond: respond, | ||||
| 			recv:    make(chan Unmarshaler, 5), | ||||
| 			info:    info, | ||||
| 		} | ||||
| 		go func() { | ||||
| 			defer cancel() | ||||
| 			p, st := s.streamCall(ctx, stream.Handler, info, sh) | ||||
| 			respond(st, p, stream.StreamingServer, true) | ||||
| 		}() | ||||
| 
 | ||||
| 		if req.Payload != nil { | ||||
| 			unmarshal := func(obj interface{}) error { | ||||
| 				return protoUnmarshal(req.Payload, obj) | ||||
| 			} | ||||
| 			if err := sh.data(unmarshal); err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return sh, nil | ||||
| 	} | ||||
| 	return nil, status.Errorf(codes.Unimplemented, "method %v", req.Method) | ||||
| } | ||||
| 
 | ||||
| type streamHandler struct { | ||||
| 	ctx     context.Context | ||||
| 	respond func(*status.Status, []byte, bool, bool) error | ||||
| 	recv    chan Unmarshaler | ||||
| 	info    *StreamServerInfo | ||||
| 
 | ||||
| 	remoteClosed bool | ||||
| 	localClosed  bool | ||||
| } | ||||
| 
 | ||||
| func (s *streamHandler) closeSend() { | ||||
| 	if !s.remoteClosed { | ||||
| 		s.remoteClosed = true | ||||
| 		close(s.recv) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *streamHandler) data(unmarshal Unmarshaler) error { | ||||
| 	if s.remoteClosed { | ||||
| 		return ErrStreamClosed | ||||
| 	} | ||||
| 	select { | ||||
| 	case s.recv <- unmarshal: | ||||
| 		return nil | ||||
| 	case <-s.ctx.Done(): | ||||
| 		return s.ctx.Err() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *streamHandler) SendMsg(m interface{}) error { | ||||
| 	if s.localClosed { | ||||
| 		return ErrStreamClosed | ||||
| 	} | ||||
| 	p, err := protoMarshal(m) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return s.respond(nil, p, true, false) | ||||
| } | ||||
| 
 | ||||
| func (s *streamHandler) RecvMsg(m interface{}) error { | ||||
| 	select { | ||||
| 	case unmarshal, ok := <-s.recv: | ||||
| 		if !ok { | ||||
| 			return io.EOF | ||||
| 		} | ||||
| 		return unmarshal(m) | ||||
| 	case <-s.ctx.Done(): | ||||
| 		return s.ctx.Err() | ||||
| 
 | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func protoUnmarshal(p []byte, obj interface{}) error { | ||||
| 	switch v := obj.(type) { | ||||
| 	case proto.Message: | ||||
| 		if err := proto.Unmarshal(p, v); err != nil { | ||||
| 			return status.Errorf(codes.Internal, "ttrpc: error unmarshalling payload: %v", err.Error()) | ||||
| 		} | ||||
| 	default: | ||||
| 		return status.Errorf(codes.Internal, "ttrpc: error unsupported request type: %T", v) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func protoMarshal(obj interface{}) ([]byte, error) { | ||||
| 	if obj == nil { | ||||
| 		return nil, nil | ||||
| 	} | ||||
| 
 | ||||
| 	switch v := obj.(type) { | ||||
| 	case proto.Message: | ||||
| 		r, err := proto.Marshal(v) | ||||
| 		if err != nil { | ||||
| 			return nil, status.Errorf(codes.Internal, "ttrpc: error marshaling payload: %v", err.Error()) | ||||
| 		} | ||||
| 
 | ||||
| 		return r, nil | ||||
| 	default: | ||||
| 		return nil, status.Errorf(codes.Internal, "ttrpc: error unsupported response type: %T", v) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // convertCode maps stdlib go errors into grpc space.
 | ||||
| //
 | ||||
| // This is ripped from the grpc-go code base.
 | ||||
| func convertCode(err error) codes.Code { | ||||
| 	switch err { | ||||
| 	case nil: | ||||
| 		return codes.OK | ||||
| 	case io.EOF: | ||||
| 		return codes.OutOfRange | ||||
| 	case io.ErrClosedPipe, io.ErrNoProgress, io.ErrShortBuffer, io.ErrShortWrite, io.ErrUnexpectedEOF: | ||||
| 		return codes.FailedPrecondition | ||||
| 	case os.ErrInvalid: | ||||
| 		return codes.InvalidArgument | ||||
| 	case context.Canceled: | ||||
| 		return codes.Canceled | ||||
| 	case context.DeadlineExceeded: | ||||
| 		return codes.DeadlineExceeded | ||||
| 	} | ||||
| 	switch { | ||||
| 	case os.IsExist(err): | ||||
| 		return codes.AlreadyExists | ||||
| 	case os.IsNotExist(err): | ||||
| 		return codes.NotFound | ||||
| 	case os.IsPermission(err): | ||||
| 		return codes.PermissionDenied | ||||
| 	} | ||||
| 	return codes.Unknown | ||||
| } | ||||
| 
 | ||||
| func fullPath(service, method string) string { | ||||
| 	return "/" + path.Join(service, method) | ||||
| } | ||||
| 
 | ||||
| func isNil(resp interface{}) bool { | ||||
| 	return (*[2]uintptr)(unsafe.Pointer(&resp))[1] == 0 | ||||
| } | ||||
|  | @ -0,0 +1,84 @@ | |||
| /* | ||||
|    Copyright The containerd 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 ttrpc | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"sync" | ||||
| ) | ||||
| 
 | ||||
| type streamID uint32 | ||||
| 
 | ||||
| type streamMessage struct { | ||||
| 	header  messageHeader | ||||
| 	payload []byte | ||||
| } | ||||
| 
 | ||||
| type stream struct { | ||||
| 	id     streamID | ||||
| 	sender sender | ||||
| 	recv   chan *streamMessage | ||||
| 
 | ||||
| 	closeOnce sync.Once | ||||
| 	recvErr   error | ||||
| 	recvClose chan struct{} | ||||
| } | ||||
| 
 | ||||
| func newStream(id streamID, send sender) *stream { | ||||
| 	return &stream{ | ||||
| 		id:        id, | ||||
| 		sender:    send, | ||||
| 		recv:      make(chan *streamMessage, 1), | ||||
| 		recvClose: make(chan struct{}), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *stream) closeWithError(err error) error { | ||||
| 	s.closeOnce.Do(func() { | ||||
| 		if err != nil { | ||||
| 			s.recvErr = err | ||||
| 		} else { | ||||
| 			s.recvErr = ErrClosed | ||||
| 		} | ||||
| 		close(s.recvClose) | ||||
| 	}) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (s *stream) send(mt messageType, flags uint8, b []byte) error { | ||||
| 	return s.sender.send(uint32(s.id), mt, flags, b) | ||||
| } | ||||
| 
 | ||||
| func (s *stream) receive(ctx context.Context, msg *streamMessage) error { | ||||
| 	select { | ||||
| 	case <-s.recvClose: | ||||
| 		return s.recvErr | ||||
| 	default: | ||||
| 	} | ||||
| 	select { | ||||
| 	case <-s.recvClose: | ||||
| 		return s.recvErr | ||||
| 	case s.recv <- msg: | ||||
| 		return nil | ||||
| 	case <-ctx.Done(): | ||||
| 		return ctx.Err() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type sender interface { | ||||
| 	send(uint32, messageType, uint8, []byte) error | ||||
| } | ||||
|  | @ -0,0 +1,22 @@ | |||
| /* | ||||
|    Copyright The containerd 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 ttrpc | ||||
| 
 | ||||
| type StreamServer interface { | ||||
| 	SendMsg(m interface{}) error | ||||
| 	RecvMsg(m interface{}) error | ||||
| } | ||||
|  | @ -0,0 +1,16 @@ | |||
| syntax = "proto3"; | ||||
| 
 | ||||
| package ttrpc; | ||||
| 
 | ||||
| option go_package = "github.com/containerd/ttrpc/internal"; | ||||
| 
 | ||||
| message TestPayload { | ||||
| 	string foo = 1; | ||||
| 	int64 deadline = 2; | ||||
| 	string metadata = 3; | ||||
| } | ||||
| 
 | ||||
| message EchoPayload { | ||||
| 	int64 seq = 1; | ||||
| 	string msg = 2; | ||||
| } | ||||
|  | @ -0,0 +1,105 @@ | |||
| /* | ||||
|    Copyright The containerd 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 ttrpc | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"syscall" | ||||
| 
 | ||||
| 	"golang.org/x/sys/unix" | ||||
| ) | ||||
| 
 | ||||
| type UnixCredentialsFunc func(*unix.Ucred) error | ||||
| 
 | ||||
| func (fn UnixCredentialsFunc) Handshake(_ context.Context, conn net.Conn) (net.Conn, interface{}, error) { | ||||
| 	uc, err := requireUnixSocket(conn) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: require unix socket: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	rs, err := uc.SyscallConn() | ||||
| 	if err != nil { | ||||
| 		return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: (net.UnixConn).SyscallConn failed: %w", err) | ||||
| 	} | ||||
| 	var ( | ||||
| 		ucred    *unix.Ucred | ||||
| 		ucredErr error | ||||
| 	) | ||||
| 	if err := rs.Control(func(fd uintptr) { | ||||
| 		ucred, ucredErr = unix.GetsockoptUcred(int(fd), unix.SOL_SOCKET, unix.SO_PEERCRED) | ||||
| 	}); err != nil { | ||||
| 		return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: (*syscall.RawConn).Control failed: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if ucredErr != nil { | ||||
| 		return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: failed to retrieve socket peer credentials: %w", ucredErr) | ||||
| 	} | ||||
| 
 | ||||
| 	if err := fn(ucred); err != nil { | ||||
| 		return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: credential check failed: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return uc, ucred, nil | ||||
| } | ||||
| 
 | ||||
| // UnixSocketRequireUidGid requires specific *effective* UID/GID, rather than the real UID/GID.
 | ||||
| //
 | ||||
| // For example, if a daemon binary is owned by the root (UID 0) with SUID bit but running as an
 | ||||
| // unprivileged user (UID 1001), the effective UID becomes 0, and the real UID becomes 1001.
 | ||||
| // So calling this function with uid=0 allows a connection from effective UID 0 but rejects
 | ||||
| // a connection from effective UID 1001.
 | ||||
| //
 | ||||
| // See socket(7), SO_PEERCRED: "The returned credentials are those that were in effect at the time of the call to connect(2) or socketpair(2)."
 | ||||
| func UnixSocketRequireUidGid(uid, gid int) UnixCredentialsFunc { | ||||
| 	return func(ucred *unix.Ucred) error { | ||||
| 		return requireUidGid(ucred, uid, gid) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func UnixSocketRequireRoot() UnixCredentialsFunc { | ||||
| 	return UnixSocketRequireUidGid(0, 0) | ||||
| } | ||||
| 
 | ||||
| // UnixSocketRequireSameUser resolves the current effective unix user and returns a
 | ||||
| // UnixCredentialsFunc that will validate incoming unix connections against the
 | ||||
| // current credentials.
 | ||||
| //
 | ||||
| // This is useful when using abstract sockets that are accessible by all users.
 | ||||
| func UnixSocketRequireSameUser() UnixCredentialsFunc { | ||||
| 	euid, egid := os.Geteuid(), os.Getegid() | ||||
| 	return UnixSocketRequireUidGid(euid, egid) | ||||
| } | ||||
| 
 | ||||
| func requireUidGid(ucred *unix.Ucred, uid, gid int) error { | ||||
| 	if (uid != -1 && uint32(uid) != ucred.Uid) || (gid != -1 && uint32(gid) != ucred.Gid) { | ||||
| 		return fmt.Errorf("ttrpc: invalid credentials: %v", syscall.EPERM) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func requireUnixSocket(conn net.Conn) (*net.UnixConn, error) { | ||||
| 	uc, ok := conn.(*net.UnixConn) | ||||
| 	if !ok { | ||||
| 		return nil, errors.New("a unix socket connection is required") | ||||
| 	} | ||||
| 
 | ||||
| 	return uc, nil | ||||
| } | ||||
|  | @ -25,6 +25,7 @@ import ( | |||
| 	sdktrace "go.opentelemetry.io/otel/sdk/trace" | ||||
| 	"go.opentelemetry.io/otel/trace" | ||||
| 	"google.golang.org/grpc" | ||||
| 	"google.golang.org/grpc/codes" | ||||
| 	"google.golang.org/grpc/credentials" | ||||
| 	"google.golang.org/grpc/credentials/insecure" | ||||
| ) | ||||
|  | @ -184,6 +185,19 @@ func (c *Client) Dialer() session.Dialer { | |||
| 	return grpchijack.Dialer(c.ControlClient()) | ||||
| } | ||||
| 
 | ||||
| func (c *Client) Wait(ctx context.Context) error { | ||||
| 	opts := []grpc.CallOption{grpc.WaitForReady(true)} | ||||
| 	_, err := c.ControlClient().Info(ctx, &controlapi.InfoRequest{}, opts...) | ||||
| 	if err != nil { | ||||
| 		if code := grpcerrors.Code(err); code == codes.Unimplemented { | ||||
| 			// only buildkit v0.11+ supports the info api, but an unimplemented
 | ||||
| 			// response error is still a response so we can ignore it
 | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (c *Client) Close() error { | ||||
| 	return c.conn.Close() | ||||
| } | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ type asyncState struct { | |||
| 	target State | ||||
| 	set    bool | ||||
| 	err    error | ||||
| 	g      flightcontrol.Group | ||||
| 	g      flightcontrol.Group[State] | ||||
| } | ||||
| 
 | ||||
| func (as *asyncState) Output() Output { | ||||
|  | @ -53,7 +53,7 @@ func (as *asyncState) ToInput(ctx context.Context, c *Constraints) (*pb.Input, e | |||
| } | ||||
| 
 | ||||
| func (as *asyncState) Do(ctx context.Context, c *Constraints) error { | ||||
| 	_, err := as.g.Do(ctx, "", func(ctx context.Context) (interface{}, error) { | ||||
| 	_, err := as.g.Do(ctx, "", func(ctx context.Context) (State, error) { | ||||
| 		if as.set { | ||||
| 			return as.target, as.err | ||||
| 		} | ||||
|  |  | |||
|  | @ -145,6 +145,7 @@ func Mkdir(p string, m os.FileMode, opt ...MkdirOption) *FileAction { | |||
| 	for _, o := range opt { | ||||
| 		o.SetMkdirOption(&mi) | ||||
| 	} | ||||
| 
 | ||||
| 	return &FileAction{ | ||||
| 		action: &fileActionMkdir{ | ||||
| 			file: p, | ||||
|  | @ -447,7 +448,6 @@ func Copy(input CopyInput, src, dest string, opts ...CopyOption) *FileAction { | |||
| 	for _, o := range opts { | ||||
| 		o.SetCopyOption(&mi) | ||||
| 	} | ||||
| 
 | ||||
| 	return &FileAction{ | ||||
| 		action: &fileActionCopy{ | ||||
| 			state: state, | ||||
|  | @ -523,22 +523,19 @@ func (a *fileActionCopy) toProtoAction(ctx context.Context, parent string, base | |||
| 
 | ||||
| func (a *fileActionCopy) sourcePath(ctx context.Context) (string, error) { | ||||
| 	p := path.Clean(a.src) | ||||
| 	dir := "/" | ||||
| 	var err error | ||||
| 	if !path.IsAbs(p) { | ||||
| 		if a.state != nil { | ||||
| 			dir, err := a.state.GetDir(ctx) | ||||
| 			if err != nil { | ||||
| 				return "", err | ||||
| 			} | ||||
| 			p = path.Join("/", dir, p) | ||||
| 			dir, err = a.state.GetDir(ctx) | ||||
| 		} else if a.fas != nil { | ||||
| 			dir, err := a.fas.state.GetDir(ctx) | ||||
| 			dir, err = a.fas.state.GetDir(ctx) | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 			p = path.Join("/", dir, p) | ||||
| 	} | ||||
| 	} | ||||
| 	return p, nil | ||||
| 	return path.Join(dir, p), nil | ||||
| } | ||||
| 
 | ||||
| func (a *fileActionCopy) addCaps(f *FileOp) { | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ import ( | |||
| 	"github.com/google/shlex" | ||||
| 	"github.com/moby/buildkit/solver/pb" | ||||
| 	ocispecs "github.com/opencontainers/image-spec/specs-go/v1" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
| 
 | ||||
| type contextKeyT string | ||||
|  | @ -78,7 +79,7 @@ func dirf(value string, replace bool, v ...interface{}) StateOption { | |||
| 			if !path.IsAbs(value) { | ||||
| 				prev, err := getDir(s)(ctx, c) | ||||
| 				if err != nil { | ||||
| 					return nil, err | ||||
| 					return nil, errors.Wrap(err, "getting dir from state") | ||||
| 				} | ||||
| 				if prev == "" { | ||||
| 					prev = "/" | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ package llb | |||
| import ( | ||||
| 	"context" | ||||
| 
 | ||||
| 	spb "github.com/moby/buildkit/sourcepolicy/pb" | ||||
| 	digest "github.com/opencontainers/go-digest" | ||||
| 	ocispecs "github.com/opencontainers/image-spec/specs-go/v1" | ||||
| ) | ||||
|  | @ -31,7 +32,7 @@ func WithLayerLimit(l int) ImageOption { | |||
| 
 | ||||
| // ImageMetaResolver can resolve image config metadata from a reference
 | ||||
| type ImageMetaResolver interface { | ||||
| 	ResolveImageConfig(ctx context.Context, ref string, opt ResolveImageConfigOpt) (digest.Digest, []byte, error) | ||||
| 	ResolveImageConfig(ctx context.Context, ref string, opt ResolveImageConfigOpt) (string, digest.Digest, []byte, error) | ||||
| } | ||||
| 
 | ||||
| type ResolverType int | ||||
|  | @ -49,6 +50,8 @@ type ResolveImageConfigOpt struct { | |||
| 	LogName     string | ||||
| 
 | ||||
| 	Store ResolveImageConfigOptStore | ||||
| 
 | ||||
| 	SourcePolicies []*spb.Policy | ||||
| } | ||||
| 
 | ||||
| type ResolveImageConfigOptStore struct { | ||||
|  |  | |||
|  | @ -135,7 +135,7 @@ func Image(ref string, opts ...ImageOption) State { | |||
| 				if p == nil { | ||||
| 					p = c.Platform | ||||
| 				} | ||||
| 				_, dt, err := info.metaResolver.ResolveImageConfig(ctx, ref, ResolveImageConfigOpt{ | ||||
| 				_, _, dt, err := info.metaResolver.ResolveImageConfig(ctx, ref, ResolveImageConfigOpt{ | ||||
| 					Platform:     p, | ||||
| 					ResolveMode:  info.resolveMode.String(), | ||||
| 					ResolverType: ResolverTypeRegistry, | ||||
|  | @ -151,7 +151,7 @@ func Image(ref string, opts ...ImageOption) State { | |||
| 			if p == nil { | ||||
| 				p = c.Platform | ||||
| 			} | ||||
| 			dgst, dt, err := info.metaResolver.ResolveImageConfig(context.TODO(), ref, ResolveImageConfigOpt{ | ||||
| 			ref, dgst, dt, err := info.metaResolver.ResolveImageConfig(context.TODO(), ref, ResolveImageConfigOpt{ | ||||
| 				Platform:     p, | ||||
| 				ResolveMode:  info.resolveMode.String(), | ||||
| 				ResolverType: ResolverTypeRegistry, | ||||
|  | @ -159,6 +159,10 @@ func Image(ref string, opts ...ImageOption) State { | |||
| 			if err != nil { | ||||
| 				return State{}, err | ||||
| 			} | ||||
| 			r, err := reference.ParseNormalizedNamed(ref) | ||||
| 			if err != nil { | ||||
| 				return State{}, err | ||||
| 			} | ||||
| 			if dgst != "" { | ||||
| 				r, err = reference.WithDigest(r, dgst) | ||||
| 				if err != nil { | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ type HealthConfig struct { | |||
| 	Interval      time.Duration `json:",omitempty"` // Interval is the time to wait between checks.
 | ||||
| 	Timeout       time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung.
 | ||||
| 	StartPeriod   time.Duration `json:",omitempty"` // The start period for the container to initialize before the retries starts to count down.
 | ||||
| 	StartInterval time.Duration `json:",omitempty"` // StartInterval is the time to wait between checks during the start period.
 | ||||
| 
 | ||||
| 	// Retries is the number of consecutive failures needed to consider a container as unhealthy.
 | ||||
| 	// Zero means inherit.
 | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ import ( | |||
| 	"github.com/moby/buildkit/frontend/gateway/client" | ||||
| 	"github.com/moby/buildkit/solver/pb" | ||||
| 	"github.com/moby/buildkit/util/flightcontrol" | ||||
| 	digest "github.com/opencontainers/go-digest" | ||||
| 	ocispecs "github.com/opencontainers/image-spec/specs-go/v1" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
|  | @ -76,7 +77,7 @@ type Client struct { | |||
| 	client      client.Client | ||||
| 	ignoreCache []string | ||||
| 	bctx        *buildContext | ||||
| 	g           flightcontrol.Group | ||||
| 	g           flightcontrol.Group[*buildContext] | ||||
| 	bopts       client.BuildOpts | ||||
| 
 | ||||
| 	dockerignore []byte | ||||
|  | @ -96,6 +97,7 @@ type ContextOpt struct { | |||
| 	LocalOpts      []llb.LocalOption | ||||
| 	Platform       *ocispecs.Platform | ||||
| 	ResolveMode    string | ||||
| 	CaptureDigest  *digest.Digest | ||||
| } | ||||
| 
 | ||||
| func validateMinCaps(c client.Client) error { | ||||
|  | @ -278,7 +280,7 @@ func (bc *Client) init() error { | |||
| } | ||||
| 
 | ||||
| func (bc *Client) buildContext(ctx context.Context) (*buildContext, error) { | ||||
| 	bctx, err := bc.g.Do(ctx, "initcontext", func(ctx context.Context) (interface{}, error) { | ||||
| 	return bc.g.Do(ctx, "initcontext", func(ctx context.Context) (*buildContext, error) { | ||||
| 		if bc.bctx != nil { | ||||
| 			return bc.bctx, nil | ||||
| 		} | ||||
|  | @ -288,10 +290,6 @@ func (bc *Client) buildContext(ctx context.Context) (*buildContext, error) { | |||
| 		} | ||||
| 		return bctx, err | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return bctx.(*buildContext), nil | ||||
| } | ||||
| 
 | ||||
| func (bc *Client) ReadEntrypoint(ctx context.Context, lang string, opts ...llb.LocalOption) (*Source, error) { | ||||
|  |  | |||
|  | @ -13,25 +13,36 @@ import ( | |||
| 	"github.com/moby/buildkit/exporter/containerimage/image" | ||||
| 	"github.com/moby/buildkit/frontend/dockerfile/dockerignore" | ||||
| 	"github.com/moby/buildkit/frontend/gateway/client" | ||||
| 	"github.com/moby/buildkit/util/imageutil" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	contextPrefix       = "context:" | ||||
| 	inputMetadataPrefix = "input-metadata:" | ||||
| 	maxContextRecursion = 10 | ||||
| ) | ||||
| 
 | ||||
| func (bc *Client) namedContext(ctx context.Context, name string, nameWithPlatform string, opt ContextOpt) (*llb.State, *image.Image, error) { | ||||
| 	return bc.namedContextRecursive(ctx, name, nameWithPlatform, opt, 0) | ||||
| } | ||||
| 
 | ||||
| func (bc *Client) namedContextRecursive(ctx context.Context, name string, nameWithPlatform string, opt ContextOpt, count int) (*llb.State, *image.Image, error) { | ||||
| 	opts := bc.bopts.Opts | ||||
| 	v, ok := opts[contextPrefix+nameWithPlatform] | ||||
| 	if !ok { | ||||
| 		return nil, nil, nil | ||||
| 	} | ||||
| 
 | ||||
| 	if count > maxContextRecursion { | ||||
| 		return nil, nil, errors.New("context recursion limit exceeded; this may indicate a cycle in the provided source policies: " + v) | ||||
| 	} | ||||
| 
 | ||||
| 	vv := strings.SplitN(v, ":", 2) | ||||
| 	if len(vv) != 2 { | ||||
| 		return nil, nil, errors.Errorf("invalid context specifier %s for %s", v, nameWithPlatform) | ||||
| 	} | ||||
| 
 | ||||
| 	// allow git@ without protocol for SSH URLs for backwards compatibility
 | ||||
| 	if strings.HasPrefix(vv[0], "git@") { | ||||
| 		vv[0] = "git" | ||||
|  | @ -58,13 +69,17 @@ func (bc *Client) namedContext(ctx context.Context, name string, nameWithPlatfor | |||
| 
 | ||||
| 		named = reference.TagNameOnly(named) | ||||
| 
 | ||||
| 		_, data, err := bc.client.ResolveImageConfig(ctx, named.String(), llb.ResolveImageConfigOpt{ | ||||
| 		ref, dgst, data, err := bc.client.ResolveImageConfig(ctx, named.String(), llb.ResolveImageConfigOpt{ | ||||
| 			Platform:     opt.Platform, | ||||
| 			ResolveMode:  opt.ResolveMode, | ||||
| 			LogName:      fmt.Sprintf("[context %s] load metadata for %s", nameWithPlatform, ref), | ||||
| 			ResolverType: llb.ResolverTypeRegistry, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			e := &imageutil.ResolveToNonImageError{} | ||||
| 			if errors.As(err, &e) { | ||||
| 				return bc.namedContextRecursive(ctx, e.Updated, name, opt, count+1) | ||||
| 			} | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| 
 | ||||
|  | @ -79,6 +94,9 @@ func (bc *Client) namedContext(ctx context.Context, name string, nameWithPlatfor | |||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| 		if opt.CaptureDigest != nil { | ||||
| 			*opt.CaptureDigest = dgst | ||||
| 		} | ||||
| 		return &st, &img, nil | ||||
| 	case "git": | ||||
| 		st, ok := DetectGitContext(v, true) | ||||
|  | @ -119,7 +137,8 @@ func (bc *Client) namedContext(ctx context.Context, name string, nameWithPlatfor | |||
| 			return nil, nil, errors.Wrapf(err, "could not wrap %q with digest", name) | ||||
| 		} | ||||
| 
 | ||||
| 		_, data, err := bc.client.ResolveImageConfig(ctx, dummyRef.String(), llb.ResolveImageConfigOpt{ | ||||
| 		// TODO: How should source policy be handled here with a dummy ref?
 | ||||
| 		_, dgst, data, err := bc.client.ResolveImageConfig(ctx, dummyRef.String(), llb.ResolveImageConfigOpt{ | ||||
| 			Platform:     opt.Platform, | ||||
| 			ResolveMode:  opt.ResolveMode, | ||||
| 			LogName:      fmt.Sprintf("[context %s] load metadata for %s", nameWithPlatform, dummyRef.String()), | ||||
|  | @ -153,6 +172,9 @@ func (bc *Client) namedContext(ctx context.Context, name string, nameWithPlatfor | |||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| 		if opt.CaptureDigest != nil { | ||||
| 			*opt.CaptureDigest = dgst | ||||
| 		} | ||||
| 		return &st, &img, nil | ||||
| 	case "local": | ||||
| 		st := llb.Local(vv[1], | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ func NewResult() *Result { | |||
| 
 | ||||
| type Client interface { | ||||
| 	Solve(ctx context.Context, req SolveRequest) (*Result, error) | ||||
| 	ResolveImageConfig(ctx context.Context, ref string, opt llb.ResolveImageConfigOpt) (digest.Digest, []byte, error) | ||||
| 	ResolveImageConfig(ctx context.Context, ref string, opt llb.ResolveImageConfigOpt) (string, digest.Digest, []byte, error) | ||||
| 	BuildOpts() BuildOpts | ||||
| 	Inputs(ctx context.Context) (map[string]llb.State, error) | ||||
| 	NewContainer(ctx context.Context, req NewContainerRequest) (Container, error) | ||||
|  | @ -71,6 +71,7 @@ type Container interface { | |||
| type StartRequest struct { | ||||
| 	Args           []string | ||||
| 	Env            []string | ||||
| 	SecretEnv      []*pb.SecretEnv | ||||
| 	User           string | ||||
| 	Cwd            string | ||||
| 	Tty            bool | ||||
|  |  | |||
|  | @ -478,7 +478,7 @@ func (c *grpcClient) Solve(ctx context.Context, creq client.SolveRequest) (res * | |||
| 	return res, nil | ||||
| } | ||||
| 
 | ||||
| func (c *grpcClient) ResolveImageConfig(ctx context.Context, ref string, opt llb.ResolveImageConfigOpt) (digest.Digest, []byte, error) { | ||||
| func (c *grpcClient) ResolveImageConfig(ctx context.Context, ref string, opt llb.ResolveImageConfigOpt) (string, digest.Digest, []byte, error) { | ||||
| 	var p *opspb.Platform | ||||
| 	if platform := opt.Platform; platform != nil { | ||||
| 		p = &opspb.Platform{ | ||||
|  | @ -489,6 +489,7 @@ func (c *grpcClient) ResolveImageConfig(ctx context.Context, ref string, opt llb | |||
| 			OSFeatures:   platform.OSFeatures, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	resp, err := c.client.ResolveImageConfig(ctx, &pb.ResolveImageConfigRequest{ | ||||
| 		ResolverType:   int32(opt.ResolverType), | ||||
| 		Ref:            ref, | ||||
|  | @ -497,11 +498,18 @@ func (c *grpcClient) ResolveImageConfig(ctx context.Context, ref string, opt llb | |||
| 		LogName:        opt.LogName, | ||||
| 		SessionID:      opt.Store.SessionID, | ||||
| 		StoreID:        opt.Store.StoreID, | ||||
| 		SourcePolicies: opt.SourcePolicies, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return "", nil, err | ||||
| 		return "", "", nil, err | ||||
| 	} | ||||
| 	return resp.Digest, resp.Config, nil | ||||
| 	newRef := resp.Ref | ||||
| 	if newRef == "" { | ||||
| 		// No ref returned, use the original one.
 | ||||
| 		// This could occur if the version of buildkitd is too old.
 | ||||
| 		newRef = ref | ||||
| 	} | ||||
| 	return newRef, resp.Digest, resp.Config, nil | ||||
| } | ||||
| 
 | ||||
| func (c *grpcClient) BuildOpts() client.BuildOpts { | ||||
|  | @ -806,6 +814,7 @@ func (c *grpcClient) NewContainer(ctx context.Context, req client.NewContainerRe | |||
| 
 | ||||
| 	return &container{ | ||||
| 		client:   c.client, | ||||
| 		caps:     c.caps, | ||||
| 		id:       id, | ||||
| 		execMsgs: c.execMsgs, | ||||
| 	}, nil | ||||
|  | @ -813,6 +822,7 @@ func (c *grpcClient) NewContainer(ctx context.Context, req client.NewContainerRe | |||
| 
 | ||||
| type container struct { | ||||
| 	client   pb.LLBBridgeClient | ||||
| 	caps     apicaps.CapSet | ||||
| 	id       string | ||||
| 	execMsgs *messageForwarder | ||||
| } | ||||
|  | @ -821,6 +831,12 @@ func (ctr *container) Start(ctx context.Context, req client.StartRequest) (clien | |||
| 	pid := fmt.Sprintf("%s:%s", ctr.id, identity.NewID()) | ||||
| 	msgs := ctr.execMsgs.Register(pid) | ||||
| 
 | ||||
| 	if len(req.SecretEnv) > 0 { | ||||
| 		if err := ctr.caps.Supports(pb.CapGatewayExecSecretEnv); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	init := &pb.InitMessage{ | ||||
| 		ContainerID: ctr.id, | ||||
| 		Meta: &opspb.Meta{ | ||||
|  | @ -831,6 +847,7 @@ func (ctr *container) Start(ctx context.Context, req client.StartRequest) (clien | |||
| 		}, | ||||
| 		Tty:       req.Tty, | ||||
| 		Security:  req.SecurityMode, | ||||
| 		Secretenv: req.SecretEnv, | ||||
| 	} | ||||
| 	init.Meta.RemoveMountStubsRecursive = req.RemoveMountStubsRecursive | ||||
| 	if req.Stdin != nil { | ||||
|  |  | |||
|  | @ -44,6 +44,10 @@ const ( | |||
| 	// /etc/hosts for containers created via gateway exec.
 | ||||
| 	CapGatewayExecExtraHosts apicaps.CapID = "gateway.exec.extrahosts" | ||||
| 
 | ||||
| 	// CapGatewayExecExtraHosts is the capability to set secrets as env vars for
 | ||||
| 	// containers created via gateway exec.
 | ||||
| 	CapGatewayExecSecretEnv apicaps.CapID = "gateway.exec.secretenv" | ||||
| 
 | ||||
| 	// CapGatewayExecExtraHosts is the capability to send signals to a process
 | ||||
| 	// created via gateway exec.
 | ||||
| 	CapGatewayExecSignals apicaps.CapID = "gateway.exec.signals" | ||||
|  | @ -179,6 +183,13 @@ func init() { | |||
| 		Status:  apicaps.CapStatusExperimental, | ||||
| 	}) | ||||
| 
 | ||||
| 	Caps.Init(apicaps.Cap{ | ||||
| 		ID:      CapGatewayExecSecretEnv, | ||||
| 		Name:    "gateway exec secret env", | ||||
| 		Enabled: true, | ||||
| 		Status:  apicaps.CapStatusExperimental, | ||||
| 	}) | ||||
| 
 | ||||
| 	Caps.Init(apicaps.Cap{ | ||||
| 		ID:      CapGatewayExecSignals, | ||||
| 		Name:    "gateway exec signals", | ||||
|  |  | |||
|  | @ -743,6 +743,7 @@ type ResolveImageConfigRequest struct { | |||
| 	ResolverType         int32         `protobuf:"varint,5,opt,name=ResolverType,proto3" json:"ResolverType,omitempty"` | ||||
| 	SessionID            string        `protobuf:"bytes,6,opt,name=SessionID,proto3" json:"SessionID,omitempty"` | ||||
| 	StoreID              string        `protobuf:"bytes,7,opt,name=StoreID,proto3" json:"StoreID,omitempty"` | ||||
| 	SourcePolicies       []*pb1.Policy `protobuf:"bytes,8,rep,name=SourcePolicies,proto3" json:"SourcePolicies,omitempty"` | ||||
| 	XXX_NoUnkeyedLiteral struct{}      `json:"-"` | ||||
| 	XXX_unrecognized     []byte        `json:"-"` | ||||
| 	XXX_sizecache        int32         `json:"-"` | ||||
|  | @ -830,9 +831,17 @@ func (m *ResolveImageConfigRequest) GetStoreID() string { | |||
| 	return "" | ||||
| } | ||||
| 
 | ||||
| func (m *ResolveImageConfigRequest) GetSourcePolicies() []*pb1.Policy { | ||||
| 	if m != nil { | ||||
| 		return m.SourcePolicies | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| type ResolveImageConfigResponse struct { | ||||
| 	Digest               github_com_opencontainers_go_digest.Digest `protobuf:"bytes,1,opt,name=Digest,proto3,customtype=github.com/opencontainers/go-digest.Digest" json:"Digest"` | ||||
| 	Config               []byte                                     `protobuf:"bytes,2,opt,name=Config,proto3" json:"Config,omitempty"` | ||||
| 	Ref                  string                                     `protobuf:"bytes,3,opt,name=Ref,proto3" json:"Ref,omitempty"` | ||||
| 	XXX_NoUnkeyedLiteral struct{}                                   `json:"-"` | ||||
| 	XXX_unrecognized     []byte                                     `json:"-"` | ||||
| 	XXX_sizecache        int32                                      `json:"-"` | ||||
|  | @ -878,6 +887,13 @@ func (m *ResolveImageConfigResponse) GetConfig() []byte { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (m *ResolveImageConfigResponse) GetRef() string { | ||||
| 	if m != nil { | ||||
| 		return m.Ref | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
| 
 | ||||
| type SolveRequest struct { | ||||
| 	Definition  *pb.Definition    `protobuf:"bytes,1,opt,name=Definition,proto3" json:"Definition,omitempty"` | ||||
| 	Frontend    string            `protobuf:"bytes,2,opt,name=Frontend,proto3" json:"Frontend,omitempty"` | ||||
|  | @ -2204,6 +2220,7 @@ type InitMessage struct { | |||
| 	Fds                  []uint32        `protobuf:"varint,3,rep,packed,name=Fds,proto3" json:"Fds,omitempty"` | ||||
| 	Tty                  bool            `protobuf:"varint,4,opt,name=Tty,proto3" json:"Tty,omitempty"` | ||||
| 	Security             pb.SecurityMode `protobuf:"varint,5,opt,name=Security,proto3,enum=pb.SecurityMode" json:"Security,omitempty"` | ||||
| 	Secretenv            []*pb.SecretEnv `protobuf:"bytes,6,rep,name=secretenv,proto3" json:"secretenv,omitempty"` | ||||
| 	XXX_NoUnkeyedLiteral struct{}        `json:"-"` | ||||
| 	XXX_unrecognized     []byte          `json:"-"` | ||||
| 	XXX_sizecache        int32           `json:"-"` | ||||
|  | @ -2277,6 +2294,13 @@ func (m *InitMessage) GetSecurity() pb.SecurityMode { | |||
| 	return pb.SecurityMode_SANDBOX | ||||
| } | ||||
| 
 | ||||
| func (m *InitMessage) GetSecretenv() []*pb.SecretEnv { | ||||
| 	if m != nil { | ||||
| 		return m.Secretenv | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| type ExitMessage struct { | ||||
| 	Code                 uint32      `protobuf:"varint,1,opt,name=Code,proto3" json:"Code,omitempty"` | ||||
| 	Error                *rpc.Status `protobuf:"bytes,2,opt,name=Error,proto3" json:"Error,omitempty"` | ||||
|  | @ -2635,161 +2659,164 @@ func init() { | |||
| func init() { proto.RegisterFile("gateway.proto", fileDescriptor_f1a937782ebbded5) } | ||||
| 
 | ||||
| var fileDescriptor_f1a937782ebbded5 = []byte{ | ||||
| 	// 2464 bytes of a gzipped FileDescriptorProto
 | ||||
| 	// 2497 bytes of a gzipped FileDescriptorProto
 | ||||
| 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x59, 0xcf, 0x6f, 0x1b, 0xc7, | ||||
| 	0xf5, 0xd7, 0x8a, 0x14, 0x25, 0x3d, 0xfe, 0x10, 0x3d, 0x71, 0xf2, 0x65, 0x16, 0x81, 0x23, 0xaf, | ||||
| 	0x63, 0x45, 0x56, 0x9c, 0xa5, 0xbf, 0xb2, 0x0d, 0xb9, 0x76, 0xeb, 0xc4, 0xfa, 0x05, 0x29, 0x96, | ||||
| 	0x6c, 0x76, 0xe4, 0xc2, 0x45, 0x90, 0x02, 0x5d, 0x71, 0x87, 0xd4, 0xd6, 0xab, 0xdd, 0xed, 0xee, | ||||
| 	0xd0, 0xb2, 0x92, 0x4b, 0x7b, 0x28, 0x50, 0xe4, 0x0f, 0xe8, 0x2d, 0x28, 0xd0, 0x02, 0x3d, 0xf5, | ||||
| 	0xd0, 0xfe, 0x01, 0xed, 0x39, 0x40, 0x2f, 0x3d, 0xf7, 0x10, 0x14, 0xfe, 0x07, 0x7a, 0x2b, 0xd0, | ||||
| 	0x5b, 0xf1, 0x66, 0x66, 0xc9, 0xe1, 0x0f, 0x2d, 0xc9, 0xfa, 0xc4, 0x99, 0x37, 0xef, 0xc7, 0xbc, | ||||
| 	0xf7, 0xe6, 0xbd, 0xf9, 0xcc, 0x12, 0xca, 0x6d, 0x87, 0xb3, 0x33, 0xe7, 0xdc, 0x8e, 0xe2, 0x90, | ||||
| 	0x87, 0xe4, 0xdd, 0xd3, 0xf0, 0xf8, 0xdc, 0x3e, 0xee, 0x78, 0xbe, 0xfb, 0xc2, 0xe3, 0xf6, 0xcb, | ||||
| 	0xff, 0xb7, 0x5b, 0x71, 0x18, 0x70, 0x16, 0xb8, 0xe6, 0xc7, 0x6d, 0x8f, 0x9f, 0x74, 0x8e, 0xed, | ||||
| 	0x66, 0x78, 0x5a, 0x6f, 0x87, 0xed, 0xb0, 0x2e, 0x24, 0x8e, 0x3b, 0x2d, 0x31, 0x13, 0x13, 0x31, | ||||
| 	0x92, 0x9a, 0xcc, 0xf5, 0x41, 0xf6, 0x76, 0x18, 0xb6, 0x7d, 0xe6, 0x44, 0x5e, 0xa2, 0x86, 0xf5, | ||||
| 	0x38, 0x6a, 0xd6, 0x13, 0xee, 0xf0, 0x4e, 0xa2, 0x64, 0x6e, 0x6a, 0x32, 0xb8, 0x91, 0x7a, 0xba, | ||||
| 	0x91, 0x7a, 0x12, 0xfa, 0x2f, 0x59, 0x5c, 0x8f, 0x8e, 0xeb, 0x61, 0x94, 0x72, 0xd7, 0x2f, 0xe4, | ||||
| 	0x76, 0x22, 0xaf, 0xce, 0xcf, 0x23, 0x96, 0xd4, 0xcf, 0xc2, 0xf8, 0x05, 0x8b, 0x95, 0xc0, 0xed, | ||||
| 	0x0b, 0x05, 0x3a, 0xdc, 0xf3, 0x51, 0xaa, 0xe9, 0x44, 0x09, 0x1a, 0xc1, 0x5f, 0x25, 0xa4, 0xbb, | ||||
| 	0xcd, 0xc3, 0xc0, 0x4b, 0xb8, 0xe7, 0xb5, 0xbd, 0x7a, 0x2b, 0x11, 0x32, 0xd2, 0x0a, 0x3a, 0xa1, | ||||
| 	0xd8, 0xef, 0x66, 0xb8, 0xd0, 0x89, 0x9b, 0x2c, 0x0a, 0x7d, 0xaf, 0x79, 0x8e, 0x36, 0xe4, 0x48, | ||||
| 	0x8a, 0x59, 0x7f, 0xcb, 0x43, 0x81, 0xb2, 0xa4, 0xe3, 0x73, 0xb2, 0x02, 0xe5, 0x98, 0xb5, 0xb6, | ||||
| 	0x59, 0x14, 0xb3, 0xa6, 0xc3, 0x99, 0x5b, 0x33, 0x96, 0x8d, 0xd5, 0xc5, 0xbd, 0x19, 0xda, 0x4f, | ||||
| 	0x26, 0x3f, 0x82, 0x4a, 0xcc, 0x5a, 0x89, 0xc6, 0x38, 0xbb, 0x6c, 0xac, 0x16, 0xd7, 0x3f, 0xb2, | ||||
| 	0x2f, 0xcc, 0xa1, 0x4d, 0x59, 0xeb, 0xd0, 0x89, 0x7a, 0x22, 0x7b, 0x33, 0x74, 0x40, 0x09, 0x59, | ||||
| 	0x87, 0x5c, 0xcc, 0x5a, 0xb5, 0x9c, 0xd0, 0x75, 0x25, 0x5b, 0xd7, 0xde, 0x0c, 0x45, 0x66, 0xb2, | ||||
| 	0x01, 0x79, 0xd4, 0x52, 0xcb, 0x0b, 0xa1, 0xab, 0x63, 0x37, 0xb0, 0x37, 0x43, 0x85, 0x00, 0x79, | ||||
| 	0x0c, 0x0b, 0xa7, 0x8c, 0x3b, 0xae, 0xc3, 0x9d, 0x1a, 0x2c, 0xe7, 0x56, 0x8b, 0xeb, 0xf5, 0x4c, | ||||
| 	0x61, 0x0c, 0x90, 0x7d, 0xa8, 0x24, 0x76, 0x02, 0x1e, 0x9f, 0xd3, 0xae, 0x02, 0xf2, 0x1c, 0x4a, | ||||
| 	0x0e, 0xe7, 0x0c, 0x93, 0xe1, 0x85, 0x41, 0x52, 0x2b, 0x09, 0x85, 0xb7, 0xc7, 0x2b, 0x7c, 0xa4, | ||||
| 	0x49, 0x49, 0xa5, 0x7d, 0x8a, 0xcc, 0x07, 0x50, 0xee, 0xb3, 0x49, 0xaa, 0x90, 0x7b, 0xc1, 0xce, | ||||
| 	0x65, 0x62, 0x28, 0x0e, 0xc9, 0x65, 0x98, 0x7b, 0xe9, 0xf8, 0x1d, 0x26, 0x72, 0x50, 0xa2, 0x72, | ||||
| 	0x72, 0x7f, 0xf6, 0x9e, 0x61, 0x9e, 0xc0, 0xa5, 0x21, 0xfd, 0x23, 0x14, 0xfc, 0x40, 0x57, 0x50, | ||||
| 	0x5c, 0xff, 0x30, 0x63, 0xd7, 0xba, 0x3a, 0xcd, 0xd2, 0xe6, 0x02, 0x14, 0x62, 0xe1, 0x90, 0xf5, | ||||
| 	0x1b, 0x03, 0xaa, 0x83, 0xa9, 0x26, 0xfb, 0x2a, 0x49, 0x86, 0x08, 0xcb, 0xdd, 0x29, 0x4e, 0x09, | ||||
| 	0x12, 0x54, 0x60, 0x84, 0x0a, 0x73, 0x03, 0x16, 0xbb, 0xa4, 0x71, 0xc1, 0x58, 0xd4, 0xb6, 0x68, | ||||
| 	0x6d, 0x40, 0x8e, 0xb2, 0x16, 0xa9, 0xc0, 0xac, 0xa7, 0xce, 0x35, 0x9d, 0xf5, 0x5c, 0xb2, 0x0c, | ||||
| 	0x39, 0x97, 0xb5, 0x94, 0xeb, 0x15, 0x3b, 0x3a, 0xb6, 0xb7, 0x59, 0xcb, 0x0b, 0x3c, 0x74, 0x91, | ||||
| 	0xe2, 0x92, 0xf5, 0x3b, 0x03, 0xeb, 0x03, 0xb7, 0x45, 0x3e, 0xe9, 0xf3, 0x63, 0xfc, 0x69, 0x1f, | ||||
| 	0xda, 0xfd, 0xf3, 0xec, 0xdd, 0xdf, 0xe9, 0xcf, 0xc4, 0x98, 0x12, 0xd0, 0xbd, 0xfb, 0x31, 0x94, | ||||
| 	0xf4, 0xdc, 0x90, 0x3d, 0x28, 0x6a, 0xe7, 0x48, 0x6d, 0x78, 0x65, 0xb2, 0xcc, 0x52, 0x5d, 0xd4, | ||||
| 	0xfa, 0x43, 0x0e, 0x8a, 0xda, 0x22, 0x79, 0x08, 0xf9, 0x17, 0x5e, 0x20, 0x43, 0x58, 0x59, 0x5f, | ||||
| 	0x9b, 0x4c, 0xe5, 0x63, 0x2f, 0x70, 0xa9, 0x90, 0x23, 0x0d, 0xad, 0xee, 0x66, 0xc5, 0xb6, 0xee, | ||||
| 	0x4c, 0xa6, 0xe3, 0xc2, 0xe2, 0xbb, 0x35, 0x45, 0xdb, 0x90, 0x4d, 0x83, 0x40, 0x3e, 0x72, 0xf8, | ||||
| 	0x89, 0x68, 0x1a, 0x8b, 0x54, 0x8c, 0xc9, 0x2d, 0x78, 0xcb, 0x0b, 0x9e, 0x85, 0x3c, 0x6c, 0xc4, | ||||
| 	0xcc, 0xf5, 0xf0, 0xf0, 0x3d, 0x3b, 0x8f, 0x58, 0x6d, 0x4e, 0xb0, 0x8c, 0x5a, 0x22, 0x0d, 0xa8, | ||||
| 	0x48, 0xf2, 0x51, 0xe7, 0xf8, 0x67, 0xac, 0xc9, 0x93, 0x5a, 0x41, 0xf8, 0xb3, 0x9a, 0xb1, 0x85, | ||||
| 	0x7d, 0x5d, 0x80, 0x0e, 0xc8, 0xbf, 0x51, 0xb5, 0x5b, 0x7f, 0x36, 0xa0, 0xdc, 0xa7, 0x9e, 0x7c, | ||||
| 	0xda, 0x97, 0xaa, 0x9b, 0x93, 0x6e, 0x4b, 0x4b, 0xd6, 0x67, 0x50, 0x70, 0xbd, 0x36, 0x4b, 0xb8, | ||||
| 	0x48, 0xd5, 0xe2, 0xe6, 0xfa, 0xb7, 0xdf, 0xbd, 0x3f, 0xf3, 0x8f, 0xef, 0xde, 0x5f, 0xd3, 0xae, | ||||
| 	0x9a, 0x30, 0x62, 0x41, 0x33, 0x0c, 0xb8, 0xe3, 0x05, 0x2c, 0xc6, 0x0b, 0xf6, 0x63, 0x29, 0x62, | ||||
| 	0x6f, 0x8b, 0x1f, 0xaa, 0x34, 0x60, 0xd0, 0x03, 0xe7, 0x94, 0x89, 0x3c, 0x2d, 0x52, 0x31, 0xb6, | ||||
| 	0x38, 0x94, 0x29, 0xe3, 0x9d, 0x38, 0xa0, 0xec, 0xe7, 0x1d, 0x64, 0xfa, 0x5e, 0xda, 0x48, 0xc4, | ||||
| 	0xa6, 0xc7, 0x35, 0x74, 0x64, 0xa4, 0x4a, 0x80, 0xac, 0xc2, 0x1c, 0x8b, 0xe3, 0x30, 0x56, 0xc5, | ||||
| 	0x43, 0x6c, 0x79, 0xd5, 0xdb, 0x71, 0xd4, 0xb4, 0x8f, 0xc4, 0x55, 0x4f, 0x25, 0x83, 0x55, 0x85, | ||||
| 	0x4a, 0x6a, 0x35, 0x89, 0xc2, 0x20, 0x61, 0xd6, 0x12, 0x86, 0x2e, 0xea, 0xf0, 0x44, 0xed, 0xc3, | ||||
| 	0xfa, 0xab, 0x01, 0x95, 0x94, 0x22, 0x79, 0xc8, 0x17, 0x50, 0xec, 0xb5, 0x86, 0xb4, 0x07, 0xdc, | ||||
| 	0xcf, 0x0c, 0xaa, 0x2e, 0xaf, 0xf5, 0x15, 0xd5, 0x12, 0x74, 0x75, 0xe6, 0x13, 0xa8, 0x0e, 0x32, | ||||
| 	0x8c, 0xc8, 0xfe, 0x07, 0xfd, 0x0d, 0x62, 0xb0, 0x5f, 0x69, 0xa7, 0xe1, 0x5f, 0x06, 0xbc, 0x4b, | ||||
| 	0x99, 0xc0, 0x2e, 0xfb, 0xa7, 0x4e, 0x9b, 0x6d, 0x85, 0x41, 0xcb, 0x6b, 0xa7, 0x61, 0xae, 0x8a, | ||||
| 	0x66, 0x98, 0x6a, 0xc6, 0xbe, 0xb8, 0x0a, 0x0b, 0x0d, 0xdf, 0xe1, 0xad, 0x30, 0x3e, 0x55, 0xca, | ||||
| 	0x4b, 0xa8, 0x3c, 0xa5, 0xd1, 0xee, 0x2a, 0x59, 0x86, 0xa2, 0x52, 0x7c, 0x18, 0xba, 0x69, 0x3a, | ||||
| 	0x75, 0x12, 0xa9, 0xc1, 0xfc, 0x41, 0xd8, 0x7e, 0x82, 0xc9, 0x96, 0x15, 0x96, 0x4e, 0x89, 0x05, | ||||
| 	0x25, 0xc5, 0x18, 0x77, 0xab, 0x6b, 0x8e, 0xf6, 0xd1, 0xc8, 0x7b, 0xb0, 0x78, 0xc4, 0x92, 0xc4, | ||||
| 	0x0b, 0x83, 0xfd, 0xed, 0x5a, 0x41, 0xc8, 0xf7, 0x08, 0xa8, 0xfb, 0x88, 0x87, 0x31, 0xdb, 0xdf, | ||||
| 	0xae, 0xcd, 0x4b, 0xdd, 0x6a, 0x6a, 0xfd, 0xc2, 0x00, 0x73, 0x94, 0xc7, 0x2a, 0x7d, 0x9f, 0x41, | ||||
| 	0x41, 0x1e, 0x48, 0xe9, 0xf5, 0xff, 0x76, 0x94, 0xe5, 0x2f, 0x79, 0x07, 0x0a, 0x52, 0xbb, 0xaa, | ||||
| 	0x42, 0x35, 0xb3, 0x7e, 0x55, 0x80, 0xd2, 0x11, 0x6e, 0x20, 0x8d, 0xb3, 0x0d, 0xd0, 0x4b, 0x8f, | ||||
| 	0x3a, 0xd2, 0x83, 0x49, 0xd3, 0x38, 0x88, 0x09, 0x0b, 0xbb, 0xea, 0xf8, 0xa8, 0x1b, 0xac, 0x3b, | ||||
| 	0x27, 0x9f, 0x43, 0x31, 0x1d, 0x3f, 0x8d, 0x78, 0x2d, 0x27, 0xce, 0xdf, 0xbd, 0x8c, 0xf3, 0xa7, | ||||
| 	0xef, 0xc4, 0xd6, 0x44, 0xd5, 0xe9, 0xd3, 0x28, 0xe4, 0x26, 0x5c, 0x72, 0x7c, 0x3f, 0x3c, 0x53, | ||||
| 	0x25, 0x25, 0x8a, 0x43, 0x24, 0x67, 0x81, 0x0e, 0x2f, 0x60, 0xab, 0xd4, 0x88, 0x8f, 0xe2, 0xd8, | ||||
| 	0x39, 0xc7, 0xd3, 0x54, 0x10, 0xfc, 0xa3, 0x96, 0xb0, 0x6b, 0xed, 0x7a, 0x81, 0xe3, 0xd7, 0x40, | ||||
| 	0xf0, 0xc8, 0x09, 0x9e, 0x86, 0x9d, 0x57, 0x51, 0x18, 0x73, 0x16, 0x3f, 0xe2, 0x3c, 0xae, 0x15, | ||||
| 	0x45, 0x30, 0xfb, 0x68, 0xa4, 0x01, 0xa5, 0x2d, 0xa7, 0x79, 0xc2, 0xf6, 0x4f, 0x91, 0x98, 0x22, | ||||
| 	0xab, 0xac, 0x5e, 0x26, 0xd8, 0x9f, 0x46, 0x3a, 0xa4, 0xd2, 0x35, 0x90, 0x26, 0x54, 0x52, 0xd7, | ||||
| 	0x65, 0x85, 0xd6, 0xca, 0x42, 0xe7, 0x83, 0x69, 0x43, 0x29, 0xa5, 0xa5, 0x89, 0x01, 0x95, 0x98, | ||||
| 	0xc8, 0x1d, 0x2c, 0x46, 0x87, 0xb3, 0x5a, 0x45, 0xf8, 0xdc, 0x9d, 0x93, 0x43, 0xa8, 0x1c, 0x09, | ||||
| 	0x40, 0xde, 0x40, 0x18, 0xee, 0xb1, 0xa4, 0xb6, 0x24, 0x36, 0x70, 0x7d, 0x78, 0x03, 0x3a, 0x70, | ||||
| 	0xb7, 0x05, 0xfb, 0x39, 0x1d, 0x10, 0x36, 0x1f, 0x42, 0x75, 0x30, 0xb9, 0xd3, 0x00, 0x23, 0xf3, | ||||
| 	0x87, 0xf0, 0xd6, 0x08, 0x8f, 0xde, 0xa8, 0xf9, 0xfc, 0xc9, 0x80, 0x4b, 0x43, 0x69, 0xc0, 0x0b, | ||||
| 	0x40, 0x14, 0xbd, 0x54, 0x29, 0xc6, 0xe4, 0x10, 0xe6, 0x30, 0xcd, 0x89, 0x82, 0x02, 0x1b, 0xd3, | ||||
| 	0xe4, 0xd5, 0x16, 0x92, 0x32, 0xfe, 0x52, 0x8b, 0x79, 0x0f, 0xa0, 0x47, 0x9c, 0x0a, 0x1e, 0x7e, | ||||
| 	0x01, 0x65, 0x95, 0x64, 0xd5, 0x2f, 0xaa, 0x12, 0x55, 0x28, 0x61, 0x44, 0x0d, 0xbd, 0xbb, 0x29, | ||||
| 	0x37, 0xe5, 0xdd, 0x64, 0x7d, 0x05, 0x4b, 0x94, 0x39, 0xee, 0xae, 0xe7, 0xb3, 0x8b, 0x5b, 0x30, | ||||
| 	0x16, 0xbf, 0xe7, 0xb3, 0x06, 0x22, 0x93, 0xb4, 0xf8, 0xd5, 0x9c, 0xdc, 0x87, 0x39, 0xea, 0x04, | ||||
| 	0x6d, 0xa6, 0x4c, 0x7f, 0x90, 0x61, 0x5a, 0x18, 0x41, 0x5e, 0x2a, 0x45, 0xac, 0x07, 0xb0, 0xd8, | ||||
| 	0xa5, 0x61, 0xeb, 0x7a, 0xda, 0x6a, 0x25, 0x4c, 0xb6, 0xc1, 0x1c, 0x55, 0x33, 0xa4, 0x1f, 0xb0, | ||||
| 	0xa0, 0xad, 0x4c, 0xe7, 0xa8, 0x9a, 0x59, 0x2b, 0x08, 0xe7, 0xd3, 0x9d, 0xab, 0xd0, 0x10, 0xc8, | ||||
| 	0x6f, 0x23, 0x7c, 0x33, 0x44, 0xbd, 0x8a, 0xb1, 0xe5, 0xe2, 0x9d, 0xea, 0xb8, 0xdb, 0x5e, 0x7c, | ||||
| 	0xb1, 0x83, 0x35, 0x98, 0xdf, 0xf6, 0x62, 0xcd, 0xbf, 0x74, 0x4a, 0x56, 0xf0, 0xb6, 0x6d, 0xfa, | ||||
| 	0x1d, 0x17, 0xbd, 0xe5, 0x2c, 0x0e, 0xd4, 0xb5, 0x32, 0x40, 0xb5, 0x3e, 0x91, 0x71, 0x14, 0x56, | ||||
| 	0xd4, 0x66, 0x6e, 0xc2, 0x3c, 0x0b, 0x78, 0x8c, 0x65, 0x24, 0xaf, 0x64, 0x62, 0xcb, 0x07, 0xb2, | ||||
| 	0x2d, 0x1e, 0xc8, 0xe2, 0xea, 0xa7, 0x29, 0x8b, 0xb5, 0x01, 0x4b, 0x48, 0xc8, 0x4e, 0x04, 0x81, | ||||
| 	0xbc, 0xb6, 0x49, 0x31, 0xb6, 0xee, 0x43, 0xb5, 0x27, 0xa8, 0x4c, 0xaf, 0x40, 0x1e, 0xb1, 0xa9, | ||||
| 	0xea, 0xeb, 0xa3, 0xec, 0x8a, 0x75, 0xeb, 0x1a, 0x2c, 0xa5, 0xc5, 0x7f, 0xa1, 0x51, 0x8b, 0x40, | ||||
| 	0xb5, 0xc7, 0xa4, 0x60, 0x49, 0x19, 0x8a, 0x0d, 0x2f, 0x48, 0x6f, 0x6d, 0xeb, 0xb5, 0x01, 0xa5, | ||||
| 	0x46, 0x18, 0xf4, 0xee, 0xb4, 0x06, 0x2c, 0xa5, 0xa5, 0xfb, 0xa8, 0xb1, 0xbf, 0xe5, 0x44, 0x69, | ||||
| 	0x0c, 0x96, 0x87, 0xcf, 0x87, 0xfa, 0xc4, 0x60, 0x4b, 0xc6, 0xcd, 0x3c, 0x5e, 0x7f, 0x74, 0x50, | ||||
| 	0x9c, 0x7c, 0x0a, 0xf3, 0x07, 0x07, 0x9b, 0x42, 0xd3, 0xec, 0x54, 0x9a, 0x52, 0x31, 0xf2, 0x10, | ||||
| 	0xe6, 0x9f, 0x8b, 0x2f, 0x1f, 0x89, 0xba, 0xa2, 0x46, 0x9c, 0x55, 0x19, 0x21, 0xc9, 0x46, 0x59, | ||||
| 	0x33, 0x8c, 0x5d, 0x9a, 0x0a, 0x59, 0xff, 0x36, 0xa0, 0xf8, 0xdc, 0xe9, 0x21, 0xc2, 0x1e, 0x04, | ||||
| 	0x7d, 0x83, 0x7b, 0x5b, 0x41, 0xd0, 0xcb, 0x30, 0xe7, 0xb3, 0x97, 0xcc, 0x57, 0x67, 0x5c, 0x4e, | ||||
| 	0x90, 0x9a, 0x9c, 0x84, 0xb1, 0x2c, 0xeb, 0x12, 0x95, 0x13, 0x2c, 0x08, 0x97, 0x71, 0xc7, 0xf3, | ||||
| 	0x6b, 0xf9, 0xe5, 0x1c, 0xde, 0xf1, 0x72, 0x86, 0x99, 0xeb, 0xc4, 0xbe, 0x7a, 0x17, 0xe0, 0x90, | ||||
| 	0x58, 0x90, 0xf7, 0x82, 0x56, 0x28, 0xee, 0x3f, 0xd5, 0x16, 0x65, 0x8b, 0xde, 0x0f, 0x5a, 0x21, | ||||
| 	0x15, 0x6b, 0xe4, 0x2a, 0x14, 0x62, 0xac, 0xbf, 0xa4, 0x36, 0x2f, 0x82, 0xb2, 0x88, 0x5c, 0xb2, | ||||
| 	0x4a, 0xd5, 0x82, 0x55, 0x81, 0x92, 0xf4, 0x5b, 0x25, 0xff, 0x8f, 0xb3, 0xf0, 0xd6, 0x13, 0x76, | ||||
| 	0xb6, 0x95, 0xfa, 0x95, 0x06, 0x64, 0x19, 0x8a, 0x5d, 0xda, 0xfe, 0xb6, 0x3a, 0x42, 0x3a, 0x09, | ||||
| 	0x8d, 0x1d, 0x86, 0x9d, 0x80, 0xa7, 0x39, 0x14, 0xc6, 0x04, 0x85, 0xaa, 0x05, 0x72, 0x1d, 0xe6, | ||||
| 	0x9f, 0x30, 0x7e, 0x16, 0xc6, 0x2f, 0x84, 0xd7, 0x95, 0xf5, 0x22, 0xf2, 0x3c, 0x61, 0x1c, 0x01, | ||||
| 	0x1c, 0x4d, 0xd7, 0x10, 0x15, 0x46, 0x29, 0x2a, 0xcc, 0x8f, 0x42, 0x85, 0xe9, 0x2a, 0xd9, 0x80, | ||||
| 	0x62, 0x33, 0x0c, 0x12, 0x1e, 0x3b, 0x1e, 0x1a, 0x9e, 0x13, 0xcc, 0x6f, 0x23, 0xb3, 0x4c, 0xec, | ||||
| 	0x56, 0x6f, 0x91, 0xea, 0x9c, 0x64, 0x0d, 0x80, 0xbd, 0xe2, 0xb1, 0xb3, 0x17, 0x26, 0xdd, 0x17, | ||||
| 	0x14, 0xa0, 0x1c, 0x12, 0xf6, 0x1b, 0x54, 0x5b, 0xc5, 0x0e, 0x79, 0x12, 0x26, 0x5c, 0x3c, 0x23, | ||||
| 	0x24, 0xfa, 0xeb, 0xce, 0xad, 0x77, 0xe0, 0x72, 0x7f, 0xb4, 0x54, 0x18, 0x1f, 0xc0, 0xff, 0x51, | ||||
| 	0xe6, 0x33, 0x27, 0x61, 0xd3, 0x47, 0xd2, 0x32, 0xa1, 0x36, 0x2c, 0xac, 0x14, 0xff, 0x27, 0x07, | ||||
| 	0xc5, 0x9d, 0x57, 0xac, 0x79, 0xc8, 0x92, 0xc4, 0x69, 0x0b, 0xdc, 0xda, 0x88, 0xc3, 0x26, 0x4b, | ||||
| 	0x92, 0xae, 0xae, 0x1e, 0x81, 0x7c, 0x1f, 0xf2, 0xfb, 0x81, 0xc7, 0xd5, 0xdd, 0xb9, 0x92, 0xf9, | ||||
| 	0x6c, 0xf0, 0xb8, 0xd2, 0xb9, 0x37, 0x43, 0x85, 0x14, 0xb9, 0x0f, 0x79, 0xec, 0x3c, 0x93, 0x74, | ||||
| 	0x7f, 0x57, 0x93, 0x45, 0x19, 0xb2, 0x29, 0x3e, 0xef, 0x79, 0x5f, 0x32, 0x95, 0xc1, 0xd5, 0xec, | ||||
| 	0x6b, 0xcb, 0xfb, 0x92, 0xf5, 0x34, 0x28, 0x49, 0xb2, 0x83, 0xa8, 0xdb, 0x89, 0x39, 0x73, 0x55, | ||||
| 	0x66, 0x6f, 0x64, 0x81, 0x25, 0xc9, 0xd9, 0xd3, 0x92, 0xca, 0x62, 0x10, 0x76, 0x5e, 0x79, 0x5c, | ||||
| 	0x55, 0x4a, 0x56, 0x10, 0x90, 0x4d, 0x73, 0x04, 0xa7, 0x28, 0xbd, 0x1d, 0x06, 0x32, 0xf3, 0xd9, | ||||
| 	0xd2, 0xc8, 0xa6, 0x49, 0xe3, 0x14, 0xc3, 0x70, 0xe4, 0xb5, 0x11, 0x83, 0x2e, 0x8c, 0x0d, 0x83, | ||||
| 	0x64, 0xd4, 0xc2, 0x20, 0x09, 0x9b, 0xf3, 0x30, 0x27, 0x20, 0x92, 0xf5, 0x5b, 0x03, 0x8a, 0x5a, | ||||
| 	0x9e, 0x26, 0xa8, 0xc9, 0xf7, 0x20, 0x8f, 0x4f, 0x7b, 0x95, 0xff, 0x05, 0x51, 0x91, 0x8c, 0x3b, | ||||
| 	0x54, 0x50, 0xb1, 0xa9, 0xec, 0xba, 0xb2, 0x61, 0x96, 0x29, 0x0e, 0x91, 0xf2, 0x8c, 0x9f, 0x8b, | ||||
| 	0x94, 0x2d, 0x50, 0x1c, 0x92, 0x9b, 0xb0, 0x70, 0xc4, 0x9a, 0x9d, 0xd8, 0xe3, 0xe7, 0x22, 0x09, | ||||
| 	0x95, 0xf5, 0xaa, 0x68, 0x35, 0x8a, 0x26, 0x0a, 0xb7, 0xcb, 0x61, 0x3d, 0xc6, 0xc3, 0xd9, 0xdb, | ||||
| 	0x20, 0x81, 0xfc, 0x16, 0xbe, 0xd6, 0x70, 0x67, 0x65, 0x2a, 0xc6, 0xf8, 0x60, 0xde, 0x19, 0xf7, | ||||
| 	0x60, 0xde, 0x49, 0x1f, 0xcc, 0xfd, 0x49, 0xc5, 0x9b, 0x49, 0x0b, 0xb2, 0xf5, 0x08, 0x16, 0xbb, | ||||
| 	0x07, 0x8f, 0x54, 0x60, 0x76, 0xd7, 0x55, 0x96, 0x66, 0x77, 0x5d, 0x74, 0x65, 0xe7, 0xe9, 0xae, | ||||
| 	0xb0, 0xb2, 0x40, 0x71, 0xd8, 0x05, 0x10, 0x39, 0x0d, 0x40, 0x6c, 0x40, 0xb9, 0xef, 0xf4, 0x21, | ||||
| 	0x13, 0x0d, 0xcf, 0x92, 0x74, 0xcb, 0x38, 0x96, 0x6e, 0xf8, 0x89, 0xd0, 0x25, 0xdc, 0xf0, 0x13, | ||||
| 	0xeb, 0x1a, 0x94, 0xfb, 0xf2, 0x85, 0x4c, 0xe2, 0xed, 0xa9, 0x70, 0x26, 0x8e, 0xd7, 0x18, 0x2c, | ||||
| 	0x0d, 0x7c, 0x8e, 0x22, 0xd7, 0xa1, 0x20, 0x3f, 0x7b, 0x54, 0x67, 0xcc, 0x77, 0xbf, 0xfe, 0x66, | ||||
| 	0xf9, 0xed, 0x01, 0x06, 0xb9, 0x88, 0x6c, 0x9b, 0x9d, 0xc0, 0xf5, 0x59, 0xd5, 0x18, 0xc9, 0x26, | ||||
| 	0x17, 0xcd, 0xfc, 0xaf, 0x7f, 0x7f, 0x65, 0x66, 0xcd, 0x81, 0x4b, 0x43, 0x9f, 0x52, 0xc8, 0x35, | ||||
| 	0xc8, 0x1f, 0x31, 0xbf, 0x95, 0x9a, 0x19, 0x62, 0xc0, 0x45, 0x72, 0x15, 0x72, 0xd4, 0x39, 0xab, | ||||
| 	0x1a, 0x66, 0xed, 0xeb, 0x6f, 0x96, 0x2f, 0x0f, 0x7f, 0x8f, 0x71, 0xce, 0xa4, 0x89, 0xf5, 0xbf, | ||||
| 	0x00, 0x2c, 0x1e, 0x1c, 0x6c, 0x6e, 0xc6, 0x9e, 0xdb, 0x66, 0xe4, 0x97, 0x06, 0x90, 0xe1, 0x47, | ||||
| 	0x2f, 0xb9, 0x93, 0x5d, 0xe3, 0xa3, 0xbf, 0x0a, 0x98, 0x77, 0xa7, 0x94, 0x52, 0x28, 0xe4, 0x73, | ||||
| 	0x98, 0x13, 0xd0, 0x99, 0x7c, 0x38, 0xe1, 0x0b, 0xca, 0x5c, 0x1d, 0xcf, 0xa8, 0x74, 0x37, 0x61, | ||||
| 	0x21, 0x85, 0x9f, 0x64, 0x2d, 0x73, 0x7b, 0x7d, 0xe8, 0xda, 0xfc, 0x68, 0x22, 0x5e, 0x65, 0xe4, | ||||
| 	0xa7, 0x30, 0xaf, 0x50, 0x25, 0xb9, 0x31, 0x46, 0xae, 0x87, 0x6f, 0xcd, 0xb5, 0x49, 0x58, 0x7b, | ||||
| 	0x6e, 0xa4, 0xe8, 0x31, 0xd3, 0x8d, 0x01, 0x6c, 0x9a, 0xe9, 0xc6, 0x10, 0x1c, 0x6d, 0xf6, 0xde, | ||||
| 	0x9c, 0x99, 0x46, 0x06, 0xb0, 0x68, 0xa6, 0x91, 0x41, 0x48, 0x4a, 0x9e, 0x43, 0x1e, 0x21, 0x29, | ||||
| 	0xc9, 0x6a, 0xbf, 0x1a, 0x66, 0x35, 0xb3, 0xce, 0x44, 0x1f, 0x96, 0xfd, 0x09, 0x5e, 0x53, 0xe2, | ||||
| 	0xf3, 0x42, 0xf6, 0x05, 0xa5, 0x7d, 0x2d, 0x34, 0x6f, 0x4c, 0xc0, 0xd9, 0x53, 0xaf, 0x9e, 0xe6, | ||||
| 	0xab, 0x13, 0x7c, 0xb2, 0x1b, 0xaf, 0x7e, 0xe0, 0xe3, 0x60, 0x08, 0x25, 0x1d, 0x7d, 0x10, 0x3b, | ||||
| 	0x43, 0x74, 0x04, 0xa8, 0x33, 0xeb, 0x13, 0xf3, 0x2b, 0x83, 0x5f, 0xe1, 0xbb, 0xac, 0x1f, 0x99, | ||||
| 	0x90, 0xf5, 0xcc, 0x70, 0x8c, 0xc4, 0x40, 0xe6, 0xed, 0xa9, 0x64, 0x94, 0x71, 0x47, 0x22, 0x1f, | ||||
| 	0x85, 0x6e, 0x48, 0xf6, 0x45, 0xde, 0x45, 0x48, 0xe6, 0x84, 0x7c, 0xab, 0xc6, 0x2d, 0x03, 0xcf, | ||||
| 	0x19, 0xa2, 0xe1, 0x4c, 0xdd, 0xda, 0x33, 0x21, 0xf3, 0x9c, 0xe9, 0xb0, 0x7a, 0xb3, 0xf4, 0xed, | ||||
| 	0xeb, 0x2b, 0xc6, 0xdf, 0x5f, 0x5f, 0x31, 0xfe, 0xf9, 0xfa, 0x8a, 0x71, 0x5c, 0x10, 0xff, 0x81, | ||||
| 	0xde, 0xfe, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xec, 0xb9, 0xed, 0xf9, 0x8c, 0x1e, 0x00, 0x00, | ||||
| 	0xf5, 0xd7, 0x8a, 0x14, 0x45, 0x3e, 0xfe, 0x10, 0x3d, 0x71, 0xf2, 0xa5, 0x17, 0x81, 0x23, 0xaf, | ||||
| 	0x63, 0x45, 0x96, 0x1d, 0xd2, 0x5f, 0xd9, 0x86, 0x5c, 0xbb, 0x75, 0x62, 0xfd, 0x82, 0x14, 0x4b, | ||||
| 	0x36, 0x3b, 0x72, 0xe1, 0x22, 0x48, 0x81, 0xae, 0xb8, 0x43, 0x6a, 0xeb, 0xd5, 0xee, 0x76, 0x77, | ||||
| 	0x28, 0x59, 0xc9, 0xa9, 0x87, 0x02, 0x45, 0x8e, 0x3d, 0xf4, 0x96, 0x4b, 0x0b, 0xf4, 0xd4, 0x43, | ||||
| 	0xfb, 0x07, 0x34, 0xe7, 0x00, 0xed, 0xa1, 0xe7, 0x1e, 0x82, 0xc2, 0x7f, 0x44, 0x81, 0xde, 0x8a, | ||||
| 	0x37, 0x33, 0x4b, 0x0e, 0x7f, 0x68, 0x45, 0xd6, 0x27, 0xce, 0xbc, 0x79, 0x3f, 0xe6, 0xbd, 0x37, | ||||
| 	0xef, 0xcd, 0x67, 0x96, 0x50, 0xee, 0xd8, 0x9c, 0x9d, 0xda, 0x67, 0xf5, 0x30, 0x0a, 0x78, 0x40, | ||||
| 	0xae, 0x1c, 0x07, 0x87, 0x67, 0xf5, 0xc3, 0xae, 0xeb, 0x39, 0xaf, 0x5c, 0x5e, 0x3f, 0xf9, 0xff, | ||||
| 	0x7a, 0x3b, 0x0a, 0x7c, 0xce, 0x7c, 0xc7, 0xfc, 0xb8, 0xe3, 0xf2, 0xa3, 0xee, 0x61, 0xbd, 0x15, | ||||
| 	0x1c, 0x37, 0x3a, 0x41, 0x27, 0x68, 0x08, 0x89, 0xc3, 0x6e, 0x5b, 0xcc, 0xc4, 0x44, 0x8c, 0xa4, | ||||
| 	0x26, 0x73, 0x75, 0x98, 0xbd, 0x13, 0x04, 0x1d, 0x8f, 0xd9, 0xa1, 0x1b, 0xab, 0x61, 0x23, 0x0a, | ||||
| 	0x5b, 0x8d, 0x98, 0xdb, 0xbc, 0x1b, 0x2b, 0x99, 0xdb, 0x9a, 0x0c, 0x6e, 0xa4, 0x91, 0x6c, 0xa4, | ||||
| 	0x11, 0x07, 0xde, 0x09, 0x8b, 0x1a, 0xe1, 0x61, 0x23, 0x08, 0x13, 0xee, 0xc6, 0xb9, 0xdc, 0x76, | ||||
| 	0xe8, 0x36, 0xf8, 0x59, 0xc8, 0xe2, 0xc6, 0x69, 0x10, 0xbd, 0x62, 0x91, 0x12, 0xb8, 0x7b, 0xae, | ||||
| 	0x40, 0x97, 0xbb, 0x1e, 0x4a, 0xb5, 0xec, 0x30, 0x46, 0x23, 0xf8, 0xab, 0x84, 0x74, 0xb7, 0x79, | ||||
| 	0xe0, 0xbb, 0x31, 0x77, 0xdd, 0x8e, 0xdb, 0x68, 0xc7, 0x42, 0x46, 0x5a, 0x41, 0x27, 0x14, 0xfb, | ||||
| 	0xfd, 0x14, 0x17, 0xba, 0x51, 0x8b, 0x85, 0x81, 0xe7, 0xb6, 0xce, 0xd0, 0x86, 0x1c, 0x49, 0x31, | ||||
| 	0xeb, 0x6f, 0x59, 0xc8, 0x51, 0x16, 0x77, 0x3d, 0x4e, 0x96, 0xa0, 0x1c, 0xb1, 0xf6, 0x26, 0x0b, | ||||
| 	0x23, 0xd6, 0xb2, 0x39, 0x73, 0x6a, 0xc6, 0xa2, 0xb1, 0x5c, 0xd8, 0x99, 0xa1, 0x83, 0x64, 0xf2, | ||||
| 	0x13, 0xa8, 0x44, 0xac, 0x1d, 0x6b, 0x8c, 0xb3, 0x8b, 0xc6, 0x72, 0x71, 0xf5, 0x56, 0xfd, 0xdc, | ||||
| 	0x1c, 0xd6, 0x29, 0x6b, 0xef, 0xdb, 0x61, 0x5f, 0x64, 0x67, 0x86, 0x0e, 0x29, 0x21, 0xab, 0x90, | ||||
| 	0x89, 0x58, 0xbb, 0x96, 0x11, 0xba, 0xae, 0xa6, 0xeb, 0xda, 0x99, 0xa1, 0xc8, 0x4c, 0xd6, 0x20, | ||||
| 	0x8b, 0x5a, 0x6a, 0x59, 0x21, 0x74, 0xed, 0xc2, 0x0d, 0xec, 0xcc, 0x50, 0x21, 0x40, 0x9e, 0x42, | ||||
| 	0xfe, 0x98, 0x71, 0xdb, 0xb1, 0xb9, 0x5d, 0x83, 0xc5, 0xcc, 0x72, 0x71, 0xb5, 0x91, 0x2a, 0x8c, | ||||
| 	0x01, 0xaa, 0xef, 0x2b, 0x89, 0x2d, 0x9f, 0x47, 0x67, 0xb4, 0xa7, 0x80, 0xbc, 0x84, 0x92, 0xcd, | ||||
| 	0x39, 0xc3, 0x64, 0xb8, 0x81, 0x1f, 0xd7, 0x4a, 0x42, 0xe1, 0xdd, 0x8b, 0x15, 0x3e, 0xd1, 0xa4, | ||||
| 	0xa4, 0xd2, 0x01, 0x45, 0xe6, 0x23, 0x28, 0x0f, 0xd8, 0x24, 0x55, 0xc8, 0xbc, 0x62, 0x67, 0x32, | ||||
| 	0x31, 0x14, 0x87, 0xe4, 0x32, 0xcc, 0x9d, 0xd8, 0x5e, 0x97, 0x89, 0x1c, 0x94, 0xa8, 0x9c, 0x3c, | ||||
| 	0x9c, 0x7d, 0x60, 0x98, 0x47, 0x70, 0x69, 0x44, 0xff, 0x18, 0x05, 0x3f, 0xd2, 0x15, 0x14, 0x57, | ||||
| 	0x3f, 0x4a, 0xd9, 0xb5, 0xae, 0x4e, 0xb3, 0xb4, 0x9e, 0x87, 0x5c, 0x24, 0x1c, 0xb2, 0x7e, 0x67, | ||||
| 	0x40, 0x75, 0x38, 0xd5, 0x64, 0x57, 0x25, 0xc9, 0x10, 0x61, 0xb9, 0x3f, 0xc5, 0x29, 0x41, 0x82, | ||||
| 	0x0a, 0x8c, 0x50, 0x61, 0xae, 0x41, 0xa1, 0x47, 0xba, 0x28, 0x18, 0x05, 0x6d, 0x8b, 0xd6, 0x1a, | ||||
| 	0x64, 0x28, 0x6b, 0x93, 0x0a, 0xcc, 0xba, 0xea, 0x5c, 0xd3, 0x59, 0xd7, 0x21, 0x8b, 0x90, 0x71, | ||||
| 	0x58, 0x5b, 0xb9, 0x5e, 0xa9, 0x87, 0x87, 0xf5, 0x4d, 0xd6, 0x76, 0x7d, 0x17, 0x5d, 0xa4, 0xb8, | ||||
| 	0x64, 0xfd, 0xde, 0xc0, 0xfa, 0xc0, 0x6d, 0x91, 0x4f, 0x06, 0xfc, 0xb8, 0xf8, 0xb4, 0x8f, 0xec, | ||||
| 	0xfe, 0x65, 0xfa, 0xee, 0xef, 0x0d, 0x66, 0xe2, 0x82, 0x12, 0xd0, 0xbd, 0xfb, 0x29, 0x94, 0xf4, | ||||
| 	0xdc, 0x90, 0x1d, 0x28, 0x6a, 0xe7, 0x48, 0x6d, 0x78, 0x69, 0xb2, 0xcc, 0x52, 0x5d, 0xd4, 0xfa, | ||||
| 	0x63, 0x06, 0x8a, 0xda, 0x22, 0x79, 0x0c, 0xd9, 0x57, 0xae, 0x2f, 0x43, 0x58, 0x59, 0x5d, 0x99, | ||||
| 	0x4c, 0xe5, 0x53, 0xd7, 0x77, 0xa8, 0x90, 0x23, 0x4d, 0xad, 0xee, 0x66, 0xc5, 0xb6, 0xee, 0x4d, | ||||
| 	0xa6, 0xe3, 0xdc, 0xe2, 0xbb, 0x33, 0x45, 0xdb, 0x90, 0x4d, 0x83, 0x40, 0x36, 0xb4, 0xf9, 0x91, | ||||
| 	0x68, 0x1a, 0x05, 0x2a, 0xc6, 0xe4, 0x0e, 0xbc, 0xe3, 0xfa, 0x2f, 0x02, 0x1e, 0x34, 0x23, 0xe6, | ||||
| 	0xb8, 0x78, 0xf8, 0x5e, 0x9c, 0x85, 0xac, 0x36, 0x27, 0x58, 0xc6, 0x2d, 0x91, 0x26, 0x54, 0x24, | ||||
| 	0xf9, 0xa0, 0x7b, 0xf8, 0x0b, 0xd6, 0xe2, 0x71, 0x2d, 0x27, 0xfc, 0x59, 0x4e, 0xd9, 0xc2, 0xae, | ||||
| 	0x2e, 0x40, 0x87, 0xe4, 0xdf, 0xaa, 0xda, 0xad, 0xbf, 0x18, 0x50, 0x1e, 0x50, 0x4f, 0x3e, 0x1d, | ||||
| 	0x48, 0xd5, 0xed, 0x49, 0xb7, 0xa5, 0x25, 0xeb, 0x33, 0xc8, 0x39, 0x6e, 0x87, 0xc5, 0x5c, 0xa4, | ||||
| 	0xaa, 0xb0, 0xbe, 0xfa, 0xdd, 0xf7, 0x1f, 0xcc, 0xfc, 0xf3, 0xfb, 0x0f, 0x56, 0xb4, 0xab, 0x26, | ||||
| 	0x08, 0x99, 0xdf, 0x0a, 0x7c, 0x6e, 0xbb, 0x3e, 0x8b, 0xf0, 0x82, 0xfd, 0x58, 0x8a, 0xd4, 0x37, | ||||
| 	0xc5, 0x0f, 0x55, 0x1a, 0x30, 0xe8, 0xbe, 0x7d, 0xcc, 0x44, 0x9e, 0x0a, 0x54, 0x8c, 0x2d, 0x0e, | ||||
| 	0x65, 0xca, 0x78, 0x37, 0xf2, 0x29, 0xfb, 0x65, 0x17, 0x99, 0x7e, 0x90, 0x34, 0x12, 0xb1, 0xe9, | ||||
| 	0x8b, 0x1a, 0x3a, 0x32, 0x52, 0x25, 0x40, 0x96, 0x61, 0x8e, 0x45, 0x51, 0x10, 0xa9, 0xe2, 0x21, | ||||
| 	0x75, 0x79, 0xd5, 0xd7, 0xa3, 0xb0, 0x55, 0x3f, 0x10, 0x57, 0x3d, 0x95, 0x0c, 0x56, 0x15, 0x2a, | ||||
| 	0x89, 0xd5, 0x38, 0x0c, 0xfc, 0x98, 0x59, 0x0b, 0x18, 0xba, 0xb0, 0xcb, 0x63, 0xb5, 0x0f, 0xeb, | ||||
| 	0x5b, 0x03, 0x2a, 0x09, 0x45, 0xf2, 0x90, 0x2f, 0xa0, 0xd8, 0x6f, 0x0d, 0x49, 0x0f, 0x78, 0x98, | ||||
| 	0x1a, 0x54, 0x5d, 0x5e, 0xeb, 0x2b, 0xaa, 0x25, 0xe8, 0xea, 0xcc, 0x67, 0x50, 0x1d, 0x66, 0x18, | ||||
| 	0x93, 0xfd, 0x0f, 0x07, 0x1b, 0xc4, 0x70, 0xbf, 0xd2, 0x4e, 0xc3, 0xb7, 0xb3, 0x70, 0x85, 0x32, | ||||
| 	0x81, 0x5d, 0x76, 0x8f, 0xed, 0x0e, 0xdb, 0x08, 0xfc, 0xb6, 0xdb, 0x49, 0xc2, 0x5c, 0x15, 0xcd, | ||||
| 	0x30, 0xd1, 0x8c, 0x7d, 0x71, 0x19, 0xf2, 0x4d, 0xcf, 0xe6, 0xed, 0x20, 0x3a, 0x56, 0xca, 0x4b, | ||||
| 	0xa8, 0x3c, 0xa1, 0xd1, 0xde, 0x2a, 0x59, 0x84, 0xa2, 0x52, 0xbc, 0x1f, 0x38, 0x49, 0x3a, 0x75, | ||||
| 	0x12, 0xa9, 0xc1, 0xfc, 0x5e, 0xd0, 0x79, 0x86, 0xc9, 0x96, 0x15, 0x96, 0x4c, 0x89, 0x05, 0x25, | ||||
| 	0xc5, 0x18, 0xf5, 0xaa, 0x6b, 0x8e, 0x0e, 0xd0, 0xc8, 0xfb, 0x50, 0x38, 0x60, 0x71, 0xec, 0x06, | ||||
| 	0xfe, 0xee, 0x66, 0x2d, 0x27, 0xe4, 0xfb, 0x04, 0xd4, 0x7d, 0xc0, 0x83, 0x88, 0xed, 0x6e, 0xd6, | ||||
| 	0xe6, 0xa5, 0x6e, 0x35, 0x25, 0xfb, 0x50, 0x39, 0x10, 0x38, 0xa7, 0x89, 0xe8, 0xc6, 0x65, 0x71, | ||||
| 	0x2d, 0x2f, 0x52, 0x74, 0x63, 0x34, 0x45, 0x3a, 0x1e, 0xaa, 0x0b, 0xf6, 0x33, 0x3a, 0x24, 0x6c, | ||||
| 	0xfd, 0xd6, 0x00, 0x73, 0x5c, 0x00, 0xd5, 0x69, 0xf8, 0x0c, 0x72, 0xf2, 0x7c, 0xcb, 0x20, 0xfe, | ||||
| 	0x6f, 0x95, 0x21, 0x7f, 0xc9, 0x7b, 0x90, 0x93, 0xda, 0x55, 0x51, 0xab, 0x59, 0x92, 0xa5, 0x4c, | ||||
| 	0x2f, 0x4b, 0xd6, 0xaf, 0x73, 0x50, 0x3a, 0xc0, 0x2d, 0x25, 0x89, 0xac, 0x03, 0xf4, 0xf3, 0xaf, | ||||
| 	0x6a, 0x66, 0xf8, 0x54, 0x68, 0x1c, 0xc4, 0x84, 0xfc, 0xb6, 0x3a, 0x9f, 0xea, 0x8a, 0xec, 0xcd, | ||||
| 	0xc9, 0xe7, 0x50, 0x4c, 0xc6, 0xcf, 0x43, 0x5e, 0xcb, 0x88, 0xe8, 0x3d, 0x48, 0x39, 0xe0, 0xfa, | ||||
| 	0x4e, 0xea, 0x9a, 0xa8, 0x3a, 0xde, 0x1a, 0x85, 0xdc, 0x86, 0x4b, 0xb6, 0xe7, 0x05, 0xa7, 0xaa, | ||||
| 	0x66, 0x45, 0xf5, 0x89, 0xec, 0xe7, 0xe9, 0xe8, 0x02, 0xf6, 0x62, 0x8d, 0xf8, 0x24, 0x8a, 0xec, | ||||
| 	0x33, 0x0c, 0x44, 0x4e, 0xf0, 0x8f, 0x5b, 0xc2, 0xb6, 0xb8, 0xed, 0xfa, 0xb6, 0x57, 0x03, 0xc1, | ||||
| 	0x23, 0x27, 0x78, 0xdc, 0xb6, 0x5e, 0x87, 0x41, 0xc4, 0x59, 0xf4, 0x84, 0xf3, 0xa8, 0x56, 0x14, | ||||
| 	0xe1, 0x1d, 0xa0, 0x91, 0x26, 0x94, 0x36, 0xec, 0xd6, 0x11, 0xdb, 0x3d, 0x46, 0x62, 0x02, 0xdd, | ||||
| 	0xd2, 0x9a, 0xa5, 0x60, 0x7f, 0x1e, 0xea, 0x98, 0x4d, 0xd7, 0x40, 0x5a, 0x50, 0x49, 0x5c, 0x97, | ||||
| 	0x2d, 0xa0, 0x56, 0x16, 0x3a, 0x1f, 0x4d, 0x1b, 0x4a, 0x29, 0x2d, 0x4d, 0x0c, 0xa9, 0xc4, 0x44, | ||||
| 	0x6e, 0x61, 0xb5, 0xdb, 0x9c, 0xd5, 0x2a, 0xc2, 0xe7, 0xde, 0x7c, 0x4c, 0x25, 0x2c, 0xbc, 0x45, | ||||
| 	0x25, 0x98, 0x8f, 0xa1, 0x3a, 0x9c, 0xdc, 0x69, 0x90, 0x97, 0xf9, 0x63, 0x78, 0x67, 0x8c, 0x47, | ||||
| 	0x6f, 0xd5, 0xdd, 0xfe, 0x6c, 0xc0, 0xa5, 0x91, 0x34, 0xe0, 0x0d, 0x23, 0xba, 0x8a, 0x54, 0x29, | ||||
| 	0xc6, 0x64, 0x1f, 0xe6, 0x30, 0xcd, 0xb1, 0xc2, 0x1a, 0x6b, 0xd3, 0xe4, 0xb5, 0x2e, 0x24, 0x65, | ||||
| 	0xfc, 0xa5, 0x16, 0xf3, 0x01, 0x40, 0x9f, 0x38, 0x15, 0xfe, 0xfc, 0x02, 0xca, 0x2a, 0xc9, 0xaa, | ||||
| 	0x83, 0x54, 0x25, 0x6c, 0x51, 0xc2, 0x08, 0x4b, 0xfa, 0x97, 0x5f, 0x66, 0xca, 0xcb, 0xcf, 0xfa, | ||||
| 	0x0a, 0x16, 0x28, 0xb3, 0x9d, 0x6d, 0xd7, 0x63, 0xe7, 0xf7, 0x78, 0x2c, 0x7e, 0xd7, 0x63, 0x4d, | ||||
| 	0x84, 0x3e, 0x49, 0xf1, 0xab, 0x39, 0x79, 0x08, 0x73, 0xd4, 0xf6, 0x3b, 0x4c, 0x99, 0xfe, 0x30, | ||||
| 	0xc5, 0xb4, 0x30, 0x82, 0xbc, 0x54, 0x8a, 0x58, 0x8f, 0xa0, 0xd0, 0xa3, 0x61, 0x33, 0x7b, 0xde, | ||||
| 	0x6e, 0xc7, 0x4c, 0x36, 0xc6, 0x0c, 0x55, 0x33, 0xa4, 0xef, 0x31, 0xbf, 0xa3, 0x4c, 0x67, 0xa8, | ||||
| 	0x9a, 0x59, 0x4b, 0xf8, 0x5e, 0x48, 0x76, 0xae, 0x42, 0x43, 0x20, 0xbb, 0x89, 0xf8, 0xd0, 0x10, | ||||
| 	0xf5, 0x2a, 0xc6, 0x96, 0x83, 0x97, 0xb6, 0xed, 0x6c, 0xba, 0xd1, 0xf9, 0x0e, 0xd6, 0x60, 0x7e, | ||||
| 	0xd3, 0x8d, 0x34, 0xff, 0x92, 0x29, 0x59, 0xc2, 0xeb, 0xbc, 0xe5, 0x75, 0x1d, 0xf4, 0x96, 0xb3, | ||||
| 	0xc8, 0x57, 0x5d, 0x75, 0x88, 0x6a, 0x7d, 0x22, 0xe3, 0x28, 0xac, 0xa8, 0xcd, 0xdc, 0x86, 0x79, | ||||
| 	0xe6, 0xf3, 0x08, 0xcb, 0x48, 0xde, 0xf9, 0xa4, 0x2e, 0x5f, 0xe0, 0x75, 0xf1, 0x02, 0x17, 0xd8, | ||||
| 	0x82, 0x26, 0x2c, 0xd6, 0x1a, 0x2c, 0x20, 0x21, 0x3d, 0x11, 0x04, 0xb2, 0xda, 0x26, 0xc5, 0xd8, | ||||
| 	0x7a, 0x08, 0xd5, 0xbe, 0xa0, 0x32, 0xbd, 0x04, 0x59, 0x04, 0xbf, 0xaa, 0xaf, 0x8f, 0xb3, 0x2b, | ||||
| 	0xd6, 0xad, 0xeb, 0xb0, 0x90, 0x14, 0xff, 0xb9, 0x46, 0x2d, 0x02, 0xd5, 0x3e, 0x93, 0xc2, 0x3d, | ||||
| 	0x65, 0x28, 0x36, 0x5d, 0x3f, 0x81, 0x05, 0xd6, 0x1b, 0x03, 0x4a, 0xcd, 0xc0, 0xef, 0xdf, 0x72, | ||||
| 	0x4d, 0x58, 0x48, 0x4a, 0xf7, 0x49, 0x73, 0x77, 0xc3, 0x0e, 0x93, 0x18, 0x2c, 0x8e, 0x9e, 0x0f, | ||||
| 	0xf5, 0x0d, 0xa3, 0x2e, 0x19, 0xd7, 0xb3, 0x78, 0x21, 0xd2, 0x61, 0x71, 0xf2, 0x29, 0xcc, 0xef, | ||||
| 	0xed, 0xad, 0x0b, 0x4d, 0xb3, 0x53, 0x69, 0x4a, 0xc4, 0xc8, 0x63, 0x98, 0x7f, 0x29, 0x3e, 0xad, | ||||
| 	0xc4, 0xea, 0x8a, 0x1a, 0x73, 0x56, 0x65, 0x84, 0x24, 0x1b, 0x65, 0xad, 0x20, 0x72, 0x68, 0x22, | ||||
| 	0x64, 0xfd, 0xdb, 0x80, 0xe2, 0x4b, 0xbb, 0x0f, 0x39, 0xfb, 0x18, 0xf7, 0x2d, 0x6e, 0x72, 0x85, | ||||
| 	0x71, 0x2f, 0xc3, 0x9c, 0xc7, 0x4e, 0x98, 0xa7, 0xce, 0xb8, 0x9c, 0x20, 0x35, 0x3e, 0x0a, 0x22, | ||||
| 	0x59, 0xd6, 0x25, 0x2a, 0x27, 0x58, 0x10, 0x0e, 0xe3, 0xb6, 0xeb, 0xd5, 0xb2, 0x8b, 0x19, 0xbc, | ||||
| 	0xf5, 0xe5, 0x0c, 0x33, 0xd7, 0x8d, 0x3c, 0xf5, 0xf0, 0xc0, 0x21, 0xb1, 0x20, 0xeb, 0xfa, 0xed, | ||||
| 	0x40, 0xdc, 0x7f, 0xaa, 0x2d, 0xca, 0x16, 0xbd, 0xeb, 0xb7, 0x03, 0x2a, 0xd6, 0xc8, 0x35, 0xc8, | ||||
| 	0x45, 0x58, 0x7f, 0x71, 0x6d, 0x5e, 0x04, 0xa5, 0x80, 0x5c, 0xb2, 0x4a, 0xd5, 0x82, 0x55, 0x81, | ||||
| 	0x92, 0xf4, 0x5b, 0x25, 0xff, 0x4f, 0xb3, 0xf0, 0xce, 0x33, 0x76, 0xba, 0x91, 0xf8, 0x95, 0x04, | ||||
| 	0x64, 0x11, 0x8a, 0x3d, 0xda, 0xee, 0xa6, 0x3a, 0x42, 0x3a, 0x09, 0x8d, 0xed, 0x07, 0x5d, 0x9f, | ||||
| 	0x27, 0x39, 0x14, 0xc6, 0x04, 0x85, 0xaa, 0x05, 0x72, 0x03, 0xe6, 0x9f, 0x31, 0x7e, 0x1a, 0x44, | ||||
| 	0xaf, 0x84, 0xd7, 0x95, 0xd5, 0x22, 0xf2, 0x3c, 0x63, 0x1c, 0x11, 0x22, 0x4d, 0xd6, 0x10, 0x76, | ||||
| 	0x86, 0x09, 0xec, 0xcc, 0x8e, 0x83, 0x9d, 0xc9, 0x2a, 0x59, 0x83, 0x62, 0x2b, 0xf0, 0x63, 0x1e, | ||||
| 	0xd9, 0x2e, 0x1a, 0x9e, 0x13, 0xcc, 0xef, 0x22, 0xb3, 0x4c, 0xec, 0x46, 0x7f, 0x91, 0xea, 0x9c, | ||||
| 	0x64, 0x05, 0x80, 0xbd, 0xe6, 0x91, 0xbd, 0x13, 0xc4, 0xbd, 0x27, 0x1a, 0xa0, 0x1c, 0x12, 0x76, | ||||
| 	0x9b, 0x54, 0x5b, 0xc5, 0x0e, 0x79, 0x14, 0xc4, 0x5c, 0xbc, 0x53, 0x24, 0xbc, 0xec, 0xcd, 0xad, | ||||
| 	0xf7, 0xe0, 0xf2, 0x60, 0xb4, 0x54, 0x18, 0x1f, 0xc1, 0xff, 0x51, 0xe6, 0x31, 0x3b, 0x66, 0xd3, | ||||
| 	0x47, 0xd2, 0x32, 0xa1, 0x36, 0x2a, 0xac, 0x14, 0xff, 0x27, 0x03, 0xc5, 0xad, 0xd7, 0xac, 0xb5, | ||||
| 	0xcf, 0xe2, 0xd8, 0xee, 0x08, 0x60, 0xdc, 0x8c, 0x82, 0x16, 0x8b, 0xe3, 0x9e, 0xae, 0x3e, 0x81, | ||||
| 	0xfc, 0x10, 0xb2, 0xbb, 0xbe, 0xcb, 0xd5, 0xdd, 0xb9, 0x94, 0xfa, 0x2e, 0x71, 0xb9, 0xd2, 0xb9, | ||||
| 	0x33, 0x43, 0x85, 0x14, 0x79, 0x08, 0x59, 0xec, 0x3c, 0x93, 0x74, 0x7f, 0x47, 0x93, 0x45, 0x19, | ||||
| 	0xb2, 0x2e, 0xbe, 0x1f, 0xba, 0x5f, 0x32, 0x95, 0xc1, 0xe5, 0xf4, 0x6b, 0xcb, 0xfd, 0x92, 0xf5, | ||||
| 	0x35, 0x28, 0x49, 0xb2, 0x85, 0xb0, 0xde, 0x8e, 0x38, 0x73, 0x54, 0x66, 0x6f, 0xa6, 0x81, 0x25, | ||||
| 	0xc9, 0xd9, 0xd7, 0x92, 0xc8, 0x62, 0x10, 0xb6, 0x5e, 0xbb, 0x5c, 0x55, 0x4a, 0x5a, 0x10, 0x90, | ||||
| 	0x4d, 0x73, 0x04, 0xa7, 0x28, 0xbd, 0x19, 0xf8, 0x32, 0xf3, 0xe9, 0xd2, 0xc8, 0xa6, 0x49, 0xe3, | ||||
| 	0x14, 0xc3, 0x70, 0xe0, 0x76, 0x10, 0x83, 0xe6, 0x2f, 0x0c, 0x83, 0x64, 0xd4, 0xc2, 0x20, 0x09, | ||||
| 	0xeb, 0xf3, 0x30, 0x27, 0x20, 0x92, 0xf5, 0x77, 0x03, 0x8a, 0x5a, 0x9e, 0x26, 0xa8, 0xc9, 0xf7, | ||||
| 	0x21, 0xbb, 0xcf, 0xc4, 0x37, 0x15, 0x34, 0x9e, 0x17, 0x15, 0xc9, 0xb8, 0x4d, 0x05, 0x15, 0x9b, | ||||
| 	0xca, 0xb6, 0x23, 0x1b, 0x66, 0x99, 0xe2, 0x10, 0x29, 0x2f, 0xf8, 0x99, 0x48, 0x59, 0x9e, 0xe2, | ||||
| 	0x90, 0xdc, 0x86, 0xfc, 0x01, 0x6b, 0x75, 0x23, 0x97, 0x9f, 0x89, 0x24, 0x54, 0x56, 0xab, 0xa2, | ||||
| 	0xd5, 0x28, 0x9a, 0x28, 0xdc, 0x1e, 0x07, 0xb9, 0x05, 0x85, 0x98, 0xb5, 0x22, 0xc6, 0x99, 0x7f, | ||||
| 	0xa2, 0xaa, 0xaa, 0xac, 0xd8, 0x23, 0xc6, 0xb7, 0xfc, 0x13, 0xda, 0x5f, 0xb7, 0x9e, 0xe2, 0x49, | ||||
| 	0xee, 0x7b, 0x43, 0x20, 0xbb, 0x81, 0x6f, 0x47, 0x74, 0xa3, 0x4c, 0xc5, 0x18, 0x9f, 0xef, 0x5b, | ||||
| 	0x17, 0x3d, 0xdf, 0xb7, 0x92, 0xe7, 0xfb, 0xe0, 0x09, 0xc0, 0x6b, 0x4c, 0xcb, 0x88, 0xf5, 0x04, | ||||
| 	0x0a, 0xbd, 0x53, 0x4a, 0x2a, 0x30, 0xbb, 0xed, 0x28, 0x4b, 0xb3, 0xdb, 0x0e, 0xfa, 0xbd, 0xf5, | ||||
| 	0x7c, 0x5b, 0x58, 0xc9, 0x53, 0x1c, 0xf6, 0xd0, 0x46, 0x46, 0x43, 0x1b, 0x6b, 0x50, 0x1e, 0x38, | ||||
| 	0xaa, 0xc8, 0x44, 0x83, 0xd3, 0x38, 0xd9, 0x32, 0x8e, 0xa5, 0x1b, 0x5e, 0x2c, 0x74, 0x09, 0x37, | ||||
| 	0xbc, 0xd8, 0xba, 0x0e, 0xe5, 0x81, 0xe4, 0x22, 0x93, 0x78, 0x09, 0x2b, 0x50, 0x8a, 0xe3, 0x15, | ||||
| 	0x06, 0x0b, 0x43, 0x1f, 0xc7, 0xc8, 0x0d, 0xc8, 0xc9, 0x8f, 0x30, 0xd5, 0x19, 0xf3, 0xca, 0xd7, | ||||
| 	0xdf, 0x2c, 0xbe, 0x3b, 0xc4, 0x20, 0x17, 0x91, 0x6d, 0xbd, 0xeb, 0x3b, 0x1e, 0xab, 0x1a, 0x63, | ||||
| 	0xd9, 0xe4, 0xa2, 0x99, 0xfd, 0xcd, 0x1f, 0xae, 0xce, 0xac, 0xd8, 0x70, 0x69, 0xe4, 0xc3, 0x0e, | ||||
| 	0xb9, 0x0e, 0xd9, 0x03, 0xe6, 0xb5, 0x13, 0x33, 0x23, 0x0c, 0xb8, 0x48, 0xae, 0x41, 0x86, 0xda, | ||||
| 	0xa7, 0x55, 0xc3, 0xac, 0x7d, 0xfd, 0xcd, 0xe2, 0xe5, 0xd1, 0xaf, 0x43, 0xf6, 0xa9, 0x34, 0xb1, | ||||
| 	0xfa, 0x57, 0x80, 0xc2, 0xde, 0xde, 0xfa, 0x7a, 0xe4, 0x3a, 0x1d, 0x46, 0x7e, 0x65, 0x00, 0x19, | ||||
| 	0x7d, 0x33, 0x93, 0x7b, 0xe9, 0x0d, 0x61, 0xfc, 0x37, 0x0a, 0xf3, 0xfe, 0x94, 0x52, 0x0a, 0xb2, | ||||
| 	0x7c, 0x0e, 0x73, 0x02, 0x67, 0x93, 0x8f, 0x26, 0x7c, 0x6e, 0x99, 0xcb, 0x17, 0x33, 0x2a, 0xdd, | ||||
| 	0x2d, 0xc8, 0x27, 0x58, 0x95, 0xac, 0xa4, 0x6e, 0x6f, 0x00, 0x8a, 0x9b, 0xb7, 0x26, 0xe2, 0x55, | ||||
| 	0x46, 0x7e, 0x0e, 0xf3, 0x0a, 0x82, 0x92, 0x9b, 0x17, 0xc8, 0xf5, 0xc1, 0xb0, 0xb9, 0x32, 0x09, | ||||
| 	0x6b, 0xdf, 0x8d, 0x04, 0x6a, 0xa6, 0xba, 0x31, 0x04, 0x64, 0x53, 0xdd, 0x18, 0xc1, 0xae, 0xad, | ||||
| 	0xfe, 0x03, 0x35, 0xd5, 0xc8, 0x10, 0x70, 0x4d, 0x35, 0x32, 0x8c, 0x5f, 0xc9, 0x4b, 0xc8, 0x22, | ||||
| 	0x7e, 0x25, 0x69, 0xbd, 0x5a, 0x03, 0xb8, 0x66, 0xda, 0x99, 0x18, 0x00, 0xbe, 0x3f, 0xc3, 0x3b, | ||||
| 	0x4d, 0x7c, 0x8b, 0x48, 0xbf, 0xcd, 0xb4, 0x6f, 0x97, 0xe6, 0xcd, 0x09, 0x38, 0xfb, 0xea, 0xd5, | ||||
| 	0x3b, 0x7e, 0x79, 0x82, 0x0f, 0x88, 0x17, 0xab, 0x1f, 0xfa, 0x54, 0x19, 0x40, 0x49, 0x87, 0x2a, | ||||
| 	0xa4, 0x9e, 0x22, 0x3a, 0x06, 0x01, 0x9a, 0x8d, 0x89, 0xf9, 0x95, 0xc1, 0xaf, 0xf0, 0x11, 0x37, | ||||
| 	0x08, 0x63, 0xc8, 0x6a, 0x6a, 0x38, 0xc6, 0x02, 0x26, 0xf3, 0xee, 0x54, 0x32, 0xca, 0xb8, 0x2d, | ||||
| 	0x61, 0x92, 0x82, 0x42, 0x24, 0xfd, 0xd6, 0xef, 0xc1, 0x29, 0x73, 0x42, 0xbe, 0x65, 0xe3, 0x8e, | ||||
| 	0x81, 0xe7, 0x0c, 0xa1, 0x73, 0xaa, 0x6e, 0xed, 0x4d, 0x91, 0x7a, 0xce, 0x74, 0x0c, 0xbe, 0x5e, | ||||
| 	0xfa, 0xee, 0xcd, 0x55, 0xe3, 0x1f, 0x6f, 0xae, 0x1a, 0xff, 0x7a, 0x73, 0xd5, 0x38, 0xcc, 0x89, | ||||
| 	0x7f, 0x64, 0xef, 0xfe, 0x37, 0x00, 0x00, 0xff, 0xff, 0x20, 0x47, 0x7d, 0x27, 0x1a, 0x1f, 0x00, | ||||
| 	0x00, | ||||
| } | ||||
| 
 | ||||
| // Reference imports to suppress errors if they are not otherwise used.
 | ||||
|  | @ -4022,6 +4049,20 @@ func (m *ResolveImageConfigRequest) MarshalToSizedBuffer(dAtA []byte) (int, erro | |||
| 		i -= len(m.XXX_unrecognized) | ||||
| 		copy(dAtA[i:], m.XXX_unrecognized) | ||||
| 	} | ||||
| 	if len(m.SourcePolicies) > 0 { | ||||
| 		for iNdEx := len(m.SourcePolicies) - 1; iNdEx >= 0; iNdEx-- { | ||||
| 			{ | ||||
| 				size, err := m.SourcePolicies[iNdEx].MarshalToSizedBuffer(dAtA[:i]) | ||||
| 				if err != nil { | ||||
| 					return 0, err | ||||
| 				} | ||||
| 				i -= size | ||||
| 				i = encodeVarintGateway(dAtA, i, uint64(size)) | ||||
| 			} | ||||
| 			i-- | ||||
| 			dAtA[i] = 0x42 | ||||
| 		} | ||||
| 	} | ||||
| 	if len(m.StoreID) > 0 { | ||||
| 		i -= len(m.StoreID) | ||||
| 		copy(dAtA[i:], m.StoreID) | ||||
|  | @ -4101,6 +4142,13 @@ func (m *ResolveImageConfigResponse) MarshalToSizedBuffer(dAtA []byte) (int, err | |||
| 		i -= len(m.XXX_unrecognized) | ||||
| 		copy(dAtA[i:], m.XXX_unrecognized) | ||||
| 	} | ||||
| 	if len(m.Ref) > 0 { | ||||
| 		i -= len(m.Ref) | ||||
| 		copy(dAtA[i:], m.Ref) | ||||
| 		i = encodeVarintGateway(dAtA, i, uint64(len(m.Ref))) | ||||
| 		i-- | ||||
| 		dAtA[i] = 0x1a | ||||
| 	} | ||||
| 	if len(m.Config) > 0 { | ||||
| 		i -= len(m.Config) | ||||
| 		copy(dAtA[i:], m.Config) | ||||
|  | @ -5348,6 +5396,20 @@ func (m *InitMessage) MarshalToSizedBuffer(dAtA []byte) (int, error) { | |||
| 		i -= len(m.XXX_unrecognized) | ||||
| 		copy(dAtA[i:], m.XXX_unrecognized) | ||||
| 	} | ||||
| 	if len(m.Secretenv) > 0 { | ||||
| 		for iNdEx := len(m.Secretenv) - 1; iNdEx >= 0; iNdEx-- { | ||||
| 			{ | ||||
| 				size, err := m.Secretenv[iNdEx].MarshalToSizedBuffer(dAtA[:i]) | ||||
| 				if err != nil { | ||||
| 					return 0, err | ||||
| 				} | ||||
| 				i -= size | ||||
| 				i = encodeVarintGateway(dAtA, i, uint64(size)) | ||||
| 			} | ||||
| 			i-- | ||||
| 			dAtA[i] = 0x32 | ||||
| 		} | ||||
| 	} | ||||
| 	if m.Security != 0 { | ||||
| 		i = encodeVarintGateway(dAtA, i, uint64(m.Security)) | ||||
| 		i-- | ||||
|  | @ -5973,6 +6035,12 @@ func (m *ResolveImageConfigRequest) Size() (n int) { | |||
| 	if l > 0 { | ||||
| 		n += 1 + l + sovGateway(uint64(l)) | ||||
| 	} | ||||
| 	if len(m.SourcePolicies) > 0 { | ||||
| 		for _, e := range m.SourcePolicies { | ||||
| 			l = e.Size() | ||||
| 			n += 1 + l + sovGateway(uint64(l)) | ||||
| 		} | ||||
| 	} | ||||
| 	if m.XXX_unrecognized != nil { | ||||
| 		n += len(m.XXX_unrecognized) | ||||
| 	} | ||||
|  | @ -5993,6 +6061,10 @@ func (m *ResolveImageConfigResponse) Size() (n int) { | |||
| 	if l > 0 { | ||||
| 		n += 1 + l + sovGateway(uint64(l)) | ||||
| 	} | ||||
| 	l = len(m.Ref) | ||||
| 	if l > 0 { | ||||
| 		n += 1 + l + sovGateway(uint64(l)) | ||||
| 	} | ||||
| 	if m.XXX_unrecognized != nil { | ||||
| 		n += len(m.XXX_unrecognized) | ||||
| 	} | ||||
|  | @ -6586,6 +6658,12 @@ func (m *InitMessage) Size() (n int) { | |||
| 	if m.Security != 0 { | ||||
| 		n += 1 + sovGateway(uint64(m.Security)) | ||||
| 	} | ||||
| 	if len(m.Secretenv) > 0 { | ||||
| 		for _, e := range m.Secretenv { | ||||
| 			l = e.Size() | ||||
| 			n += 1 + l + sovGateway(uint64(l)) | ||||
| 		} | ||||
| 	} | ||||
| 	if m.XXX_unrecognized != nil { | ||||
| 		n += len(m.XXX_unrecognized) | ||||
| 	} | ||||
|  | @ -8819,6 +8897,40 @@ func (m *ResolveImageConfigRequest) Unmarshal(dAtA []byte) error { | |||
| 			} | ||||
| 			m.StoreID = string(dAtA[iNdEx:postIndex]) | ||||
| 			iNdEx = postIndex | ||||
| 		case 8: | ||||
| 			if wireType != 2 { | ||||
| 				return fmt.Errorf("proto: wrong wireType = %d for field SourcePolicies", wireType) | ||||
| 			} | ||||
| 			var msglen int | ||||
| 			for shift := uint(0); ; shift += 7 { | ||||
| 				if shift >= 64 { | ||||
| 					return ErrIntOverflowGateway | ||||
| 				} | ||||
| 				if iNdEx >= l { | ||||
| 					return io.ErrUnexpectedEOF | ||||
| 				} | ||||
| 				b := dAtA[iNdEx] | ||||
| 				iNdEx++ | ||||
| 				msglen |= int(b&0x7F) << shift | ||||
| 				if b < 0x80 { | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 			if msglen < 0 { | ||||
| 				return ErrInvalidLengthGateway | ||||
| 			} | ||||
| 			postIndex := iNdEx + msglen | ||||
| 			if postIndex < 0 { | ||||
| 				return ErrInvalidLengthGateway | ||||
| 			} | ||||
| 			if postIndex > l { | ||||
| 				return io.ErrUnexpectedEOF | ||||
| 			} | ||||
| 			m.SourcePolicies = append(m.SourcePolicies, &pb1.Policy{}) | ||||
| 			if err := m.SourcePolicies[len(m.SourcePolicies)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			iNdEx = postIndex | ||||
| 		default: | ||||
| 			iNdEx = preIndex | ||||
| 			skippy, err := skipGateway(dAtA[iNdEx:]) | ||||
|  | @ -8936,6 +9048,38 @@ func (m *ResolveImageConfigResponse) Unmarshal(dAtA []byte) error { | |||
| 				m.Config = []byte{} | ||||
| 			} | ||||
| 			iNdEx = postIndex | ||||
| 		case 3: | ||||
| 			if wireType != 2 { | ||||
| 				return fmt.Errorf("proto: wrong wireType = %d for field Ref", wireType) | ||||
| 			} | ||||
| 			var stringLen uint64 | ||||
| 			for shift := uint(0); ; shift += 7 { | ||||
| 				if shift >= 64 { | ||||
| 					return ErrIntOverflowGateway | ||||
| 				} | ||||
| 				if iNdEx >= l { | ||||
| 					return io.ErrUnexpectedEOF | ||||
| 				} | ||||
| 				b := dAtA[iNdEx] | ||||
| 				iNdEx++ | ||||
| 				stringLen |= uint64(b&0x7F) << shift | ||||
| 				if b < 0x80 { | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 			intStringLen := int(stringLen) | ||||
| 			if intStringLen < 0 { | ||||
| 				return ErrInvalidLengthGateway | ||||
| 			} | ||||
| 			postIndex := iNdEx + intStringLen | ||||
| 			if postIndex < 0 { | ||||
| 				return ErrInvalidLengthGateway | ||||
| 			} | ||||
| 			if postIndex > l { | ||||
| 				return io.ErrUnexpectedEOF | ||||
| 			} | ||||
| 			m.Ref = string(dAtA[iNdEx:postIndex]) | ||||
| 			iNdEx = postIndex | ||||
| 		default: | ||||
| 			iNdEx = preIndex | ||||
| 			skippy, err := skipGateway(dAtA[iNdEx:]) | ||||
|  | @ -12261,6 +12405,40 @@ func (m *InitMessage) Unmarshal(dAtA []byte) error { | |||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 		case 6: | ||||
| 			if wireType != 2 { | ||||
| 				return fmt.Errorf("proto: wrong wireType = %d for field Secretenv", wireType) | ||||
| 			} | ||||
| 			var msglen int | ||||
| 			for shift := uint(0); ; shift += 7 { | ||||
| 				if shift >= 64 { | ||||
| 					return ErrIntOverflowGateway | ||||
| 				} | ||||
| 				if iNdEx >= l { | ||||
| 					return io.ErrUnexpectedEOF | ||||
| 				} | ||||
| 				b := dAtA[iNdEx] | ||||
| 				iNdEx++ | ||||
| 				msglen |= int(b&0x7F) << shift | ||||
| 				if b < 0x80 { | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 			if msglen < 0 { | ||||
| 				return ErrInvalidLengthGateway | ||||
| 			} | ||||
| 			postIndex := iNdEx + msglen | ||||
| 			if postIndex < 0 { | ||||
| 				return ErrInvalidLengthGateway | ||||
| 			} | ||||
| 			if postIndex > l { | ||||
| 				return io.ErrUnexpectedEOF | ||||
| 			} | ||||
| 			m.Secretenv = append(m.Secretenv, &pb.SecretEnv{}) | ||||
| 			if err := m.Secretenv[len(m.Secretenv)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			iNdEx = postIndex | ||||
| 		default: | ||||
| 			iNdEx = preIndex | ||||
| 			skippy, err := skipGateway(dAtA[iNdEx:]) | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ import "github.com/tonistiigi/fsutil/types/stat.proto"; | |||
| import "github.com/moby/buildkit/sourcepolicy/pb/policy.proto"; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| option (gogoproto.sizer_all) = true; | ||||
| option (gogoproto.marshaler_all) = true; | ||||
| option (gogoproto.unmarshaler_all) = true; | ||||
|  | @ -124,11 +125,13 @@ message ResolveImageConfigRequest { | |||
| 	int32 ResolverType = 5; | ||||
| 	string SessionID = 6; | ||||
| 	string StoreID = 7; | ||||
| 	repeated moby.buildkit.v1.sourcepolicy.Policy SourcePolicies = 8; | ||||
| } | ||||
| 
 | ||||
| message ResolveImageConfigResponse { | ||||
| 	string Digest = 1 [(gogoproto.customtype) = "github.com/opencontainers/go-digest.Digest", (gogoproto.nullable) = false]; | ||||
| 	bytes Config = 2; | ||||
| 	string Ref = 3; | ||||
| } | ||||
| 
 | ||||
| message SolveRequest { | ||||
|  | @ -281,6 +284,7 @@ message InitMessage{ | |||
| 	repeated uint32 Fds = 3; | ||||
| 	bool Tty = 4; | ||||
| 	pb.SecurityMode Security = 5; | ||||
| 	repeated pb.SecretEnv secretenv = 6; | ||||
| } | ||||
| 
 | ||||
| message ExitMessage { | ||||
|  |  | |||
|  | @ -4,8 +4,11 @@ import ( | |||
| 	"context" | ||||
| 	"fmt" | ||||
| 	io "io" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"unicode" | ||||
| 
 | ||||
| 	"github.com/moby/buildkit/session" | ||||
| 	"github.com/pkg/errors" | ||||
|  | @ -82,6 +85,7 @@ func (sp *fsSyncProvider) handle(method string, stream grpc.ServerStream) (retEr | |||
| 	} | ||||
| 
 | ||||
| 	opts, _ := metadata.FromIncomingContext(stream.Context()) // if no metadata continue with empty object
 | ||||
| 	opts = decodeOpts(opts) | ||||
| 
 | ||||
| 	dirName := "" | ||||
| 	name, ok := opts[keyDirName] | ||||
|  | @ -209,6 +213,8 @@ func FSSync(ctx context.Context, c session.Caller, opt FSSendRequestOpt) error { | |||
| 
 | ||||
| 	var stream grpc.ClientStream | ||||
| 
 | ||||
| 	opts = encodeOpts(opts) | ||||
| 
 | ||||
| 	ctx = metadata.NewOutgoingContext(ctx, opts) | ||||
| 
 | ||||
| 	switch pr.name { | ||||
|  | @ -337,3 +343,60 @@ func (e InvalidSessionError) Error() string { | |||
| func (e InvalidSessionError) Unwrap() error { | ||||
| 	return e.err | ||||
| } | ||||
| 
 | ||||
| func encodeOpts(opts map[string][]string) map[string][]string { | ||||
| 	md := make(map[string][]string, len(opts)) | ||||
| 	for k, v := range opts { | ||||
| 		out, encoded := encodeStringForHeader(v) | ||||
| 		md[k] = out | ||||
| 		if encoded { | ||||
| 			md[k+"-encoded"] = []string{"1"} | ||||
| 		} | ||||
| 	} | ||||
| 	return md | ||||
| } | ||||
| 
 | ||||
| func decodeOpts(opts map[string][]string) map[string][]string { | ||||
| 	md := make(map[string][]string, len(opts)) | ||||
| 	for k, v := range opts { | ||||
| 		out := make([]string, len(v)) | ||||
| 		var isDecoded bool | ||||
| 		if v, ok := opts[k+"-encoded"]; ok && len(v) > 0 { | ||||
| 			if b, _ := strconv.ParseBool(v[0]); b { | ||||
| 				isDecoded = true | ||||
| 			} | ||||
| 		} | ||||
| 		if isDecoded { | ||||
| 			for i, s := range v { | ||||
| 				out[i], _ = url.QueryUnescape(s) | ||||
| 			} | ||||
| 		} else { | ||||
| 			copy(out, v) | ||||
| 		} | ||||
| 		md[k] = out | ||||
| 	} | ||||
| 	return md | ||||
| } | ||||
| 
 | ||||
| // encodeStringForHeader encodes a string value so it can be used in grpc header. This encoding
 | ||||
| // is backwards compatible and avoids encoding ASCII characters.
 | ||||
| func encodeStringForHeader(inputs []string) ([]string, bool) { | ||||
| 	var encode bool | ||||
| 	for _, input := range inputs { | ||||
| 		for _, runeVal := range input { | ||||
| 			// Only encode non-ASCII characters, and characters that have special
 | ||||
| 			// meaning during decoding.
 | ||||
| 			if runeVal > unicode.MaxASCII { | ||||
| 				encode = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if !encode { | ||||
| 		return inputs, false | ||||
| 	} | ||||
| 	for i, input := range inputs { | ||||
| 		inputs[i] = url.QueryEscape(input) | ||||
| 	} | ||||
| 	return inputs, true | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,10 @@ | |||
| package srctypes | ||||
| 
 | ||||
| const ( | ||||
| 	DockerImageScheme = "docker-image" | ||||
| 	GitScheme         = "git" | ||||
| 	LocalScheme       = "local" | ||||
| 	HTTPScheme        = "http" | ||||
| 	HTTPSScheme       = "https" | ||||
| 	OCIScheme         = "oci-layout" | ||||
| ) | ||||
|  | @ -0,0 +1,161 @@ | |||
| package sourcepolicy | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 
 | ||||
| 	"github.com/moby/buildkit/solver/pb" | ||||
| 	spb "github.com/moby/buildkit/sourcepolicy/pb" | ||||
| 	"github.com/moby/buildkit/util/bklog" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	// ErrSourceDenied is returned by the policy engine when a source is denied by the policy.
 | ||||
| 	ErrSourceDenied = errors.New("source denied by policy") | ||||
| 
 | ||||
| 	// ErrTooManyOps is returned by the policy engine when there are too many converts for a single source op.
 | ||||
| 	ErrTooManyOps = errors.New("too many operations") | ||||
| ) | ||||
| 
 | ||||
| // Engine is the source policy engine.
 | ||||
| // It is responsible for evaluating a source policy against a source operation.
 | ||||
| // Create one with `NewEngine`
 | ||||
| //
 | ||||
| // Rule matching is delegated to the `Matcher` interface.
 | ||||
| // Mutations are delegated to the `Mutater` interface.
 | ||||
| type Engine struct { | ||||
| 	pol     []*spb.Policy | ||||
| 	sources map[string]*selectorCache | ||||
| } | ||||
| 
 | ||||
| // NewEngine creates a new source policy engine.
 | ||||
| func NewEngine(pol []*spb.Policy) *Engine { | ||||
| 	return &Engine{ | ||||
| 		pol: pol, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // TODO: The key here can't be used to cache attr constraint regexes.
 | ||||
| func (e *Engine) selectorCache(src *spb.Selector) *selectorCache { | ||||
| 	if e.sources == nil { | ||||
| 		e.sources = map[string]*selectorCache{} | ||||
| 	} | ||||
| 
 | ||||
| 	key := src.MatchType.String() + " " + src.Identifier | ||||
| 
 | ||||
| 	if s, ok := e.sources[key]; ok { | ||||
| 		return s | ||||
| 	} | ||||
| 
 | ||||
| 	s := &selectorCache{Selector: src} | ||||
| 
 | ||||
| 	e.sources[key] = s | ||||
| 	return s | ||||
| } | ||||
| 
 | ||||
| // Evaluate evaluates a source operation against the policy.
 | ||||
| //
 | ||||
| // Policies are re-evaluated for each convert rule.
 | ||||
| // Evaluate will error if the there are too many converts for a single source op to prevent infinite loops.
 | ||||
| // This function may error out even if the op was mutated, in which case `true` will be returned along with the error.
 | ||||
| //
 | ||||
| // An error is returned when the source is denied by the policy.
 | ||||
| func (e *Engine) Evaluate(ctx context.Context, op *pb.Op) (bool, error) { | ||||
| 	if len(e.pol) == 0 { | ||||
| 		return false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	var mutated bool | ||||
| 	const maxIterr = 20 | ||||
| 
 | ||||
| 	for i := 0; ; i++ { | ||||
| 		if i > maxIterr { | ||||
| 			return mutated, errors.Wrapf(ErrTooManyOps, "too many mutations on a single source") | ||||
| 		} | ||||
| 
 | ||||
| 		srcOp := op.GetSource() | ||||
| 		if srcOp == nil { | ||||
| 			return false, nil | ||||
| 		} | ||||
| 		if i == 0 { | ||||
| 			ctx = bklog.WithLogger(ctx, bklog.G(ctx).WithField("orig", *srcOp).WithField("updated", op.GetSource())) | ||||
| 		} | ||||
| 
 | ||||
| 		mut, err := e.evaluatePolicies(ctx, srcOp) | ||||
| 		if mut { | ||||
| 			mutated = true | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return mutated, err | ||||
| 		} | ||||
| 		if !mut { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return mutated, nil | ||||
| } | ||||
| 
 | ||||
| func (e *Engine) evaluatePolicies(ctx context.Context, srcOp *pb.SourceOp) (bool, error) { | ||||
| 	for _, pol := range e.pol { | ||||
| 		mut, err := e.evaluatePolicy(ctx, pol, srcOp) | ||||
| 		if mut || err != nil { | ||||
| 			return mut, err | ||||
| 		} | ||||
| 	} | ||||
| 	return false, nil | ||||
| } | ||||
| 
 | ||||
| // evaluatePolicy evaluates a single policy against a source operation.
 | ||||
| // If the source is mutated the policy is short-circuited and `true` is returned.
 | ||||
| // If the source is denied, an error will be returned.
 | ||||
| //
 | ||||
| // For Allow/Deny rules, the last matching rule wins.
 | ||||
| // E.g. `ALLOW foo; DENY foo` will deny `foo`, `DENY foo; ALLOW foo` will allow `foo`.
 | ||||
| func (e *Engine) evaluatePolicy(ctx context.Context, pol *spb.Policy, srcOp *pb.SourceOp) (retMut bool, retErr error) { | ||||
| 	ident := srcOp.GetIdentifier() | ||||
| 
 | ||||
| 	ctx = bklog.WithLogger(ctx, bklog.G(ctx).WithField("ref", ident)) | ||||
| 	defer func() { | ||||
| 		if retMut || retErr != nil { | ||||
| 			bklog.G(ctx).WithFields( | ||||
| 				logrus.Fields{ | ||||
| 					"mutated":       retMut, | ||||
| 					"updated":       srcOp.GetIdentifier(), | ||||
| 					logrus.ErrorKey: retErr, | ||||
| 				}).Debug("Evaluated source policy") | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	var deny bool | ||||
| 	for _, rule := range pol.Rules { | ||||
| 		selector := e.selectorCache(rule.Selector) | ||||
| 		matched, err := match(ctx, selector, ident, srcOp.Attrs) | ||||
| 		if err != nil { | ||||
| 			return false, errors.Wrap(err, "error matching source policy") | ||||
| 		} | ||||
| 		if !matched { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		switch rule.Action { | ||||
| 		case spb.PolicyAction_ALLOW: | ||||
| 			deny = false | ||||
| 		case spb.PolicyAction_DENY: | ||||
| 			deny = true | ||||
| 		case spb.PolicyAction_CONVERT: | ||||
| 			mut, err := mutate(ctx, srcOp, rule, selector, ident) | ||||
| 			if err != nil || mut { | ||||
| 				return mut, errors.Wrap(err, "error mutating source policy") | ||||
| 			} | ||||
| 		default: | ||||
| 			return false, errors.Errorf("source policy: rule %s %s: unknown type %q", rule.Action, rule.Selector.Identifier, ident) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if deny { | ||||
| 		return false, errors.Wrapf(ErrSourceDenied, "source %q denied by policy", ident) | ||||
| 	} | ||||
| 	return false, nil | ||||
| } | ||||
|  | @ -0,0 +1,92 @@ | |||
| package sourcepolicy | ||||
| 
 | ||||
| import ( | ||||
| 	"regexp" | ||||
| 
 | ||||
| 	spb "github.com/moby/buildkit/sourcepolicy/pb" | ||||
| 	"github.com/moby/buildkit/util/wildcard" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
| 
 | ||||
| // Source wraps a a protobuf source in order to store cached state such as the compiled regexes.
 | ||||
| type selectorCache struct { | ||||
| 	*spb.Selector | ||||
| 
 | ||||
| 	re *regexp.Regexp | ||||
| 	w  *wildcardCache | ||||
| } | ||||
| 
 | ||||
| // Format formats the provided ref according to the match/type of the source.
 | ||||
| //
 | ||||
| // For example, if the source is a wildcard, the ref will be formatted with the wildcard in the source replacing the parameters in the destination.
 | ||||
| //
 | ||||
| //	matcher: wildcard source: "docker.io/library/golang:*"  match: "docker.io/library/golang:1.19" format: "docker.io/library/golang:${1}-alpine" result: "docker.io/library/golang:1.19-alpine"
 | ||||
| func (s *selectorCache) Format(match, format string) (string, error) { | ||||
| 	switch s.MatchType { | ||||
| 	case spb.MatchType_EXACT: | ||||
| 		return s.Identifier, nil | ||||
| 	case spb.MatchType_REGEX: | ||||
| 		re, err := s.regex() | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		return re.ReplaceAllString(match, format), nil | ||||
| 	case spb.MatchType_WILDCARD: | ||||
| 		w, err := s.wildcard() | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		m := w.Match(match) | ||||
| 		if m == nil { | ||||
| 			return match, nil | ||||
| 		} | ||||
| 
 | ||||
| 		return m.Format(format) | ||||
| 	} | ||||
| 	return "", errors.Errorf("unknown match type: %s", s.MatchType) | ||||
| } | ||||
| 
 | ||||
| // wildcardCache wraps a wildcard.Wildcard to cache returned matches by ref.
 | ||||
| // This way a match only needs to be computed once per ref.
 | ||||
| type wildcardCache struct { | ||||
| 	w *wildcard.Wildcard | ||||
| 	m map[string]*wildcard.Match | ||||
| } | ||||
| 
 | ||||
| func (w *wildcardCache) Match(ref string) *wildcard.Match { | ||||
| 	if w.m == nil { | ||||
| 		w.m = make(map[string]*wildcard.Match) | ||||
| 	} | ||||
| 
 | ||||
| 	if m, ok := w.m[ref]; ok { | ||||
| 		return m | ||||
| 	} | ||||
| 
 | ||||
| 	m := w.w.Match(ref) | ||||
| 	w.m[ref] = m | ||||
| 	return m | ||||
| } | ||||
| 
 | ||||
| func (s *selectorCache) wildcard() (*wildcardCache, error) { | ||||
| 	if s.w != nil { | ||||
| 		return s.w, nil | ||||
| 	} | ||||
| 	w, err := wildcard.New(s.Identifier) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	s.w = &wildcardCache{w: w} | ||||
| 	return s.w, nil | ||||
| } | ||||
| 
 | ||||
| func (s *selectorCache) regex() (*regexp.Regexp, error) { | ||||
| 	if s.re != nil { | ||||
| 		return s.re, nil | ||||
| 	} | ||||
| 	re, err := regexp.Compile(s.Identifier) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	s.re = re | ||||
| 	return re, nil | ||||
| } | ||||
|  | @ -0,0 +1,58 @@ | |||
| package sourcepolicy | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"regexp" | ||||
| 
 | ||||
| 	spb "github.com/moby/buildkit/sourcepolicy/pb" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
| 
 | ||||
| func match(ctx context.Context, src *selectorCache, ref string, attrs map[string]string) (bool, error) { | ||||
| 	for _, c := range src.Constraints { | ||||
| 		switch c.Condition { | ||||
| 		case spb.AttrMatch_EQUAL: | ||||
| 			if attrs[c.Key] != c.Value { | ||||
| 				return false, nil | ||||
| 			} | ||||
| 		case spb.AttrMatch_NOTEQUAL: | ||||
| 			if attrs[c.Key] == c.Value { | ||||
| 				return false, nil | ||||
| 			} | ||||
| 		case spb.AttrMatch_MATCHES: | ||||
| 			// TODO: Cache the compiled regex
 | ||||
| 			matches, err := regexp.MatchString(c.Value, attrs[c.Key]) | ||||
| 			if err != nil { | ||||
| 				return false, errors.Errorf("invalid regex %q: %v", c.Value, err) | ||||
| 			} | ||||
| 			if !matches { | ||||
| 				return false, nil | ||||
| 			} | ||||
| 		default: | ||||
| 			return false, errors.Errorf("unknown attr condition: %s", c.Condition) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if src.Identifier == ref { | ||||
| 		return true, nil | ||||
| 	} | ||||
| 
 | ||||
| 	switch src.MatchType { | ||||
| 	case spb.MatchType_EXACT: | ||||
| 		return false, nil | ||||
| 	case spb.MatchType_REGEX: | ||||
| 		re, err := src.regex() | ||||
| 		if err != nil { | ||||
| 			return false, err | ||||
| 		} | ||||
| 		return re.MatchString(ref), nil | ||||
| 	case spb.MatchType_WILDCARD: | ||||
| 		w, err := src.wildcard() | ||||
| 		if err != nil { | ||||
| 			return false, err | ||||
| 		} | ||||
| 		return w.Match(ref) != nil, nil | ||||
| 	default: | ||||
| 		return false, errors.Errorf("unknown match type: %s", src.MatchType) | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,50 @@ | |||
| package sourcepolicy | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 
 | ||||
| 	"github.com/moby/buildkit/solver/pb" | ||||
| 	spb "github.com/moby/buildkit/sourcepolicy/pb" | ||||
| 	"github.com/moby/buildkit/util/bklog" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
| 
 | ||||
| // mutate is a MutateFn which converts the source operation to the identifier and attributes provided by the policy.
 | ||||
| // If there is no change, then the return value should be false and is not considered an error.
 | ||||
| func mutate(ctx context.Context, op *pb.SourceOp, rule *spb.Rule, selector *selectorCache, ref string) (bool, error) { | ||||
| 	if rule.Updates == nil { | ||||
| 		return false, errors.Errorf("missing destination for convert rule") | ||||
| 	} | ||||
| 
 | ||||
| 	dest := rule.Updates.Identifier | ||||
| 	if dest == "" { | ||||
| 		dest = rule.Selector.Identifier | ||||
| 	} | ||||
| 	dest, err := selector.Format(ref, dest) | ||||
| 	if err != nil { | ||||
| 		return false, errors.Wrap(err, "error formatting destination") | ||||
| 	} | ||||
| 
 | ||||
| 	bklog.G(ctx).Debugf("sourcepolicy: converting %s to %s, pattern: %s", ref, dest, rule.Updates.Identifier) | ||||
| 
 | ||||
| 	var mutated bool | ||||
| 	if op.Identifier != dest && dest != "" { | ||||
| 		mutated = true | ||||
| 		op.Identifier = dest | ||||
| 	} | ||||
| 
 | ||||
| 	if rule.Updates.Attrs != nil { | ||||
| 		if op.Attrs == nil { | ||||
| 			op.Attrs = make(map[string]string, len(rule.Updates.Attrs)) | ||||
| 		} | ||||
| 		for k, v := range rule.Updates.Attrs { | ||||
| 			if op.Attrs[k] != v { | ||||
| 				bklog.G(ctx).Debugf("setting attr %s=%s", k, v) | ||||
| 				op.Attrs[k] = v | ||||
| 				mutated = true | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return mutated, nil | ||||
| } | ||||
|  | @ -20,7 +20,6 @@ func ProviderFromRef(ref string) (ocispecs.Descriptor, content.Provider, error) | |||
| 	headers := http.Header{} | ||||
| 	headers.Set("User-Agent", version.UserAgent()) | ||||
| 	remote := docker.NewResolver(docker.ResolverOptions{ | ||||
| 		Client:  http.DefaultClient, | ||||
| 		Headers: headers, | ||||
| 	}) | ||||
| 
 | ||||
|  | @ -40,7 +39,6 @@ func IngesterFromRef(ref string) (content.Ingester, error) { | |||
| 	headers := http.Header{} | ||||
| 	headers.Set("User-Agent", version.UserAgent()) | ||||
| 	remote := docker.NewResolver(docker.ResolverOptions{ | ||||
| 		Client:  http.DefaultClient, | ||||
| 		Headers: headers, | ||||
| 	}) | ||||
| 
 | ||||
|  |  | |||
|  | @ -25,13 +25,13 @@ type contextKeyT string | |||
| var contextKey = contextKeyT("buildkit/util/flightcontrol.progress") | ||||
| 
 | ||||
| // Group is a flightcontrol synchronization group
 | ||||
| type Group struct { | ||||
| type Group[T any] struct { | ||||
| 	mu sync.Mutex          // protects m
 | ||||
| 	m  map[string]*call // lazily initialized
 | ||||
| 	m  map[string]*call[T] // lazily initialized
 | ||||
| } | ||||
| 
 | ||||
| // Do executes a context function syncronized by the key
 | ||||
| func (g *Group) Do(ctx context.Context, key string, fn func(ctx context.Context) (interface{}, error)) (v interface{}, err error) { | ||||
| func (g *Group[T]) Do(ctx context.Context, key string, fn func(ctx context.Context) (T, error)) (v T, err error) { | ||||
| 	var backoff time.Duration | ||||
| 	for { | ||||
| 		v, err = g.do(ctx, key, fn) | ||||
|  | @ -53,10 +53,10 @@ func (g *Group) Do(ctx context.Context, key string, fn func(ctx context.Context) | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (g *Group) do(ctx context.Context, key string, fn func(ctx context.Context) (interface{}, error)) (interface{}, error) { | ||||
| func (g *Group[T]) do(ctx context.Context, key string, fn func(ctx context.Context) (T, error)) (T, error) { | ||||
| 	g.mu.Lock() | ||||
| 	if g.m == nil { | ||||
| 		g.m = make(map[string]*call) | ||||
| 		g.m = make(map[string]*call[T]) | ||||
| 	} | ||||
| 
 | ||||
| 	if c, ok := g.m[key]; ok { // register 2nd waiter
 | ||||
|  | @ -78,16 +78,16 @@ func (g *Group) do(ctx context.Context, key string, fn func(ctx context.Context) | |||
| 	return c.wait(ctx) | ||||
| } | ||||
| 
 | ||||
| type call struct { | ||||
| type call[T any] struct { | ||||
| 	mu      sync.Mutex | ||||
| 	result  interface{} | ||||
| 	result  T | ||||
| 	err     error | ||||
| 	ready   chan struct{} | ||||
| 	cleaned chan struct{} | ||||
| 
 | ||||
| 	ctx  *sharedContext | ||||
| 	ctx  *sharedContext[T] | ||||
| 	ctxs []context.Context | ||||
| 	fn   func(ctx context.Context) (interface{}, error) | ||||
| 	fn   func(ctx context.Context) (T, error) | ||||
| 	once sync.Once | ||||
| 
 | ||||
| 	closeProgressWriter func() | ||||
|  | @ -95,8 +95,8 @@ type call struct { | |||
| 	progressCtx         context.Context | ||||
| } | ||||
| 
 | ||||
| func newCall(fn func(ctx context.Context) (interface{}, error)) *call { | ||||
| 	c := &call{ | ||||
| func newCall[T any](fn func(ctx context.Context) (T, error)) *call[T] { | ||||
| 	c := &call[T]{ | ||||
| 		fn:            fn, | ||||
| 		ready:         make(chan struct{}), | ||||
| 		cleaned:       make(chan struct{}), | ||||
|  | @ -114,7 +114,7 @@ func newCall(fn func(ctx context.Context) (interface{}, error)) *call { | |||
| 	return c | ||||
| } | ||||
| 
 | ||||
| func (c *call) run() { | ||||
| func (c *call[T]) run() { | ||||
| 	defer c.closeProgressWriter() | ||||
| 	ctx, cancel := context.WithCancel(c.ctx) | ||||
| 	defer cancel() | ||||
|  | @ -126,7 +126,8 @@ func (c *call) run() { | |||
| 	close(c.ready) | ||||
| } | ||||
| 
 | ||||
| func (c *call) wait(ctx context.Context) (v interface{}, err error) { | ||||
| func (c *call[T]) wait(ctx context.Context) (v T, err error) { | ||||
| 	var empty T | ||||
| 	c.mu.Lock() | ||||
| 	// detect case where caller has just returned, let it clean up before
 | ||||
| 	select { | ||||
|  | @ -134,7 +135,7 @@ func (c *call) wait(ctx context.Context) (v interface{}, err error) { | |||
| 		c.mu.Unlock() | ||||
| 		if c.err != nil { // on error retry
 | ||||
| 			<-c.cleaned | ||||
| 			return nil, errRetry | ||||
| 			return empty, errRetry | ||||
| 		} | ||||
| 		pw, ok, _ := progress.NewFromContext(ctx) | ||||
| 		if ok { | ||||
|  | @ -145,7 +146,7 @@ func (c *call) wait(ctx context.Context) (v interface{}, err error) { | |||
| 	case <-c.ctx.done: // could return if no error
 | ||||
| 		c.mu.Unlock() | ||||
| 		<-c.cleaned | ||||
| 		return nil, errRetry | ||||
| 		return empty, errRetry | ||||
| 	default: | ||||
| 	} | ||||
| 
 | ||||
|  | @ -174,13 +175,13 @@ func (c *call) wait(ctx context.Context) (v interface{}, err error) { | |||
| 		if ok { | ||||
| 			c.progressState.close(pw) | ||||
| 		} | ||||
| 		return nil, ctx.Err() | ||||
| 		return empty, ctx.Err() | ||||
| 	case <-c.ready: | ||||
| 		return c.result, c.err // shared not implemented yet
 | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (c *call) Deadline() (deadline time.Time, ok bool) { | ||||
| func (c *call[T]) Deadline() (deadline time.Time, ok bool) { | ||||
| 	c.mu.Lock() | ||||
| 	defer c.mu.Unlock() | ||||
| 	for _, ctx := range c.ctxs { | ||||
|  | @ -196,11 +197,11 @@ func (c *call) Deadline() (deadline time.Time, ok bool) { | |||
| 	return time.Time{}, false | ||||
| } | ||||
| 
 | ||||
| func (c *call) Done() <-chan struct{} { | ||||
| func (c *call[T]) Done() <-chan struct{} { | ||||
| 	return c.ctx.done | ||||
| } | ||||
| 
 | ||||
| func (c *call) Err() error { | ||||
| func (c *call[T]) Err() error { | ||||
| 	select { | ||||
| 	case <-c.ctx.Done(): | ||||
| 		return c.ctx.err | ||||
|  | @ -209,7 +210,7 @@ func (c *call) Err() error { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (c *call) Value(key interface{}) interface{} { | ||||
| func (c *call[T]) Value(key interface{}) interface{} { | ||||
| 	if key == contextKey { | ||||
| 		return c.progressState | ||||
| 	} | ||||
|  | @ -239,17 +240,17 @@ func (c *call) Value(key interface{}) interface{} { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| type sharedContext struct { | ||||
| 	*call | ||||
| type sharedContext[T any] struct { | ||||
| 	*call[T] | ||||
| 	done chan struct{} | ||||
| 	err  error | ||||
| } | ||||
| 
 | ||||
| func newContext(c *call) *sharedContext { | ||||
| 	return &sharedContext{call: c, done: make(chan struct{})} | ||||
| func newContext[T any](c *call[T]) *sharedContext[T] { | ||||
| 	return &sharedContext[T]{call: c, done: make(chan struct{})} | ||||
| } | ||||
| 
 | ||||
| func (sc *sharedContext) checkDone() bool { | ||||
| func (sc *sharedContext[T]) checkDone() bool { | ||||
| 	sc.mu.Lock() | ||||
| 	select { | ||||
| 	case <-sc.done: | ||||
|  |  | |||
|  | @ -0,0 +1,285 @@ | |||
| package imageutil | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/containerd/containerd/content" | ||||
| 	"github.com/containerd/containerd/images" | ||||
| 	"github.com/containerd/containerd/leases" | ||||
| 	"github.com/containerd/containerd/platforms" | ||||
| 	"github.com/containerd/containerd/reference" | ||||
| 	"github.com/containerd/containerd/remotes" | ||||
| 	"github.com/containerd/containerd/remotes/docker" | ||||
| 	intoto "github.com/in-toto/in-toto-golang/in_toto" | ||||
| 	"github.com/moby/buildkit/solver/pb" | ||||
| 	srctypes "github.com/moby/buildkit/source/types" | ||||
| 	"github.com/moby/buildkit/sourcepolicy" | ||||
| 	spb "github.com/moby/buildkit/sourcepolicy/pb" | ||||
| 	"github.com/moby/buildkit/util/contentutil" | ||||
| 	"github.com/moby/buildkit/util/leaseutil" | ||||
| 	"github.com/moby/buildkit/util/resolver/limited" | ||||
| 	"github.com/moby/buildkit/util/resolver/retryhandler" | ||||
| 	digest "github.com/opencontainers/go-digest" | ||||
| 	ocispecs "github.com/opencontainers/image-spec/specs-go/v1" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
| 
 | ||||
| type ContentCache interface { | ||||
| 	content.Ingester | ||||
| 	content.Provider | ||||
| 	content.Manager | ||||
| } | ||||
| 
 | ||||
| var leasesMu sync.Mutex | ||||
| var leasesF []func(context.Context) error | ||||
| 
 | ||||
| func CancelCacheLeases() { | ||||
| 	leasesMu.Lock() | ||||
| 	for _, f := range leasesF { | ||||
| 		f(context.TODO()) | ||||
| 	} | ||||
| 	leasesF = nil | ||||
| 	leasesMu.Unlock() | ||||
| } | ||||
| 
 | ||||
| func AddLease(f func(context.Context) error) { | ||||
| 	leasesMu.Lock() | ||||
| 	leasesF = append(leasesF, f) | ||||
| 	leasesMu.Unlock() | ||||
| } | ||||
| 
 | ||||
| // ResolveToNonImageError is returned by the resolver when the ref is mutated by policy to a non-image ref
 | ||||
| type ResolveToNonImageError struct { | ||||
| 	Ref     string | ||||
| 	Updated string | ||||
| } | ||||
| 
 | ||||
| func (e ResolveToNonImageError) Error() string { | ||||
| 	return fmt.Sprintf("ref mutated by policy to non-image: %s://%s -> %s", srctypes.DockerImageScheme, e.Ref, e.Updated) | ||||
| } | ||||
| 
 | ||||
| func Config(ctx context.Context, str string, resolver remotes.Resolver, cache ContentCache, leaseManager leases.Manager, p *ocispecs.Platform, spls []*spb.Policy) (string, digest.Digest, []byte, error) { | ||||
| 	// TODO: fix buildkit to take interface instead of struct
 | ||||
| 	var platform platforms.MatchComparer | ||||
| 	if p != nil { | ||||
| 		platform = platforms.Only(*p) | ||||
| 	} else { | ||||
| 		platform = platforms.Default() | ||||
| 	} | ||||
| 	ref, err := reference.Parse(str) | ||||
| 	if err != nil { | ||||
| 		return "", "", nil, errors.WithStack(err) | ||||
| 	} | ||||
| 
 | ||||
| 	op := &pb.Op{ | ||||
| 		Op: &pb.Op_Source{ | ||||
| 			Source: &pb.SourceOp{ | ||||
| 				Identifier: srctypes.DockerImageScheme + "://" + ref.String(), | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	mut, err := sourcepolicy.NewEngine(spls).Evaluate(ctx, op) | ||||
| 	if err != nil { | ||||
| 		return "", "", nil, errors.Wrap(err, "could not resolve image due to policy") | ||||
| 	} | ||||
| 
 | ||||
| 	if mut { | ||||
| 		var ( | ||||
| 			t  string | ||||
| 			ok bool | ||||
| 		) | ||||
| 		t, newRef, ok := strings.Cut(op.GetSource().GetIdentifier(), "://") | ||||
| 		if !ok { | ||||
| 			return "", "", nil, errors.Errorf("could not parse ref: %s", op.GetSource().GetIdentifier()) | ||||
| 		} | ||||
| 		if ok && t != srctypes.DockerImageScheme { | ||||
| 			return "", "", nil, &ResolveToNonImageError{Ref: str, Updated: newRef} | ||||
| 		} | ||||
| 		ref, err = reference.Parse(newRef) | ||||
| 		if err != nil { | ||||
| 			return "", "", nil, errors.WithStack(err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if leaseManager != nil { | ||||
| 		ctx2, done, err := leaseutil.WithLease(ctx, leaseManager, leases.WithExpiration(5*time.Minute), leaseutil.MakeTemporary) | ||||
| 		if err != nil { | ||||
| 			return "", "", nil, errors.WithStack(err) | ||||
| 		} | ||||
| 		ctx = ctx2 | ||||
| 		defer func() { | ||||
| 			// this lease is not deleted to allow other components to access manifest/config from cache. It will be deleted after 5 min deadline or on pruning inactive builder
 | ||||
| 			AddLease(done) | ||||
| 		}() | ||||
| 	} | ||||
| 
 | ||||
| 	desc := ocispecs.Descriptor{ | ||||
| 		Digest: ref.Digest(), | ||||
| 	} | ||||
| 	if desc.Digest != "" { | ||||
| 		ra, err := cache.ReaderAt(ctx, desc) | ||||
| 		if err == nil { | ||||
| 			info, err := cache.Info(ctx, desc.Digest) | ||||
| 			if err == nil { | ||||
| 				if ok, err := contentutil.HasSource(info, ref); err == nil && ok { | ||||
| 					desc.Size = ra.Size() | ||||
| 					mt, err := DetectManifestMediaType(ra) | ||||
| 					if err == nil { | ||||
| 						desc.MediaType = mt | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	// use resolver if desc is incomplete
 | ||||
| 	if desc.MediaType == "" { | ||||
| 		_, desc, err = resolver.Resolve(ctx, ref.String()) | ||||
| 		if err != nil { | ||||
| 			return "", "", nil, err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fetcher, err := resolver.Fetcher(ctx, ref.String()) | ||||
| 	if err != nil { | ||||
| 		return "", "", nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if desc.MediaType == images.MediaTypeDockerSchema1Manifest { | ||||
| 		dgst, dt, err := readSchema1Config(ctx, ref.String(), desc, fetcher, cache) | ||||
| 		return ref.String(), dgst, dt, err | ||||
| 	} | ||||
| 
 | ||||
| 	children := childrenConfigHandler(cache, platform) | ||||
| 
 | ||||
| 	dslHandler, err := docker.AppendDistributionSourceLabel(cache, ref.String()) | ||||
| 	if err != nil { | ||||
| 		return "", "", nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	handlers := []images.Handler{ | ||||
| 		retryhandler.New(limited.FetchHandler(cache, fetcher, str), func(_ []byte) {}), | ||||
| 		dslHandler, | ||||
| 		children, | ||||
| 	} | ||||
| 	if err := images.Dispatch(ctx, images.Handlers(handlers...), nil, desc); err != nil { | ||||
| 		return "", "", nil, err | ||||
| 	} | ||||
| 	config, err := images.Config(ctx, cache, desc, platform) | ||||
| 	if err != nil { | ||||
| 		return "", "", nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	dt, err := content.ReadBlob(ctx, cache, config) | ||||
| 	if err != nil { | ||||
| 		return "", "", nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return ref.String(), desc.Digest, dt, nil | ||||
| } | ||||
| 
 | ||||
| func childrenConfigHandler(provider content.Provider, platform platforms.MatchComparer) images.HandlerFunc { | ||||
| 	return func(ctx context.Context, desc ocispecs.Descriptor) ([]ocispecs.Descriptor, error) { | ||||
| 		var descs []ocispecs.Descriptor | ||||
| 		switch desc.MediaType { | ||||
| 		case images.MediaTypeDockerSchema2Manifest, ocispecs.MediaTypeImageManifest: | ||||
| 			p, err := content.ReadBlob(ctx, provider, desc) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 
 | ||||
| 			// TODO(stevvooe): We just assume oci manifest, for now. There may be
 | ||||
| 			// subtle differences from the docker version.
 | ||||
| 			var manifest ocispecs.Manifest | ||||
| 			if err := json.Unmarshal(p, &manifest); err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 
 | ||||
| 			descs = append(descs, manifest.Config) | ||||
| 		case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex: | ||||
| 			p, err := content.ReadBlob(ctx, provider, desc) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 
 | ||||
| 			var index ocispecs.Index | ||||
| 			if err := json.Unmarshal(p, &index); err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 
 | ||||
| 			if platform != nil { | ||||
| 				for _, d := range index.Manifests { | ||||
| 					if d.Platform == nil || platform.Match(*d.Platform) { | ||||
| 						descs = append(descs, d) | ||||
| 					} | ||||
| 				} | ||||
| 			} else { | ||||
| 				descs = append(descs, index.Manifests...) | ||||
| 			} | ||||
| 		case images.MediaTypeDockerSchema2Config, ocispecs.MediaTypeImageConfig, docker.LegacyConfigMediaType, | ||||
| 			intoto.PayloadType: | ||||
| 			// childless data types.
 | ||||
| 			return nil, nil | ||||
| 		default: | ||||
| 			return nil, errors.Errorf("encountered unknown type %v; children may not be fetched", desc.MediaType) | ||||
| 		} | ||||
| 
 | ||||
| 		return descs, nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // specs.MediaTypeImageManifest, // TODO: detect schema1/manifest-list
 | ||||
| func DetectManifestMediaType(ra content.ReaderAt) (string, error) { | ||||
| 	// TODO: schema1
 | ||||
| 
 | ||||
| 	dt := make([]byte, ra.Size()) | ||||
| 	if _, err := ra.ReadAt(dt, 0); err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 
 | ||||
| 	return DetectManifestBlobMediaType(dt) | ||||
| } | ||||
| 
 | ||||
| func DetectManifestBlobMediaType(dt []byte) (string, error) { | ||||
| 	var mfst struct { | ||||
| 		MediaType *string         `json:"mediaType"` | ||||
| 		Config    json.RawMessage `json:"config"` | ||||
| 		Manifests json.RawMessage `json:"manifests"` | ||||
| 		Layers    json.RawMessage `json:"layers"` | ||||
| 	} | ||||
| 
 | ||||
| 	if err := json.Unmarshal(dt, &mfst); err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 
 | ||||
| 	mt := images.MediaTypeDockerSchema2ManifestList | ||||
| 
 | ||||
| 	if mfst.Config != nil || mfst.Layers != nil { | ||||
| 		mt = images.MediaTypeDockerSchema2Manifest | ||||
| 
 | ||||
| 		if mfst.Manifests != nil { | ||||
| 			return "", errors.Errorf("invalid ambiguous manifest and manifest list") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if mfst.MediaType != nil { | ||||
| 		switch *mfst.MediaType { | ||||
| 		case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex: | ||||
| 			if mt != images.MediaTypeDockerSchema2ManifestList { | ||||
| 				return "", errors.Errorf("mediaType in manifest does not match manifest contents") | ||||
| 			} | ||||
| 			mt = *mfst.MediaType | ||||
| 		case images.MediaTypeDockerSchema2Manifest, ocispecs.MediaTypeImageManifest: | ||||
| 			if mt != images.MediaTypeDockerSchema2Manifest { | ||||
| 				return "", errors.Errorf("mediaType in manifest does not match manifest contents") | ||||
| 			} | ||||
| 			mt = *mfst.MediaType | ||||
| 		} | ||||
| 	} | ||||
| 	return mt, nil | ||||
| } | ||||
|  | @ -0,0 +1,88 @@ | |||
| package imageutil | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/containerd/containerd/remotes" | ||||
| 	"github.com/moby/buildkit/exporter/containerimage/image" | ||||
| 	digest "github.com/opencontainers/go-digest" | ||||
| 	ocispecs "github.com/opencontainers/image-spec/specs-go/v1" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
| 
 | ||||
| func readSchema1Config(ctx context.Context, ref string, desc ocispecs.Descriptor, fetcher remotes.Fetcher, cache ContentCache) (digest.Digest, []byte, error) { | ||||
| 	rc, err := fetcher.Fetch(ctx, desc) | ||||
| 	if err != nil { | ||||
| 		return "", nil, err | ||||
| 	} | ||||
| 	defer rc.Close() | ||||
| 	dt, err := io.ReadAll(rc) | ||||
| 	if err != nil { | ||||
| 		return "", nil, errors.Wrap(err, "failed to fetch schema1 manifest") | ||||
| 	} | ||||
| 	dt, err = convertSchema1ConfigMeta(dt) | ||||
| 	if err != nil { | ||||
| 		return "", nil, err | ||||
| 	} | ||||
| 	return desc.Digest, dt, nil | ||||
| } | ||||
| 
 | ||||
| func convertSchema1ConfigMeta(in []byte) ([]byte, error) { | ||||
| 	type history struct { | ||||
| 		V1Compatibility string `json:"v1Compatibility"` | ||||
| 	} | ||||
| 	var m struct { | ||||
| 		History []history `json:"history"` | ||||
| 	} | ||||
| 	if err := json.Unmarshal(in, &m); err != nil { | ||||
| 		return nil, errors.Wrap(err, "failed to unmarshal schema1 manifest") | ||||
| 	} | ||||
| 	if len(m.History) == 0 { | ||||
| 		return nil, errors.Errorf("invalid schema1 manifest") | ||||
| 	} | ||||
| 
 | ||||
| 	var img image.Image | ||||
| 	if err := json.Unmarshal([]byte(m.History[0].V1Compatibility), &img); err != nil { | ||||
| 		return nil, errors.Wrap(err, "failed to unmarshal image from schema 1 history") | ||||
| 	} | ||||
| 
 | ||||
| 	img.RootFS = ocispecs.RootFS{ | ||||
| 		Type: "layers", // filled in by exporter
 | ||||
| 	} | ||||
| 	img.History = make([]ocispecs.History, len(m.History)) | ||||
| 
 | ||||
| 	for i := range m.History { | ||||
| 		var h v1History | ||||
| 		if err := json.Unmarshal([]byte(m.History[i].V1Compatibility), &h); err != nil { | ||||
| 			return nil, errors.Wrap(err, "failed to unmarshal history") | ||||
| 		} | ||||
| 		img.History[len(m.History)-i-1] = ocispecs.History{ | ||||
| 			Author:     h.Author, | ||||
| 			Comment:    h.Comment, | ||||
| 			Created:    &h.Created, | ||||
| 			CreatedBy:  strings.Join(h.ContainerConfig.Cmd, " "), | ||||
| 			EmptyLayer: (h.ThrowAway != nil && *h.ThrowAway) || (h.Size != nil && *h.Size == 0), | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	dt, err := json.MarshalIndent(img, "", "  ") | ||||
| 	if err != nil { | ||||
| 		return nil, errors.Wrap(err, "failed to marshal schema1 config") | ||||
| 	} | ||||
| 	return dt, nil | ||||
| } | ||||
| 
 | ||||
| type v1History struct { | ||||
| 	Author          string    `json:"author,omitempty"` | ||||
| 	Created         time.Time `json:"created"` | ||||
| 	Comment         string    `json:"comment,omitempty"` | ||||
| 	ThrowAway       *bool     `json:"throwaway,omitempty"` | ||||
| 	Size            *int      `json:"Size,omitempty"` // used before ThrowAway field
 | ||||
| 	ContainerConfig struct { | ||||
| 		Cmd []string `json:"Cmd,omitempty"` | ||||
| 	} `json:"container_config,omitempty"` | ||||
| } | ||||
|  | @ -0,0 +1,83 @@ | |||
| package leaseutil | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/containerd/containerd/leases" | ||||
| 	"github.com/containerd/containerd/namespaces" | ||||
| ) | ||||
| 
 | ||||
| func WithLease(ctx context.Context, ls leases.Manager, opts ...leases.Opt) (context.Context, func(context.Context) error, error) { | ||||
| 	_, ok := leases.FromContext(ctx) | ||||
| 	if ok { | ||||
| 		return ctx, func(context.Context) error { | ||||
| 			return nil | ||||
| 		}, nil | ||||
| 	} | ||||
| 
 | ||||
| 	l, err := ls.Create(ctx, append([]leases.Opt{leases.WithRandomID(), leases.WithExpiration(time.Hour)}, opts...)...) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	ctx = leases.WithLease(ctx, l.ID) | ||||
| 	return ctx, func(ctx context.Context) error { | ||||
| 		return ls.Delete(ctx, l) | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func MakeTemporary(l *leases.Lease) error { | ||||
| 	if l.Labels == nil { | ||||
| 		l.Labels = map[string]string{} | ||||
| 	} | ||||
| 	l.Labels["buildkit/lease.temporary"] = time.Now().UTC().Format(time.RFC3339Nano) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func WithNamespace(lm leases.Manager, ns string) *Manager { | ||||
| 	return &Manager{manager: lm, ns: ns} | ||||
| } | ||||
| 
 | ||||
| type Manager struct { | ||||
| 	manager leases.Manager | ||||
| 	ns      string | ||||
| } | ||||
| 
 | ||||
| func (l *Manager) Namespace() string { | ||||
| 	return l.ns | ||||
| } | ||||
| 
 | ||||
| func (l *Manager) WithNamespace(ns string) *Manager { | ||||
| 	return WithNamespace(l.manager, ns) | ||||
| } | ||||
| 
 | ||||
| func (l *Manager) Create(ctx context.Context, opts ...leases.Opt) (leases.Lease, error) { | ||||
| 	ctx = namespaces.WithNamespace(ctx, l.ns) | ||||
| 	return l.manager.Create(ctx, opts...) | ||||
| } | ||||
| 
 | ||||
| func (l *Manager) Delete(ctx context.Context, lease leases.Lease, opts ...leases.DeleteOpt) error { | ||||
| 	ctx = namespaces.WithNamespace(ctx, l.ns) | ||||
| 	return l.manager.Delete(ctx, lease, opts...) | ||||
| } | ||||
| 
 | ||||
| func (l *Manager) List(ctx context.Context, filters ...string) ([]leases.Lease, error) { | ||||
| 	ctx = namespaces.WithNamespace(ctx, l.ns) | ||||
| 	return l.manager.List(ctx, filters...) | ||||
| } | ||||
| 
 | ||||
| func (l *Manager) AddResource(ctx context.Context, lease leases.Lease, resource leases.Resource) error { | ||||
| 	ctx = namespaces.WithNamespace(ctx, l.ns) | ||||
| 	return l.manager.AddResource(ctx, lease, resource) | ||||
| } | ||||
| 
 | ||||
| func (l *Manager) DeleteResource(ctx context.Context, lease leases.Lease, resource leases.Resource) error { | ||||
| 	ctx = namespaces.WithNamespace(ctx, l.ns) | ||||
| 	return l.manager.DeleteResource(ctx, lease, resource) | ||||
| } | ||||
| 
 | ||||
| func (l *Manager) ListResources(ctx context.Context, lease leases.Lease) ([]leases.Resource, error) { | ||||
| 	ctx = namespaces.WithNamespace(ctx, l.ns) | ||||
| 	return l.manager.ListResources(ctx, lease) | ||||
| } | ||||
|  | @ -37,8 +37,8 @@ func NormalizePath(parent, newPath, inputOS string, keepSlash bool) (string, err | |||
| 		inputOS = "linux" | ||||
| 	} | ||||
| 
 | ||||
| 	newPath = toSlash(newPath, inputOS) | ||||
| 	parent = toSlash(parent, inputOS) | ||||
| 	newPath = ToSlash(newPath, inputOS) | ||||
| 	parent = ToSlash(parent, inputOS) | ||||
| 	origPath := newPath | ||||
| 
 | ||||
| 	if parent == "" { | ||||
|  | @ -82,18 +82,17 @@ func NormalizePath(parent, newPath, inputOS string, keepSlash bool) (string, err | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return toSlash(newPath, inputOS), nil | ||||
| 	return ToSlash(newPath, inputOS), nil | ||||
| } | ||||
| 
 | ||||
| func toSlash(inputPath, inputOS string) string { | ||||
| 	separator := "/" | ||||
| 	if inputOS == "windows" { | ||||
| 		separator = "\\" | ||||
| func ToSlash(inputPath, inputOS string) string { | ||||
| 	if inputOS != "windows" { | ||||
| 		return inputPath | ||||
| 	} | ||||
| 	return strings.Replace(inputPath, separator, "/", -1) | ||||
| 	return strings.Replace(inputPath, "\\", "/", -1) | ||||
| } | ||||
| 
 | ||||
| func fromSlash(inputPath, inputOS string) string { | ||||
| func FromSlash(inputPath, inputOS string) string { | ||||
| 	separator := "/" | ||||
| 	if inputOS == "windows" { | ||||
| 		separator = "\\" | ||||
|  | @ -119,7 +118,7 @@ func NormalizeWorkdir(current, wd string, inputOS string) (string, error) { | |||
| 
 | ||||
| 	// Make sure we use the platform specific path separator. HCS does not like forward
 | ||||
| 	// slashes in CWD.
 | ||||
| 	return fromSlash(wd, inputOS), nil | ||||
| 	return FromSlash(wd, inputOS), nil | ||||
| } | ||||
| 
 | ||||
| // IsAbs returns a boolean value indicating whether or not the path
 | ||||
|  | @ -142,7 +141,7 @@ func IsAbs(pth, inputOS string) bool { | |||
| 	if err != nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	cleanedPath = toSlash(cleanedPath, inputOS) | ||||
| 	cleanedPath = ToSlash(cleanedPath, inputOS) | ||||
| 	// We stripped any potential drive letter and converted any backslashes to
 | ||||
| 	// forward slashes. We can safely use path.IsAbs() for both Windows and Linux.
 | ||||
| 	return path.IsAbs(cleanedPath) | ||||
|  | @ -189,14 +188,14 @@ func CheckSystemDriveAndRemoveDriveLetter(path string, inputOS string) (string, | |||
| 	} | ||||
| 
 | ||||
| 	// UNC paths should error out
 | ||||
| 	if len(path) >= 2 && toSlash(path[:2], inputOS) == "//" { | ||||
| 	if len(path) >= 2 && ToSlash(path[:2], inputOS) == "//" { | ||||
| 		return "", errors.Errorf("UNC paths are not supported") | ||||
| 	} | ||||
| 
 | ||||
| 	parts := strings.SplitN(path, ":", 2) | ||||
| 	// Path does not have a drive letter. Just return it.
 | ||||
| 	if len(parts) < 2 { | ||||
| 		return toSlash(filepath.Clean(path), inputOS), nil | ||||
| 		return ToSlash(filepath.Clean(path), inputOS), nil | ||||
| 	} | ||||
| 
 | ||||
| 	// We expect all paths to be in C:
 | ||||
|  | @ -221,5 +220,5 @@ func CheckSystemDriveAndRemoveDriveLetter(path string, inputOS string) (string, | |||
| 	//
 | ||||
| 	// We must return the second element of the split path, as is, without attempting to convert
 | ||||
| 	// it to an absolute path. We have no knowledge of the CWD; that is treated elsewhere.
 | ||||
| 	return toSlash(filepath.Clean(parts[1]), inputOS), nil | ||||
| 	return ToSlash(filepath.Clean(parts[1]), inputOS), nil | ||||
| } | ||||
|  |  | |||
|  | @ -137,6 +137,7 @@ func (c Moby) New(ctx context.Context, cfg *BackendConfig) (b Backend, cl func() | |||
| 	dockerdFlags := []string{ | ||||
| 		"--config-file", dockerdConfigFile, | ||||
| 		"--userland-proxy=false", | ||||
| 		"--tls=false", | ||||
| 		"--debug", | ||||
| 	} | ||||
| 	if s := os.Getenv("BUILDKIT_INTEGRATION_DOCKERD_FLAGS"); s != "" { | ||||
|  |  | |||
|  | @ -0,0 +1,87 @@ | |||
| package wildcard | ||||
| 
 | ||||
| import ( | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
| 
 | ||||
| // New returns a wildcard object for a string that contains "*" symbols.
 | ||||
| func New(s string) (*Wildcard, error) { | ||||
| 	reStr, err := Wildcard2Regexp(s) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.Wrapf(err, "failed to translate wildcard %q to regexp", s) | ||||
| 	} | ||||
| 	re, err := regexp.Compile(reStr) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.Wrapf(err, "failed to compile regexp %q (translated from wildcard %q)", reStr, s) | ||||
| 	} | ||||
| 	w := &Wildcard{ | ||||
| 		orig: s, | ||||
| 		re:   re, | ||||
| 	} | ||||
| 	return w, nil | ||||
| } | ||||
| 
 | ||||
| // Wildcard2Regexp translates a wildcard string to a regexp string.
 | ||||
| func Wildcard2Regexp(wildcard string) (string, error) { | ||||
| 	s := regexp.QuoteMeta(wildcard) | ||||
| 	if strings.Contains(s, "\\*\\*") { | ||||
| 		return "", errors.New("invalid wildcard: \"**\"") | ||||
| 	} | ||||
| 	s = strings.ReplaceAll(s, "\\*", "(.*)") | ||||
| 	s = "^" + s + "$" | ||||
| 	return s, nil | ||||
| } | ||||
| 
 | ||||
| // Wildcard is a wildcard matcher object.
 | ||||
| type Wildcard struct { | ||||
| 	orig string | ||||
| 	re   *regexp.Regexp | ||||
| } | ||||
| 
 | ||||
| // String implements fmt.Stringer.
 | ||||
| func (w *Wildcard) String() string { | ||||
| 	return w.orig | ||||
| } | ||||
| 
 | ||||
| // Match returns a non-nil Match on match.
 | ||||
| func (w *Wildcard) Match(q string) *Match { | ||||
| 	submatches := w.re.FindStringSubmatch(q) | ||||
| 	if len(submatches) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	m := &Match{ | ||||
| 		w:          w, | ||||
| 		Submatches: submatches, | ||||
| 		// FIXME: avoid executing regexp twice
 | ||||
| 		idx: w.re.FindStringSubmatchIndex(q), | ||||
| 	} | ||||
| 	return m | ||||
| } | ||||
| 
 | ||||
| // Match is a matched result.
 | ||||
| type Match struct { | ||||
| 	w          *Wildcard | ||||
| 	Submatches []string // 0: the entire query, 1: the first submatch, 2: the second submatch, ...
 | ||||
| 	idx        []int | ||||
| } | ||||
| 
 | ||||
| // String implements fmt.Stringer.
 | ||||
| func (m *Match) String() string { | ||||
| 	if len(m.Submatches) == 0 { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return m.Submatches[0] | ||||
| } | ||||
| 
 | ||||
| // Format formats submatch strings like "$1", "$2".
 | ||||
| func (m *Match) Format(f string) (string, error) { | ||||
| 	if m.w == nil || len(m.Submatches) == 0 || len(m.idx) == 0 { | ||||
| 		return "", errors.New("invalid state") | ||||
| 	} | ||||
| 	var b []byte | ||||
| 	b = m.w.re.ExpandString(b, f, m.Submatches[0], m.idx) | ||||
| 	return string(b), nil | ||||
| } | ||||
|  | @ -1,5 +1,5 @@ | |||
| #syntax=docker/dockerfile:1 | ||||
| ARG GO_VERSION=1.18 | ||||
| ARG GO_VERSION=1.20 | ||||
| 
 | ||||
| FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.1.0 AS xx | ||||
| 
 | ||||
|  |  | |||
|  | @ -162,6 +162,10 @@ func (dw *DiskWriter) HandleChange(kind ChangeKind, p string, fi os.FileInfo, er | |||
| 	switch { | ||||
| 	case fi.IsDir(): | ||||
| 		if err := os.Mkdir(newPath, fi.Mode()); err != nil { | ||||
| 			if errors.Is(err, syscall.EEXIST) { | ||||
| 				// we saw a race to create this directory, so try again
 | ||||
| 				return dw.HandleChange(kind, p, fi, nil) | ||||
| 			} | ||||
| 			return errors.Wrapf(err, "failed to create dir %s", newPath) | ||||
| 		} | ||||
| 		dw.dirModTimes[destPath] = statCopy.ModTime | ||||
|  | @ -188,7 +192,6 @@ func (dw *DiskWriter) HandleChange(kind ChangeKind, p string, fi os.FileInfo, er | |||
| 				file.Close() | ||||
| 				return err | ||||
| 			} | ||||
| 			break | ||||
| 		} | ||||
| 		if err := file.Close(); err != nil { | ||||
| 			return errors.Wrapf(err, "failed to close %s", newPath) | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| variable "GO_VERSION" { | ||||
|   default = "1.18" | ||||
|   default = "1.20" | ||||
| } | ||||
| 
 | ||||
| group "default" { | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ package fsutil | |||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	gofs "io/fs" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
|  | @ -47,11 +48,11 @@ func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) err | |||
| 	if err != nil { | ||||
| 		return errors.WithStack(&os.PathError{Op: "resolve", Path: root, Err: err}) | ||||
| 	} | ||||
| 	fi, err := os.Stat(root) | ||||
| 	rootFI, err := os.Stat(root) | ||||
| 	if err != nil { | ||||
| 		return errors.WithStack(err) | ||||
| 	} | ||||
| 	if !fi.IsDir() { | ||||
| 	if !rootFI.IsDir() { | ||||
| 		return errors.WithStack(&os.PathError{Op: "walk", Path: root, Err: syscall.ENOTDIR}) | ||||
| 	} | ||||
| 
 | ||||
|  | @ -126,7 +127,7 @@ func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) err | |||
| 	var parentDirs []visitedDir | ||||
| 
 | ||||
| 	seenFiles := make(map[uint64]string) | ||||
| 	return filepath.Walk(root, func(path string, fi os.FileInfo, walkErr error) (retErr error) { | ||||
| 	return filepath.WalkDir(root, func(path string, dirEntry gofs.DirEntry, walkErr error) (retErr error) { | ||||
| 		defer func() { | ||||
| 			if retErr != nil && isNotExist(retErr) { | ||||
| 				retErr = filepath.SkipDir | ||||
|  | @ -146,9 +147,10 @@ func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) err | |||
| 		var ( | ||||
| 			dir   visitedDir | ||||
| 			isDir bool | ||||
| 			fi    gofs.FileInfo | ||||
| 		) | ||||
| 		if fi != nil { | ||||
| 			isDir = fi.IsDir() | ||||
| 		if dirEntry != nil { | ||||
| 			isDir = dirEntry.IsDir() | ||||
| 		} | ||||
| 
 | ||||
| 		if includeMatcher != nil || excludeMatcher != nil { | ||||
|  | @ -161,6 +163,11 @@ func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) err | |||
| 			} | ||||
| 
 | ||||
| 			if isDir { | ||||
| 				fi, err = dirEntry.Info() | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 
 | ||||
| 				dir = visitedDir{ | ||||
| 					fi:          fi, | ||||
| 					path:        path, | ||||
|  | @ -268,6 +275,14 @@ func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) err | |||
| 
 | ||||
| 		dir.calledFn = true | ||||
| 
 | ||||
| 		// The FileInfo might have already been read further up.
 | ||||
| 		if fi == nil { | ||||
| 			fi, err = dirEntry.Info() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		stat, err := mkstat(origpath, path, fi, seenFiles) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
|  |  | |||
|  | @ -181,6 +181,13 @@ func (v *VT100) UsedHeight() int { | |||
| } | ||||
| 
 | ||||
| func (v *VT100) Resize(y, x int) { | ||||
| 	// add some minimal defaults to handle zero and negative values
 | ||||
| 	if x < 6 { | ||||
| 		x = 6 | ||||
| 	} | ||||
| 	if y < 1 { | ||||
| 		y = 1 | ||||
| 	} | ||||
| 	if y > v.Height { | ||||
| 		n := y - v.Height | ||||
| 		for row := 0; row < n; row++ { | ||||
|  | @ -329,6 +336,10 @@ func (v *VT100) advance() { | |||
| } | ||||
| 
 | ||||
| func (v *VT100) scrollIfNeeded() { | ||||
| 	if v.Cursor.X >= v.Width { | ||||
| 		v.Cursor.X = 0 | ||||
| 		v.Cursor.Y++ | ||||
| 	} | ||||
| 	if v.Cursor.Y >= v.Height { | ||||
| 		first := v.Content[0] | ||||
| 		copy(v.Content, v.Content[1:]) | ||||
|  |  | |||
|  | @ -35,7 +35,7 @@ An implementation of JSON Pointer - Go language | |||
| 
 | ||||
| 
 | ||||
| ## References | ||||
| http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07 | ||||
| https://tools.ietf.org/html/rfc6901 | ||||
| 
 | ||||
| ### Note | ||||
| The 4.Evaluation part of the previous reference, starting with 'If the currently referenced value is a JSON array, the reference token MUST contain either...' is not implemented. | ||||
|  |  | |||
|  | @ -130,10 +130,10 @@ func (p *JsonPointer) implementation(i *implStruct) { | |||
| 				node = v[decodedToken] | ||||
| 				if isLastToken && i.mode == "SET" { | ||||
| 					v[decodedToken] = i.setInValue | ||||
| 				} else if isLastToken && i.mode =="DEL" { | ||||
| 					delete(v,decodedToken) | ||||
| 				} else if isLastToken && i.mode == "DEL" { | ||||
| 					delete(v, decodedToken) | ||||
| 				} | ||||
| 			} else if (isLastToken && i.mode == "SET") { | ||||
| 			} else if isLastToken && i.mode == "SET" { | ||||
| 				v[decodedToken] = i.setInValue | ||||
| 			} else { | ||||
| 				i.outError = fmt.Errorf("Object has no key '%s'", decodedToken) | ||||
|  | @ -160,7 +160,7 @@ func (p *JsonPointer) implementation(i *implStruct) { | |||
| 			node = v[tokenIndex] | ||||
| 			if isLastToken && i.mode == "SET" { | ||||
| 				v[tokenIndex] = i.setInValue | ||||
| 			}  else if isLastToken && i.mode =="DEL" { | ||||
| 			} else if isLastToken && i.mode == "DEL" { | ||||
| 				v[tokenIndex] = v[len(v)-1] | ||||
| 				v[len(v)-1] = nil | ||||
| 				v = v[:len(v)-1] | ||||
|  |  | |||
|  | @ -157,10 +157,13 @@ github.com/containerd/containerd/content/proxy | |||
| github.com/containerd/containerd/defaults | ||||
| github.com/containerd/containerd/errdefs | ||||
| github.com/containerd/containerd/filters | ||||
| github.com/containerd/containerd/identifiers | ||||
| github.com/containerd/containerd/images | ||||
| github.com/containerd/containerd/images/archive | ||||
| github.com/containerd/containerd/labels | ||||
| github.com/containerd/containerd/leases | ||||
| github.com/containerd/containerd/log | ||||
| github.com/containerd/containerd/namespaces | ||||
| github.com/containerd/containerd/pkg/dialer | ||||
| github.com/containerd/containerd/pkg/randutil | ||||
| github.com/containerd/containerd/pkg/seed | ||||
|  | @ -188,6 +191,9 @@ github.com/containerd/continuity/fs/fstest | |||
| github.com/containerd/continuity/pathdriver | ||||
| github.com/containerd/continuity/proto | ||||
| github.com/containerd/continuity/sysx | ||||
| # github.com/containerd/ttrpc v1.2.2 | ||||
| ## explicit; go 1.13 | ||||
| github.com/containerd/ttrpc | ||||
| # github.com/containerd/typeurl/v2 v2.1.1 | ||||
| ## explicit; go 1.13 | ||||
| github.com/containerd/typeurl/v2 | ||||
|  | @ -495,7 +501,7 @@ github.com/mitchellh/go-wordwrap | |||
| # github.com/mitchellh/mapstructure v1.5.0 | ||||
| ## explicit; go 1.14 | ||||
| github.com/mitchellh/mapstructure | ||||
| # github.com/moby/buildkit v0.11.0-rc3.0.20230620112432-2d91ddcceedc | ||||
| # github.com/moby/buildkit v0.12.1-0.20230717122532-faa0cc7da353 | ||||
| ## explicit; go 1.20 | ||||
| github.com/moby/buildkit/api/services/control | ||||
| github.com/moby/buildkit/api/types | ||||
|  | @ -536,6 +542,8 @@ github.com/moby/buildkit/session/upload/uploadprovider | |||
| github.com/moby/buildkit/solver/errdefs | ||||
| github.com/moby/buildkit/solver/pb | ||||
| github.com/moby/buildkit/solver/result | ||||
| github.com/moby/buildkit/source/types | ||||
| github.com/moby/buildkit/sourcepolicy | ||||
| github.com/moby/buildkit/sourcepolicy/pb | ||||
| github.com/moby/buildkit/util/apicaps | ||||
| github.com/moby/buildkit/util/apicaps/pb | ||||
|  | @ -547,6 +555,8 @@ github.com/moby/buildkit/util/entitlements | |||
| github.com/moby/buildkit/util/flightcontrol | ||||
| github.com/moby/buildkit/util/gitutil | ||||
| github.com/moby/buildkit/util/grpcerrors | ||||
| github.com/moby/buildkit/util/imageutil | ||||
| github.com/moby/buildkit/util/leaseutil | ||||
| github.com/moby/buildkit/util/progress | ||||
| github.com/moby/buildkit/util/progress/progressui | ||||
| github.com/moby/buildkit/util/progress/progresswriter | ||||
|  | @ -564,6 +574,7 @@ github.com/moby/buildkit/util/tracing/detect | |||
| github.com/moby/buildkit/util/tracing/detect/delegated | ||||
| github.com/moby/buildkit/util/tracing/env | ||||
| github.com/moby/buildkit/util/tracing/otlptracegrpc | ||||
| github.com/moby/buildkit/util/wildcard | ||||
| github.com/moby/buildkit/version | ||||
| # github.com/moby/locker v1.0.1 | ||||
| ## explicit; go 1.13 | ||||
|  | @ -680,17 +691,17 @@ github.com/theupdateframework/notary/tuf/data | |||
| github.com/theupdateframework/notary/tuf/signed | ||||
| github.com/theupdateframework/notary/tuf/utils | ||||
| github.com/theupdateframework/notary/tuf/validation | ||||
| # github.com/tonistiigi/fsutil v0.0.0-20230407161946-9e7a6df48576 | ||||
| ## explicit; go 1.18 | ||||
| # github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb | ||||
| ## explicit; go 1.19 | ||||
| github.com/tonistiigi/fsutil | ||||
| github.com/tonistiigi/fsutil/types | ||||
| # github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea | ||||
| ## explicit | ||||
| github.com/tonistiigi/units | ||||
| # github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f | ||||
| # github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531 | ||||
| ## explicit; go 1.12 | ||||
| github.com/tonistiigi/vt100 | ||||
| # github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f | ||||
| # github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb | ||||
| ## explicit | ||||
| github.com/xeipuuv/gojsonpointer | ||||
| # github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue