diff --git a/.bingo/protoc-gen-doc.sum b/.bingo/protoc-gen-doc.sum index 1e30e1e74..5d254fafb 100644 --- a/.bingo/protoc-gen-doc.sum +++ b/.bingo/protoc-gen-doc.sum @@ -1,28 +1,61 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/sprig v2.15.0+incompatible h1:0gSxPGWS9PAr7U2NsQ2YQg6juRDINkUyuvbb4b2Xm8w= github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/aokoli/goutils v1.0.1 h1:7fpzNGoJ3VA8qcrm++XEE1QUe0mIwNeLa02Nwq7RDkg= github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.3.0-java h1:bV5JGEB1ouEzZa0hgVDFFiClrUEuGWRaAc/3mxR2QK0= github.com/envoyproxy/protoc-gen-validate v0.3.0-java/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/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/uuid v0.0.0-20161128191214-064e2069ce9c h1:jWtZjFEUE/Bz0IeIhqCnyZ3HG6KRXSntXe4SjtuTH7c= github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/huandu/xstrings v1.0.0 h1:pO2K/gKgKaat5LdpAhxhluX2GPQMaI3W5FUz/I/UnWk= github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= github.com/imdario/mergo v0.3.4 h1:mKkfHkZWD8dC7WxKx3N9WCF0Y+dLau45704YQmY6H94= @@ -30,28 +63,107 @@ github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007 h1:28i1IjGcx8AofiB4N3q5Yls55VEaitzuEPkFJEVgGkA= github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/pseudomuto/protoc-gen-doc v1.5.0 h1:pHZp0MEiT68jrZV8js8BS7E9ZEnlSLegoQbbtXj5lfo= github.com/pseudomuto/protoc-gen-doc v1.5.0/go.mod h1:exDTOVwqpp30eV/EDPFLZy3Pwr2sn6hBC1WIYH/UbIg= github.com/pseudomuto/protoc-gen-doc v1.5.1 h1:Ah259kcrio7Ix1Rhb6u8FCaOkzf9qRBqXnvAufg061w= github.com/pseudomuto/protoc-gen-doc v1.5.1/go.mod h1:XpMKYg6zkcpgfpCfQ8GcWBDRtRxOmMR5w7pz4Xo+dYM= github.com/pseudomuto/protokit v0.2.0 h1:hlnBDcy3YEDXH7kc9gV+NLaN0cDzhDvD1s7Y6FZ8RpM= github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307 h1:O5C+XK++apFo5B+Vq4ujc/LkLwHxg9fDdgjgoIikBdA= golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362 h1:b69RmkJsx8NyRJsKF2mQ/AF8s4BNxwNsT4rQ3wON1U0= google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4 h1:ysnBoUyeL/H6RCvNRhWHjKoDEmguI+mPU+qHgK8qv/w= +google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/.bingo/protoc-gen-go.sum b/.bingo/protoc-gen-go.sum index 4736afdf1..65a6aa148 100644 --- a/.bingo/protoc-gen-go.sum +++ b/.bingo/protoc-gen-go.sum @@ -1,3 +1,7 @@ +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= diff --git a/.bingo/protoc-gen-micro.sum b/.bingo/protoc-gen-micro.sum index 527805071..147f01465 100644 --- a/.bingo/protoc-gen-micro.sum +++ b/.bingo/protoc-gen-micro.sum @@ -1,5 +1,54 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-micro/generator/cmd/protoc-gen-micro v1.0.0 h1:O/L+BqbnzsWV7ks5WO+dPYK0+6K60xCEFB+GW3GSbtg= github.com/go-micro/generator/cmd/protoc-gen-micro v1.0.0/go.mod h1:10I+mxNk2en3yjwqYn5DVU9Iv9l9uXyVuEYyNNxYGek= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200305110556-506484158171 h1:xes2Q2k+d/+YNXVw0FpZkIDJiaux4OVrRKXRAzH6A0U= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/.bingo/protoc-gen-microweb.sum b/.bingo/protoc-gen-microweb.sum index df4318c6b..fe38e02cd 100644 --- a/.bingo/protoc-gen-microweb.sum +++ b/.bingo/protoc-gen-microweb.sum @@ -1,7 +1,42 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg= @@ -17,6 +52,7 @@ github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQ github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= @@ -32,6 +68,7 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190808125512-07798873deee/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aws/aws-sdk-go v1.23.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= @@ -48,9 +85,20 @@ github.com/bwmarrin/discordgo v0.20.1/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVO github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.10.2/go.mod h1:qhVI5MKwBGhdNU89ZRz2plgYutcJ5PCekLxXn56w6SY= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= @@ -87,6 +135,11 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= @@ -102,6 +155,9 @@ github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITL github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.44.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-log/log v0.1.0/go.mod h1:4mBwpdRMFLiuXZDCwU2lKQFsoSCo72j3HqBK9d81N2M= @@ -119,28 +175,70 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191002201903-404acd9df4cc/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.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-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= @@ -153,6 +251,7 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= @@ -161,6 +260,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= github.com/ijc/Gotty v0.0.0-20170406111628-a8b993ba6abd/go.mod h1:3LVOLeyx9XVvwPgrt2be44XgSqndprz1G18rSk8KD84= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -174,6 +275,7 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= @@ -183,6 +285,7 @@ github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -200,6 +303,7 @@ github.com/lucas-clemente/quic-go v0.13.1/go.mod h1:Vn3/Fb0/77b02SGhQk36KzOUmXgV github.com/lyft/protoc-gen-star v0.4.12 h1:xDL+gBceXl36jtKMkJKixY7T0S2V+EccszLmfh8RppE= github.com/lyft/protoc-gen-star v0.4.12/go.mod h1:mE8fbna26u7aEA2QCVvvfBU/ZrPgocG1206xAFPcs94= github.com/lyft/protoc-gen-star v0.6.0 h1:xOpFu4vwmIoUeUrRuAtdCrZZymT/6AkW/bsUWA506Fo= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/marten-seemann/chacha20 v0.2.0/go.mod h1:HSdjFau7GzYRj+ahFNwsO3ouVJr1HFkWoEwNDb4TMtE= github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI= github.com/marten-seemann/qtls v0.3.2/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= @@ -268,6 +372,9 @@ github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtb github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= @@ -288,6 +395,7 @@ github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sacloud/libsacloud v1.26.1/go.mod h1:79ZwATmHLIFZIMd7sxA3LwzVy/B77uj3LDoToVTxDoQ= @@ -303,7 +411,9 @@ github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:s github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= +github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -311,6 +421,8 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= @@ -327,11 +439,20 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1: github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -353,21 +474,51 @@ golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191108234033-bd318be0434a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -382,21 +533,54 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191011234655-491137f69257/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191109021931-daa7c04131f5/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -409,12 +593,15 @@ golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -422,57 +609,213 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191110163157-d32e6e3b99c4/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= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11 h1:51D++eCgOHufw5VfDE9Uzqyyc+OyQIjb9hkYy9LN5Fk= google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf h1:JTjwKJX9erVpsw17w+OIPP7iAgEkN/r8urhWSunEDTs= +google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -496,6 +839,7 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -504,5 +848,10 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/.bingo/protoc-gen-openapiv2.sum b/.bingo/protoc-gen-openapiv2.sum index 53eb13b67..796936c93 100644 --- a/.bingo/protoc-gen-openapiv2.sum +++ b/.bingo/protoc-gen-openapiv2.sum @@ -1,5 +1,9 @@ github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.2 h1:I/pwhnUln5wbMnTyRbzswA0/JxpK8sZj0aUfI3TV1So= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.2/go.mod h1:lsuH8kb4GlMdSlI4alNIBBSAt5CHJtg3i+0WuN9J5YM= github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.0 h1:ESEyqQqXXFIcImj/BE8oKEX37Zsuceb2cZI+EL/zNCY= @@ -7,11 +11,19 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.0/go.mod h1:XnLCLFp3tjoZJszVKjfp github.com/grpc-ecosystem/grpc-gateway/v2 v2.13.0 h1:fi9bGIUJOGzzrHBbP8NWbTfNC5fKO6X7kFw40TOqGB8= github.com/grpc-ecosystem/grpc-gateway/v2 v2.13.0/go.mod h1:uY3Aurq+SxwQCpdX91xZ9CgxIMT1EsYtcidljXufYIY= golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0= google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e h1:fNKDNuUyC4WH+inqDMpfXDdfvwfYILbsX+oskGZ8hxg= google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c h1:QgY/XxIAIeccR+Ca/rDdKubLIU9rcJ3xfy1DC/Wd2Oo= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= diff --git a/.drone.env b/.drone.env index efbcd5d9b..28e8fe9ad 100644 --- a/.drone.env +++ b/.drone.env @@ -1,3 +1,3 @@ # The test runner source for UI tests -WEB_COMMITID=e8bdc25468e9a68e58cffd778209e039dd4beaf1 +WEB_COMMITID=69ab5c83e6eba00ec99d1c8c5dcb62927722e9e7 WEB_BRANCH=stable-7.0 diff --git a/.drone.star b/.drone.star index 02df53746..e653edeec 100644 --- a/.drone.star +++ b/.drone.star @@ -9,6 +9,7 @@ INBUCKET_INBUCKET = "inbucket/inbucket" MINIO_MC = "minio/mc:RELEASE.2021-10-07T04-19-58Z" OC_CI_ALPINE = "owncloudci/alpine:latest" OC_CI_BAZEL_BUILDIFIER = "owncloudci/bazel-buildifier:latest" +OC_CI_CLAMAVD = "owncloudci/clamavd" OC_CI_DRONE_ANSIBLE = "owncloudci/drone-ansible:latest" OC_CI_DRONE_CANCEL_PREVIOUS_BUILDS = "owncloudci/drone-cancel-previous-builds" OC_CI_DRONE_SKIP_PIPELINE = "owncloudci/drone-skip-pipeline" @@ -134,6 +135,21 @@ config = { "NOTIFICATIONS_SMTP_INSECURE": "true", }, }, + "apiAntivirus": { + "suites": [ + "apiAntivirus", + ], + "skip": False, + "earlyFail": True, + "antivirusNeeded": True, + "extraServerEnvironment": { + "ANTIVIRUS_SCANNER_TYPE": "clamav", + "ANTIVIRUS_CLAMAV_SOCKET": "tcp://clamav:3310", + "POSTPROCESSING_STEPS": "virusscan", + "OCIS_ASYNC_UPLOADS": True, + "OCIS_ADD_RUN_SERVICES": "antivirus", + }, + }, }, "apiTests": { "numberOfParts": 10, @@ -761,6 +777,7 @@ def localApiTestPipeline(ctx): "storages": ["ocis"], "accounts_hash_difficulty": 4, "emailNeeded": False, + "antivirusNeeded": False, } if "localApiTests" in config: @@ -783,10 +800,11 @@ def localApiTestPipeline(ctx): "steps": skipIfUnchanged(ctx, "acceptance-tests") + restoreBuildArtifactCache(ctx, "ocis-binary-amd64", "ocis/bin") + ocisServer(storage, params["accounts_hash_difficulty"], extra_server_environment = params["extraServerEnvironment"], with_wrapper = True) + + (waitForClamavService() if params["antivirusNeeded"] else []) + (waitForEmailService() if params["emailNeeded"] else []) + localApiTests(suite, storage, params["extraEnvironment"]) + failEarly(ctx, early_fail), - "services": emailService() if params["emailNeeded"] else [], + "services": emailService() if params["emailNeeded"] else [] + clamavService() if params["antivirusNeeded"] else [], "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)]), "trigger": { "ref": [ @@ -2108,7 +2126,7 @@ def ocisServer(storage, accounts_hash_difficulty = 4, volumes = [], depends_on = "IDM_ADMIN_PASSWORD": "admin", # override the random admin password from `ocis init` "FRONTEND_SEARCH_MIN_LENGTH": "2", "GATEWAY_GRPC_ADDR": "0.0.0.0:9142", # make gateway available to wopi server - "APP_PROVIDER_EXTERNAL_ADDR": "127.0.0.1:9164", + "APP_PROVIDER_EXTERNAL_ADDR": "com.owncloud.api.app-provider", "APP_PROVIDER_DRIVER": "wopi", "APP_PROVIDER_WOPI_APP_NAME": "FakeOffice", "APP_PROVIDER_WOPI_APP_URL": "http://fakeoffice:8080", @@ -2919,3 +2937,18 @@ def waitForEmailService(): "wait-for -it email:9000 -t 600", ], }] + +def clamavService(): + return [{ + "name": "clamav", + "image": OC_CI_CLAMAVD, + }] + +def waitForClamavService(): + return [{ + "name": "wait-for-clamav", + "image": OC_CI_WAIT_FOR, + "commands": [ + "wait-for -it clamav:3310 -t 600", + ], + }] diff --git a/.github/release_template.md b/.github/release_template.md new file mode 100644 index 000000000..51312c295 --- /dev/null +++ b/.github/release_template.md @@ -0,0 +1,77 @@ +### Template +[Release Template](https://github.com/owncloud/ocis/blob/master/.github/release_template.md) + +### Prerequisites + +* [ ] DEV/QA: Kickoff meeting [Kickoff meeting] (https://confluence.owncloud.com/display/QA/Technical+release+3.0.0+Overview) +* [ ] DEV/QA: Define client versions and provide a list of new features +* [ ] DEV: Create branch `release-x.x.x-rc.x` -> CODEFREEZE + * [ ] DEV: bump ocis version in necessary files + * [ ] DEV: `changelog/CHANGELOG.tmpl` + * [ ] DEV: `ocis-pkg/version/version.go` + * [ ] DEV: `sonar-project.properties` + * [ ] DEV: prepare changelog folder in `changelog/x.x.x_????_??_??` +* [ ] DEV: Check successful CI run on release branch +* [ ] DEV: Create signed tag `vx.y.z-rc.x` +* [ ] DEV: Check successful CI run on `vx.y.z-rc.x` tag / BLOCKING for all further activity +* [ ] DEV: Merge back release branch +* [ ] DEV: bump released deployments to `vx.y.z-rc.x` +* [ ] DEV: update `ocis.released.owncloud.works` +* [ ] DEV: https://ocis.team.owncloud.works/ + * [ ] DEV: needs snapshot and migration + +### QA Phase + +* [ ] QA: Confirmatory testing (if needed) +* [ ] QA: [Compatibility test](https://github.com/owncloud/QA/blob/master/Server/Test_Plan_OCIS_Compatibility_Test.md) +* [ ] QA: [Performance test](https://github.com/owncloud/cdperf/tree/main/packages/k6-tests/src) +* [ ] QA: Documentation test: + * [ ] QA: Single binary - setup + * [ ] QA: Docker - setup + * [ ] QA: Docker-compose - setup + * [ ] QA: helm/k8s - setup +* [ ] QA: e2e with different deployment: + * [ ] QA: [wopi](ocis.ocis-wopi.released.owncloud.works) + * [ ] QA: [traefik](ocis.ocis-traefik.released.owncloud.works) + * [ ] QA: [ldap](ocis.ocis-ldap.released.owncloud.works) +* [ ] QA: e2e with different storage: + * [ ] QA: local + * [ ] QA: nfs + * [ ] QA: s3 +* [ ] QA: Different clients: + * [ ] QA: desktop (define version) https://github.com/owncloud/client/releases + * [ ] QA: against mac - smoke test + * [ ] QA: against windows - smoke test + * [ ] QA: against linux (use auto tests) + * [ ] QA: android (define version) https://github.com/owncloud/android/releases + * [ ] QA: ios (define version) +* [ ] QA: [Smoke test](https://github.com/owncloud/QA/blob/master/Server/Test_Plan_OCIS_Smoke_Web_Office_Test.md) on Web Office (Collabora, Onlyoffice, Microsoft office) +* [ ] QA: Smoke test Hello extension +* [ ] QA: [Smoke test](https://github.com/owncloud/QA/blob/master/Server/Test_Plan_OCIS_Smoke_Ldap_Test.md) ldap +* [ ] QA: Collecting errors found + +### After QA Phase + +* [ ] Brief company-wide heads up via mail @tbsbdr +* [ ] DEV: Create branch `release-x.x.x` + * [ ] DEV: bump ocis version in necessary files + * [ ] DEV: `ocis-pkg/version/version.go` + * [ ] DEV: `sonar-project.properties` + * [ ] DEV: released deployment versions + * [ ] DEV: prepare changelog folder in `changelog/x.x.x_???` +* [ ] Release Notes + Breaking Changes @tbsbdr +* [ ] Migration + Breaking Changes Admin Doc @mmattel +* [ ] Migration + Breaking Changes Helm Chart Doc @wkloucek @mmattel +* [ ] DEV: Create final signed tag +* [ ] DEV: Check successful CI run on `vx.y.z` tag / BLOCKING for all further activity +* [ ] Merge release notes + +### Post-release communication +* [ ] DEV: Create a `docs-stable-x.y` branch based on the docs folder in the ocis repo @micbar +* [ ] DEV: Create a `x.y.z` release in the ocis-helm repo (frozen state) @wkloucek +* [ ] DEV/QA: Ping documentation in RC about the new release tag (for ocis/helm chart version bump in docs) +* [ ] DEV/QA: Ping marketing to update all download links (download mirrors are updated at the full hour, wait with ping until download is actually available) +* [ ] DEV/QA: Ping @hodyroff once the demo instances are running this release +* [ ] DEV/QA: notify @michaelstingl @hosy @fmoc @jesmrec to publish client finals +* [ ] DEV: Merge back release branch +* [ ] DEV: Create stable-x.y branch in the ocis repo from final tag diff --git a/CHANGELOG.md b/CHANGELOG.md index 05979683c..a2dcf37e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,20 +6,590 @@ The following sections list the changes for unreleased. ## Summary -* Bugfix - Trace proxy middlewares: [#6313](https://github.com/owncloud/ocis/pull/6313) -* Enhancement - Bump Reva: [#6381](https://github.com/owncloud/ocis/pull/6381) -* Enhancement - Default LDAP write to true: [#6362](https://github.com/owncloud/ocis/pull/6362) -* Enhancement - Fix preview or viewing of shared animated GIFs: [#6386](https://github.com/owncloud/ocis/pull/6386) -* Enhancement - Fix err when the user share the locked file: [#6357](https://github.com/owncloud/ocis/pull/6357) -* Enhancement - Add fulltextsearch capabilty: [#6366](https://github.com/owncloud/ocis/pull/6366) -* Enhancement - Configurable ID Cache: [#6353](https://github.com/owncloud/ocis/pull/6353) -* Enhancement - Add otlp tracing exporter: [#5132](https://github.com/owncloud/ocis/pull/5132) -* Enhancement - Remove the email logo: [#6359](https://github.com/owncloud/ocis/issues/6359) -* Enhancement - Unify CA Cert envvars: [#6392](https://github.com/owncloud/ocis/pull/6392) -* Enhancement - Update web to v7.0.0-rc.38: [#6375](https://github.com/owncloud/ocis/pull/6375) +* Bugfix - Add default store to postprocessing: [#6578](https://github.com/owncloud/ocis/pull/6578) +* Bugfix - Add token to LinkAccessedEvent: [#6554](https://github.com/owncloud/ocis/pull/6554) +* Bugfix - Add missing timestamps: [#6515](https://github.com/owncloud/ocis/pull/6515) +* Bugfix - Don't connect to ldap on startup: [#6565](https://github.com/owncloud/ocis/pull/6565) +* Bugfix - Handle the bad request status: [#6469](https://github.com/owncloud/ocis/pull/6469) +* Bugfix - Fix the oidc role assigner: [#6605](https://github.com/owncloud/ocis/pull/6605) +* Change - YAML configuration files are restricted to yaml-1.2: [#6510](https://github.com/owncloud/ocis/issues/6510) +* Enhancement - Add 'ocis decomposedfs check-treesize' command: [#6556](https://github.com/owncloud/ocis/pull/6556) +* Enhancement - Add IDs to graph resource logging: [#6593](https://github.com/owncloud/ocis/pull/6593) +* Enhancement - Add permissions to report: [#6528](https://github.com/owncloud/ocis/pull/6528) +* Enhancement - Add more metadata to the remote item: [#6300](https://github.com/owncloud/ocis/pull/6300) +* Enhancement - Add SSE Endpoint: [#5998](https://github.com/owncloud/ocis/pull/5998) +* Enhancement - We added the storage id to the audit log for spaces: [#6548](https://github.com/owncloud/ocis/pull/6548) +* Enhancement - Make the post logout redirect uri configurable: [#6583](https://github.com/owncloud/ocis/pull/6583) +* Enhancement - Make the app provider service name configurable: [#6482](https://github.com/owncloud/ocis/pull/6482) +* Enhancement - Add old & new values to audit logs: [#6537](https://github.com/owncloud/ocis/pull/6537) +* Enhancement - Update reva: [#6529](https://github.com/owncloud/ocis/pull/6529) +* Enhancement - Use reva client selectors: [#6452](https://github.com/owncloud/ocis/pull/6452) +* Enhancement - Thumbnails can be disabled for webdav & web now: [#6577](https://github.com/owncloud/ocis/pull/6577) +* Enhancement - Add companion URL config: [#6453](https://github.com/owncloud/ocis/pull/6453) +* Enhancement - Add imprint and privacy url config: [#6462](https://github.com/owncloud/ocis/pull/6462) +* Enhancement - Add logged out url config: [#6549](https://github.com/owncloud/ocis/pull/6549) +* Enhancement - Add search result content preview and term highlighting: [#6634](https://github.com/owncloud/ocis/pull/6634) +* Enhancement - Fix envvar defaults: [#6516](https://github.com/owncloud/ocis/pull/6516) +* Enhancement - Skip if the simulink is a directory: [#6574](https://github.com/owncloud/ocis/pull/6574) +* Enhancement - Fix the groupname validation: [#6490](https://github.com/owncloud/ocis/pull/6490) +* Enhancement - Fix the username validation: [#6437](https://github.com/owncloud/ocis/pull/6437) +* Enhancement - Move proxy to service tracerprovider: [#6591](https://github.com/owncloud/ocis/pull/6591) +* Enhancement - Add functionality to retry postprocessing: [#6500](https://github.com/owncloud/ocis/pull/6500) +* Enhancement - Update go-micro kubernetes registry: [#6457](https://github.com/owncloud/ocis/pull/6457) +* Enhancement - Update web to v7.0.1: [#6470](https://github.com/owncloud/ocis/pull/6470) +* Enhancement - Allow disabling wopi chat: [#6544](https://github.com/owncloud/ocis/pull/6544) ## Details +* Bugfix - Add default store to postprocessing: [#6578](https://github.com/owncloud/ocis/pull/6578) + + Postprocessing did not have a default store especially `database` and `table` are needed to + talk to nats-js + + https://github.com/owncloud/ocis/pull/6578 + +* Bugfix - Add token to LinkAccessedEvent: [#6554](https://github.com/owncloud/ocis/pull/6554) + + We added the link token to the LinkAccessedEvent + + https://github.com/owncloud/ocis/issues/3753 + https://github.com/owncloud/ocis/pull/6554 + https://github.com/cs3org/reva/pull/3993 + +* Bugfix - Add missing timestamps: [#6515](https://github.com/owncloud/ocis/pull/6515) + + We have added missing timestamps to the audit service + + https://github.com/owncloud/ocis/issues/3753 + https://github.com/owncloud/ocis/pull/6515 + +* Bugfix - Don't connect to ldap on startup: [#6565](https://github.com/owncloud/ocis/pull/6565) + + This leads to misleading error messages. Instead we connect on first request + + https://github.com/owncloud/ocis/pull/6565 + +* Bugfix - Handle the bad request status: [#6469](https://github.com/owncloud/ocis/pull/6469) + + Handle the bad request status for the CreateStorageSpace function + + https://github.com/owncloud/ocis/issues/6414 + https://github.com/owncloud/ocis/pull/6469 + https://github.com/cs3org/reva/pull/3948 + +* Bugfix - Fix the oidc role assigner: [#6605](https://github.com/owncloud/ocis/pull/6605) + + The update role method did not allow to set a role when the user already has two roles. This makes + no sense as the user is supposed to have only one and the update will fix that. We still log an error + level log to make the admin aware of that. + + https://github.com/owncloud/ocis/pull/6605 + https://github.com/owncloud/ocis/pull/6618 + +* Change - YAML configuration files are restricted to yaml-1.2: [#6510](https://github.com/owncloud/ocis/issues/6510) + + For parsing YAML based configuration files we utilize the gookit/config module. That module + has dropped support for older variants of the YAML format. It now only supports the YAML 1.2 + syntax. If you're using yaml configuration files, please make sure to update your files + accordingly. The most significant change likely is that only the string `true` and `false` + (including `TRUE`,`True`, `FALSE` and `False`) are now parsed as booleans. `Yes`, `On` and + other values are not longer considered valid values for booleans. + + https://github.com/owncloud/ocis/issues/6510 + https://github.com/owncloud/ocis/pull/6493 + +* Enhancement - Add 'ocis decomposedfs check-treesize' command: [#6556](https://github.com/owncloud/ocis/pull/6556) + + We added a 'ocis decomposedfs check-treesize' command for checking (and reparing) the + treesize metadata of a storage space. + + https://github.com/owncloud/ocis/pull/6556 + +* Enhancement - Add IDs to graph resource logging: [#6593](https://github.com/owncloud/ocis/pull/6593) + + Graph access logs were unsuable as they didn't contain IDs to match them to a request + + https://github.com/owncloud/ocis/pull/6593 + +* Enhancement - Add permissions to report: [#6528](https://github.com/owncloud/ocis/pull/6528) + + The webdav REPORT endpoint only returned permissions for personal spaces and shares. Now also + for project spaces. + + https://github.com/owncloud/ocis/pull/6528 + +* Enhancement - Add more metadata to the remote item: [#6300](https://github.com/owncloud/ocis/pull/6300) + + We added the drive alias, the space name and the relative path to the remote item. This is needed + to resolve shared files directly on the source space. + + https://github.com/owncloud/ocis/pull/6300 + +* Enhancement - Add SSE Endpoint: [#5998](https://github.com/owncloud/ocis/pull/5998) + + Add a server-sent events (sse) endpoint for the userlog service + + https://github.com/owncloud/ocis/pull/5998 + +* Enhancement - We added the storage id to the audit log for spaces: [#6548](https://github.com/owncloud/ocis/pull/6548) + + We added the storage id to the audit log for spaces + + https://github.com/owncloud/ocis/issues/3753 + https://github.com/owncloud/ocis/pull/6548 + +* Enhancement - Make the post logout redirect uri configurable: [#6583](https://github.com/owncloud/ocis/pull/6583) + + We added a config option to change the redirect uri after the logout action of the web client. + + https://github.com/owncloud/ocis/issues/6536 + https://github.com/owncloud/ocis/pull/6583 + +* Enhancement - Make the app provider service name configurable: [#6482](https://github.com/owncloud/ocis/pull/6482) + + We needed to make the service name of the app provider configurable. This needs to be changed + when using more than one app provider. Each of them needs be found by a unique service name. + Possible examples are: `app-provider-collabora`, `app-provider-onlyoffice`, + `app-provider-office365`. + + https://github.com/owncloud/ocis/pull/6482 + +* Enhancement - Add old & new values to audit logs: [#6537](https://github.com/owncloud/ocis/pull/6537) + + We have added old & new values to the audit logs We have added the missing events for role changes + + https://github.com/owncloud/ocis/pull/6537 + +* Enhancement - Update reva: [#6529](https://github.com/owncloud/ocis/pull/6529) + + Update reva to latest edge + + * Bugfix [cs3org/reva#3963](https://github.com/cs3org/reva/pull/3963): Treesize interger overflows + * Bugfix [cs3org/reva#3943](https://github.com/cs3org/reva/pull/3943): When removing metadata always use correct database and table + * Bugfix [cs3org/reva#3978](https://github.com/cs3org/reva/pull/3978): Decomposedfs no longer os.Stats when reading node metadata + * Bugfix [cs3org/reva#3959](https://github.com/cs3org/reva/pull/3959): Drop unnecessary stat + * Bugfix [cs3org/reva#3948](https://github.com/cs3org/reva/pull/3948): Handle the bad request status + * Bugfix [cs3org/reva#3955](https://github.com/cs3org/reva/pull/3955): Fix panic + * Bugfix [cs3org/reva#3977](https://github.com/cs3org/reva/pull/3977): Prevent direct access to trash items + * Bugfix [cs3org/reva#3933](https://github.com/cs3org/reva/pull/3933): Concurrently invalidate mtime cache in jsoncs3 share manager + * Bugfix [cs3org/reva#3985](https://github.com/cs3org/reva/pull/3985): Reduce jsoncs3 lock congestion + * Bugfix [cs3org/reva#3960](https://github.com/cs3org/reva/pull/3960): Add trace span details + * Bugfix [cs3org/reva#3951](https://github.com/cs3org/reva/pull/3951): Link context in metadata client + * Bugfix [cs3org/reva#3950](https://github.com/cs3org/reva/pull/3950): Use plain otel tracing in metadata client + * Bugfix [cs3org/reva#3975](https://github.com/cs3org/reva/pull/3975): Decomposedfs now resolves the parent without an os.Stat + * Change [cs3org/reva#3947](https://github.com/cs3org/reva/pull/3947): Bump golangci-lint to 1.51.2 + * Change [cs3org/reva#3945](https://github.com/cs3org/reva/pull/3945): Revert golangci-lint back to 1.50.1 + * Enhancement [cs3org/reva#3966](https://github.com/cs3org/reva/pull/3966): Add space metadata to ocs shares list + * Enhancement [cs3org/reva#3953](https://github.com/cs3org/reva/pull/3953): Client selector pool + * Enhancement [cs3org/reva#3941](https://github.com/cs3org/reva/pull/3941): Adding tracing for jsoncs3 + * Enhancement [cs3org/reva#3965](https://github.com/cs3org/reva/pull/3965): ResumePostprocessing Event + * Enhancement [cs3org/reva#3981](https://github.com/cs3org/reva/pull/3981): We have updated the UserFeatureChangedEvent to reflect value changes + * Enhancement [cs3org/reva#3986](https://github.com/cs3org/reva/pull/3986): Allow disabling wopi chat + + https://github.com/owncloud/ocis/pull/6529 + https://github.com/owncloud/ocis/pull/6544 + https://github.com/owncloud/ocis/pull/6507 + https://github.com/owncloud/ocis/pull/6572 + https://github.com/owncloud/ocis/pull/6590 + +* Enhancement - Use reva client selectors: [#6452](https://github.com/owncloud/ocis/pull/6452) + + Use reva client selectors instead of the static clients, this introduces the ocis service + registry in reva. The service discovery now resolves reva services by name and the client + selectors pick a random registered service node. + + https://github.com/owncloud/ocis/pull/6452 + https://github.com/cs3org/reva/pull/3939 + https://github.com/cs3org/reva/pull/3953 + +* Enhancement - Thumbnails can be disabled for webdav & web now: [#6577](https://github.com/owncloud/ocis/pull/6577) + + We added an env var `OCIS_DISABLE_PREVIEWS` to disable the thumbnails for web & webdav via a + global setting. For each service this behaviour can be disabled using the local env vars + `WEB_OPTION_DISABLE_PREVIEWS` (old) and `WEBDAV_DISABLE_PREVIEWS` (new). + + https://github.com/owncloud/ocis/issues/192 + https://github.com/owncloud/ocis/pull/6577 + +* Enhancement - Add companion URL config: [#6453](https://github.com/owncloud/ocis/pull/6453) + + Introduce a config to set the Uppy Companion URL via `WEB_OPTION_UPLOAD_COMPANION_URL`. + + https://github.com/owncloud/ocis/pull/6453 + +* Enhancement - Add imprint and privacy url config: [#6462](https://github.com/owncloud/ocis/pull/6462) + + Introduce a config to set the imprint and privacy url via `WEB_OPTION_IMPRINT_URL` and + `WEB_OPTION_PRIVACY_URL`. + + https://github.com/owncloud/ocis/pull/6462 + +* Enhancement - Add logged out url config: [#6549](https://github.com/owncloud/ocis/pull/6549) + + Introduce a config to set the more button url on the access denied page in web via + `WEB_OPTION_ACCESS_DENIED_HELP_URL`. + + https://github.com/owncloud/ocis/pull/6549 + +* Enhancement - Add search result content preview and term highlighting: [#6634](https://github.com/owncloud/ocis/pull/6634) + + The search result REPORT response now contains a content preview which highlights the search + term. The feature is only available if content extraction (e.g. apache tika) is configured + + https://github.com/owncloud/ocis/issues/6426 + https://github.com/owncloud/ocis/pull/6634 + +* Enhancement - Fix envvar defaults: [#6516](https://github.com/owncloud/ocis/pull/6516) + + Defaults for the envvar OCIS_LDAP_DISABLE_USER_MECHANISM were not used consistently, + correct is `attribute`. + + https://github.com/owncloud/ocis/issues/6513 + https://github.com/owncloud/ocis/pull/6516 + +* Enhancement - Skip if the simulink is a directory: [#6574](https://github.com/owncloud/ocis/pull/6574) + + Skip the error if the simulink is pointed to a directory + + https://github.com/owncloud/ocis/issues/6567 + https://github.com/owncloud/ocis/pull/6574 + +* Enhancement - Fix the groupname validation: [#6490](https://github.com/owncloud/ocis/pull/6490) + + Fixed the ability to create a group with an empty name + + https://github.com/owncloud/ocis/issues/5050 + https://github.com/owncloud/ocis/pull/6490 + +* Enhancement - Fix the username validation: [#6437](https://github.com/owncloud/ocis/pull/6437) + + Fix the username validation when an admin update the user + + https://github.com/owncloud/ocis/issues/6436 + https://github.com/owncloud/ocis/pull/6437 + +* Enhancement - Move proxy to service tracerprovider: [#6591](https://github.com/owncloud/ocis/pull/6591) + + This moves the proxy to initialise a service tracer provider at service initialisation time, + instead of using a package global tracer provider. + + https://github.com/owncloud/ocis/pull/6591 + +* Enhancement - Add functionality to retry postprocessing: [#6500](https://github.com/owncloud/ocis/pull/6500) + + Adds a ctl command to manually retry failed postprocessing on uploads + + https://github.com/owncloud/ocis/pull/6500 + +* Enhancement - Update go-micro kubernetes registry: [#6457](https://github.com/owncloud/ocis/pull/6457) + + https://github.com/owncloud/ocis/pull/6457 + https://github.com/go-micro/plugins/pull/114 + https://github.com/go-micro/plugins/pull/113 + +* Enhancement - Update web to v7.0.1: [#6470](https://github.com/owncloud/ocis/pull/6470) + + Tags: web + + We updated ownCloud Web to v7.0.1. Please refer to the changelog (linked) for details on the web + release. + + ## Summary * Bugfix [owncloud/web#9153](https://github.com/owncloud/web/pull/9153): + Reduce space preloading + + https://github.com/owncloud/ocis/pull/6470 + https://github.com/owncloud/web/releases/tag/v7.0.1 + +* Enhancement - Allow disabling wopi chat: [#6544](https://github.com/owncloud/ocis/pull/6544) + + Add a configreva for the new reva disable-chat feature + + https://github.com/owncloud/ocis/pull/6544 +# Changelog for [3.0.0] (2023-06-06) + +The following sections list the changes for 3.0.0. + +[3.0.0]: https://github.com/owncloud/ocis/compare/v2.0.0...v3.0.0 + +## Summary + +* Bugfix - Return 425 on Thumbnails: [#5300](https://github.com/owncloud/ocis/pull/5300) +* Bugfix - Allow selected updates on graph users: [#6233](https://github.com/owncloud/ocis/pull/6233) +* Bugfix - Disassociate users from deleted school: [#5343](https://github.com/owncloud/ocis/pull/5343) +* Bugfix - Fix error message when disabling users: [#6435](https://github.com/owncloud/ocis/pull/6435) +* Bugfix - Fix default role assignment for demo users: [#3432](https://github.com/owncloud/ocis/issues/3432) +* Bugfix - Empty exact list while searching for a sharee: [#6398](https://github.com/owncloud/ocis/pull/6398) +* Bugfix - Reduced default TTL of user and group caches in graph API: [#6320](https://github.com/owncloud/ocis/issues/6320) +* Bugfix - Fix so that PATCH requests for groups actually updates the group name: [#5949](https://github.com/owncloud/ocis/pull/5949) +* Bugfix - Use UUID attribute for computing "sub" claim in lico idp: [#904](https://github.com/owncloud/ocis/issues/904) +* Bugfix - Hide the existence of space when deleting/updating: [#5031](https://github.com/owncloud/ocis/issues/5031) +* Bugfix - Fix OIDC auth cache: [#5997](https://github.com/owncloud/ocis/pull/5997) +* Bugfix - Fix the empty string givenName attribute when creating user: [#5431](https://github.com/owncloud/ocis/issues/5431) +* Bugfix - Fix Postprocessing events: [#5269](https://github.com/owncloud/ocis/pull/5269) +* Bugfix - Fix Search reindexing performance regression: [#6085](https://github.com/owncloud/ocis/pull/6085) +* Bugfix - Fix Search tag indexing: [#5405](https://github.com/owncloud/ocis/pull/5405) +* Bugfix - Fix the wrong status code when appRoleAssignments is forbidden: [#6037](https://github.com/owncloud/ocis/issues/6037) +* Bugfix - Fix user type config for user provider: [#6027](https://github.com/owncloud/ocis/pull/6027) +* Bugfix - Fix userlog panic: [#6114](https://github.com/owncloud/ocis/pull/6114) +* Bugfix - Fix Logout Url config name: [#6227](https://github.com/owncloud/ocis/pull/6227) +* Bugfix - Add missing CORS config: [#5987](https://github.com/owncloud/ocis/pull/5987) +* Bugfix - Add missing response to blocked requests: [#6277](https://github.com/owncloud/ocis/pull/6277) +* Bugfix - Populate expanded properties: [#5421](https://github.com/owncloud/ocis/pull/5421) +* Bugfix - Add portrait thumbnail resolutions: [#5656](https://github.com/owncloud/ocis/pull/5656) +* Bugfix - Trace proxy middlewares: [#6313](https://github.com/owncloud/ocis/pull/6313) +* Bugfix - Update the default admin role: [#6310](https://github.com/owncloud/ocis/pull/6310) +* Bugfix - Fix authenticate headers for API requests: [#5992](https://github.com/owncloud/ocis/pull/5992) +* Change - Bump libregraph lico: [#5768](https://github.com/owncloud/ocis/pull/5768) +* Change - Updated Cache Configuration: [#5829](https://github.com/owncloud/ocis/pull/5829) +* Change - Remove the settings ui: [#5463](https://github.com/owncloud/ocis/pull/5463) +* Change - Do not share versions: [#5531](https://github.com/owncloud/ocis/pull/5531) +* Change - We renamed the guest role to user light: [#6456](https://github.com/owncloud/ocis/pull/6456) +* Enhancement - Add specific result to antivirus for debugging: [#6265](https://github.com/owncloud/ocis/pull/6265) +* Enhancement - Add debug server to audit: [#6178](https://github.com/owncloud/ocis/pull/6178) +* Enhancement - Add debug server to idm: [#6153](https://github.com/owncloud/ocis/pull/6153) +* Enhancement - Add debug server to postprocessing: [#6203](https://github.com/owncloud/ocis/pull/6203) +* Enhancement - Add debug server to userlog: [#6202](https://github.com/owncloud/ocis/pull/6202) +* Enhancement - Add 'ocis decomposedfs metadata' command: [#5858](https://github.com/owncloud/ocis/pull/5858) +* Enhancement - Add debug server to eventhistory: [#6204](https://github.com/owncloud/ocis/pull/6204) +* Enhancement - Add global env variable extractor: [#5164](https://github.com/owncloud/ocis/pull/5164) +* Enhancement - Add the email HTML templates: [#6147](https://github.com/owncloud/ocis/pull/6147) +* Enhancement - Open Debug endpoint for Notifications: [#5002](https://github.com/owncloud/ocis/issues/5002) +* Enhancement - Add MessageRichParameters: [#5927](https://github.com/owncloud/ocis/pull/5927) +* Enhancement - Add webfinger service: [#5373](https://github.com/owncloud/ocis/pull/5373) +* Enhancement - Async Postprocessing: [#5207](https://github.com/owncloud/ocis/pull/5207) +* Enhancement - Automate md creation: [#5901](https://github.com/owncloud/ocis/pull/5901) +* Enhancement - Add more logging to av service: [#5973](https://github.com/owncloud/ocis/pull/5973) +* Enhancement - Return Bad Request when requesting GDPR export for another user: [#6123](https://github.com/owncloud/ocis/pull/6123) +* Enhancement - Add endpoints to upload a custom logo: [#5735](https://github.com/owncloud/ocis/pull/5735) +* Enhancement - Bump go-ldap version: [#6004](https://github.com/owncloud/ocis/pull/6004) +* Enhancement - Bump libre-graph-api-go: [#5309](https://github.com/owncloud/ocis/pull/5309) +* Enhancement - Update Reva to version 2.14.0: [#6448](https://github.com/owncloud/ocis/pull/6448) +* Enhancement - Collect global envvars: [#5367](https://github.com/owncloud/ocis/pull/5367) +* Enhancement - Make the settings bundles part of the service config: [#5589](https://github.com/owncloud/ocis/pull/5589) +* Enhancement - Configure GRPC in ocs: [#6022](https://github.com/owncloud/ocis/pull/6022) +* Enhancement - Default LDAP write to true: [#6362](https://github.com/owncloud/ocis/pull/6362) +* Enhancement - Disable Notifications: [#6137](https://github.com/owncloud/ocis/pull/6137) +* Enhancement - Drive group permissions: [#5312](https://github.com/owncloud/ocis/pull/5312) +* Enhancement - Make the group members addition limit configurable: [#5357](https://github.com/owncloud/ocis/pull/5357) +* Enhancement - Allow username to be changed: [#5509](https://github.com/owncloud/ocis/pull/5509) +* Enhancement - Graph Drives IdentitySet displayName: [#5347](https://github.com/owncloud/ocis/pull/5347) +* Enhancement - Make the LDAP base DN for new groups configurable: [#5974](https://github.com/owncloud/ocis/pull/5974) +* Enhancement - Update to go 1.20 to use memlimit: [#5732](https://github.com/owncloud/ocis/pull/5732) +* Enhancement - Display surname and givenName attributes: [#5388](https://github.com/owncloud/ocis/pull/5388) +* Enhancement - Extended search: [#5221](https://github.com/owncloud/ocis/pull/5221) +* Enhancement - Resource tags: [#5227](https://github.com/owncloud/ocis/pull/5227) +* Enhancement - Allow users to be disabled: [#5588](https://github.com/owncloud/ocis/pull/5588) +* Enhancement - Web config additions: [#6032](https://github.com/owncloud/ocis/pull/6032) +* Enhancement - Eventhistory service: [#5600](https://github.com/owncloud/ocis/pull/5600) +* Enhancement - Expiration Notifications: [#5330](https://github.com/owncloud/ocis/pull/5330) +* Enhancement - Fix to prevent the email X-Site scripting: [#6429](https://github.com/owncloud/ocis/pull/6429) +* Enhancement - Fix preview or viewing of shared animated GIFs: [#6386](https://github.com/owncloud/ocis/pull/6386) +* Enhancement - Fix err when the user share the locked file: [#6358](https://github.com/owncloud/ocis/pull/6358) +* Enhancement - Add fulltextsearch capabilty: [#6366](https://github.com/owncloud/ocis/pull/6366) +* Enhancement - GDPR Export: [#6064](https://github.com/owncloud/ocis/pull/6064) +* Enhancement - Make graph/education API errors more consistent: [#5682](https://github.com/owncloud/ocis/pull/5682) +* Enhancement - Graph user capabilities: [#6339](https://github.com/owncloud/ocis/pull/6339) +* Enhancement - Configurable ID Cache: [#6353](https://github.com/owncloud/ocis/pull/6353) +* Enhancement - Add endpoint to list permissions: [#5594](https://github.com/owncloud/ocis/pull/5594) +* Enhancement - Notifications: [#6038](https://github.com/owncloud/ocis/pull/6038) +* Enhancement - Open Debug endpoint for Nats: [#5002](https://github.com/owncloud/ocis/issues/5002) +* Enhancement - No Notifications for own actions: [#5871](https://github.com/owncloud/ocis/pull/5871) +* Enhancement - Notify about policies: [#5912](https://github.com/owncloud/ocis/pull/5912) +* Enhancement - Add otlp tracing exporter: [#5132](https://github.com/owncloud/ocis/pull/5132) +* Enhancement - Add a capability for the Personal Data export: [#5984](https://github.com/owncloud/ocis/pull/5984) +* Enhancement - Introduce policies-service: [#5714](https://github.com/owncloud/ocis/pull/5714) +* Enhancement - Better config for postprocessing service: [#5457](https://github.com/owncloud/ocis/pull/5457) +* Enhancement - Add Store to `postprocessing`: [#6281](https://github.com/owncloud/ocis/pull/6281) +* Enhancement - Add config option to enforce passwords on public links: [#5848](https://github.com/owncloud/ocis/pull/5848) +* Enhancement - Add new permission for public links: [#5690](https://github.com/owncloud/ocis/pull/5690) +* Enhancement - Remove the email logo: [#6359](https://github.com/owncloud/ocis/issues/6359) +* Enhancement - Remove quota from share jails api responses: [#6309](https://github.com/owncloud/ocis/pull/6309) +* Enhancement - Rename permissions: [#3922](https://github.com/cs3org/reva/pull/3922) +* Enhancement - Added possibility to assign roles based on OIDC claims: [#6048](https://github.com/owncloud/ocis/pull/6048) +* Enhancement - Added option to configure default quota per role: [#5616](https://github.com/owncloud/ocis/pull/5616) +* Enhancement - Add optional services to the runtime: [#6071](https://github.com/owncloud/ocis/pull/6071) +* Enhancement - Add new SetProjectSpaceQuota permission: [#5660](https://github.com/owncloud/ocis/pull/5660) +* Enhancement - Add expiration to user and group shares: [#5389](https://github.com/owncloud/ocis/pull/5389) +* Enhancement - Space Management permissions: [#5441](https://github.com/owncloud/ocis/pull/5441) +* Enhancement - Cli to purge expired trash-bin items: [#5500](https://github.com/owncloud/ocis/pull/5500) +* Enhancement - Unify CA Cert envvars: [#6392](https://github.com/owncloud/ocis/pull/6392) +* Enhancement - Update web to v7.0.0-rc.37: [#6294](https://github.com/owncloud/ocis/pull/6294) +* Enhancement - Update web to v7.0.0-rc.38: [#6375](https://github.com/owncloud/ocis/pull/6375) +* Enhancement - Update web to v7.0.0: [#6438](https://github.com/owncloud/ocis/pull/6438) +* Enhancement - Use Accept-Language Header: [#5918](https://github.com/owncloud/ocis/pull/5918) +* Enhancement - Use gotext master: [#5867](https://github.com/owncloud/ocis/pull/5867) +* Enhancement - Userlog: [#5699](https://github.com/owncloud/ocis/pull/5699) +* Enhancement - Userlog Service: [#5610](https://github.com/owncloud/ocis/pull/5610) +* Enhancement - Determine the users language to translate via Transifex: [#6089](https://github.com/owncloud/ocis/pull/6089) +* Enhancement - Web options configuration: [#6188](https://github.com/owncloud/ocis/pull/6188) + +## Details + +* Bugfix - Return 425 on Thumbnails: [#5300](https://github.com/owncloud/ocis/pull/5300) + + Return `425` on thumbnails `GET` when file is processing. Pass `425` also through webdav + endpoint + + https://github.com/owncloud/ocis/pull/5300 + +* Bugfix - Allow selected updates on graph users: [#6233](https://github.com/owncloud/ocis/pull/6233) + + We are now allowing a couple of update request to complete even if + GRAPH_LDAP_SERVER_WRITE_ENABLED=false: + + * When using a group to disable users (OCIS_LDAP_DISABLE_USER_MECHANISM=group) updates to the accountEnabled property of a user will be allowed + * When a distinct base dn for new groups is configured ( GRAPH_LDAP_GROUP_CREATE_BASE_DN is set to a different value than GRAPH_LDAP_GROUP_BASE_DN), allow the creation/update of local groups. + + https://github.com/owncloud/ocis/pull/6233 + +* Bugfix - Disassociate users from deleted school: [#5343](https://github.com/owncloud/ocis/pull/5343) + + When a school is deleted, users should be disassociated from it. + + https://github.com/owncloud/ocis/issues/5246 + https://github.com/owncloud/ocis/pull/5343 + +* Bugfix - Fix error message when disabling users: [#6435](https://github.com/owncloud/ocis/pull/6435) + + When we disable users by adding them to a group we do not need to update the user entry. + + https://github.com/owncloud/ocis/pull/6435 + +* Bugfix - Fix default role assignment for demo users: [#3432](https://github.com/owncloud/ocis/issues/3432) + + The roles-assignments for demo users where duplicated with every restart of the settings + service. + + https://github.com/owncloud/ocis/issues/3432 + +* Bugfix - Empty exact list while searching for a sharee: [#6398](https://github.com/owncloud/ocis/pull/6398) + + We fixed a bug in the sharing api, it always returns an empty exact list while searching for a + sharee + + https://github.com/owncloud/ocis/issues/4265 + https://github.com/owncloud/ocis/pull/6398 + https://github.com/cs3org/reva/pull/3877 + +* Bugfix - Reduced default TTL of user and group caches in graph API: [#6320](https://github.com/owncloud/ocis/issues/6320) + + We reduced the default TTL of the cache for user and group information on the /drives endpoints + to 60 seconds. This fixes in issue where outdated information was show on the spaces list for a + very long time. + + https://github.com/owncloud/ocis/issues/6320 + +* Bugfix - Fix so that PATCH requests for groups actually updates the group name: [#5949](https://github.com/owncloud/ocis/pull/5949) + + https://github.com/owncloud/ocis/pull/5949 + +* Bugfix - Use UUID attribute for computing "sub" claim in lico idp: [#904](https://github.com/owncloud/ocis/issues/904) + + By default the LDAP backend for lico uses the User DN for computing the "sub" claim of a user. This + caused the "sub" claim to stay the same even if a user was deleted and recreated (and go a new UUID + assgined with that). We now use the user's unique id (`owncloudUUID` by default) for computing + the `sub` claim. So that user's recreated with the same name will be treated as different users + by the IDP. + + https://github.com/owncloud/ocis/issues/904 + https://github.com/owncloud/ocis/pull/6326 + https://github.com/owncloud/ocis/pull/6338 + https://github.com/owncloud/ocis/pull/6420 + +* Bugfix - Hide the existence of space when deleting/updating: [#5031](https://github.com/owncloud/ocis/issues/5031) + + The "code": "notAllowed" changed to "code": "itemNotFound" + + https://github.com/owncloud/ocis/issues/5031 + https://github.com/owncloud/ocis/pull/6220 + +* Bugfix - Fix OIDC auth cache: [#5997](https://github.com/owncloud/ocis/pull/5997) + + We've fixed an issue rendering the OIDC auth cache useless. + + https://github.com/owncloud/ocis/pull/5997 + +* Bugfix - Fix the empty string givenName attribute when creating user: [#5431](https://github.com/owncloud/ocis/issues/5431) + + Omitempty givenName attribute when creating user + + https://github.com/owncloud/ocis/issues/5431 + https://github.com/owncloud/ocis/pull/6259 + +* Bugfix - Fix Postprocessing events: [#5269](https://github.com/owncloud/ocis/pull/5269) + + Postprocessing service did not want to play with non-tls events. That is fixed now + + https://github.com/owncloud/ocis/pull/5269 + +* Bugfix - Fix Search reindexing performance regression: [#6085](https://github.com/owncloud/ocis/pull/6085) + + We've fixed a regression in the search service reindexing step, causing the whole space to be + reindexed instead of just the changed resources. + + https://github.com/owncloud/ocis/pull/6085 + +* Bugfix - Fix Search tag indexing: [#5405](https://github.com/owncloud/ocis/pull/5405) + + We've fixed an issue where search is not able to index tags for space resources. + + https://github.com/owncloud/ocis/pull/5405 + +* Bugfix - Fix the wrong status code when appRoleAssignments is forbidden: [#6037](https://github.com/owncloud/ocis/issues/6037) + + Fix the wrong status code when appRoleAssignments is forbidden in the + CreateAppRoleAssignment and DeleteAppRoleAssignment methods. + + https://github.com/owncloud/ocis/issues/6037 + https://github.com/owncloud/ocis/pull/6276 + +* Bugfix - Fix user type config for user provider: [#6027](https://github.com/owncloud/ocis/pull/6027) + + We needed to provide a default value for the user type property in the user provider. + + https://github.com/owncloud/ocis/pull/6027 + +* Bugfix - Fix userlog panic: [#6114](https://github.com/owncloud/ocis/pull/6114) + + Userlog services paniced because of `nil` ctx. That is fixed now + + https://github.com/owncloud/ocis/pull/6114 + +* Bugfix - Fix Logout Url config name: [#6227](https://github.com/owncloud/ocis/pull/6227) + + We fixed the yaml and json name of the logout url option. + + https://github.com/owncloud/ocis/pull/6227 + +* Bugfix - Add missing CORS config: [#5987](https://github.com/owncloud/ocis/pull/5987) + + The graph, userlog and ocdav services had no CORS config options. + + https://github.com/owncloud/ocis/pull/5987 + +* Bugfix - Add missing response to blocked requests: [#6277](https://github.com/owncloud/ocis/pull/6277) + + We added the missing response body to requests which were blocked by the policy engine. + + https://github.com/owncloud/ocis/pull/6277 + +* Bugfix - Populate expanded properties: [#5421](https://github.com/owncloud/ocis/pull/5421) + + We now return an empty array when an expanded relation has no entries. This makes consuming the + responses a little easier. + + https://github.com/owncloud/ocis/issues/5419 + https://github.com/owncloud/ocis/pull/5421 + https://github.com/owncloud/ocis/pull/5426 + +* Bugfix - Add portrait thumbnail resolutions: [#5656](https://github.com/owncloud/ocis/pull/5656) + + Add portrait-orientation resolutions to the thumbnail service's default configuration. + This prevents portrait photos from being heavily cropped into landscape resolutions in the + web viewer. + + https://github.com/owncloud/ocis/pull/5656 + * Bugfix - Trace proxy middlewares: [#6313](https://github.com/owncloud/ocis/pull/6313) We moved trace initialization to an early middleware to also trace requests made by other proxy @@ -27,11 +597,366 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/6313 -* Enhancement - Bump Reva: [#6381](https://github.com/owncloud/ocis/pull/6381) +* Bugfix - Update the default admin role: [#6310](https://github.com/owncloud/ocis/pull/6310) - Bumps reva + The admin role was missing two permissions. We added them to make the space admin role a subset of + the admin role. This matches better with the default user expectations. + https://github.com/owncloud/ocis/pull/6310 + +* Bugfix - Fix authenticate headers for API requests: [#5992](https://github.com/owncloud/ocis/pull/5992) + + We changed the www-authenticate header which should not be sent when the `XMLHttpRequest` + header is set. + + https://github.com/owncloud/ocis/issues/5986 + https://github.com/owncloud/ocis/pull/5992 + +* Change - Bump libregraph lico: [#5768](https://github.com/owncloud/ocis/pull/5768) + + We updated lico to the latest version * Update to 0.59.4 - upstream dropped the kc and cookie + backends + + https://github.com/owncloud/ocis/pull/5768 + +* Change - Updated Cache Configuration: [#5829](https://github.com/owncloud/ocis/pull/5829) + + We updated all cache related environment vars to more closely follow the go micro naming + pattern: - `{service}_CACHE_STORE_TYPE` becomes `{service}_CACHE_STORE` or + `{service}_PERSISTENT_STORE` - `{service}_CACHE_STORE_ADDRESS(ES)` becomes + `{service}_CACHE_STORE_NODES` - The `mem` store implementation name changes to `memory` - + In yaml files the cache `type` becomes `store` We introduced `redis-sentinel` as a store + implementation. + + https://github.com/owncloud/ocis/pull/5829 + +* Change - Remove the settings ui: [#5463](https://github.com/owncloud/ocis/pull/5463) + + With ownCloud Web having transitioned to Vue 3 recently, we would have had to port the settings + ui as well. The decision was made to discontinue the settings ui instead. As a result all traces + of the settings ui have been removed. + + The only user facing setting that ever existed in the settings service is now integrated into + the `account` page of ownCloud Web (click on top right user menu, then on your username to reach + the account page). + + https://github.com/owncloud/ocis/pull/5463 + +* Change - Do not share versions: [#5531](https://github.com/owncloud/ocis/pull/5531) + + We changed the default behavior of shares: Share receivers have no access to versions. People + in spaces with the "Editor" or "Manager" role can still see versions and work with them. + + https://github.com/owncloud/ocis/pull/5531 + +* Change - We renamed the guest role to user light: [#6456](https://github.com/owncloud/ocis/pull/6456) + + We needed to rename the "Guest" role to "User Light" because the naming was creating + confusions. The roles are not bound to a user type. + + https://github.com/owncloud/ocis/issues/6058 + https://github.com/owncloud/ocis/pull/6456 + +* Enhancement - Add specific result to antivirus for debugging: [#6265](https://github.com/owncloud/ocis/pull/6265) + + We added the ability to define a specific result for the virus scanner via env-var + (ANTIVIRUS_DEBUG_SCAN_OUTCOME) + + https://github.com/owncloud/ocis/pull/6265 + +* Enhancement - Add debug server to audit: [#6178](https://github.com/owncloud/ocis/pull/6178) + + We added a debug server to audit. + + https://github.com/owncloud/ocis/issues/5002 + https://github.com/owncloud/ocis/pull/6178 + +* Enhancement - Add debug server to idm: [#6153](https://github.com/owncloud/ocis/pull/6153) + + We added a debug server to idm. + + https://github.com/owncloud/ocis/issues/5003 + https://github.com/owncloud/ocis/pull/6153 + +* Enhancement - Add debug server to postprocessing: [#6203](https://github.com/owncloud/ocis/pull/6203) + + We added a debug server to postprocessing. + + https://github.com/owncloud/ocis/issues/5002 + https://github.com/owncloud/ocis/pull/6203 + +* Enhancement - Add debug server to userlog: [#6202](https://github.com/owncloud/ocis/pull/6202) + + We added a debug server to userlog. + + https://github.com/owncloud/ocis/issues/5002 + https://github.com/owncloud/ocis/pull/6202 + +* Enhancement - Add 'ocis decomposedfs metadata' command: [#5858](https://github.com/owncloud/ocis/pull/5858) + + We added a 'ocis decomposedfs metadata' command for inspecting and manipulating node + metadata. + + https://github.com/owncloud/ocis/pull/5858 + +* Enhancement - Add debug server to eventhistory: [#6204](https://github.com/owncloud/ocis/pull/6204) + + We added a debug server to eventhistory. + + https://github.com/owncloud/ocis/issues/5002 + https://github.com/owncloud/ocis/pull/6204 + +* Enhancement - Add global env variable extractor: [#5164](https://github.com/owncloud/ocis/pull/5164) + + We have added a little tool that will extract global env vars, that are loaded only through + os.Getenv for documentation purposes + + https://github.com/owncloud/ocis/issues/4916 + https://github.com/owncloud/ocis/pull/5164 + +* Enhancement - Add the email HTML templates: [#6147](https://github.com/owncloud/ocis/pull/6147) + + Add the email HTML templates + + https://github.com/owncloud/ocis/issues/6146 + https://github.com/owncloud/ocis/pull/6147 + +* Enhancement - Open Debug endpoint for Notifications: [#5002](https://github.com/owncloud/ocis/issues/5002) + + We added a debug server to the notifications service + + https://github.com/owncloud/ocis/issues/5002 + https://github.com/owncloud/ocis/pull/6155 + +* Enhancement - Add MessageRichParameters: [#5927](https://github.com/owncloud/ocis/pull/5927) + + Adds the messageRichParameters to virus and policies notifications + + https://github.com/owncloud/ocis/pull/5927 + +* Enhancement - Add webfinger service: [#5373](https://github.com/owncloud/ocis/pull/5373) + + Adds a webfinger service to redirect ocis clients + + https://github.com/owncloud/ocis/issues/6102 + https://github.com/owncloud/ocis/pull/5373 + https://github.com/owncloud/ocis/pull/6110 + +* Enhancement - Async Postprocessing: [#5207](https://github.com/owncloud/ocis/pull/5207) + + Provides functionality for async postprocessing. This will allow the system to do the + postprocessing (virusscan, copying of bytes to their final destination, ...) asynchronous + to the users request. Major change when active. + + https://github.com/owncloud/ocis/pull/5207 + +* Enhancement - Automate md creation: [#5901](https://github.com/owncloud/ocis/pull/5901) + + Automatically create `_index.md` files from the services `README.md` + + https://github.com/owncloud/ocis/pull/5901 + +* Enhancement - Add more logging to av service: [#5973](https://github.com/owncloud/ocis/pull/5973) + + We need more debug logging in some situations to understand the state of a virus scan. + + https://github.com/owncloud/ocis/pull/5973 + +* Enhancement - Return Bad Request when requesting GDPR export for another user: [#6123](https://github.com/owncloud/ocis/pull/6123) + + This is an enhancement, not security related as the requested uid is never used + + https://github.com/owncloud/ocis/pull/6123 + +* Enhancement - Add endpoints to upload a custom logo: [#5735](https://github.com/owncloud/ocis/pull/5735) + + Added endpoints to upload and reset custom logos. The files are stored under the + `WEB_ASSET_PATH` which defaults to `$OCIS_BASE_DATA_PATH/web/assets`. + + https://github.com/owncloud/ocis/pull/5735 + https://github.com/owncloud/ocis/pull/5559 + +* Enhancement - Bump go-ldap version: [#6004](https://github.com/owncloud/ocis/pull/6004) + + Use master version of go-ldap to get rid of nasty `=` bug. See + https://github.com/go-ldap/ldap/issues/416 + + https://github.com/owncloud/ocis/pull/6004 + +* Enhancement - Bump libre-graph-api-go: [#5309](https://github.com/owncloud/ocis/pull/5309) + + We fixed a couple of issues in libre-graph-api-go package. + + * rename drive permission grantedTo to grantedToIdentities to be ms graph spec compatible. + * drive.name is a required property now. + * add group property to the identitySet. + + https://github.com/owncloud/ocis/pull/5309 + https://github.com/owncloud/ocis/pull/5312 + +* Enhancement - Update Reva to version 2.14.0: [#6448](https://github.com/owncloud/ocis/pull/6448) + + Changelog for reva 2.14.0 (2023-06-05) ======================================= + + * Bugfix [cs3org/reva#3919](https://github.com/cs3org/reva/pull/3919): We added missing timestamps to events + * Bugfix [cs3org/reva#3911](https://github.com/cs3org/reva/pull/3911): Clean IDCache properly + * Bugfix [cs3org/reva#3896](https://github.com/cs3org/reva/pull/3896): Do not lose old revisions when overwriting a file during copy + * Bugfix [cs3org/reva#3918](https://github.com/cs3org/reva/pull/3918): Dont enumerate users + * Bugfix [cs3org/reva#3902](https://github.com/cs3org/reva/pull/3902): Do not try to use the cache for empty node + * Bugfix [cs3org/reva#3877](https://github.com/cs3org/reva/pull/3877): Empty exact list while searching for a sharee + * Bugfix [cs3org/reva#3906](https://github.com/cs3org/reva/pull/3906): Fix preflight requests + * Bugfix [cs3org/reva#3934](https://github.com/cs3org/reva/pull/3934): Fix the space editor permissions + * Bugfix [cs3org/reva#3899](https://github.com/cs3org/reva/pull/3899): Harden uploads + * Bugfix [cs3org/reva#3917](https://github.com/cs3org/reva/pull/3917): Prevent last space manager from leaving + * Bugfix [cs3org/reva#3866](https://github.com/cs3org/reva/pull/3866): Fix public link lookup performance + * Bugfix [cs3org/reva#3904](https://github.com/cs3org/reva/pull/3904): Improve performance of directory listings + * Enhancement [cs3org/reva#3893](https://github.com/cs3org/reva/pull/3893): Cleanup Space Delete permissions + * Enhancement [cs3org/reva#3894](https://github.com/cs3org/reva/pull/3894): Fix err when the user share the locked file + * Enhancement [cs3org/reva#3913](https://github.com/cs3org/reva/pull/3913): Introduce FullTextSearch Capability + * Enhancement [cs3org/reva#3898](https://github.com/cs3org/reva/pull/3898): Add Graph User capabilities + * Enhancement [cs3org/reva#3496](https://github.com/cs3org/reva/pull/3496): Add otlp tracing exporter + * Enhancement [cs3org/reva#3922](https://github.com/cs3org/reva/pull/3922): Rename permissions + + Changelog for reva 2.13.3 (2023-05-17) ======================================= + + * Bugfix [cs3org/reva#3890](https://github.com/cs3org/reva/pull/3890): Bring back public link sharing of project space roots + * Bugfix [cs3org/reva#3888](https://github.com/cs3org/reva/pull/3888): We fixed a bug that unnecessarily fetched all members of a group + * Bugfix [cs3org/reva#3886](https://github.com/cs3org/reva/pull/3886): Decomposedfs no longer deadlocks when cache is disabled + * Bugfix [cs3org/reva#3892](https://github.com/cs3org/reva/pull/3892): Fix public links + * Bugfix [cs3org/reva#3876](https://github.com/cs3org/reva/pull/3876): Remove go-micro/store/redis specific workaround + * Bugfix [cs3org/reva#3889](https://github.com/cs3org/reva/pull/3889): Update space root mtime when changing space metadata + * Bugfix [cs3org/reva#3836](https://github.com/cs3org/reva/pull/3836): Fix spaceID in the decomposedFS + * Bugfix [cs3org/reva#3867](https://github.com/cs3org/reva/pull/3867): Restore last version after positive result + * Bugfix [cs3org/reva#3849](https://github.com/cs3org/reva/pull/3849): Prevent sharing space roots and personal spaces + * Enhancement [cs3org/reva#3865](https://github.com/cs3org/reva/pull/3865): Remove unneccessary code from gateway + * Enhancement [cs3org/reva#3895](https://github.com/cs3org/reva/pull/3895): Add missing expiry date to shares + + Changelog for reva 2.13.2 (2023-05-08) ======================================= + + * Bugfix [cs3org/reva#3845](https://github.com/cs3org/reva/pull/3845): Fix propagation + * Bugfix [cs3org/reva#3856](https://github.com/cs3org/reva/pull/3856): Fix response code + * Bugfix [cs3org/reva#3857](https://github.com/cs3org/reva/pull/3857): Fix trashbin purge + + Changelog for reva 2.13.1 (2023-05-03) ======================================= + + * Bugfix [cs3org/reva#3843](https://github.com/cs3org/reva/pull/3843): Allow scope check to impersonate space owners + + Changelog for reva 2.13.0 (2023-05-02) ======================================= + + * Bugfix [cs3org/reva#3570](https://github.com/cs3org/reva/pull/3570): Return 425 on HEAD + * Bugfix [cs3org/reva#3830](https://github.com/cs3org/reva/pull/3830): Be more robust when logging errors + * Bugfix [cs3org/reva#3815](https://github.com/cs3org/reva/pull/3815): Bump micro redis store + * Bugfix [cs3org/reva#3596](https://github.com/cs3org/reva/pull/3596): Cache CreateHome calls + * Bugfix [cs3org/reva#3823](https://github.com/cs3org/reva/pull/3823): Deny correctly in decomposedfs + * Bugfix [cs3org/reva#3826](https://github.com/cs3org/reva/pull/3826): Add by group index to decomposedfs + * Bugfix [cs3org/reva#3618](https://github.com/cs3org/reva/pull/3618): Drain body on failed put + * Bugfix [cs3org/reva#3685](https://github.com/cs3org/reva/pull/3685): Send fileid on copy + * Bugfix [cs3org/reva#3688](https://github.com/cs3org/reva/pull/3688): Return 425 on GET + * Bugfix [cs3org/reva#3755](https://github.com/cs3org/reva/pull/3755): Fix app provider language validation + * Bugfix [cs3org/reva#3800](https://github.com/cs3org/reva/pull/3800): Fix building for freebsd + * Bugfix [cs3org/reva#3700](https://github.com/cs3org/reva/pull/3700): Fix caching + * Bugfix [cs3org/reva#3535](https://github.com/cs3org/reva/pull/3535): Fix ceph driver storage fs implementation + * Bugfix [cs3org/reva#3764](https://github.com/cs3org/reva/pull/3764): Fix missing CORS config in ocdav service + * Bugfix [cs3org/reva#3710](https://github.com/cs3org/reva/pull/3710): Fix error when try to delete space without permission + * Bugfix [cs3org/reva#3822](https://github.com/cs3org/reva/pull/3822): Fix deleting spaces + * Bugfix [cs3org/reva#3718](https://github.com/cs3org/reva/pull/3718): Fix revad-eos docker image which was failing to build + * Bugfix [cs3org/reva#3559](https://github.com/cs3org/reva/pull/3559): Fix build on freebsd + * Bugfix [cs3org/reva#3696](https://github.com/cs3org/reva/pull/3696): Fix ldap filters when checking for enabled users + * Bugfix [cs3org/reva#3767](https://github.com/cs3org/reva/pull/3767): Decode binary UUID when looking up a users group memberships + * Bugfix [cs3org/reva#3741](https://github.com/cs3org/reva/pull/3741): Fix listing shares to multiple groups + * Bugfix [cs3org/reva#3834](https://github.com/cs3org/reva/pull/3834): Return correct error during MKCOL + * Bugfix [cs3org/reva#3841](https://github.com/cs3org/reva/pull/3841): Fix nil pointer and improve logging + * Bugfix [cs3org/reva#3831](https://github.com/cs3org/reva/pull/3831): Ignore 'null' mtime on tus upload + * Bugfix [cs3org/reva#3758](https://github.com/cs3org/reva/pull/3758): Fix public links with enforced password + * Bugfix [cs3org/reva#3814](https://github.com/cs3org/reva/pull/3814): Fix stat cache access + * Bugfix [cs3org/reva#3650](https://github.com/cs3org/reva/pull/3650): FreeBSD xattr support + * Bugfix [cs3org/reva#3827](https://github.com/cs3org/reva/pull/3827): Initialize user cache for decomposedfs + * Bugfix [cs3org/reva#3818](https://github.com/cs3org/reva/pull/3818): Invalidate cache when deleting space + * Bugfix [cs3org/reva#3812](https://github.com/cs3org/reva/pull/3812): Filemetadata Cache now deletes keys without listing them first + * Bugfix [cs3org/reva#3817](https://github.com/cs3org/reva/pull/3817): Pipeline cache deletes + * Bugfix [cs3org/reva#3711](https://github.com/cs3org/reva/pull/3711): Replace ini metadata backend by messagepack backend + * Bugfix [cs3org/reva#3828](https://github.com/cs3org/reva/pull/3828): Send quota when listing spaces in decomposedfs + * Bugfix [cs3org/reva#3681](https://github.com/cs3org/reva/pull/3681): Fix etag of "empty" shares jail + * Bugfix [cs3org/reva#3748](https://github.com/cs3org/reva/pull/3748): Prevent service from panicking + * Bugfix [cs3org/reva#3816](https://github.com/cs3org/reva/pull/3816): Write Metadata once + * Change [cs3org/reva#3641](https://github.com/cs3org/reva/pull/3641): Hide file versions for share receivers + * Change [cs3org/reva#3820](https://github.com/cs3org/reva/pull/3820): Streamline stores + * Enhancement [cs3org/reva#3732](https://github.com/cs3org/reva/pull/3732): Make method for detecting the metadata backend public + * Enhancement [cs3org/reva#3789](https://github.com/cs3org/reva/pull/3789): Add capabilities indicating if user attributes are read-only + * Enhancement [cs3org/reva#3792](https://github.com/cs3org/reva/pull/3792): Add a prometheus gauge to keep track of active uploads and downloads + * Enhancement [cs3org/reva#3637](https://github.com/cs3org/reva/pull/3637): Add an ID to each events + * Enhancement [cs3org/reva#3704](https://github.com/cs3org/reva/pull/3704): Add more information to events + * Enhancement [cs3org/reva#3744](https://github.com/cs3org/reva/pull/3744): Add LDAP user type attribute + * Enhancement [cs3org/reva#3806](https://github.com/cs3org/reva/pull/3806): Decomposedfs now supports filtering spaces by owner + * Enhancement [cs3org/reva#3730](https://github.com/cs3org/reva/pull/3730): Antivirus + * Enhancement [cs3org/reva#3531](https://github.com/cs3org/reva/pull/3531): Async Postprocessing + * Enhancement [cs3org/reva#3571](https://github.com/cs3org/reva/pull/3571): Async Upload Improvements + * Enhancement [cs3org/reva#3801](https://github.com/cs3org/reva/pull/3801): Cache node ids + * Enhancement [cs3org/reva#3690](https://github.com/cs3org/reva/pull/3690): Check set project space quota permission + * Enhancement [cs3org/reva#3686](https://github.com/cs3org/reva/pull/3686): User disabling functionality + * Enhancement [cs3org/reva#3505](https://github.com/cs3org/reva/pull/3505): Fix eosgrpc package + * Enhancement [cs3org/reva#3575](https://github.com/cs3org/reva/pull/3575): Fix skip group grant index cleanup + * Enhancement [cs3org/reva#3564](https://github.com/cs3org/reva/pull/3564): Fix tag pkg + * Enhancement [cs3org/reva#3756](https://github.com/cs3org/reva/pull/3756): Prepare for GDPR export + * Enhancement [cs3org/reva#3612](https://github.com/cs3org/reva/pull/3612): Group feature changed event added + * Enhancement [cs3org/reva#3729](https://github.com/cs3org/reva/pull/3729): Improve decomposedfs performance, esp. with network fs/cache + * Enhancement [cs3org/reva#3697](https://github.com/cs3org/reva/pull/3697): Improve the ini file metadata backend + * Enhancement [cs3org/reva#3819](https://github.com/cs3org/reva/pull/3819): Allow creating internal links without permission + * Enhancement [cs3org/reva#3740](https://github.com/cs3org/reva/pull/3740): Limit concurrency in decomposedfs + * Enhancement [cs3org/reva#3569](https://github.com/cs3org/reva/pull/3569): Always list shares jail when listing spaces + * Enhancement [cs3org/reva#3788](https://github.com/cs3org/reva/pull/3788): Make resharing configurable + * Enhancement [cs3org/reva#3674](https://github.com/cs3org/reva/pull/3674): Introduce ini file based metadata backend + * Enhancement [cs3org/reva#3728](https://github.com/cs3org/reva/pull/3728): Automatically migrate file metadata from xattrs to messagepack + * Enhancement [cs3org/reva#3807](https://github.com/cs3org/reva/pull/3807): Name Validation + * Enhancement [cs3org/reva#3574](https://github.com/cs3org/reva/pull/3574): Opaque space group + * Enhancement [cs3org/reva#3598](https://github.com/cs3org/reva/pull/3598): Pass estream to Storage Providers + * Enhancement [cs3org/reva#3763](https://github.com/cs3org/reva/pull/3763): Add a capability for personal data export + * Enhancement [cs3org/reva#3577](https://github.com/cs3org/reva/pull/3577): Prepare for SSE + * Enhancement [cs3org/reva#3731](https://github.com/cs3org/reva/pull/3731): Add config option to enforce passwords on public links + * Enhancement [cs3org/reva#3693](https://github.com/cs3org/reva/pull/3693): Enforce the PublicLink.Write permission + * Enhancement [cs3org/reva#3497](https://github.com/cs3org/reva/pull/3497): Introduce owncloud 10 publiclink manager + * Enhancement [cs3org/reva#3714](https://github.com/cs3org/reva/pull/3714): Add global max quota option and quota for CreateHome + * Enhancement [cs3org/reva#3759](https://github.com/cs3org/reva/pull/3759): Set correct share type when listing shares + * Enhancement [cs3org/reva#3594](https://github.com/cs3org/reva/pull/3594): Add expiration to user and group shares + * Enhancement [cs3org/reva#3580](https://github.com/cs3org/reva/pull/3580): Share expired event + * Enhancement [cs3org/reva#3620](https://github.com/cs3org/reva/pull/3620): Allow a new ShareType `SpaceMembershipGroup` + * Enhancement [cs3org/reva#3609](https://github.com/cs3org/reva/pull/3609): Space Management Permissions + * Enhancement [cs3org/reva#3655](https://github.com/cs3org/reva/pull/3655): Add expiration date to space memberships + * Enhancement [cs3org/reva#3697](https://github.com/cs3org/reva/pull/3697): Add support for redis sentinel caches + * Enhancement [cs3org/reva#3552](https://github.com/cs3org/reva/pull/3552): Suppress tusd logs + * Enhancement [cs3org/reva#3555](https://github.com/cs3org/reva/pull/3555): Tags + * Enhancement [cs3org/reva#3785](https://github.com/cs3org/reva/pull/3785): Increase unit test coverage in the ocdav service + * Enhancement [cs3org/reva#3739](https://github.com/cs3org/reva/pull/3739): Try to rename uploaded files to their final position + * Enhancement [cs3org/reva#3610](https://github.com/cs3org/reva/pull/3610): Walk and log chi routes + + https://github.com/owncloud/ocis/pull/6448 + https://github.com/owncloud/ocis/pull/6447 https://github.com/owncloud/ocis/pull/6381 + https://github.com/owncloud/ocis/pull/6305 + https://github.com/owncloud/ocis/pull/6339 + https://github.com/owncloud/ocis/pull/6205 + https://github.com/owncloud/ocis/pull/6186 + +* Enhancement - Collect global envvars: [#5367](https://github.com/owncloud/ocis/pull/5367) + + Compose a list of all envvars living in more than 1 service + + https://github.com/owncloud/ocis/pull/5367 + +* Enhancement - Make the settings bundles part of the service config: [#5589](https://github.com/owncloud/ocis/pull/5589) + + We added the settings bundles to the config. The default roles are still unchanged. You can now + override the defaults by replacing the whole bundles list via json config files. The config + file is loaded from a specified path which can be configured with `SETTINGS_BUNDLES_PATH`. + + https://github.com/owncloud/ocis/pull/5589 + https://github.com/owncloud/ocis/pull/5607 + +* Enhancement - Configure GRPC in ocs: [#6022](https://github.com/owncloud/ocis/pull/6022) + + Fixes a panic in ocs when running not in single binary + + https://github.com/owncloud/ocis/pull/6022 * Enhancement - Default LDAP write to true: [#6362](https://github.com/owncloud/ocis/pull/6362) @@ -39,6 +964,126 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/6362 +* Enhancement - Disable Notifications: [#6137](https://github.com/owncloud/ocis/pull/6137) + + Introduce new setting to disable notifications + + https://github.com/owncloud/ocis/pull/6137 + +* Enhancement - Drive group permissions: [#5312](https://github.com/owncloud/ocis/pull/5312) + + We've updated the libregraph.Drive response to contain group permissions. + + https://github.com/owncloud/ocis/pull/5312 + +* Enhancement - Make the group members addition limit configurable: [#5357](https://github.com/owncloud/ocis/pull/5357) + + It's now possible to configure the limit of group members addition by PATCHing + `/graph/v1.0/groups/{groupID}`. It still defaults to 20 as defined in the spec but it can be + configured via `.graph.api.group_members_patch_limit` in `ocis.yaml` or via the + `GRAPH_GROUP_MEMBERS_PATCH_LIMIT` environment variable. + + https://github.com/owncloud/ocis/issues/5262 + https://github.com/owncloud/ocis/pull/5357 + +* Enhancement - Allow username to be changed: [#5509](https://github.com/owncloud/ocis/pull/5509) + + When OnPremisesSamAccountName is present in a PATCH on `{apiRoot}/users/{userID}` it will + change the username of the user. This also changes the references to this user in the groups. + + https://github.com/owncloud/ocis/issues/4988 + https://github.com/owncloud/ocis/pull/5509 + +* Enhancement - Graph Drives IdentitySet displayName: [#5347](https://github.com/owncloud/ocis/pull/5347) + + We've added the IdentitySet displayName property to the group and user sets for the graph + drives endpoint. The values for groups and users get cached. + + https://github.com/owncloud/ocis/pull/5347 + https://github.com/owncloud/web/pull/8178 + +* Enhancement - Make the LDAP base DN for new groups configurable: [#5974](https://github.com/owncloud/ocis/pull/5974) + + The LDAP backend for the Graph service introduced a new config option for setting the Parent DN + for new groups created via the `/groups/` endpoint. (`GRAPH_LDAP_GROUP_CREATE_BASE_DN`) + + It defaults to the value of `GRAPH_LDAP_GROUP_BASE_DN`. If set to a different value the + `GRAPH_LDAP_GROUP_CREATE_BASE_DN` needs to be a subordinate DN of + `GRAPH_LDAP_GROUP_BASE_DN`. + + All existing groups with a DN outside the `GRAPH_LDAP_GROUP_CREATE_BASE_DN` tree will be + treated as read-only groups. So it is not possible to edit these groups. + + https://github.com/owncloud/ocis/pull/5974 + +* Enhancement - Update to go 1.20 to use memlimit: [#5732](https://github.com/owncloud/ocis/pull/5732) + + We updated to go 1.20 which allows setting GOMEMLIMIT, which we by default set to 0.9. + + https://github.com/owncloud/ocis/pull/5732 + +* Enhancement - Display surname and givenName attributes: [#5388](https://github.com/owncloud/ocis/pull/5388) + + When querying the graph API, the surname and givenName attributes are now displayed for users. + + https://github.com/owncloud/ocis/issues/5386 + https://github.com/owncloud/ocis/pull/5388 + +* Enhancement - Extended search: [#5221](https://github.com/owncloud/ocis/pull/5221) + + Provides multiple enhancement to the search implementation. * content extraction, search + now supports apache tika to extract resource contents. * search engine, underlying search + engine is swappable now. * event consumers, the number of event consumers can now be set, which + improves the speed of the individual tasks + + https://github.com/owncloud/ocis/issues/5184 + https://github.com/owncloud/ocis/pull/5221 + +* Enhancement - Resource tags: [#5227](https://github.com/owncloud/ocis/pull/5227) + + We've added the ability to tag resources via the graph api. Tags can be added (put request) and + removed (delete request) from a resource, a list of available tags can also be requested by + sending a get request to the graph endpoint. + + https://github.com/owncloud/ocis/issues/5184 + https://github.com/owncloud/ocis/pull/5227 + https://github.com/owncloud/ocis/pull/5271 + +* Enhancement - Allow users to be disabled: [#5588](https://github.com/owncloud/ocis/pull/5588) + + By setting the `accountEnabled` property to `false` for a user via the graph API. Users can be + disabled (i.e. they can no longer login) + + https://github.com/owncloud/ocis/pull/5588 + https://github.com/owncloud/ocis/pull/5620 + +* Enhancement - Web config additions: [#6032](https://github.com/owncloud/ocis/pull/6032) + + We've added config keys for defining additional css, scripts and translations for ownCloud + Web. + + https://github.com/owncloud/ocis/pull/6032 + +* Enhancement - Eventhistory service: [#5600](https://github.com/owncloud/ocis/pull/5600) + + Introduces the `eventhistory` service. It is a service that stores events and provides a grpc + API to retrieve them. + + https://github.com/owncloud/ocis/pull/5600 + +* Enhancement - Expiration Notifications: [#5330](https://github.com/owncloud/ocis/pull/5330) + + Send emails to the user informing that a share or a space membership expires. + + https://github.com/owncloud/ocis/pull/5330 + +* Enhancement - Fix to prevent the email X-Site scripting: [#6429](https://github.com/owncloud/ocis/pull/6429) + + Fix to prevent the email notification X-Site scripting + + https://github.com/owncloud/ocis/issues/6411 + https://github.com/owncloud/ocis/pull/6429 + * Enhancement - Fix preview or viewing of shared animated GIFs: [#6386](https://github.com/owncloud/ocis/pull/6386) Fix preview or viewing of shared animated GIFs @@ -46,12 +1091,12 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/issues/5418 https://github.com/owncloud/ocis/pull/6386 -* Enhancement - Fix err when the user share the locked file: [#6357](https://github.com/owncloud/ocis/pull/6357) +* Enhancement - Fix err when the user share the locked file: [#6358](https://github.com/owncloud/ocis/pull/6358) Fix unexpected behavior when the user try to share the locked file https://github.com/owncloud/ocis/issues/6197 - https://github.com/owncloud/ocis/pull/6357 + https://github.com/owncloud/ocis/pull/6358 * Enhancement - Add fulltextsearch capabilty: [#6366](https://github.com/owncloud/ocis/pull/6366) @@ -59,6 +1104,28 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/6366 +* Enhancement - GDPR Export: [#6064](https://github.com/owncloud/ocis/pull/6064) + + Adds an endpoint to collect all data that is related to a user + + https://github.com/owncloud/ocis/pull/6064 + https://github.com/owncloud/ocis/pull/5950 + +* Enhancement - Make graph/education API errors more consistent: [#5682](https://github.com/owncloud/ocis/pull/5682) + + Aligned the error messages when creating schools and classes fail and changed the response + code from 500 to 409. + + https://github.com/owncloud/ocis/issues/5660 + https://github.com/owncloud/ocis/pull/5682 + +* Enhancement - Graph user capabilities: [#6339](https://github.com/owncloud/ocis/pull/6339) + + Adds capablities to show if users are writeable in LDAP so clients can block their specific + fields + + https://github.com/owncloud/ocis/pull/6339 + * Enhancement - Configurable ID Cache: [#6353](https://github.com/owncloud/ocis/pull/6353) Makes the integrated idcache (used to reduce reads from disc) configurable with the general @@ -66,6 +1133,43 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/6353 +* Enhancement - Add endpoint to list permissions: [#5594](https://github.com/owncloud/ocis/pull/5594) + + We added 'https://cloud.ocis.test/api/v0/settings/permissions-list' to retrieve all + permissions of the logged in user. + + https://github.com/owncloud/ocis/pull/5594 + https://github.com/owncloud/ocis/pull/5571 + +* Enhancement - Notifications: [#6038](https://github.com/owncloud/ocis/pull/6038) + + Make Emails translatable via transifex The transifex translation add in to the email + templates. The optional environment variable NOTIFICATIONS_TRANSLATION_PATH added to + config. The optional global environment variable OCIS_TRANSLATION_PATH added to + notifications and userlog config. + + https://github.com/owncloud/ocis/issues/6025 + https://github.com/owncloud/ocis/pull/6038 + +* Enhancement - Open Debug endpoint for Nats: [#5002](https://github.com/owncloud/ocis/issues/5002) + + We added a debug server to nats + + https://github.com/owncloud/ocis/issues/5002 + https://github.com/owncloud/ocis/pull/6139 + +* Enhancement - No Notifications for own actions: [#5871](https://github.com/owncloud/ocis/pull/5871) + + Don't send notifications on space events when the user has executed them herself. + + https://github.com/owncloud/ocis/pull/5871 + +* Enhancement - Notify about policies: [#5912](https://github.com/owncloud/ocis/pull/5912) + + Notify the user when a file was deleted due to policies (policies service) + + https://github.com/owncloud/ocis/pull/5912 + * Enhancement - Add otlp tracing exporter: [#5132](https://github.com/owncloud/ocis/pull/5132) We can now configure otlp to send traces using the otlp exporter. @@ -73,6 +1177,73 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/5132 https://github.com/cs3org/reva/pull/3496 +* Enhancement - Add a capability for the Personal Data export: [#5984](https://github.com/owncloud/ocis/pull/5984) + + Adds a capability for the personal data export endpoint + + https://github.com/owncloud/ocis/pull/5984 + +* Enhancement - Introduce policies-service: [#5714](https://github.com/owncloud/ocis/pull/5714) + + Introduces policies service. The policies-service provides a new grpc api which can be used to + return whether a requested operation is allowed or not. Open Policy Agent is used to determine + the set of rules of what is permitted and what is not. + + 2 further levels of authorization build on this: + + * Proxy Authorization + * Event Authorization (needs async post-processing enabled) + + The simplest authorization layer is in the proxy, since every request is processed here, only + simple decisions that can be processed quickly are made here, more complex queries such as file + evaluation are explicitly excluded in this layer. + + The next layer is event-based as a pipeline step in asynchronous post-processing, since + processing at this point is asynchronous, the operations there can also take longer and be more + expensive, the bytes of a file can be examined here as an example. + + Since the base block is a grpc api, it is also possible to use it directly. The policies are + written in the [rego query + language](https://www.openpolicyagent.org/docs/latest/policy-language/). + + https://github.com/owncloud/ocis/issues/5580 + https://github.com/owncloud/ocis/pull/5714 + +* Enhancement - Better config for postprocessing service: [#5457](https://github.com/owncloud/ocis/pull/5457) + + The postprocessing service is now individually configurable. This is achieved by allowing a + list of postprocessing steps that are processed in order of their appearance in the + `POSTPROCESSING_STEPS` envvar. + + https://github.com/owncloud/ocis/pull/5457 + +* Enhancement - Add Store to `postprocessing`: [#6281](https://github.com/owncloud/ocis/pull/6281) + + Add a gomicro store for the postprocessing service. Needed to run multiple postprocessing + instances + + https://github.com/owncloud/ocis/pull/6281 + +* Enhancement - Add config option to enforce passwords on public links: [#5848](https://github.com/owncloud/ocis/pull/5848) + + Added a new config option to enforce passwords on public links with "Uploader, Editor, + Contributor" roles. + + The new options are: `OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD`, + `SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD` and + `FRONTEND_OCS_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD`. Check the docs on how to + properly set them. + + https://github.com/owncloud/ocis/pull/5848 + https://github.com/owncloud/ocis/pull/5785 + https://github.com/owncloud/ocis/pull/5720 + +* Enhancement - Add new permission for public links: [#5690](https://github.com/owncloud/ocis/pull/5690) + + Added a new permission 'PublicLink.Write' to check if a user can create or update public links. + + https://github.com/owncloud/ocis/pull/5690 + * Enhancement - Remove the email logo: [#6359](https://github.com/owncloud/ocis/issues/6359) Remove the email logo @@ -80,6 +1251,105 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/issues/6359 https://github.com/owncloud/ocis/pull/6361 +* Enhancement - Remove quota from share jails api responses: [#6309](https://github.com/owncloud/ocis/pull/6309) + + We have removed the quota object from api responses for share jails, which would permanently + show exceeded due to restrictions in the permission system. + + https://github.com/owncloud/ocis/issues/4472 + https://github.com/owncloud/ocis/pull/6309 + +* Enhancement - Rename permissions: [#3922](https://github.com/cs3org/reva/pull/3922) + + Rename permissions to be consistent and future proof + + https://github.com/cs3org/reva/pull/3922 + https://github.com/owncloud/ocis/pull/6418 + +* Enhancement - Added possibility to assign roles based on OIDC claims: [#6048](https://github.com/owncloud/ocis/pull/6048) + + OCIS can now be configured to update a user's role assignment from the values of a claim provided + via the IDPs userinfo endpoint. The claim name and the mapping between claim values and ocis + role name can be configured via the configuration of the proxy service. Example: + + ```yaml role_assignment: driver: oidc oidc_role_mapper: role_claim: ocisRoles + role_mapping: - role_name: admin claim_value: myAdminRole - role_name: spaceadmin + claim_value: mySpaceAdminRole - role_name: user claim_value: myUserRole - role_name: + guest: claim_value: myGuestRole ``` + + https://github.com/owncloud/ocis/pull/6048 + +* Enhancement - Added option to configure default quota per role: [#5616](https://github.com/owncloud/ocis/pull/5616) + + Admins can assign default quotas to users with certain roles by adding the following config to + the `proxy.yaml`. E.g.: ``` role_quotas: d7beeea8-8ff4-406b-8fb6-ab2dd81e6b11: 2300000 + ``` + + It maps a role ID to the quota in bytes. + + https://github.com/owncloud/ocis/pull/5616 + +* Enhancement - Add optional services to the runtime: [#6071](https://github.com/owncloud/ocis/pull/6071) + + Make it possible to start optional services in the ocis runtime. Instead of using + `OCIS_RUN_SERVICES` to define all services we can now use `OCIS_ADD_RUN_SERVICES` to add a + comma separated list of additional services which are not started in the single process by + default. + + https://github.com/owncloud/ocis/pull/6071 + +* Enhancement - Add new SetProjectSpaceQuota permission: [#5660](https://github.com/owncloud/ocis/pull/5660) + + Additionally to `set-space-quota` for setting quota on personal spaces we now have + `Drive.ReadWriteQuota.Project` for setting project spaces quota + + https://github.com/owncloud/ocis/pull/5660 + +* Enhancement - Add expiration to user and group shares: [#5389](https://github.com/owncloud/ocis/pull/5389) + + Added expiration to user and group shares. + + https://github.com/owncloud/ocis/pull/5389 + +* Enhancement - Space Management permissions: [#5441](https://github.com/owncloud/ocis/pull/5441) + + We added new space management permissions. `space-properties` will allow changing space + properties (name, description, ...). `space-ability` will allow enabling and disabling + spaces + + https://github.com/owncloud/ocis/pull/5441 + +* Enhancement - Cli to purge expired trash-bin items: [#5500](https://github.com/owncloud/ocis/pull/5500) + + Introduction of a new cli command to purge old trash-bin items. The command is part of the + `storage-users` service and can be used as follows: + + `ocis storage-users trash-bin purge-expired`. + + The `purge-expired` command configuration is done in the `ocis`configuration or as usual by + using environment variables. + + ENV `STORAGE_USERS_PURGE_TRASH_BIN_USER_ID` is used to obtain space trash-bin + information and takes the system admin user as the default `OCIS_ADMIN_USER_ID`. It should be + noted, that this is only set by default in the single binary. The command only considers spaces + to which the user has access and delete permission. + + ENV `STORAGE_USERS_PURGE_TRASH_BIN_PERSONAL_DELETE_BEFORE` has a default value of `30 + days`, which means the command will delete all files older than `30 days`. The value is + human-readable, valid values are `24h`, `60m`, `60s` etc. `0` is equivalent to disable and + prevents the deletion of `personal space` trash-bin files. + + ENV `STORAGE_USERS_PURGE_TRASH_BIN_PROJECT_DELETE_BEFORE` has a default value of `30 + days`, which means the command will delete all files older than `30 days`. The value is + human-readable, valid values are `24h`, `60m`, `60s` etc. `0` is equivalent to disable and + prevents the deletion of `project space` trash-bin files. + + Likewise, only spaces of the type `project` and `personal` are taken into account. Spaces of + type `virtual`, for example, are ignored. + + https://github.com/owncloud/ocis/issues/5499 + https://github.com/owncloud/ocis/pull/5500 + * Enhancement - Unify CA Cert envvars: [#6392](https://github.com/owncloud/ocis/pull/6392) Introduce a global `OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE` to avoid needing to configure @@ -87,6 +1357,161 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/6392 +* Enhancement - Update web to v7.0.0-rc.37: [#6294](https://github.com/owncloud/ocis/pull/6294) + + Tags: web + + We updated ownCloud Web to v7.0.0-rc.37. Please refer to the changelog (linked) for details on + the web release. + + * Bugfix [owncloud/web#6423](https://github.com/owncloud/web/issues/6423): Archiver in protected public links + * Bugfix [owncloud/web#6434](https://github.com/owncloud/web/issues/6434): Endless lazy loading indicator after sorting file table + * Bugfix [owncloud/web#6731](https://github.com/owncloud/web/issues/6731): Layout with long breadcrumb + * Bugfix [owncloud/web#6768](https://github.com/owncloud/web/issues/6768): Pagination after increasing items per page + * Bugfix [owncloud/web#7513](https://github.com/owncloud/web/issues/7513): Calendar popup position in right sidebar + * Bugfix [owncloud/web#7655](https://github.com/owncloud/web/issues/7655): Loading shares in deep nested folders + * Bugfix [owncloud/web#7925](https://github.com/owncloud/web/pull/7925): "Paste"-action without write permissions + * Bugfix [owncloud/web#7926](https://github.com/owncloud/web/pull/7926): Include spaces in the list info + * Bugfix [owncloud/web#7958](https://github.com/owncloud/web/pull/7958): Prevent deletion of own account + * Bugfix [owncloud/web#7966](https://github.com/owncloud/web/pull/7966): UI fixes for sorting and quickactions + * Bugfix [owncloud/web#7969](https://github.com/owncloud/web/pull/7969): Space quota not displayed after creation + * Bugfix [owncloud/web#8026](https://github.com/owncloud/web/pull/8026): Text editor appearance + * Bugfix [owncloud/web#8040](https://github.com/owncloud/web/pull/8040): Reverting versions for read-only shares + * Bugfix [owncloud/web#8045](https://github.com/owncloud/web/pull/8045): Resolving drives in search + * Bugfix [owncloud/web#8054](https://github.com/owncloud/web/issues/8054): Search repeating no results message + * Bugfix [owncloud/web#8058](https://github.com/owncloud/web/pull/8058): Current year selection in the date picker + * Bugfix [owncloud/web#8061](https://github.com/owncloud/web/pull/8061): Omit "page"-query in breadcrumb navigation + * Bugfix [owncloud/web#8080](https://github.com/owncloud/web/pull/8080): Left sidebar navigation item text flickers on transition + * Bugfix [owncloud/web#8081](https://github.com/owncloud/web/issues/8081): Space member disappearing + * Bugfix [owncloud/web#8083](https://github.com/owncloud/web/issues/8083): Re-using space images + * Bugfix [owncloud/web#8148](https://github.com/owncloud/web/issues/8148): Show space members despite deleted entries + * Bugfix [owncloud/web#8158](https://github.com/owncloud/web/issues/8158): Search bar input appearance + * Bugfix [owncloud/web#8265](https://github.com/owncloud/web/pull/8265): Application menu active display on hover + * Bugfix [owncloud/web#8276](https://github.com/owncloud/web/pull/8276): Loading additional user data + * Bugfix [owncloud/web#8300](https://github.com/owncloud/web/pull/8300): Re-loading space members panel + * Bugfix [owncloud/web#8326](https://github.com/owncloud/web/pull/8326): Editing users who never logged in + * Bugfix [owncloud/web#8340](https://github.com/owncloud/web/pull/8340): Cancel custom permissions + * Bugfix [owncloud/web#8411](https://github.com/owncloud/web/issues/8411): Drop menus with limited vertical screen space + * Bugfix [owncloud/web#8420](https://github.com/owncloud/web/issues/8420): Token renewal in vue router hash mode + * Bugfix [owncloud/web#8434](https://github.com/owncloud/web/issues/8434): Accessing route in admin-settings with insufficient permissions + * Bugfix [owncloud/web#8479](https://github.com/owncloud/web/issues/8479): "Show more"-action in shares panel + * Bugfix [owncloud/web#8480](https://github.com/owncloud/web/pull/8480): Paste action conflict dialog broken + * Bugfix [owncloud/web#8498](https://github.com/owncloud/web/pull/8498): PDF display issue - Update CSP object-src policy + * Bugfix [owncloud/web#8508](https://github.com/owncloud/web/pull/8508): Remove fuzzy search results + * Bugfix [owncloud/web#8523](https://github.com/owncloud/web/issues/8523): Space image upload + * Bugfix [owncloud/web#8549](https://github.com/owncloud/web/issues/8549): Batch context actions in admin settings + * Bugfix [owncloud/web#8554](https://github.com/owncloud/web/pull/8554): Height of dropdown no-option + * Bugfix [owncloud/web#8576](https://github.com/owncloud/web/pull/8576): De-duplicate event handling to prevent errors on Draw-io + * Bugfix [owncloud/web#8585](https://github.com/owncloud/web/issues/8585): Users without role assignment + * Bugfix [owncloud/web#8587](https://github.com/owncloud/web/issues/8587): Password enforced check for public links + * Bugfix [owncloud/web#8592](https://github.com/owncloud/web/issues/8592): Group members sorting + * Bugfix [owncloud/web#8694](https://github.com/owncloud/web/pull/8694): Broken re-login after logout + * Bugfix [owncloud/web#8695](https://github.com/owncloud/web/issues/8695): Open files in external app + * Bugfix [owncloud/web#8756](https://github.com/owncloud/web/pull/8756): Copy link to clipboard text + * Bugfix [owncloud/web#8758](https://github.com/owncloud/web/pull/8758): Preview controls colors + * Bugfix [owncloud/web#8776](https://github.com/owncloud/web/issues/8776): Selection reset on action click + * Bugfix [owncloud/web#8814](https://github.com/owncloud/web/pull/8814): Share recipient container exceed + * Bugfix [owncloud/web#8825](https://github.com/owncloud/web/pull/8825): Remove drop target in read-only folders + * Bugfix [owncloud/web#8827](https://github.com/owncloud/web/pull/8827): Opening context menu via keyboard + * Bugfix [owncloud/web#8834](https://github.com/owncloud/web/issues/8834): Hide upload hint in empty read-only folders + * Bugfix [owncloud/web#8864](https://github.com/owncloud/web/pull/8864): Public link empty password stays forever + * Bugfix [owncloud/web#8880](https://github.com/owncloud/web/issues/8880): Sidebar header after deleting resource + * Bugfix [owncloud/web#8928](https://github.com/owncloud/web/issues/8928): Infinite login redirect + * Bugfix [owncloud/web#8987](https://github.com/owncloud/web/pull/8987): Limit amount of concurrent tus requests + * Bugfix [owncloud/web#8992](https://github.com/owncloud/web/pull/8992): Personal space name after language change + * Bugfix [owncloud/web#9004](https://github.com/owncloud/web/issues/9004): Endless loading when encountering a public link error + * Bugfix [owncloud/web#9015](https://github.com/owncloud/web/pull/9015): Prevent "virtual" spaces from being displayed in the UI + * Change [owncloud/web#6661](https://github.com/owncloud/web/issues/6661): Streamline new tab handling in extensions + * Change [owncloud/web#7948](https://github.com/owncloud/web/issues/7948): Update Vue to v3.2 + * Change [owncloud/web#8431](https://github.com/owncloud/web/pull/8431): Remove permission manager + * Change [owncloud/web#8455](https://github.com/owncloud/web/pull/8455): Configurable extension autosave + * Change [owncloud/web#8563](https://github.com/owncloud/web/pull/8563): Theme colors + * Enhancement [owncloud/web#6183](https://github.com/owncloud/web/issues/6183): Global loading indicator + * Enhancement [owncloud/web#7388](https://github.com/owncloud/web/pull/7388): Add tag support + * Enhancement [owncloud/web#7721](https://github.com/owncloud/web/issues/7721): Improve performance when loading folders and share indicators + * Enhancement [owncloud/web#7942](https://github.com/owncloud/web/pull/7942): Warn users when using unsupported browsers + * Enhancement [owncloud/web#7965](https://github.com/owncloud/web/pull/7965): Optional Contributor role and configurable resharing permissions + * Enhancement [owncloud/web#7968](https://github.com/owncloud/web/pull/7968): Group and user creation forms submit on enter + * Enhancement [owncloud/web#7976](https://github.com/owncloud/web/pull/7976): Add switch to enable condensed resource table + * Enhancement [owncloud/web#7977](https://github.com/owncloud/web/pull/7977): Introduce zoom and rotate to the preview app + * Enhancement [owncloud/web#7983](https://github.com/owncloud/web/pull/7983): Conflict dialog UX + * Enhancement [owncloud/web#7991](https://github.com/owncloud/web/pull/7991): Add tiles view for resource display + * Enhancement [owncloud/web#7994](https://github.com/owncloud/web/pull/7994): Introduce full screen mode to the preview app + * Enhancement [owncloud/web#7995](https://github.com/owncloud/web/pull/7995): Enable autoplay in the preview app + * Enhancement [owncloud/web#8008](https://github.com/owncloud/web/issues/8008): Don't open sidebar when copying quicklink + * Enhancement [owncloud/web#8021](https://github.com/owncloud/web/pull/8021): Access right sidebar panels via URL + * Enhancement [owncloud/web#8051](https://github.com/owncloud/web/pull/8051): Introduce image preloading to the preview app + * Enhancement [owncloud/web#8055](https://github.com/owncloud/web/pull/8055): Retry failed uploads on re-upload + * Enhancement [owncloud/web#8056](https://github.com/owncloud/web/pull/8056): Increase Searchbar height + * Enhancement [owncloud/web#8057](https://github.com/owncloud/web/pull/8057): Show text file icon for empty text files + * Enhancement [owncloud/web#8132](https://github.com/owncloud/web/pull/8132): Update libre-graph-api to v1.0 + * Enhancement [owncloud/web#8136](https://github.com/owncloud/web/pull/8136): Make clipboard copy available to more browsers + * Enhancement [owncloud/web#8161](https://github.com/owncloud/web/pull/8161): Space group members + * Enhancement [owncloud/web#8161](https://github.com/owncloud/web/pull/8161): Space group shares + * Enhancement [owncloud/web#8166](https://github.com/owncloud/web/issues/8166): Show upload speed + * Enhancement [owncloud/web#8175](https://github.com/owncloud/web/pull/8175): Rename "user management" app + * Enhancement [owncloud/web#8178](https://github.com/owncloud/web/pull/8178): Spaces list in admin settings + * Enhancement [owncloud/web#8261](https://github.com/owncloud/web/pull/8261): Admin settings users section uses graph api for role assignments + * Enhancement [owncloud/web#8279](https://github.com/owncloud/web/pull/8279): Move user group select to edit panel + * Enhancement [owncloud/web#8280](https://github.com/owncloud/web/pull/8280): Add support for multiple clients in `theme.json` + * Enhancement [owncloud/web#8294](https://github.com/owncloud/web/pull/8294): Move language selection to user account page + * Enhancement [owncloud/web#8306](https://github.com/owncloud/web/pull/8306): Show selectable groups only + * Enhancement [owncloud/web#8317](https://github.com/owncloud/web/pull/8317): Add context menu to groups + * Enhancement [owncloud/web#8320](https://github.com/owncloud/web/pull/8320): Space member expiration + * Enhancement [owncloud/web#8320](https://github.com/owncloud/web/pull/8320): Update SDK to v3.1.0-alpha.3 + * Enhancement [owncloud/web#8324](https://github.com/owncloud/web/pull/8324): Add context menu to users + * Enhancement [owncloud/web#8331](https://github.com/owncloud/web/pull/8331): Admin settings users section details improvement + * Enhancement [owncloud/web#8354](https://github.com/owncloud/web/issues/8354): Add `ItemFilter` component + * Enhancement [owncloud/web#8356](https://github.com/owncloud/web/pull/8356): Slight improvement of key up/down performance + * Enhancement [owncloud/web#8363](https://github.com/owncloud/web/issues/8363): Admin settings general section + * Enhancement [owncloud/web#8375](https://github.com/owncloud/web/pull/8375): Add appearance section in general settings + * Enhancement [owncloud/web#8377](https://github.com/owncloud/web/issues/8377): User group filter + * Enhancement [owncloud/web#8387](https://github.com/owncloud/web/pull/8387): Batch edit quota in admin panel + * Enhancement [owncloud/web#8398](https://github.com/owncloud/web/pull/8398): Use standardized layout for file/space action list + * Enhancement [owncloud/web#8425](https://github.com/owncloud/web/issues/8425): Add dark ownCloud logo + * Enhancement [owncloud/web#8432](https://github.com/owncloud/web/pull/8432): Inject customizations + * Enhancement [owncloud/web#8433](https://github.com/owncloud/web/pull/8433): User settings login field + * Enhancement [owncloud/web#8441](https://github.com/owncloud/web/pull/8441): Skeleton App + * Enhancement [owncloud/web#8449](https://github.com/owncloud/web/pull/8449): Configurable top bar + * Enhancement [owncloud/web#8450](https://github.com/owncloud/web/pull/8450): Rework notification bell + * Enhancement [owncloud/web#8455](https://github.com/owncloud/web/pull/8455): Autosave content changes in text editor + * Enhancement [owncloud/web#8473](https://github.com/owncloud/web/pull/8473): Update CERN links + * Enhancement [owncloud/web#8489](https://github.com/owncloud/web/pull/8489): Respect max quota + * Enhancement [owncloud/web#8492](https://github.com/owncloud/web/pull/8492): User role filter + * Enhancement [owncloud/web#8503](https://github.com/owncloud/web/issues/8503): Beautify file version list + * Enhancement [owncloud/web#8515](https://github.com/owncloud/web/pull/8515): Introduce trashbin overview + * Enhancement [owncloud/web#8518](https://github.com/owncloud/web/pull/8518): Make notifications work with oCIS + * Enhancement [owncloud/web#8541](https://github.com/owncloud/web/pull/8541): Public link permission `PublicLink.Write.all` + * Enhancement [owncloud/web#8553](https://github.com/owncloud/web/pull/8553): Add and remove users from groups batch actions + * Enhancement [owncloud/web#8554](https://github.com/owncloud/web/pull/8554): Beautify form inputs + * Enhancement [owncloud/web#8557](https://github.com/owncloud/web/issues/8557): Rework mobile navigation + * Enhancement [owncloud/web#8566](https://github.com/owncloud/web/pull/8566): QuickActions role configurable + * Enhancement [owncloud/web#8612](https://github.com/owncloud/web/issues/8612): Add `Accept-Language` header to all outgoing requests + * Enhancement [owncloud/web#8630](https://github.com/owncloud/web/pull/8630): Add logout url + * Enhancement [owncloud/web#8652](https://github.com/owncloud/web/pull/8652): Enable guest users + * Enhancement [owncloud/web#8711](https://github.com/owncloud/web/pull/8711): Remove placeholder, add customizable label + * Enhancement [owncloud/web#8713](https://github.com/owncloud/web/pull/8713): Context helper read more link configurable + * Enhancement [owncloud/web#8715](https://github.com/owncloud/web/pull/8715): Enable rename groups + * Enhancement [owncloud/web#8730](https://github.com/owncloud/web/pull/8730): Create Space from selection + * Enhancement [owncloud/web#8738](https://github.com/owncloud/web/issues/8738): GDPR export + * Enhancement [owncloud/web#8762](https://github.com/owncloud/web/pull/8762): Stop bootstrapping application earlier in anonymous contexts + * Enhancement [owncloud/web#8766](https://github.com/owncloud/web/pull/8766): Add support for read-only groups + * Enhancement [owncloud/web#8790](https://github.com/owncloud/web/pull/8790): Custom translations + * Enhancement [owncloud/web#8797](https://github.com/owncloud/web/pull/8797): Font family in theming + * Enhancement [owncloud/web#8806](https://github.com/owncloud/web/pull/8806): Preview app sorting + * Enhancement [owncloud/web#8820](https://github.com/owncloud/web/pull/8820): Adjust missing reshare permissions message + * Enhancement [owncloud/web#8822](https://github.com/owncloud/web/pull/8822): Fix quicklink icon alignment + * Enhancement [owncloud/web#8826](https://github.com/owncloud/web/pull/8826): Admin settings groups members panel + * Enhancement [owncloud/web#8868](https://github.com/owncloud/web/pull/8868): Respect user read-only configuration by the server + * Enhancement [owncloud/web#8876](https://github.com/owncloud/web/pull/8876): Update roles and permissions names, labels, texts and icons + * Enhancement [owncloud/web#8882](https://github.com/owncloud/web/pull/8882): Layout of Share role and expiration date dropdown + * Enhancement [owncloud/web#8883](https://github.com/owncloud/web/issues/8883): Webfinger redirect app + * Enhancement [owncloud/web#8898](https://github.com/owncloud/web/pull/8898): Rename "Quicklink" to "link" + * Enhancement [owncloud/web#8911](https://github.com/owncloud/web/pull/8911): Add notification setting to account page + + https://github.com/owncloud/ocis/pull/6294 + https://github.com/owncloud/web/releases/tag/v7.0.0-rc.37 + * Enhancement - Update web to v7.0.0-rc.38: [#6375](https://github.com/owncloud/ocis/pull/6375) Tags: web @@ -246,1084 +1671,313 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/6375 https://github.com/owncloud/web/releases/tag/v7.0.0-rc.38 -# Changelog for [3.0.0] (2023-05-22) -The following sections list the changes for 3.0.0. - -[3.0.0]: https://github.com/owncloud/ocis/compare/v2.0.0...v3.0.0 - -## Summary - -* Bugfix - Return 425 on Thumbnails: [#5300](https://github.com/owncloud/ocis/pull/5300) -* Bugfix - Allow selected updates on graph users: [#6233](https://github.com/owncloud/ocis/pull/6233) -* Bugfix - Disassociate users from deleted school: [#5343](https://github.com/owncloud/ocis/pull/5343) -* Bugfix - Fix default role assignment for demo users: [#3432](https://github.com/owncloud/ocis/issues/3432) -* Bugfix - Reduced default TTL of user and group caches in graph API: [#6320](https://github.com/owncloud/ocis/issues/6320) -* Bugfix - Fix so that PATCH requests for groups actually updates the group name: [#5949](https://github.com/owncloud/ocis/pull/5949) -* Bugfix - Use UUID attribute for computing "sub" claim in lico idp: [#904](https://github.com/owncloud/ocis/issues/904) -* Bugfix - Hide the existence of space when deleting/updating: [#5031](https://github.com/owncloud/ocis/issues/5031) -* Bugfix - Fix OIDC auth cache: [#5997](https://github.com/owncloud/ocis/pull/5997) -* Bugfix - Fix the empty string givenName attribute when creating user: [#5431](https://github.com/owncloud/ocis/issues/5431) -* Bugfix - Fix Postprocessing events: [#5269](https://github.com/owncloud/ocis/pull/5269) -* Bugfix - Fix Search reindexing performance regression: [#6085](https://github.com/owncloud/ocis/pull/6085) -* Bugfix - Fix Search tag indexing: [#5405](https://github.com/owncloud/ocis/pull/5405) -* Bugfix - Fix the wrong status code when appRoleAssignments is forbidden: [#6037](https://github.com/owncloud/ocis/issues/6037) -* Bugfix - Fix user type config for user provider: [#6027](https://github.com/owncloud/ocis/pull/6027) -* Bugfix - Fix userlog panic: [#6114](https://github.com/owncloud/ocis/pull/6114) -* Bugfix - Fix Logout Url config name: [#6227](https://github.com/owncloud/ocis/pull/6227) -* Bugfix - Add missing CORS config: [#5987](https://github.com/owncloud/ocis/pull/5987) -* Bugfix - Add missing response to blocked requests: [#6277](https://github.com/owncloud/ocis/pull/6277) -* Bugfix - Populate expanded properties: [#5421](https://github.com/owncloud/ocis/pull/5421) -* Bugfix - Add portrait thumbnail resolutions: [#5656](https://github.com/owncloud/ocis/pull/5656) -* Bugfix - Update the default admin role: [#6310](https://github.com/owncloud/ocis/pull/6310) -* Bugfix - Fix authenticate headers for API requests: [#5992](https://github.com/owncloud/ocis/pull/5992) -* Change - Bump libregraph lico: [#5768](https://github.com/owncloud/ocis/pull/5768) -* Change - Updated Cache Configuration: [#5829](https://github.com/owncloud/ocis/pull/5829) -* Change - Remove the settings ui: [#5463](https://github.com/owncloud/ocis/pull/5463) -* Change - Do not share versions: [#5531](https://github.com/owncloud/ocis/pull/5531) -* Enhancement - Add specific result to antivirus for debugging: [#6265](https://github.com/owncloud/ocis/pull/6265) -* Enhancement - Add debug server to audit: [#6178](https://github.com/owncloud/ocis/pull/6178) -* Enhancement - Add debug server to idm: [#6153](https://github.com/owncloud/ocis/pull/6153) -* Enhancement - Add debug server to postprocessing: [#6203](https://github.com/owncloud/ocis/pull/6203) -* Enhancement - Add debug server to userlog: [#6202](https://github.com/owncloud/ocis/pull/6202) -* Enhancement - Add 'ocis decomposedfs metadata' command: [#5858](https://github.com/owncloud/ocis/pull/5858) -* Enhancement - Add debug server to eventhistory: [#6204](https://github.com/owncloud/ocis/pull/6204) -* Enhancement - Add global env variable extractor: [#5164](https://github.com/owncloud/ocis/pull/5164) -* Enhancement - Add the email HTML templates: [#6147](https://github.com/owncloud/ocis/pull/6147) -* Enhancement - Open Debug endpoint for Notifications: [#5002](https://github.com/owncloud/ocis/issues/5002) -* Enhancement - Add MessageRichParameters: [#5927](https://github.com/owncloud/ocis/pull/5927) -* Enhancement - Add webfinger service: [#5373](https://github.com/owncloud/ocis/pull/5373) -* Enhancement - Async Postprocessing: [#5207](https://github.com/owncloud/ocis/pull/5207) -* Enhancement - Automate md creation: [#5901](https://github.com/owncloud/ocis/pull/5901) -* Enhancement - Add more logging to av service: [#5973](https://github.com/owncloud/ocis/pull/5973) -* Enhancement - Return Bad Request when requesting GDPR export for another user: [#6123](https://github.com/owncloud/ocis/pull/6123) -* Enhancement - Add endpoints to upload a custom logo: [#5735](https://github.com/owncloud/ocis/pull/5735) -* Enhancement - Bump go-ldap version: [#6004](https://github.com/owncloud/ocis/pull/6004) -* Enhancement - Bump libre-graph-api-go: [#5309](https://github.com/owncloud/ocis/pull/5309) -* Enhancement - Update Reva to version 2.13.3 and beyond: [#6205](https://github.com/owncloud/ocis/pull/6205) -* Enhancement - Collect global envvars: [#5367](https://github.com/owncloud/ocis/pull/5367) -* Enhancement - Make the settings bundles part of the service config: [#5589](https://github.com/owncloud/ocis/pull/5589) -* Enhancement - Configure GRPC in ocs: [#6022](https://github.com/owncloud/ocis/pull/6022) -* Enhancement - Disable Notifications: [#6137](https://github.com/owncloud/ocis/pull/6137) -* Enhancement - Drive group permissions: [#5312](https://github.com/owncloud/ocis/pull/5312) -* Enhancement - Make the group members addition limit configurable: [#5357](https://github.com/owncloud/ocis/pull/5357) -* Enhancement - Allow username to be changed: [#5509](https://github.com/owncloud/ocis/pull/5509) -* Enhancement - Graph Drives IdentitySet displayName: [#5347](https://github.com/owncloud/ocis/pull/5347) -* Enhancement - Make the LDAP base DN for new groups configurable: [#5974](https://github.com/owncloud/ocis/pull/5974) -* Enhancement - Update to go 1.20 to use memlimit: [#5732](https://github.com/owncloud/ocis/pull/5732) -* Enhancement - Display surname and givenName attributes: [#5388](https://github.com/owncloud/ocis/pull/5388) -* Enhancement - Extended search: [#5221](https://github.com/owncloud/ocis/pull/5221) -* Enhancement - Resource tags: [#5227](https://github.com/owncloud/ocis/pull/5227) -* Enhancement - Allow users to be disabled: [#5588](https://github.com/owncloud/ocis/pull/5588) -* Enhancement - Web config additions: [#6032](https://github.com/owncloud/ocis/pull/6032) -* Enhancement - Eventhistory service: [#5600](https://github.com/owncloud/ocis/pull/5600) -* Enhancement - Expiration Notifications: [#5330](https://github.com/owncloud/ocis/pull/5330) -* Enhancement - GDPR Export: [#6064](https://github.com/owncloud/ocis/pull/6064) -* Enhancement - Make graph/education API errors more consistent: [#5682](https://github.com/owncloud/ocis/pull/5682) -* Enhancement - Graph user capabilities: [#6339](https://github.com/owncloud/ocis/pull/6339) -* Enhancement - Add endpoint to list permissions: [#5594](https://github.com/owncloud/ocis/pull/5594) -* Enhancement - Notifications: [#6038](https://github.com/owncloud/ocis/pull/6038) -* Enhancement - Open Debug endpoint for Nats: [#5002](https://github.com/owncloud/ocis/issues/5002) -* Enhancement - No Notifications for own actions: [#5871](https://github.com/owncloud/ocis/pull/5871) -* Enhancement - Notify about policies: [#5912](https://github.com/owncloud/ocis/pull/5912) -* Enhancement - Add a capability for the Personal Data export: [#5984](https://github.com/owncloud/ocis/pull/5984) -* Enhancement - Introduce policies-service: [#5714](https://github.com/owncloud/ocis/pull/5714) -* Enhancement - Better config for postprocessing service: [#5457](https://github.com/owncloud/ocis/pull/5457) -* Enhancement - Add Store to `postprocessing`: [#6281](https://github.com/owncloud/ocis/pull/6281) -* Enhancement - Add config option to enforce passwords on public links: [#5848](https://github.com/owncloud/ocis/pull/5848) -* Enhancement - Add new permission for public links: [#5690](https://github.com/owncloud/ocis/pull/5690) -* Enhancement - Remove quota from share jails api responses: [#6309](https://github.com/owncloud/ocis/pull/6309) -* Enhancement - Added possibility to assign roles based on OIDC claims: [#6048](https://github.com/owncloud/ocis/pull/6048) -* Enhancement - Added option to configure default quota per role: [#5616](https://github.com/owncloud/ocis/pull/5616) -* Enhancement - Add optional services to the runtime: [#6071](https://github.com/owncloud/ocis/pull/6071) -* Enhancement - Add new SetProjectSpaceQuota permission: [#5660](https://github.com/owncloud/ocis/pull/5660) -* Enhancement - Add expiration to user and group shares: [#5389](https://github.com/owncloud/ocis/pull/5389) -* Enhancement - Space Management permissions: [#5441](https://github.com/owncloud/ocis/pull/5441) -* Enhancement - Cli to purge expired trash-bin items: [#5500](https://github.com/owncloud/ocis/pull/5500) -* Enhancement - Update web to v7.0.0-rc.37: [#6294](https://github.com/owncloud/ocis/pull/6294) -* Enhancement - Use Accept-Language Header: [#5918](https://github.com/owncloud/ocis/pull/5918) -* Enhancement - Use gotext master: [#5867](https://github.com/owncloud/ocis/pull/5867) -* Enhancement - Userlog: [#5699](https://github.com/owncloud/ocis/pull/5699) -* Enhancement - Userlog Service: [#5610](https://github.com/owncloud/ocis/pull/5610) -* Enhancement - Determine the users language to translate via Transifex: [#6089](https://github.com/owncloud/ocis/pull/6089) -* Enhancement - Web options configuration: [#6188](https://github.com/owncloud/ocis/pull/6188) - -## Details - -* Bugfix - Return 425 on Thumbnails: [#5300](https://github.com/owncloud/ocis/pull/5300) - - Return `425` on thumbnails `GET` when file is processing. Pass `425` also through webdav - endpoint - - https://github.com/owncloud/ocis/pull/5300 - -* Bugfix - Allow selected updates on graph users: [#6233](https://github.com/owncloud/ocis/pull/6233) - - We are now allowing a couple of update request to complete even if - GRAPH_LDAP_SERVER_WRITE_ENABLED=false: - - * When using a group to disable users (OCIS_LDAP_DISABLE_USER_MECHANISM=group) updates to the accountEnabled property of a user will be allowed - * When a distinct base dn for new groups is configured ( GRAPH_LDAP_GROUP_CREATE_BASE_DN is set to a different value than GRAPH_LDAP_GROUP_BASE_DN), allow the creation/update of local groups. - - https://github.com/owncloud/ocis/pull/6233 - -* Bugfix - Disassociate users from deleted school: [#5343](https://github.com/owncloud/ocis/pull/5343) - - When a school is deleted, users should be disassociated from it. - - https://github.com/owncloud/ocis/issues/5246 - https://github.com/owncloud/ocis/pull/5343 - -* Bugfix - Fix default role assignment for demo users: [#3432](https://github.com/owncloud/ocis/issues/3432) - - The roles-assignments for demo users where duplicated with every restart of the settings - service. - - https://github.com/owncloud/ocis/issues/3432 - -* Bugfix - Reduced default TTL of user and group caches in graph API: [#6320](https://github.com/owncloud/ocis/issues/6320) - - We reduced the default TTL of the cache for user and group information on the /drives endpoints - to 60 seconds. This fixes in issue where outdated information was show on the spaces list for a - very long time. - - https://github.com/owncloud/ocis/issues/6320 - -* Bugfix - Fix so that PATCH requests for groups actually updates the group name: [#5949](https://github.com/owncloud/ocis/pull/5949) - - https://github.com/owncloud/ocis/pull/5949 - -* Bugfix - Use UUID attribute for computing "sub" claim in lico idp: [#904](https://github.com/owncloud/ocis/issues/904) - - By default the LDAP backend for lico uses the User DN for computing the "sub" claim of a user. This - caused the "sub" claim to stay the same even if a user was deleted and recreated (and go a new UUID - assgined with that). We now use the user's unique id (`owncloudUUID` by default) for computing - the `sub` claim. So that user's recreated with the same name will be treated as different users - by the IDP. - - This changed also the default values of the 'PROXY_USER_OIDC_CLAIM' and - 'PROXY_USER_CS3_CLAIM' settings map the IDP users to CS3 user by the userid in the default - setup. The old behavior of matching by username can be achieved byt setting - 'PROXY_USER_OIDC_CLAIM=preferred_username' and 'PROXY_USER_CS3_CLAIM=username'. - - https://github.com/owncloud/ocis/issues/904 - https://github.com/owncloud/ocis/pull/6326 - https://github.com/owncloud/ocis/pull/6338 - -* Bugfix - Hide the existence of space when deleting/updating: [#5031](https://github.com/owncloud/ocis/issues/5031) - - The "code": "notAllowed" changed to "code": "itemNotFound" - - https://github.com/owncloud/ocis/issues/5031 - https://github.com/owncloud/ocis/pull/6220 - -* Bugfix - Fix OIDC auth cache: [#5997](https://github.com/owncloud/ocis/pull/5997) - - We've fixed an issue rendering the OIDC auth cache useless. - - https://github.com/owncloud/ocis/pull/5997 - -* Bugfix - Fix the empty string givenName attribute when creating user: [#5431](https://github.com/owncloud/ocis/issues/5431) - - Omitempty givenName attribute when creating user - - https://github.com/owncloud/ocis/issues/5431 - https://github.com/owncloud/ocis/pull/6259 - -* Bugfix - Fix Postprocessing events: [#5269](https://github.com/owncloud/ocis/pull/5269) - - Postprocessing service did not want to play with non-tls events. That is fixed now - - https://github.com/owncloud/ocis/pull/5269 - -* Bugfix - Fix Search reindexing performance regression: [#6085](https://github.com/owncloud/ocis/pull/6085) - - We've fixed a regression in the search service reindexing step, causing the whole space to be - reindexed instead of just the changed resources. - - https://github.com/owncloud/ocis/pull/6085 - -* Bugfix - Fix Search tag indexing: [#5405](https://github.com/owncloud/ocis/pull/5405) - - We've fixed an issue where search is not able to index tags for space resources. - - https://github.com/owncloud/ocis/pull/5405 - -* Bugfix - Fix the wrong status code when appRoleAssignments is forbidden: [#6037](https://github.com/owncloud/ocis/issues/6037) - - Fix the wrong status code when appRoleAssignments is forbidden in the - CreateAppRoleAssignment and DeleteAppRoleAssignment methods. - - https://github.com/owncloud/ocis/issues/6037 - https://github.com/owncloud/ocis/pull/6276 - -* Bugfix - Fix user type config for user provider: [#6027](https://github.com/owncloud/ocis/pull/6027) - - We needed to provide a default value for the user type property in the user provider. - - https://github.com/owncloud/ocis/pull/6027 - -* Bugfix - Fix userlog panic: [#6114](https://github.com/owncloud/ocis/pull/6114) - - Userlog services paniced because of `nil` ctx. That is fixed now - - https://github.com/owncloud/ocis/pull/6114 - -* Bugfix - Fix Logout Url config name: [#6227](https://github.com/owncloud/ocis/pull/6227) - - We fixed the yaml and json name of the logout url option. - - https://github.com/owncloud/ocis/pull/6227 - -* Bugfix - Add missing CORS config: [#5987](https://github.com/owncloud/ocis/pull/5987) - - The graph, userlog and ocdav services had no CORS config options. - - https://github.com/owncloud/ocis/pull/5987 - -* Bugfix - Add missing response to blocked requests: [#6277](https://github.com/owncloud/ocis/pull/6277) - - We added the missing response body to requests which were blocked by the policy engine. - - https://github.com/owncloud/ocis/pull/6277 - -* Bugfix - Populate expanded properties: [#5421](https://github.com/owncloud/ocis/pull/5421) - - We now return an empty array when an expanded relation has no entries. This makes consuming the - responses a little easier. - - https://github.com/owncloud/ocis/issues/5419 - https://github.com/owncloud/ocis/pull/5421 - https://github.com/owncloud/ocis/pull/5426 - -* Bugfix - Add portrait thumbnail resolutions: [#5656](https://github.com/owncloud/ocis/pull/5656) - - Add portrait-orientation resolutions to the thumbnail service's default configuration. - This prevents portrait photos from being heavily cropped into landscape resolutions in the - web viewer. - - https://github.com/owncloud/ocis/pull/5656 - -* Bugfix - Update the default admin role: [#6310](https://github.com/owncloud/ocis/pull/6310) - - The admin role was missing two permissions. We added them to make the space admin role a subset of - the admin role. This matches better with the default user expectations. - - https://github.com/owncloud/ocis/pull/6310 - -* Bugfix - Fix authenticate headers for API requests: [#5992](https://github.com/owncloud/ocis/pull/5992) - - We changed the www-authenticate header which should not be sent when the `XMLHttpRequest` - header is set. - - https://github.com/owncloud/ocis/issues/5986 - https://github.com/owncloud/ocis/pull/5992 - -* Change - Bump libregraph lico: [#5768](https://github.com/owncloud/ocis/pull/5768) - - We updated lico to the latest version * Update to 0.59.4 - upstream dropped the kc and cookie - backends - - https://github.com/owncloud/ocis/pull/5768 - -* Change - Updated Cache Configuration: [#5829](https://github.com/owncloud/ocis/pull/5829) - - We updated all cache related environment vars to more closely follow the go micro naming - pattern: - `{service}_CACHE_STORE_TYPE` becomes `{service}_CACHE_STORE` or - `{service}_PERSISTENT_STORE` - `{service}_CACHE_STORE_ADDRESS(ES)` becomes - `{service}_CACHE_STORE_NODES` - The `mem` store implementation name changes to `memory` - - In yaml files the cache `type` becomes `store` We introduced `redis-sentinel` as a store - implementation. - - https://github.com/owncloud/ocis/pull/5829 - -* Change - Remove the settings ui: [#5463](https://github.com/owncloud/ocis/pull/5463) - - With ownCloud Web having transitioned to Vue 3 recently, we would have had to port the settings - ui as well. The decision was made to discontinue the settings ui instead. As a result all traces - of the settings ui have been removed. - - The only user facing setting that ever existed in the settings service is now integrated into - the `account` page of ownCloud Web (click on top right user menu, then on your username to reach - the account page). - - https://github.com/owncloud/ocis/pull/5463 - -* Change - Do not share versions: [#5531](https://github.com/owncloud/ocis/pull/5531) - - We changed the default behavior of shares: Share receivers have no access to versions. People - in spaces with the "Editor" or "Manager" role can still see versions and work with them. - - https://github.com/owncloud/ocis/pull/5531 - -* Enhancement - Add specific result to antivirus for debugging: [#6265](https://github.com/owncloud/ocis/pull/6265) - - We added the ability to define a specific result for the virus scanner via env-var - (ANTIVIRUS_DEBUG_SCAN_OUTCOME) - - https://github.com/owncloud/ocis/pull/6265 - -* Enhancement - Add debug server to audit: [#6178](https://github.com/owncloud/ocis/pull/6178) - - We added a debug server to audit. - - https://github.com/owncloud/ocis/issues/5002 - https://github.com/owncloud/ocis/pull/6178 - -* Enhancement - Add debug server to idm: [#6153](https://github.com/owncloud/ocis/pull/6153) - - We added a debug server to idm. - - https://github.com/owncloud/ocis/issues/5003 - https://github.com/owncloud/ocis/pull/6153 - -* Enhancement - Add debug server to postprocessing: [#6203](https://github.com/owncloud/ocis/pull/6203) - - We added a debug server to postprocessing. - - https://github.com/owncloud/ocis/issues/5002 - https://github.com/owncloud/ocis/pull/6203 - -* Enhancement - Add debug server to userlog: [#6202](https://github.com/owncloud/ocis/pull/6202) - - We added a debug server to userlog. - - https://github.com/owncloud/ocis/issues/5002 - https://github.com/owncloud/ocis/pull/6202 - -* Enhancement - Add 'ocis decomposedfs metadata' command: [#5858](https://github.com/owncloud/ocis/pull/5858) - - We added a 'ocis decomposedfs metadata' command for inspecting and manipulating node - metadata. - - https://github.com/owncloud/ocis/pull/5858 - -* Enhancement - Add debug server to eventhistory: [#6204](https://github.com/owncloud/ocis/pull/6204) - - We added a debug server to eventhistory. - - https://github.com/owncloud/ocis/issues/5002 - https://github.com/owncloud/ocis/pull/6204 - -* Enhancement - Add global env variable extractor: [#5164](https://github.com/owncloud/ocis/pull/5164) - - We have added a little tool that will extract global env vars, that are loaded only through - os.Getenv for documentation purposes - - https://github.com/owncloud/ocis/issues/4916 - https://github.com/owncloud/ocis/pull/5164 - -* Enhancement - Add the email HTML templates: [#6147](https://github.com/owncloud/ocis/pull/6147) - - Add the email HTML templates - - https://github.com/owncloud/ocis/issues/6146 - https://github.com/owncloud/ocis/pull/6147 - -* Enhancement - Open Debug endpoint for Notifications: [#5002](https://github.com/owncloud/ocis/issues/5002) - - We added a debug server to the notifications service - - https://github.com/owncloud/ocis/issues/5002 - https://github.com/owncloud/ocis/pull/6155 - -* Enhancement - Add MessageRichParameters: [#5927](https://github.com/owncloud/ocis/pull/5927) - - Adds the messageRichParameters to virus and policies notifications - - https://github.com/owncloud/ocis/pull/5927 - -* Enhancement - Add webfinger service: [#5373](https://github.com/owncloud/ocis/pull/5373) - - Adds a webfinger service to redirect ocis clients - - https://github.com/owncloud/ocis/issues/6102 - https://github.com/owncloud/ocis/pull/5373 - https://github.com/owncloud/ocis/pull/6110 - -* Enhancement - Async Postprocessing: [#5207](https://github.com/owncloud/ocis/pull/5207) - - Provides functionality for async postprocessing. This will allow the system to do the - postprocessing (virusscan, copying of bytes to their final destination, ...) asynchronous - to the users request. Major change when active. - - https://github.com/owncloud/ocis/pull/5207 - -* Enhancement - Automate md creation: [#5901](https://github.com/owncloud/ocis/pull/5901) - - Automatically create `_index.md` files from the services `README.md` - - https://github.com/owncloud/ocis/pull/5901 - -* Enhancement - Add more logging to av service: [#5973](https://github.com/owncloud/ocis/pull/5973) - - We need more debug logging in some situations to understand the state of a virus scan. - - https://github.com/owncloud/ocis/pull/5973 - -* Enhancement - Return Bad Request when requesting GDPR export for another user: [#6123](https://github.com/owncloud/ocis/pull/6123) - - This is an enhancement, not security related as the requested uid is never used - - https://github.com/owncloud/ocis/pull/6123 - -* Enhancement - Add endpoints to upload a custom logo: [#5735](https://github.com/owncloud/ocis/pull/5735) - - Added endpoints to upload and reset custom logos. The files are stored under the - `WEB_ASSET_PATH` which defaults to `$OCIS_BASE_DATA_PATH/web/assets`. - - https://github.com/owncloud/ocis/pull/5735 - https://github.com/owncloud/ocis/pull/5559 - -* Enhancement - Bump go-ldap version: [#6004](https://github.com/owncloud/ocis/pull/6004) - - Use master version of go-ldap to get rid of nasty `=` bug. See - https://github.com/go-ldap/ldap/issues/416 - - https://github.com/owncloud/ocis/pull/6004 - -* Enhancement - Bump libre-graph-api-go: [#5309](https://github.com/owncloud/ocis/pull/5309) - - We fixed a couple of issues in libre-graph-api-go package. - - * rename drive permission grantedTo to grantedToIdentities to be ms graph spec compatible. - * drive.name is a required property now. - * add group property to the identitySet. - - https://github.com/owncloud/ocis/pull/5309 - https://github.com/owncloud/ocis/pull/5312 - -* Enhancement - Update Reva to version 2.13.3 and beyond: [#6205](https://github.com/owncloud/ocis/pull/6205) - - Changelog for reva unreleased ======================================= - - * Bugfix [cs3org/reva#3899](https://github.com/cs3org/reva/pull/3899): Harden uploads - * Enhancement [cs3org/reva#3893](https://github.com/cs3org/reva/pull/3893): Cleanup Space Delete permissions - * Enhancement [cs3org/reva#3898](https://github.com/cs3org/reva/pull/3898): Add Graph User capabilities - - Changelog for reva 2.13.3 (2023-05-17) ======================================= - - * Bugfix [cs3org/reva#3890](https://github.com/cs3org/reva/pull/3890): Bring back public link sharing of project space roots - * Bugfix [cs3org/reva#3888](https://github.com/cs3org/reva/pull/3888): We fixed a bug that unnecessarily fetched all members of a group - * Bugfix [cs3org/reva#3886](https://github.com/cs3org/reva/pull/3886): Decomposedfs no longer deadlocks when cache is disabled - * Bugfix [cs3org/reva#3892](https://github.com/cs3org/reva/pull/3892): Fix public links - * Bugfix [cs3org/reva#3876](https://github.com/cs3org/reva/pull/3876): Remove go-micro/store/redis specific workaround - * Bugfix [cs3org/reva#3889](https://github.com/cs3org/reva/pull/3889): Update space root mtime when changing space metadata - * Bugfix [cs3org/reva#3836](https://github.com/cs3org/reva/pull/3836): Fix spaceID in the decomposedFS - * Bugfix [cs3org/reva#3867](https://github.com/cs3org/reva/pull/3867): Restore last version after positive result - * Bugfix [cs3org/reva#3849](https://github.com/cs3org/reva/pull/3849): Prevent sharing space roots and personal spaces - * Enhancement [cs3org/reva#3865](https://github.com/cs3org/reva/pull/3865): Remove unneccessary code from gateway - * Enhancement [cs3org/reva#3895](https://github.com/cs3org/reva/pull/3895): Add missing expiry date to shares - - Https://github.com/owncloud/ocis/pull/6305 - https://github.com/owncloud/ocis/pull/6339 Changelog for reva 2.13.2 (2023-05-08) - ======================================= - - * Bugfix [cs3org/reva#3845](https://github.com/cs3org/reva/pull/3845): Fix propagation - * Bugfix [cs3org/reva#3856](https://github.com/cs3org/reva/pull/3856): Fix response code - * Bugfix [cs3org/reva#3857](https://github.com/cs3org/reva/pull/3857): Fix trashbin purge - - Changelog for reva 2.13.1 (2023-05-03) ======================================= - - * Bugfix [cs3org/reva#3843](https://github.com/cs3org/reva/pull/3843): Allow scope check to impersonate space owners - - Changelog for reva 2.13.0 (2023-05-02) ======================================= - - * Bugfix [cs3org/reva#3570](https://github.com/cs3org/reva/pull/3570): Return 425 on HEAD - * Bugfix [cs3org/reva#3830](https://github.com/cs3org/reva/pull/3830): Be more robust when logging errors - * Bugfix [cs3org/reva#3815](https://github.com/cs3org/reva/pull/3815): Bump micro redis store - * Bugfix [cs3org/reva#3596](https://github.com/cs3org/reva/pull/3596): Cache CreateHome calls - * Bugfix [cs3org/reva#3823](https://github.com/cs3org/reva/pull/3823): Deny correctly in decomposedfs - * Bugfix [cs3org/reva#3826](https://github.com/cs3org/reva/pull/3826): Add by group index to decomposedfs - * Bugfix [cs3org/reva#3618](https://github.com/cs3org/reva/pull/3618): Drain body on failed put - * Bugfix [cs3org/reva#3685](https://github.com/cs3org/reva/pull/3685): Send fileid on copy - * Bugfix [cs3org/reva#3688](https://github.com/cs3org/reva/pull/3688): Return 425 on GET - * Bugfix [cs3org/reva#3755](https://github.com/cs3org/reva/pull/3755): Fix app provider language validation - * Bugfix [cs3org/reva#3800](https://github.com/cs3org/reva/pull/3800): Fix building for freebsd - * Bugfix [cs3org/reva#3700](https://github.com/cs3org/reva/pull/3700): Fix caching - * Bugfix [cs3org/reva#3535](https://github.com/cs3org/reva/pull/3535): Fix ceph driver storage fs implementation - * Bugfix [cs3org/reva#3764](https://github.com/cs3org/reva/pull/3764): Fix missing CORS config in ocdav service - * Bugfix [cs3org/reva#3710](https://github.com/cs3org/reva/pull/3710): Fix error when try to delete space without permission - * Bugfix [cs3org/reva#3822](https://github.com/cs3org/reva/pull/3822): Fix deleting spaces - * Bugfix [cs3org/reva#3718](https://github.com/cs3org/reva/pull/3718): Fix revad-eos docker image which was failing to build - * Bugfix [cs3org/reva#3559](https://github.com/cs3org/reva/pull/3559): Fix build on freebsd - * Bugfix [cs3org/reva#3696](https://github.com/cs3org/reva/pull/3696): Fix ldap filters when checking for enabled users - * Bugfix [cs3org/reva#3767](https://github.com/cs3org/reva/pull/3767): Decode binary UUID when looking up a users group memberships - * Bugfix [cs3org/reva#3741](https://github.com/cs3org/reva/pull/3741): Fix listing shares to multiple groups - * Bugfix [cs3org/reva#3834](https://github.com/cs3org/reva/pull/3834): Return correct error during MKCOL - * Bugfix [cs3org/reva#3841](https://github.com/cs3org/reva/pull/3841): Fix nil pointer and improve logging - * Bugfix [cs3org/reva#3831](https://github.com/cs3org/reva/pull/3831): Ignore 'null' mtime on tus upload - * Bugfix [cs3org/reva#3758](https://github.com/cs3org/reva/pull/3758): Fix public links with enforced password - * Bugfix [cs3org/reva#3814](https://github.com/cs3org/reva/pull/3814): Fix stat cache access - * Bugfix [cs3org/reva#3650](https://github.com/cs3org/reva/pull/3650): FreeBSD xattr support - * Bugfix [cs3org/reva#3827](https://github.com/cs3org/reva/pull/3827): Initialize user cache for decomposedfs - * Bugfix [cs3org/reva#3818](https://github.com/cs3org/reva/pull/3818): Invalidate cache when deleting space - * Bugfix [cs3org/reva#3812](https://github.com/cs3org/reva/pull/3812): Filemetadata Cache now deletes keys without listing them first - * Bugfix [cs3org/reva#3817](https://github.com/cs3org/reva/pull/3817): Pipeline cache deletes - * Bugfix [cs3org/reva#3711](https://github.com/cs3org/reva/pull/3711): Replace ini metadata backend by messagepack backend - * Bugfix [cs3org/reva#3828](https://github.com/cs3org/reva/pull/3828): Send quota when listing spaces in decomposedfs - * Bugfix [cs3org/reva#3681](https://github.com/cs3org/reva/pull/3681): Fix etag of "empty" shares jail - * Bugfix [cs3org/reva#3748](https://github.com/cs3org/reva/pull/3748): Prevent service from panicking - * Bugfix [cs3org/reva#3816](https://github.com/cs3org/reva/pull/3816): Write Metadata once - * Change [cs3org/reva#3641](https://github.com/cs3org/reva/pull/3641): Hide file versions for share receivers - * Change [cs3org/reva#3820](https://github.com/cs3org/reva/pull/3820): Streamline stores - * Enhancement [cs3org/reva#3732](https://github.com/cs3org/reva/pull/3732): Make method for detecting the metadata backend public - * Enhancement [cs3org/reva#3789](https://github.com/cs3org/reva/pull/3789): Add capabilities indicating if user attributes are read-only - * Enhancement [cs3org/reva#3792](https://github.com/cs3org/reva/pull/3792): Add a prometheus gauge to keep track of active uploads and downloads - * Enhancement [cs3org/reva#3637](https://github.com/cs3org/reva/pull/3637): Add an ID to each events - * Enhancement [cs3org/reva#3704](https://github.com/cs3org/reva/pull/3704): Add more information to events - * Enhancement [cs3org/reva#3744](https://github.com/cs3org/reva/pull/3744): Add LDAP user type attribute - * Enhancement [cs3org/reva#3806](https://github.com/cs3org/reva/pull/3806): Decomposedfs now supports filtering spaces by owner - * Enhancement [cs3org/reva#3730](https://github.com/cs3org/reva/pull/3730): Antivirus - * Enhancement [cs3org/reva#3531](https://github.com/cs3org/reva/pull/3531): Async Postprocessing - * Enhancement [cs3org/reva#3571](https://github.com/cs3org/reva/pull/3571): Async Upload Improvements - * Enhancement [cs3org/reva#3801](https://github.com/cs3org/reva/pull/3801): Cache node ids - * Enhancement [cs3org/reva#3690](https://github.com/cs3org/reva/pull/3690): Check set project space quota permission - * Enhancement [cs3org/reva#3686](https://github.com/cs3org/reva/pull/3686): User disabling functionality - * Enhancement [cs3org/reva#3505](https://github.com/cs3org/reva/pull/3505): Fix eosgrpc package - * Enhancement [cs3org/reva#3575](https://github.com/cs3org/reva/pull/3575): Fix skip group grant index cleanup - * Enhancement [cs3org/reva#3564](https://github.com/cs3org/reva/pull/3564): Fix tag pkg - * Enhancement [cs3org/reva#3756](https://github.com/cs3org/reva/pull/3756): Prepare for GDPR export - * Enhancement [cs3org/reva#3612](https://github.com/cs3org/reva/pull/3612): Group feature changed event added - * Enhancement [cs3org/reva#3729](https://github.com/cs3org/reva/pull/3729): Improve decomposedfs performance, esp. with network fs/cache - * Enhancement [cs3org/reva#3697](https://github.com/cs3org/reva/pull/3697): Improve the ini file metadata backend - * Enhancement [cs3org/reva#3819](https://github.com/cs3org/reva/pull/3819): Allow creating internal links without permission - * Enhancement [cs3org/reva#3740](https://github.com/cs3org/reva/pull/3740): Limit concurrency in decomposedfs - * Enhancement [cs3org/reva#3569](https://github.com/cs3org/reva/pull/3569): Always list shares jail when listing spaces - * Enhancement [cs3org/reva#3788](https://github.com/cs3org/reva/pull/3788): Make resharing configurable - * Enhancement [cs3org/reva#3674](https://github.com/cs3org/reva/pull/3674): Introduce ini file based metadata backend - * Enhancement [cs3org/reva#3728](https://github.com/cs3org/reva/pull/3728): Automatically migrate file metadata from xattrs to messagepack - * Enhancement [cs3org/reva#3807](https://github.com/cs3org/reva/pull/3807): Name Validation - * Enhancement [cs3org/reva#3574](https://github.com/cs3org/reva/pull/3574): Opaque space group - * Enhancement [cs3org/reva#3598](https://github.com/cs3org/reva/pull/3598): Pass estream to Storage Providers - * Enhancement [cs3org/reva#3763](https://github.com/cs3org/reva/pull/3763): Add a capability for personal data export - * Enhancement [cs3org/reva#3577](https://github.com/cs3org/reva/pull/3577): Prepare for SSE - * Enhancement [cs3org/reva#3731](https://github.com/cs3org/reva/pull/3731): Add config option to enforce passwords on public links - * Enhancement [cs3org/reva#3693](https://github.com/cs3org/reva/pull/3693): Enforce the PublicLink.Write permission - * Enhancement [cs3org/reva#3497](https://github.com/cs3org/reva/pull/3497): Introduce owncloud 10 publiclink manager - * Enhancement [cs3org/reva#3714](https://github.com/cs3org/reva/pull/3714): Add global max quota option and quota for CreateHome - * Enhancement [cs3org/reva#3759](https://github.com/cs3org/reva/pull/3759): Set correct share type when listing shares - * Enhancement [cs3org/reva#3594](https://github.com/cs3org/reva/pull/3594): Add expiration to user and group shares - * Enhancement [cs3org/reva#3580](https://github.com/cs3org/reva/pull/3580): Share expired event - * Enhancement [cs3org/reva#3620](https://github.com/cs3org/reva/pull/3620): Allow a new ShareType `SpaceMembershipGroup` - * Enhancement [cs3org/reva#3609](https://github.com/cs3org/reva/pull/3609): Space Management Permissions - * Enhancement [cs3org/reva#3655](https://github.com/cs3org/reva/pull/3655): Add expiration date to space memberships - * Enhancement [cs3org/reva#3697](https://github.com/cs3org/reva/pull/3697): Add support for redis sentinel caches - * Enhancement [cs3org/reva#3552](https://github.com/cs3org/reva/pull/3552): Suppress tusd logs - * Enhancement [cs3org/reva#3555](https://github.com/cs3org/reva/pull/3555): Tags - * Enhancement [cs3org/reva#3785](https://github.com/cs3org/reva/pull/3785): Increase unit test coverage in the ocdav service - * Enhancement [cs3org/reva#3739](https://github.com/cs3org/reva/pull/3739): Try to rename uploaded files to their final position - * Enhancement [cs3org/reva#3610](https://github.com/cs3org/reva/pull/3610): Walk and log chi routes - - https://github.com/owncloud/ocis/pull/6205 - https://github.com/owncloud/ocis/pull/6186 - -* Enhancement - Collect global envvars: [#5367](https://github.com/owncloud/ocis/pull/5367) - - Compose a list of all envvars living in more than 1 service - - https://github.com/owncloud/ocis/pull/5367 - -* Enhancement - Make the settings bundles part of the service config: [#5589](https://github.com/owncloud/ocis/pull/5589) - - We added the settings bundles to the config. The default roles are still unchanged. You can now - override the defaults by replacing the whole bundles list via json config files. The config - file is loaded from a specified path which can be configured with `SETTINGS_BUNDLES_PATH`. - - https://github.com/owncloud/ocis/pull/5589 - https://github.com/owncloud/ocis/pull/5607 - -* Enhancement - Configure GRPC in ocs: [#6022](https://github.com/owncloud/ocis/pull/6022) - - Fixes a panic in ocs when running not in single binary - - https://github.com/owncloud/ocis/pull/6022 - -* Enhancement - Disable Notifications: [#6137](https://github.com/owncloud/ocis/pull/6137) - - Introduce new setting to disable notifications - - https://github.com/owncloud/ocis/pull/6137 - -* Enhancement - Drive group permissions: [#5312](https://github.com/owncloud/ocis/pull/5312) - - We've updated the libregraph.Drive response to contain group permissions. - - https://github.com/owncloud/ocis/pull/5312 - -* Enhancement - Make the group members addition limit configurable: [#5357](https://github.com/owncloud/ocis/pull/5357) - - It's now possible to configure the limit of group members addition by PATCHing - `/graph/v1.0/groups/{groupID}`. It still defaults to 20 as defined in the spec but it can be - configured via `.graph.api.group_members_patch_limit` in `ocis.yaml` or via the - `GRAPH_GROUP_MEMBERS_PATCH_LIMIT` environment variable. - - https://github.com/owncloud/ocis/issues/5262 - https://github.com/owncloud/ocis/pull/5357 - -* Enhancement - Allow username to be changed: [#5509](https://github.com/owncloud/ocis/pull/5509) - - When OnPremisesSamAccountName is present in a PATCH on `{apiRoot}/users/{userID}` it will - change the username of the user. This also changes the references to this user in the groups. - - https://github.com/owncloud/ocis/issues/4988 - https://github.com/owncloud/ocis/pull/5509 - -* Enhancement - Graph Drives IdentitySet displayName: [#5347](https://github.com/owncloud/ocis/pull/5347) - - We've added the IdentitySet displayName property to the group and user sets for the graph - drives endpoint. The values for groups and users get cached. - - https://github.com/owncloud/ocis/pull/5347 - https://github.com/owncloud/web/pull/8178 - -* Enhancement - Make the LDAP base DN for new groups configurable: [#5974](https://github.com/owncloud/ocis/pull/5974) - - The LDAP backend for the Graph service introduced a new config option for setting the Parent DN - for new groups created via the `/groups/` endpoint. (`GRAPH_LDAP_GROUP_CREATE_BASE_DN`) - - It defaults to the value of `GRAPH_LDAP_GROUP_BASE_DN`. If set to a different value the - `GRAPH_LDAP_GROUP_CREATE_BASE_DN` needs to be a subordinate DN of - `GRAPH_LDAP_GROUP_BASE_DN`. - - All existing groups with a DN outside the `GRAPH_LDAP_GROUP_CREATE_BASE_DN` tree will be - treated as read-only groups. So it is not possible to edit these groups. - - https://github.com/owncloud/ocis/pull/5974 - -* Enhancement - Update to go 1.20 to use memlimit: [#5732](https://github.com/owncloud/ocis/pull/5732) - - We updated to go 1.20 which allows setting GOMEMLIMIT, which we by default set to 0.9. - - https://github.com/owncloud/ocis/pull/5732 - -* Enhancement - Display surname and givenName attributes: [#5388](https://github.com/owncloud/ocis/pull/5388) - - When querying the graph API, the surname and givenName attributes are now displayed for users. - - https://github.com/owncloud/ocis/issues/5386 - https://github.com/owncloud/ocis/pull/5388 - -* Enhancement - Extended search: [#5221](https://github.com/owncloud/ocis/pull/5221) - - Provides multiple enhancement to the search implementation. * content extraction, search - now supports apache tika to extract resource contents. * search engine, underlying search - engine is swappable now. * event consumers, the number of event consumers can now be set, which - improves the speed of the individual tasks - - https://github.com/owncloud/ocis/issues/5184 - https://github.com/owncloud/ocis/pull/5221 - -* Enhancement - Resource tags: [#5227](https://github.com/owncloud/ocis/pull/5227) - - We've added the ability to tag resources via the graph api. Tags can be added (put request) and - removed (delete request) from a resource, a list of available tags can also be requested by - sending a get request to the graph endpoint. - - https://github.com/owncloud/ocis/issues/5184 - https://github.com/owncloud/ocis/pull/5227 - https://github.com/owncloud/ocis/pull/5271 - -* Enhancement - Allow users to be disabled: [#5588](https://github.com/owncloud/ocis/pull/5588) - - By setting the `accountEnabled` property to `false` for a user via the graph API. Users can be - disabled (i.e. they can no longer login) - - https://github.com/owncloud/ocis/pull/5588 - https://github.com/owncloud/ocis/pull/5620 - -* Enhancement - Web config additions: [#6032](https://github.com/owncloud/ocis/pull/6032) - - We've added config keys for defining additional css, scripts and translations for ownCloud - Web. - - https://github.com/owncloud/ocis/pull/6032 - -* Enhancement - Eventhistory service: [#5600](https://github.com/owncloud/ocis/pull/5600) - - Introduces the `eventhistory` service. It is a service that stores events and provides a grpc - API to retrieve them. - - https://github.com/owncloud/ocis/pull/5600 - -* Enhancement - Expiration Notifications: [#5330](https://github.com/owncloud/ocis/pull/5330) - - Send emails to the user informing that a share or a space membership expires. - - https://github.com/owncloud/ocis/pull/5330 - -* Enhancement - GDPR Export: [#6064](https://github.com/owncloud/ocis/pull/6064) - - Adds an endpoint to collect all data that is related to a user - - https://github.com/owncloud/ocis/pull/6064 - https://github.com/owncloud/ocis/pull/5950 - -* Enhancement - Make graph/education API errors more consistent: [#5682](https://github.com/owncloud/ocis/pull/5682) - - Aligned the error messages when creating schools and classes fail and changed the response - code from 500 to 409. - - https://github.com/owncloud/ocis/issues/5660 - https://github.com/owncloud/ocis/pull/5682 - -* Enhancement - Graph user capabilities: [#6339](https://github.com/owncloud/ocis/pull/6339) - - Adds capablities to show if users are writeable in LDAP so clients can block their specific - fields - - https://github.com/owncloud/ocis/pull/6339 - -* Enhancement - Add endpoint to list permissions: [#5594](https://github.com/owncloud/ocis/pull/5594) - - We added 'https://cloud.ocis.test/api/v0/settings/permissions-list' to retrieve all - permissions of the logged in user. - - https://github.com/owncloud/ocis/pull/5594 - https://github.com/owncloud/ocis/pull/5571 - -* Enhancement - Notifications: [#6038](https://github.com/owncloud/ocis/pull/6038) - - Make Emails translatable via transifex The transifex translation add in to the email - templates. The optional environment variable NOTIFICATIONS_TRANSLATION_PATH added to - config. The optional global environment variable OCIS_TRANSLATION_PATH added to - notifications and userlog config. - - https://github.com/owncloud/ocis/issues/6025 - https://github.com/owncloud/ocis/pull/6038 - -* Enhancement - Open Debug endpoint for Nats: [#5002](https://github.com/owncloud/ocis/issues/5002) - - We added a debug server to nats - - https://github.com/owncloud/ocis/issues/5002 - https://github.com/owncloud/ocis/pull/6139 - -* Enhancement - No Notifications for own actions: [#5871](https://github.com/owncloud/ocis/pull/5871) - - Don't send notifications on space events when the user has executed them herself. - - https://github.com/owncloud/ocis/pull/5871 - -* Enhancement - Notify about policies: [#5912](https://github.com/owncloud/ocis/pull/5912) - - Notify the user when a file was deleted due to policies (policies service) - - https://github.com/owncloud/ocis/pull/5912 - -* Enhancement - Add a capability for the Personal Data export: [#5984](https://github.com/owncloud/ocis/pull/5984) - - Adds a capability for the personal data export endpoint - - https://github.com/owncloud/ocis/pull/5984 - -* Enhancement - Introduce policies-service: [#5714](https://github.com/owncloud/ocis/pull/5714) - - Introduces policies service. The policies-service provides a new grpc api which can be used to - return whether a requested operation is allowed or not. Open Policy Agent is used to determine - the set of rules of what is permitted and what is not. - - 2 further levels of authorization build on this: - - * Proxy Authorization - * Event Authorization (needs async post-processing enabled) - - The simplest authorization layer is in the proxy, since every request is processed here, only - simple decisions that can be processed quickly are made here, more complex queries such as file - evaluation are explicitly excluded in this layer. - - The next layer is event-based as a pipeline step in asynchronous post-processing, since - processing at this point is asynchronous, the operations there can also take longer and be more - expensive, the bytes of a file can be examined here as an example. - - Since the base block is a grpc api, it is also possible to use it directly. The policies are - written in the [rego query - language](https://www.openpolicyagent.org/docs/latest/policy-language/). - - https://github.com/owncloud/ocis/issues/5580 - https://github.com/owncloud/ocis/pull/5714 - -* Enhancement - Better config for postprocessing service: [#5457](https://github.com/owncloud/ocis/pull/5457) - - The postprocessing service is now individually configurable. This is achieved by allowing a - list of postprocessing steps that are processed in order of their appearance in the - `POSTPROCESSING_STEPS` envvar. - - https://github.com/owncloud/ocis/pull/5457 - -* Enhancement - Add Store to `postprocessing`: [#6281](https://github.com/owncloud/ocis/pull/6281) - - Add a gomicro store for the postprocessing service. Needed to run multiple postprocessing - instances - - https://github.com/owncloud/ocis/pull/6281 - -* Enhancement - Add config option to enforce passwords on public links: [#5848](https://github.com/owncloud/ocis/pull/5848) - - Added a new config option to enforce passwords on public links with "Uploader, Editor, - Contributor" roles. - - The new options are: `OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD`, - `SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD` and - `FRONTEND_OCS_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD`. Check the docs on how to - properly set them. - - https://github.com/owncloud/ocis/pull/5848 - https://github.com/owncloud/ocis/pull/5785 - https://github.com/owncloud/ocis/pull/5720 - -* Enhancement - Add new permission for public links: [#5690](https://github.com/owncloud/ocis/pull/5690) - - Added a new permission 'PublicLink.Write' to check if a user can create or update public links. - - https://github.com/owncloud/ocis/pull/5690 - -* Enhancement - Remove quota from share jails api responses: [#6309](https://github.com/owncloud/ocis/pull/6309) - - We have removed the quota object from api responses for share jails, which would permanently - show exceeded due to restrictions in the permission system. - - https://github.com/owncloud/ocis/issues/4472 - https://github.com/owncloud/ocis/pull/6309 - -* Enhancement - Added possibility to assign roles based on OIDC claims: [#6048](https://github.com/owncloud/ocis/pull/6048) - - OCIS can now be configured to update a user's role assignment from the values of a claim provided - via the IDPs userinfo endpoint. The claim name and the mapping between claim values and ocis - role name can be configured via the configuration of the proxy service. Example: - - ```yaml role_assignment: driver: oidc oidc_role_mapper: role_claim: ocisRoles - role_mapping: - role_name: admin claim_value: myAdminRole - role_name: spaceadmin - claim_value: mySpaceAdminRole - role_name: user claim_value: myUserRole - role_name: - guest: claim_value: myGuestRole ``` - - https://github.com/owncloud/ocis/pull/6048 - -* Enhancement - Added option to configure default quota per role: [#5616](https://github.com/owncloud/ocis/pull/5616) - - Admins can assign default quotas to users with certain roles by adding the following config to - the `proxy.yaml`. E.g.: ``` role_quotas: d7beeea8-8ff4-406b-8fb6-ab2dd81e6b11: 2300000 - ``` - - It maps a role ID to the quota in bytes. - - https://github.com/owncloud/ocis/pull/5616 - -* Enhancement - Add optional services to the runtime: [#6071](https://github.com/owncloud/ocis/pull/6071) - - Make it possible to start optional services in the ocis runtime. Instead of using - `OCIS_RUN_SERVICES` to define all services we can now use `OCIS_ADD_RUN_SERVICES` to add a - comma separated list of additional services which are not started in the single process by - default. - - https://github.com/owncloud/ocis/pull/6071 - -* Enhancement - Add new SetProjectSpaceQuota permission: [#5660](https://github.com/owncloud/ocis/pull/5660) - - Additionally to `set-space-quota` for setting quota on personal spaces we now have - `Drive.ReadWriteQuota.Project` for setting project spaces quota - - https://github.com/owncloud/ocis/pull/5660 - -* Enhancement - Add expiration to user and group shares: [#5389](https://github.com/owncloud/ocis/pull/5389) - - Added expiration to user and group shares. - - https://github.com/owncloud/ocis/pull/5389 - -* Enhancement - Space Management permissions: [#5441](https://github.com/owncloud/ocis/pull/5441) - - We added new space management permissions. `space-properties` will allow changing space - properties (name, description, ...). `space-ability` will allow enabling and disabling - spaces - - https://github.com/owncloud/ocis/pull/5441 - -* Enhancement - Cli to purge expired trash-bin items: [#5500](https://github.com/owncloud/ocis/pull/5500) - - Introduction of a new cli command to purge old trash-bin items. The command is part of the - `storage-users` service and can be used as follows: - - `ocis storage-users trash-bin purge-expired`. - - The `purge-expired` command configuration is done in the `ocis`configuration or as usual by - using environment variables. - - ENV `STORAGE_USERS_PURGE_TRASH_BIN_USER_ID` is used to obtain space trash-bin - information and takes the system admin user as the default `OCIS_ADMIN_USER_ID`. It should be - noted, that this is only set by default in the single binary. The command only considers spaces - to which the user has access and delete permission. - - ENV `STORAGE_USERS_PURGE_TRASH_BIN_PERSONAL_DELETE_BEFORE` has a default value of `30 - days`, which means the command will delete all files older than `30 days`. The value is - human-readable, valid values are `24h`, `60m`, `60s` etc. `0` is equivalent to disable and - prevents the deletion of `personal space` trash-bin files. - - ENV `STORAGE_USERS_PURGE_TRASH_BIN_PROJECT_DELETE_BEFORE` has a default value of `30 - days`, which means the command will delete all files older than `30 days`. The value is - human-readable, valid values are `24h`, `60m`, `60s` etc. `0` is equivalent to disable and - prevents the deletion of `project space` trash-bin files. - - Likewise, only spaces of the type `project` and `personal` are taken into account. Spaces of - type `virtual`, for example, are ignored. - - https://github.com/owncloud/ocis/issues/5499 - https://github.com/owncloud/ocis/pull/5500 - -* Enhancement - Update web to v7.0.0-rc.37: [#6294](https://github.com/owncloud/ocis/pull/6294) +* Enhancement - Update web to v7.0.0: [#6438](https://github.com/owncloud/ocis/pull/6438) Tags: web - We updated ownCloud Web to v7.0.0-rc.37. Please refer to the changelog (linked) for details on - the web release. + We updated ownCloud Web to v7.0.0. Please refer to the changelog (linked) for details on the web + release. - * Bugfix [owncloud/web#6423](https://github.com/owncloud/web/issues/6423): Archiver in protected public links - * Bugfix [owncloud/web#6434](https://github.com/owncloud/web/issues/6434): Endless lazy loading indicator after sorting file table - * Bugfix [owncloud/web#6731](https://github.com/owncloud/web/issues/6731): Layout with long breadcrumb - * Bugfix [owncloud/web#6768](https://github.com/owncloud/web/issues/6768): Pagination after increasing items per page - * Bugfix [owncloud/web#7513](https://github.com/owncloud/web/issues/7513): Calendar popup position in right sidebar - * Bugfix [owncloud/web#7655](https://github.com/owncloud/web/issues/7655): Loading shares in deep nested folders - * Bugfix [owncloud/web#7925](https://github.com/owncloud/web/pull/7925): "Paste"-action without write permissions - * Bugfix [owncloud/web#7926](https://github.com/owncloud/web/pull/7926): Include spaces in the list info - * Bugfix [owncloud/web#7958](https://github.com/owncloud/web/pull/7958): Prevent deletion of own account - * Bugfix [owncloud/web#7966](https://github.com/owncloud/web/pull/7966): UI fixes for sorting and quickactions - * Bugfix [owncloud/web#7969](https://github.com/owncloud/web/pull/7969): Space quota not displayed after creation - * Bugfix [owncloud/web#8026](https://github.com/owncloud/web/pull/8026): Text editor appearance - * Bugfix [owncloud/web#8040](https://github.com/owncloud/web/pull/8040): Reverting versions for read-only shares - * Bugfix [owncloud/web#8045](https://github.com/owncloud/web/pull/8045): Resolving drives in search - * Bugfix [owncloud/web#8054](https://github.com/owncloud/web/issues/8054): Search repeating no results message - * Bugfix [owncloud/web#8058](https://github.com/owncloud/web/pull/8058): Current year selection in the date picker - * Bugfix [owncloud/web#8061](https://github.com/owncloud/web/pull/8061): Omit "page"-query in breadcrumb navigation - * Bugfix [owncloud/web#8080](https://github.com/owncloud/web/pull/8080): Left sidebar navigation item text flickers on transition - * Bugfix [owncloud/web#8081](https://github.com/owncloud/web/issues/8081): Space member disappearing - * Bugfix [owncloud/web#8083](https://github.com/owncloud/web/issues/8083): Re-using space images - * Bugfix [owncloud/web#8148](https://github.com/owncloud/web/issues/8148): Show space members despite deleted entries - * Bugfix [owncloud/web#8158](https://github.com/owncloud/web/issues/8158): Search bar input appearance - * Bugfix [owncloud/web#8265](https://github.com/owncloud/web/pull/8265): Application menu active display on hover - * Bugfix [owncloud/web#8276](https://github.com/owncloud/web/pull/8276): Loading additional user data - * Bugfix [owncloud/web#8300](https://github.com/owncloud/web/pull/8300): Re-loading space members panel - * Bugfix [owncloud/web#8326](https://github.com/owncloud/web/pull/8326): Editing users who never logged in - * Bugfix [owncloud/web#8340](https://github.com/owncloud/web/pull/8340): Cancel custom permissions - * Bugfix [owncloud/web#8411](https://github.com/owncloud/web/issues/8411): Drop menus with limited vertical screen space - * Bugfix [owncloud/web#8420](https://github.com/owncloud/web/issues/8420): Token renewal in vue router hash mode - * Bugfix [owncloud/web#8434](https://github.com/owncloud/web/issues/8434): Accessing route in admin-settings with insufficient permissions - * Bugfix [owncloud/web#8479](https://github.com/owncloud/web/issues/8479): "Show more"-action in shares panel - * Bugfix [owncloud/web#8480](https://github.com/owncloud/web/pull/8480): Paste action conflict dialog broken - * Bugfix [owncloud/web#8498](https://github.com/owncloud/web/pull/8498): PDF display issue - Update CSP object-src policy - * Bugfix [owncloud/web#8508](https://github.com/owncloud/web/pull/8508): Remove fuzzy search results - * Bugfix [owncloud/web#8523](https://github.com/owncloud/web/issues/8523): Space image upload - * Bugfix [owncloud/web#8549](https://github.com/owncloud/web/issues/8549): Batch context actions in admin settings - * Bugfix [owncloud/web#8554](https://github.com/owncloud/web/pull/8554): Height of dropdown no-option - * Bugfix [owncloud/web#8576](https://github.com/owncloud/web/pull/8576): De-duplicate event handling to prevent errors on Draw-io - * Bugfix [owncloud/web#8585](https://github.com/owncloud/web/issues/8585): Users without role assignment - * Bugfix [owncloud/web#8587](https://github.com/owncloud/web/issues/8587): Password enforced check for public links - * Bugfix [owncloud/web#8592](https://github.com/owncloud/web/issues/8592): Group members sorting - * Bugfix [owncloud/web#8694](https://github.com/owncloud/web/pull/8694): Broken re-login after logout - * Bugfix [owncloud/web#8695](https://github.com/owncloud/web/issues/8695): Open files in external app - * Bugfix [owncloud/web#8756](https://github.com/owncloud/web/pull/8756): Copy link to clipboard text - * Bugfix [owncloud/web#8758](https://github.com/owncloud/web/pull/8758): Preview controls colors - * Bugfix [owncloud/web#8776](https://github.com/owncloud/web/issues/8776): Selection reset on action click - * Bugfix [owncloud/web#8814](https://github.com/owncloud/web/pull/8814): Share recipient container exceed - * Bugfix [owncloud/web#8825](https://github.com/owncloud/web/pull/8825): Remove drop target in read-only folders - * Bugfix [owncloud/web#8827](https://github.com/owncloud/web/pull/8827): Opening context menu via keyboard - * Bugfix [owncloud/web#8834](https://github.com/owncloud/web/issues/8834): Hide upload hint in empty read-only folders - * Bugfix [owncloud/web#8864](https://github.com/owncloud/web/pull/8864): Public link empty password stays forever - * Bugfix [owncloud/web#8880](https://github.com/owncloud/web/issues/8880): Sidebar header after deleting resource - * Bugfix [owncloud/web#8928](https://github.com/owncloud/web/issues/8928): Infinite login redirect - * Bugfix [owncloud/web#8987](https://github.com/owncloud/web/pull/8987): Limit amount of concurrent tus requests - * Bugfix [owncloud/web#8992](https://github.com/owncloud/web/pull/8992): Personal space name after language change - * Bugfix [owncloud/web#9004](https://github.com/owncloud/web/issues/9004): Endless loading when encountering a public link error - * Bugfix [owncloud/web#9015](https://github.com/owncloud/web/pull/9015): Prevent "virtual" spaces from being displayed in the UI - * Change [owncloud/web#6661](https://github.com/owncloud/web/issues/6661): Streamline new tab handling in extensions - * Change [owncloud/web#7948](https://github.com/owncloud/web/issues/7948): Update Vue to v3.2 - * Change [owncloud/web#8431](https://github.com/owncloud/web/pull/8431): Remove permission manager - * Change [owncloud/web#8455](https://github.com/owncloud/web/pull/8455): Configurable extension autosave - * Change [owncloud/web#8563](https://github.com/owncloud/web/pull/8563): Theme colors - * Enhancement [owncloud/web#6183](https://github.com/owncloud/web/issues/6183): Global loading indicator - * Enhancement [owncloud/web#7388](https://github.com/owncloud/web/pull/7388): Add tag support - * Enhancement [owncloud/web#7721](https://github.com/owncloud/web/issues/7721): Improve performance when loading folders and share indicators - * Enhancement [owncloud/web#7942](https://github.com/owncloud/web/pull/7942): Warn users when using unsupported browsers - * Enhancement [owncloud/web#7965](https://github.com/owncloud/web/pull/7965): Optional Contributor role and configurable resharing permissions - * Enhancement [owncloud/web#7968](https://github.com/owncloud/web/pull/7968): Group and user creation forms submit on enter - * Enhancement [owncloud/web#7976](https://github.com/owncloud/web/pull/7976): Add switch to enable condensed resource table - * Enhancement [owncloud/web#7977](https://github.com/owncloud/web/pull/7977): Introduce zoom and rotate to the preview app - * Enhancement [owncloud/web#7983](https://github.com/owncloud/web/pull/7983): Conflict dialog UX - * Enhancement [owncloud/web#7991](https://github.com/owncloud/web/pull/7991): Add tiles view for resource display - * Enhancement [owncloud/web#7994](https://github.com/owncloud/web/pull/7994): Introduce full screen mode to the preview app - * Enhancement [owncloud/web#7995](https://github.com/owncloud/web/pull/7995): Enable autoplay in the preview app - * Enhancement [owncloud/web#8008](https://github.com/owncloud/web/issues/8008): Don't open sidebar when copying quicklink - * Enhancement [owncloud/web#8021](https://github.com/owncloud/web/pull/8021): Access right sidebar panels via URL - * Enhancement [owncloud/web#8051](https://github.com/owncloud/web/pull/8051): Introduce image preloading to the preview app - * Enhancement [owncloud/web#8055](https://github.com/owncloud/web/pull/8055): Retry failed uploads on re-upload - * Enhancement [owncloud/web#8056](https://github.com/owncloud/web/pull/8056): Increase Searchbar height - * Enhancement [owncloud/web#8057](https://github.com/owncloud/web/pull/8057): Show text file icon for empty text files - * Enhancement [owncloud/web#8132](https://github.com/owncloud/web/pull/8132): Update libre-graph-api to v1.0 - * Enhancement [owncloud/web#8136](https://github.com/owncloud/web/pull/8136): Make clipboard copy available to more browsers - * Enhancement [owncloud/web#8161](https://github.com/owncloud/web/pull/8161): Space group members - * Enhancement [owncloud/web#8161](https://github.com/owncloud/web/pull/8161): Space group shares - * Enhancement [owncloud/web#8166](https://github.com/owncloud/web/issues/8166): Show upload speed - * Enhancement [owncloud/web#8175](https://github.com/owncloud/web/pull/8175): Rename "user management" app - * Enhancement [owncloud/web#8178](https://github.com/owncloud/web/pull/8178): Spaces list in admin settings - * Enhancement [owncloud/web#8261](https://github.com/owncloud/web/pull/8261): Admin settings users section uses graph api for role assignments - * Enhancement [owncloud/web#8279](https://github.com/owncloud/web/pull/8279): Move user group select to edit panel - * Enhancement [owncloud/web#8280](https://github.com/owncloud/web/pull/8280): Add support for multiple clients in `theme.json` - * Enhancement [owncloud/web#8294](https://github.com/owncloud/web/pull/8294): Move language selection to user account page - * Enhancement [owncloud/web#8306](https://github.com/owncloud/web/pull/8306): Show selectable groups only - * Enhancement [owncloud/web#8317](https://github.com/owncloud/web/pull/8317): Add context menu to groups - * Enhancement [owncloud/web#8320](https://github.com/owncloud/web/pull/8320): Space member expiration - * Enhancement [owncloud/web#8320](https://github.com/owncloud/web/pull/8320): Update SDK to v3.1.0-alpha.3 - * Enhancement [owncloud/web#8324](https://github.com/owncloud/web/pull/8324): Add context menu to users - * Enhancement [owncloud/web#8331](https://github.com/owncloud/web/pull/8331): Admin settings users section details improvement - * Enhancement [owncloud/web#8354](https://github.com/owncloud/web/issues/8354): Add `ItemFilter` component - * Enhancement [owncloud/web#8356](https://github.com/owncloud/web/pull/8356): Slight improvement of key up/down performance - * Enhancement [owncloud/web#8363](https://github.com/owncloud/web/issues/8363): Admin settings general section - * Enhancement [owncloud/web#8375](https://github.com/owncloud/web/pull/8375): Add appearance section in general settings - * Enhancement [owncloud/web#8377](https://github.com/owncloud/web/issues/8377): User group filter - * Enhancement [owncloud/web#8387](https://github.com/owncloud/web/pull/8387): Batch edit quota in admin panel - * Enhancement [owncloud/web#8398](https://github.com/owncloud/web/pull/8398): Use standardized layout for file/space action list - * Enhancement [owncloud/web#8425](https://github.com/owncloud/web/issues/8425): Add dark ownCloud logo - * Enhancement [owncloud/web#8432](https://github.com/owncloud/web/pull/8432): Inject customizations - * Enhancement [owncloud/web#8433](https://github.com/owncloud/web/pull/8433): User settings login field - * Enhancement [owncloud/web#8441](https://github.com/owncloud/web/pull/8441): Skeleton App - * Enhancement [owncloud/web#8449](https://github.com/owncloud/web/pull/8449): Configurable top bar - * Enhancement [owncloud/web#8450](https://github.com/owncloud/web/pull/8450): Rework notification bell - * Enhancement [owncloud/web#8455](https://github.com/owncloud/web/pull/8455): Autosave content changes in text editor - * Enhancement [owncloud/web#8473](https://github.com/owncloud/web/pull/8473): Update CERN links - * Enhancement [owncloud/web#8489](https://github.com/owncloud/web/pull/8489): Respect max quota - * Enhancement [owncloud/web#8492](https://github.com/owncloud/web/pull/8492): User role filter - * Enhancement [owncloud/web#8503](https://github.com/owncloud/web/issues/8503): Beautify file version list - * Enhancement [owncloud/web#8515](https://github.com/owncloud/web/pull/8515): Introduce trashbin overview - * Enhancement [owncloud/web#8518](https://github.com/owncloud/web/pull/8518): Make notifications work with oCIS - * Enhancement [owncloud/web#8541](https://github.com/owncloud/web/pull/8541): Public link permission `PublicLink.Write.all` - * Enhancement [owncloud/web#8553](https://github.com/owncloud/web/pull/8553): Add and remove users from groups batch actions - * Enhancement [owncloud/web#8554](https://github.com/owncloud/web/pull/8554): Beautify form inputs - * Enhancement [owncloud/web#8557](https://github.com/owncloud/web/issues/8557): Rework mobile navigation - * Enhancement [owncloud/web#8566](https://github.com/owncloud/web/pull/8566): QuickActions role configurable - * Enhancement [owncloud/web#8612](https://github.com/owncloud/web/issues/8612): Add `Accept-Language` header to all outgoing requests - * Enhancement [owncloud/web#8630](https://github.com/owncloud/web/pull/8630): Add logout url - * Enhancement [owncloud/web#8652](https://github.com/owncloud/web/pull/8652): Enable guest users - * Enhancement [owncloud/web#8711](https://github.com/owncloud/web/pull/8711): Remove placeholder, add customizable label - * Enhancement [owncloud/web#8713](https://github.com/owncloud/web/pull/8713): Context helper read more link configurable - * Enhancement [owncloud/web#8715](https://github.com/owncloud/web/pull/8715): Enable rename groups - * Enhancement [owncloud/web#8730](https://github.com/owncloud/web/pull/8730): Create Space from selection - * Enhancement [owncloud/web#8738](https://github.com/owncloud/web/issues/8738): GDPR export - * Enhancement [owncloud/web#8762](https://github.com/owncloud/web/pull/8762): Stop bootstrapping application earlier in anonymous contexts - * Enhancement [owncloud/web#8766](https://github.com/owncloud/web/pull/8766): Add support for read-only groups - * Enhancement [owncloud/web#8790](https://github.com/owncloud/web/pull/8790): Custom translations - * Enhancement [owncloud/web#8797](https://github.com/owncloud/web/pull/8797): Font family in theming - * Enhancement [owncloud/web#8806](https://github.com/owncloud/web/pull/8806): Preview app sorting - * Enhancement [owncloud/web#8820](https://github.com/owncloud/web/pull/8820): Adjust missing reshare permissions message - * Enhancement [owncloud/web#8822](https://github.com/owncloud/web/pull/8822): Fix quicklink icon alignment - * Enhancement [owncloud/web#8826](https://github.com/owncloud/web/pull/8826): Admin settings groups members panel - * Enhancement [owncloud/web#8868](https://github.com/owncloud/web/pull/8868): Respect user read-only configuration by the server - * Enhancement [owncloud/web#8876](https://github.com/owncloud/web/pull/8876): Update roles and permissions names, labels, texts and icons - * Enhancement [owncloud/web#8882](https://github.com/owncloud/web/pull/8882): Layout of Share role and expiration date dropdown - * Enhancement [owncloud/web#8883](https://github.com/owncloud/web/issues/8883): Webfinger redirect app - * Enhancement [owncloud/web#8898](https://github.com/owncloud/web/pull/8898): Rename "Quicklink" to "link" - * Enhancement [owncloud/web#8911](https://github.com/owncloud/web/pull/8911): Add notification setting to account page + ## Breaking changes * BREAKING CHANGE for developers and admins in + [owncloud/web#7948](https://github.com/owncloud/web/issues/7948): we've updated + Vue.js to version 3. Existing apps that have not been updated to Vue.js version 3 will not be + compatible anymore. * BREAKING CHANGE for admins in + [owncloud/web#8563](https://github.com/owncloud/web/pull/8563): we've introduced + contrast colors in our theming. In case you have created a custom `theme.json` it needs to be + adjusted accordingly: `-contrast` color values need to be added to all `swatches`, e.g. to + `swatch-brand-contrast`. See https://owncloud.dev/clients/web/theming/#colors - https://github.com/owncloud/ocis/pull/6294 - https://github.com/owncloud/web/releases/tag/v7.0.0-rc.37 + ## Summary * Bugfix + [owncloud/web#6423](https://github.com/owncloud/web/issues/6423): Archiver in + protected public links * Bugfix + [owncloud/web#6434](https://github.com/owncloud/web/issues/6434): Endless lazy + loading indicator after sorting file table * Bugfix + [owncloud/web#6731](https://github.com/owncloud/web/issues/6731): Layout with long + breadcrumb * Bugfix + [owncloud/web#6768](https://github.com/owncloud/web/issues/6768): Pagination after + increasing items per page * Bugfix + [owncloud/web#7513](https://github.com/owncloud/web/issues/7513): Calendar popup + position in right sidebar * Bugfix + [owncloud/web#7655](https://github.com/owncloud/web/issues/7655): Loading shares in + deep nested folders * Bugfix + [owncloud/web#7925](https://github.com/owncloud/web/pull/7925): "Paste"-action + without write permissions * Bugfix + [owncloud/web#7926](https://github.com/owncloud/web/pull/7926): Include spaces in + the list info * Bugfix + [owncloud/web#7958](https://github.com/owncloud/web/pull/7958): Prevent deletion of + own account * Bugfix [owncloud/web#7966](https://github.com/owncloud/web/pull/7966): + UI fixes for sorting and quickactions * Bugfix + [owncloud/web#7969](https://github.com/owncloud/web/pull/7969): Space quota not + displayed after creation * Bugfix + [owncloud/web#8026](https://github.com/owncloud/web/pull/8026): Text editor + appearance * Bugfix [owncloud/web#8040](https://github.com/owncloud/web/pull/8040): + Reverting versions for read-only shares * Bugfix + [owncloud/web#8045](https://github.com/owncloud/web/pull/8045): Resolving drives in + search * Bugfix [owncloud/web#8054](https://github.com/owncloud/web/issues/8054): + Search repeating no results message * Bugfix + [owncloud/web#8058](https://github.com/owncloud/web/pull/8058): Current year + selection in the date picker * Bugfix + [owncloud/web#8061](https://github.com/owncloud/web/pull/8061): Omit "page"-query + in breadcrumb navigation * Bugfix + [owncloud/web#8080](https://github.com/owncloud/web/pull/8080): Left sidebar + navigation item text flickers on transition * Bugfix + [owncloud/web#8081](https://github.com/owncloud/web/issues/8081): Space member + disappearing * Bugfix + [owncloud/web#8083](https://github.com/owncloud/web/issues/8083): Re-using space + images * Bugfix [owncloud/web#8148](https://github.com/owncloud/web/issues/8148): + Show space members despite deleted entries * Bugfix + [owncloud/web#8158](https://github.com/owncloud/web/issues/8158): Search bar input + appearance * Bugfix [owncloud/web#8265](https://github.com/owncloud/web/pull/8265): + Application menu active display on hover * Bugfix + [owncloud/web#8276](https://github.com/owncloud/web/pull/8276): Loading additional + user data * Bugfix [owncloud/web#8300](https://github.com/owncloud/web/pull/8300): + Re-loading space members panel * Bugfix + [owncloud/web#8326](https://github.com/owncloud/web/pull/8326): Editing users who + never logged in * Bugfix + [owncloud/web#8340](https://github.com/owncloud/web/pull/8340): Cancel custom + permissions * Bugfix + [owncloud/web#8411](https://github.com/owncloud/web/issues/8411): Drop menus with + limited vertical screen space * Bugfix + [owncloud/web#8420](https://github.com/owncloud/web/issues/8420): Token renewal in + vue router hash mode * Bugfix + [owncloud/web#8434](https://github.com/owncloud/web/issues/8434): Accessing route + in admin-settings with insufficient permissions * Bugfix + [owncloud/web#8479](https://github.com/owncloud/web/issues/8479): "Show + more"-action in shares panel * Bugfix + [owncloud/web#8480](https://github.com/owncloud/web/pull/8480): Paste action + conflict dialog broken * Bugfix + [owncloud/web#8498](https://github.com/owncloud/web/pull/8498): PDF display issue - + Update CSP object-src policy * Bugfix + [owncloud/web#8508](https://github.com/owncloud/web/pull/8508): Remove fuzzy search + results * Bugfix [owncloud/web#8523](https://github.com/owncloud/web/issues/8523): + Space image upload * Bugfix + [owncloud/web#8549](https://github.com/owncloud/web/issues/8549): Batch context + actions in admin settings * Bugfix + [owncloud/web#8554](https://github.com/owncloud/web/pull/8554): Height of dropdown + no-option * Bugfix [owncloud/web#8576](https://github.com/owncloud/web/pull/8576): + De-duplicate event handling to prevent errors on Draw-io * Bugfix + [owncloud/web#8585](https://github.com/owncloud/web/issues/8585): Users without + role assignment * Bugfix + [owncloud/web#8587](https://github.com/owncloud/web/issues/8587): Password + enforced check for public links * Bugfix + [owncloud/web#8592](https://github.com/owncloud/web/issues/8592): Group members + sorting * Bugfix [owncloud/web#8694](https://github.com/owncloud/web/pull/8694): + Broken re-login after logout * Bugfix + [owncloud/web#8695](https://github.com/owncloud/web/issues/8695): Open files in + external app * Bugfix + [owncloud/web#8756](https://github.com/owncloud/web/pull/8756): Copy link to + clipboard text * Bugfix + [owncloud/web#8758](https://github.com/owncloud/web/pull/8758): Preview controls + colors * Bugfix [owncloud/web#8776](https://github.com/owncloud/web/issues/8776): + Selection reset on action click * Bugfix + [owncloud/web#8814](https://github.com/owncloud/web/pull/8814): Share recipient + container exceed * Bugfix + [owncloud/web#8825](https://github.com/owncloud/web/pull/8825): Remove drop target + in read-only folders * Bugfix + [owncloud/web#8827](https://github.com/owncloud/web/pull/8827): Opening context + menu via keyboard * Bugfix + [owncloud/web#8834](https://github.com/owncloud/web/issues/8834): Hide upload hint + in empty read-only folders * Bugfix + [owncloud/web#8864](https://github.com/owncloud/web/pull/8864): Public link empty + password stays forever * Bugfix + [owncloud/web#8880](https://github.com/owncloud/web/issues/8880): Sidebar header + after deleting resource * Bugfix + [owncloud/web#8928](https://github.com/owncloud/web/issues/8928): Infinite login + redirect * Bugfix [owncloud/web#8987](https://github.com/owncloud/web/pull/8987): + Limit amount of concurrent tus requests * Bugfix + [owncloud/web#8992](https://github.com/owncloud/web/pull/8992): Personal space name + after language change * Bugfix + [owncloud/web#9004](https://github.com/owncloud/web/issues/9004): Endless loading + when encountering a public link error * Bugfix + [owncloud/web#9009](https://github.com/owncloud/web/pull/9009): Public link file + previews * Bugfix [owncloud/web#9014](https://github.com/owncloud/web/issues/9014): + Empty file list after deleting resources * Bugfix + [owncloud/web#9015](https://github.com/owncloud/web/pull/9015): Prevent "virtual" + spaces from being displayed in the UI * Bugfix + [owncloud/web#9020](https://github.com/owncloud/web/issues/9020): Sidebar for + spaces on "Shared via link"-page * Bugfix + [owncloud/web#9022](https://github.com/owncloud/web/issues/9022): Spaces in search + results * Bugfix [owncloud/web#9030](https://github.com/owncloud/web/issues/9030): + Share indicator loading after pasting resources * Bugfix + [owncloud/web#9050](https://github.com/owncloud/web/issues/9050): Preview app mime + type detection * Bugfix + [owncloud/web#9061](https://github.com/owncloud/web/issues/9061): Resource not + found and No content message at the same time * Bugfix + [owncloud/web#9080](https://github.com/owncloud/web/issues/9080): Incorrect pause + state in upload info * Bugfix + [owncloud/web#9131](https://github.com/owncloud/web/pull/9131): Select all checkbox + * Bugfix [owncloud/web#9144](https://github.com/owncloud/web/pull/9144): + Notifications link overflow * Change + [owncloud/web#6661](https://github.com/owncloud/web/issues/6661): Streamline new + tab handling in extensions * Change + [owncloud/web#7948](https://github.com/owncloud/web/issues/7948): Update Vue to v3.2 + * Change [owncloud/web#8431](https://github.com/owncloud/web/pull/8431): Remove + permission manager * Change + [owncloud/web#8455](https://github.com/owncloud/web/pull/8455): Configurable + extension autosave * Change + [owncloud/web#8563](https://github.com/owncloud/web/pull/8563): Theme colors * + Enhancement [owncloud/web#6183](https://github.com/owncloud/web/issues/6183): + Global loading indicator * Enhancement + [owncloud/web#7388](https://github.com/owncloud/web/pull/7388): Add tag support * + Enhancement [owncloud/web#7721](https://github.com/owncloud/web/issues/7721): + Improve performance when loading folders and share indicators * Enhancement + [owncloud/web#7942](https://github.com/owncloud/web/pull/7942): Warn users when + using unsupported browsers * Enhancement + [owncloud/web#7965](https://github.com/owncloud/web/pull/7965): Optional + Contributor role and configurable resharing permissions * Enhancement + [owncloud/web#7968](https://github.com/owncloud/web/pull/7968): Group and user + creation forms submit on enter * Enhancement + [owncloud/web#7976](https://github.com/owncloud/web/pull/7976): Add switch to enable + condensed resource table * Enhancement + [owncloud/web#7977](https://github.com/owncloud/web/pull/7977): Introduce zoom and + rotate to the preview app * Enhancement + [owncloud/web#7983](https://github.com/owncloud/web/pull/7983): Conflict dialog UX * + Enhancement [owncloud/web#7991](https://github.com/owncloud/web/pull/7991): Add + tiles view for resource display * Enhancement + [owncloud/web#7994](https://github.com/owncloud/web/pull/7994): Introduce full + screen mode to the preview app * Enhancement + [owncloud/web#7995](https://github.com/owncloud/web/pull/7995): Enable autoplay in + the preview app * Enhancement + [owncloud/web#8008](https://github.com/owncloud/web/issues/8008): Don't open + sidebar when copying quicklink * Enhancement + [owncloud/web#8021](https://github.com/owncloud/web/pull/8021): Access right + sidebar panels via URL * Enhancement + [owncloud/web#8051](https://github.com/owncloud/web/pull/8051): Introduce image + preloading to the preview app * Enhancement + [owncloud/web#8055](https://github.com/owncloud/web/pull/8055): Retry failed + uploads on re-upload * Enhancement + [owncloud/web#8056](https://github.com/owncloud/web/pull/8056): Increase Searchbar + height * Enhancement + [owncloud/web#8057](https://github.com/owncloud/web/pull/8057): Show text file icon + for empty text files * Enhancement + [owncloud/web#8132](https://github.com/owncloud/web/pull/8132): Update + libre-graph-api to v1.0 * Enhancement + [owncloud/web#8136](https://github.com/owncloud/web/pull/8136): Make clipboard copy + available to more browsers * Enhancement + [owncloud/web#8161](https://github.com/owncloud/web/pull/8161): Space group members + * Enhancement [owncloud/web#8161](https://github.com/owncloud/web/pull/8161): Space + group shares * Enhancement + [owncloud/web#8166](https://github.com/owncloud/web/issues/8166): Show upload speed + * Enhancement [owncloud/web#8175](https://github.com/owncloud/web/pull/8175): + Rename "user management" app * Enhancement + [owncloud/web#8178](https://github.com/owncloud/web/pull/8178): Spaces list in admin + settings * Enhancement + [owncloud/web#8261](https://github.com/owncloud/web/pull/8261): Admin settings + users section uses graph api for role assignments * Enhancement + [owncloud/web#8279](https://github.com/owncloud/web/pull/8279): Move user group + select to edit panel * Enhancement + [owncloud/web#8280](https://github.com/owncloud/web/pull/8280): Add support for + multiple clients in `theme.json` * Enhancement + [owncloud/web#8294](https://github.com/owncloud/web/pull/8294): Move language + selection to user account page * Enhancement + [owncloud/web#8306](https://github.com/owncloud/web/pull/8306): Show selectable + groups only * Enhancement + [owncloud/web#8317](https://github.com/owncloud/web/pull/8317): Add context menu to + groups * Enhancement + [owncloud/web#8320](https://github.com/owncloud/web/pull/8320): Space member + expiration * Enhancement + [owncloud/web#8320](https://github.com/owncloud/web/pull/8320): Update SDK to + v3.1.0-alpha.3 * Enhancement + [owncloud/web#8324](https://github.com/owncloud/web/pull/8324): Add context menu to + users * Enhancement [owncloud/web#8331](https://github.com/owncloud/web/pull/8331): + Admin settings users section details improvement * Enhancement + [owncloud/web#8354](https://github.com/owncloud/web/issues/8354): Add `ItemFilter` + component * Enhancement + [owncloud/web#8356](https://github.com/owncloud/web/pull/8356): Slight improvement + of key up/down performance * Enhancement + [owncloud/web#8363](https://github.com/owncloud/web/issues/8363): Admin settings + general section * Enhancement + [owncloud/web#8375](https://github.com/owncloud/web/pull/8375): Add appearance + section in general settings * Enhancement + [owncloud/web#8377](https://github.com/owncloud/web/issues/8377): User group filter + * Enhancement [owncloud/web#8387](https://github.com/owncloud/web/pull/8387): Batch + edit quota in admin panel * Enhancement + [owncloud/web#8398](https://github.com/owncloud/web/pull/8398): Use standardized + layout for file/space action list * Enhancement + [owncloud/web#8425](https://github.com/owncloud/web/issues/8425): Add dark ownCloud + logo * Enhancement [owncloud/web#8432](https://github.com/owncloud/web/pull/8432): + Inject customizations * Enhancement + [owncloud/web#8433](https://github.com/owncloud/web/pull/8433): User settings login + field * Enhancement [owncloud/web#8441](https://github.com/owncloud/web/pull/8441): + Skeleton App * Enhancement + [owncloud/web#8449](https://github.com/owncloud/web/pull/8449): Configurable top + bar * Enhancement [owncloud/web#8450](https://github.com/owncloud/web/pull/8450): + Rework notification bell * Enhancement + [owncloud/web#8455](https://github.com/owncloud/web/pull/8455): Autosave content + changes in text editor * Enhancement + [owncloud/web#8473](https://github.com/owncloud/web/pull/8473): Update CERN links * + Enhancement [owncloud/web#8489](https://github.com/owncloud/web/pull/8489): + Respect max quota * Enhancement + [owncloud/web#8492](https://github.com/owncloud/web/pull/8492): User role filter * + Enhancement [owncloud/web#8503](https://github.com/owncloud/web/issues/8503): + Beautify file version list * Enhancement + [owncloud/web#8515](https://github.com/owncloud/web/pull/8515): Introduce trashbin + overview * Enhancement + [owncloud/web#8518](https://github.com/owncloud/web/pull/8518): Make notifications + work with oCIS * Enhancement + [owncloud/web#8541](https://github.com/owncloud/web/pull/8541): Public link + permission `PublicLink.Write.all` * Enhancement + [owncloud/web#8553](https://github.com/owncloud/web/pull/8553): Add and remove users + from groups batch actions * Enhancement + [owncloud/web#8554](https://github.com/owncloud/web/pull/8554): Beautify form + inputs * Enhancement + [owncloud/web#8557](https://github.com/owncloud/web/issues/8557): Rework mobile + navigation * Enhancement + [owncloud/web#8566](https://github.com/owncloud/web/pull/8566): QuickActions role + configurable * Enhancement + [owncloud/web#8612](https://github.com/owncloud/web/issues/8612): Add + `Accept-Language` header to all outgoing requests * Enhancement + [owncloud/web#8630](https://github.com/owncloud/web/pull/8630): Add logout url * + Enhancement [owncloud/web#8652](https://github.com/owncloud/web/pull/8652): Enable + guest users * Enhancement + [owncloud/web#8711](https://github.com/owncloud/web/pull/8711): Remove + placeholder, add customizable label * Enhancement + [owncloud/web#8713](https://github.com/owncloud/web/pull/8713): Context helper read + more link configurable * Enhancement + [owncloud/web#8715](https://github.com/owncloud/web/pull/8715): Enable rename + groups * Enhancement + [owncloud/web#8730](https://github.com/owncloud/web/pull/8730): Create Space from + selection * Enhancement + [owncloud/web#8738](https://github.com/owncloud/web/issues/8738): GDPR export * + Enhancement [owncloud/web#8762](https://github.com/owncloud/web/pull/8762): Stop + bootstrapping application earlier in anonymous contexts * Enhancement + [owncloud/web#8766](https://github.com/owncloud/web/pull/8766): Add support for + read-only groups * Enhancement + [owncloud/web#8790](https://github.com/owncloud/web/pull/8790): Custom + translations * Enhancement + [owncloud/web#8797](https://github.com/owncloud/web/pull/8797): Font family in + theming * Enhancement + [owncloud/web#8806](https://github.com/owncloud/web/pull/8806): Preview app sorting + * Enhancement [owncloud/web#8820](https://github.com/owncloud/web/pull/8820): + Adjust missing reshare permissions message * Enhancement + [owncloud/web#8822](https://github.com/owncloud/web/pull/8822): Fix quicklink icon + alignment * Enhancement + [owncloud/web#8826](https://github.com/owncloud/web/pull/8826): Admin settings + groups members panel * Enhancement + [owncloud/web#8868](https://github.com/owncloud/web/pull/8868): Respect user + read-only configuration by the server * Enhancement + [owncloud/web#8876](https://github.com/owncloud/web/pull/8876): Update roles and + permissions names, labels, texts and icons * Enhancement + [owncloud/web#8882](https://github.com/owncloud/web/pull/8882): Layout of Share role + and expiration date dropdown * Enhancement + [owncloud/web#8883](https://github.com/owncloud/web/issues/8883): Webfinger + redirect app * Enhancement + [owncloud/web#8898](https://github.com/owncloud/web/pull/8898): Rename "Quicklink" + to "link" * Enhancement + [owncloud/web#8911](https://github.com/owncloud/web/pull/8911): Add notification + setting to account page * Enhancement + [owncloud/web#9048](https://github.com/owncloud/web/issues/9048): Support + pagination in admin settings app * Enhancement + [owncloud/web#9070](https://github.com/owncloud/web/pull/9070): Disable change + password capability * Enhancement + [owncloud/web#9070](https://github.com/owncloud/web/pull/9070): Disable create user + and delete user via capabilities * Enhancement + [owncloud/web#9076](https://github.com/owncloud/web/pull/9076): Show detailed error + messages while upload fails + + https://github.com/owncloud/ocis/pull/6438 + https://github.com/owncloud/web/releases/tag/v7.0.0 * Enhancement - Use Accept-Language Header: [#5918](https://github.com/owncloud/ocis/pull/5918) diff --git a/README.md b/README.md index 6b455abdb..b984204e7 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,6 @@ ownCloud Infinite Scale (oCIS) is the new file sync & share platform that will be the foundation of your data management platform. -**Infinite Scale is officially released and supported as of version 2.0.0** - Make sure to download the [latest released version](https://download.owncloud.com/ocis/ocis/stable/?sort=time&order=desc) today! ## Overview diff --git a/changelog/3.0.0_2023-05-22/425-on-thumbnails.md b/changelog/3.0.0_2023-06-06/425-on-thumbnails.md similarity index 100% rename from changelog/3.0.0_2023-05-22/425-on-thumbnails.md rename to changelog/3.0.0_2023-06-06/425-on-thumbnails.md diff --git a/changelog/3.0.0_2023-05-22/add-debug-result-for-av.md b/changelog/3.0.0_2023-06-06/add-debug-result-for-av.md similarity index 100% rename from changelog/3.0.0_2023-05-22/add-debug-result-for-av.md rename to changelog/3.0.0_2023-06-06/add-debug-result-for-av.md diff --git a/changelog/3.0.0_2023-05-22/add-debug-to-audit.md b/changelog/3.0.0_2023-06-06/add-debug-to-audit.md similarity index 100% rename from changelog/3.0.0_2023-05-22/add-debug-to-audit.md rename to changelog/3.0.0_2023-06-06/add-debug-to-audit.md diff --git a/changelog/3.0.0_2023-05-22/add-debug-to-idm.md b/changelog/3.0.0_2023-06-06/add-debug-to-idm.md similarity index 100% rename from changelog/3.0.0_2023-05-22/add-debug-to-idm.md rename to changelog/3.0.0_2023-06-06/add-debug-to-idm.md diff --git a/changelog/3.0.0_2023-05-22/add-debug-to-postprocessing.md b/changelog/3.0.0_2023-06-06/add-debug-to-postprocessing.md similarity index 100% rename from changelog/3.0.0_2023-05-22/add-debug-to-postprocessing.md rename to changelog/3.0.0_2023-06-06/add-debug-to-postprocessing.md diff --git a/changelog/3.0.0_2023-05-22/add-debug-to-userlog.md b/changelog/3.0.0_2023-06-06/add-debug-to-userlog.md similarity index 100% rename from changelog/3.0.0_2023-05-22/add-debug-to-userlog.md rename to changelog/3.0.0_2023-06-06/add-debug-to-userlog.md diff --git a/changelog/3.0.0_2023-05-22/add-decomposedfs-metadata-command.md b/changelog/3.0.0_2023-06-06/add-decomposedfs-metadata-command.md similarity index 100% rename from changelog/3.0.0_2023-05-22/add-decomposedfs-metadata-command.md rename to changelog/3.0.0_2023-06-06/add-decomposedfs-metadata-command.md diff --git a/changelog/3.0.0_2023-05-22/add-eventhistory-debug.md b/changelog/3.0.0_2023-06-06/add-eventhistory-debug.md similarity index 100% rename from changelog/3.0.0_2023-05-22/add-eventhistory-debug.md rename to changelog/3.0.0_2023-06-06/add-eventhistory-debug.md diff --git a/changelog/3.0.0_2023-05-22/add-global-vars-extrator.md b/changelog/3.0.0_2023-06-06/add-global-vars-extrator.md similarity index 100% rename from changelog/3.0.0_2023-05-22/add-global-vars-extrator.md rename to changelog/3.0.0_2023-06-06/add-global-vars-extrator.md diff --git a/changelog/3.0.0_2023-05-22/add-html-email-templates.md b/changelog/3.0.0_2023-06-06/add-html-email-templates.md similarity index 100% rename from changelog/3.0.0_2023-05-22/add-html-email-templates.md rename to changelog/3.0.0_2023-06-06/add-html-email-templates.md diff --git a/changelog/3.0.0_2023-05-22/add-notifications-debug-server.md b/changelog/3.0.0_2023-06-06/add-notifications-debug-server.md similarity index 100% rename from changelog/3.0.0_2023-05-22/add-notifications-debug-server.md rename to changelog/3.0.0_2023-06-06/add-notifications-debug-server.md diff --git a/changelog/3.0.0_2023-05-22/add-parameters.md b/changelog/3.0.0_2023-06-06/add-parameters.md similarity index 100% rename from changelog/3.0.0_2023-05-22/add-parameters.md rename to changelog/3.0.0_2023-06-06/add-parameters.md diff --git a/changelog/3.0.0_2023-05-22/add-webfinger.md b/changelog/3.0.0_2023-06-06/add-webfinger.md similarity index 100% rename from changelog/3.0.0_2023-05-22/add-webfinger.md rename to changelog/3.0.0_2023-06-06/add-webfinger.md diff --git a/changelog/3.0.0_2023-05-22/allow-graph-selected-updates.md b/changelog/3.0.0_2023-06-06/allow-graph-selected-updates.md similarity index 100% rename from changelog/3.0.0_2023-05-22/allow-graph-selected-updates.md rename to changelog/3.0.0_2023-06-06/allow-graph-selected-updates.md diff --git a/changelog/3.0.0_2023-05-22/async-postprocessing.md b/changelog/3.0.0_2023-06-06/async-postprocessing.md similarity index 100% rename from changelog/3.0.0_2023-05-22/async-postprocessing.md rename to changelog/3.0.0_2023-06-06/async-postprocessing.md diff --git a/changelog/3.0.0_2023-05-22/automate-md-creation.md b/changelog/3.0.0_2023-06-06/automate-md-creation.md similarity index 100% rename from changelog/3.0.0_2023-05-22/automate-md-creation.md rename to changelog/3.0.0_2023-06-06/automate-md-creation.md diff --git a/changelog/3.0.0_2023-05-22/av-more-logging.md b/changelog/3.0.0_2023-06-06/av-more-logging.md similarity index 100% rename from changelog/3.0.0_2023-05-22/av-more-logging.md rename to changelog/3.0.0_2023-06-06/av-more-logging.md diff --git a/changelog/3.0.0_2023-05-22/bad-request-on-gdpr-endpoint.md b/changelog/3.0.0_2023-06-06/bad-request-on-gdpr-endpoint.md similarity index 100% rename from changelog/3.0.0_2023-05-22/bad-request-on-gdpr-endpoint.md rename to changelog/3.0.0_2023-06-06/bad-request-on-gdpr-endpoint.md diff --git a/changelog/3.0.0_2023-05-22/branding-api.md b/changelog/3.0.0_2023-06-06/branding-api.md similarity index 100% rename from changelog/3.0.0_2023-05-22/branding-api.md rename to changelog/3.0.0_2023-06-06/branding-api.md diff --git a/changelog/3.0.0_2023-05-22/bugfix-disassociate-users-from-deleted-school.md b/changelog/3.0.0_2023-06-06/bugfix-disassociate-users-from-deleted-school.md similarity index 100% rename from changelog/3.0.0_2023-05-22/bugfix-disassociate-users-from-deleted-school.md rename to changelog/3.0.0_2023-06-06/bugfix-disassociate-users-from-deleted-school.md diff --git a/changelog/3.0.0_2023-05-22/bump-go-ldap-version.md b/changelog/3.0.0_2023-06-06/bump-go-ldap-version.md similarity index 100% rename from changelog/3.0.0_2023-05-22/bump-go-ldap-version.md rename to changelog/3.0.0_2023-06-06/bump-go-ldap-version.md diff --git a/changelog/3.0.0_2023-05-22/bump-libregraph.md b/changelog/3.0.0_2023-06-06/bump-libregraph.md similarity index 100% rename from changelog/3.0.0_2023-05-22/bump-libregraph.md rename to changelog/3.0.0_2023-06-06/bump-libregraph.md diff --git a/changelog/3.0.0_2023-05-22/bump-lico.md b/changelog/3.0.0_2023-06-06/bump-lico.md similarity index 100% rename from changelog/3.0.0_2023-05-22/bump-lico.md rename to changelog/3.0.0_2023-06-06/bump-lico.md diff --git a/changelog/3.0.0_2023-05-22/bump-reva.md b/changelog/3.0.0_2023-06-06/bump-reva.md similarity index 86% rename from changelog/3.0.0_2023-05-22/bump-reva.md rename to changelog/3.0.0_2023-06-06/bump-reva.md index 3b532d858..0040e22e1 100644 --- a/changelog/3.0.0_2023-05-22/bump-reva.md +++ b/changelog/3.0.0_2023-06-06/bump-reva.md @@ -1,11 +1,26 @@ -Enhancement: Update Reva to version 2.13.3 and beyond +Enhancement: Update Reva to version 2.14.0 -Changelog for reva unreleased +Changelog for reva 2.14.0 (2023-06-05) ======================================= +* Bugfix [cs3org/reva#3919](https://github.com/cs3org/reva/pull/3919): We added missing timestamps to events +* Bugfix [cs3org/reva#3911](https://github.com/cs3org/reva/pull/3911): Clean IDCache properly +* Bugfix [cs3org/reva#3896](https://github.com/cs3org/reva/pull/3896): Do not lose old revisions when overwriting a file during copy +* Bugfix [cs3org/reva#3918](https://github.com/cs3org/reva/pull/3918): Dont enumerate users +* Bugfix [cs3org/reva#3902](https://github.com/cs3org/reva/pull/3902): Do not try to use the cache for empty node +* Bugfix [cs3org/reva#3877](https://github.com/cs3org/reva/pull/3877): Empty exact list while searching for a sharee +* Bugfix [cs3org/reva#3906](https://github.com/cs3org/reva/pull/3906): Fix preflight requests +* Bugfix [cs3org/reva#3934](https://github.com/cs3org/reva/pull/3934): Fix the space editor permissions * Bugfix [cs3org/reva#3899](https://github.com/cs3org/reva/pull/3899): Harden uploads +* Bugfix [cs3org/reva#3917](https://github.com/cs3org/reva/pull/3917): Prevent last space manager from leaving +* Bugfix [cs3org/reva#3866](https://github.com/cs3org/reva/pull/3866): Fix public link lookup performance +* Bugfix [cs3org/reva#3904](https://github.com/cs3org/reva/pull/3904): Improve performance of directory listings * Enhancement [cs3org/reva#3893](https://github.com/cs3org/reva/pull/3893): Cleanup Space Delete permissions +* Enhancement [cs3org/reva#3894](https://github.com/cs3org/reva/pull/3894): Fix err when the user share the locked file +* Enhancement [cs3org/reva#3913](https://github.com/cs3org/reva/pull/3913): Introduce FullTextSearch Capability * Enhancement [cs3org/reva#3898](https://github.com/cs3org/reva/pull/3898): Add Graph User capabilities +* Enhancement [cs3org/reva#3496](https://github.com/cs3org/reva/pull/3496): Add otlp tracing exporter +* Enhancement [cs3org/reva#3922](https://github.com/cs3org/reva/pull/3922): Rename permissions Changelog for reva 2.13.3 (2023-05-17) ======================================= @@ -22,8 +37,6 @@ Changelog for reva 2.13.3 (2023-05-17) * Enhancement [cs3org/reva#3865](https://github.com/cs3org/reva/pull/3865): Remove unneccessary code from gateway * Enhancement [cs3org/reva#3895](https://github.com/cs3org/reva/pull/3895): Add missing expiry date to shares -https://github.com/owncloud/ocis/pull/6305 -https://github.com/owncloud/ocis/pull/6339 Changelog for reva 2.13.2 (2023-05-08) ======================================= @@ -125,5 +138,11 @@ Changelog for reva 2.13.0 (2023-05-02) * Enhancement [cs3org/reva#3739](https://github.com/cs3org/reva/pull/3739): Try to rename uploaded files to their final position * Enhancement [cs3org/reva#3610](https://github.com/cs3org/reva/pull/3610): Walk and log chi routes + +https://github.com/owncloud/ocis/pull/6448 +https://github.com/owncloud/ocis/pull/6447 +https://github.com/owncloud/ocis/pull/6381 +https://github.com/owncloud/ocis/pull/6305 +https://github.com/owncloud/ocis/pull/6339 https://github.com/owncloud/ocis/pull/6205 https://github.com/owncloud/ocis/pull/6186 diff --git a/changelog/3.0.0_2023-05-22/change-cache-configuration.md b/changelog/3.0.0_2023-06-06/change-cache-configuration.md similarity index 100% rename from changelog/3.0.0_2023-05-22/change-cache-configuration.md rename to changelog/3.0.0_2023-06-06/change-cache-configuration.md diff --git a/changelog/3.0.0_2023-05-22/change-remove-settings-ui.md b/changelog/3.0.0_2023-06-06/change-remove-settings-ui.md similarity index 100% rename from changelog/3.0.0_2023-05-22/change-remove-settings-ui.md rename to changelog/3.0.0_2023-06-06/change-remove-settings-ui.md diff --git a/changelog/3.0.0_2023-05-22/collect-global-envvars.md b/changelog/3.0.0_2023-06-06/collect-global-envvars.md similarity index 100% rename from changelog/3.0.0_2023-05-22/collect-global-envvars.md rename to changelog/3.0.0_2023-06-06/collect-global-envvars.md diff --git a/changelog/3.0.0_2023-05-22/configure-bundles.md b/changelog/3.0.0_2023-06-06/configure-bundles.md similarity index 100% rename from changelog/3.0.0_2023-05-22/configure-bundles.md rename to changelog/3.0.0_2023-06-06/configure-bundles.md diff --git a/changelog/3.0.0_2023-05-22/configure-grpc-in-ocs.md b/changelog/3.0.0_2023-06-06/configure-grpc-in-ocs.md similarity index 100% rename from changelog/3.0.0_2023-05-22/configure-grpc-in-ocs.md rename to changelog/3.0.0_2023-06-06/configure-grpc-in-ocs.md diff --git a/changelog/unreleased/default-ldap-write-enabled.md b/changelog/3.0.0_2023-06-06/default-ldap-write-enabled.md similarity index 100% rename from changelog/unreleased/default-ldap-write-enabled.md rename to changelog/3.0.0_2023-06-06/default-ldap-write-enabled.md diff --git a/changelog/3.0.0_2023-05-22/disable-notifications.md b/changelog/3.0.0_2023-06-06/disable-notifications.md similarity index 100% rename from changelog/3.0.0_2023-05-22/disable-notifications.md rename to changelog/3.0.0_2023-06-06/disable-notifications.md diff --git a/changelog/3.0.0_2023-05-22/do-not-share-versions.md b/changelog/3.0.0_2023-06-06/do-not-share-versions.md similarity index 100% rename from changelog/3.0.0_2023-05-22/do-not-share-versions.md rename to changelog/3.0.0_2023-06-06/do-not-share-versions.md diff --git a/changelog/3.0.0_2023-05-22/drive-group-permissions.md b/changelog/3.0.0_2023-06-06/drive-group-permissions.md similarity index 100% rename from changelog/3.0.0_2023-05-22/drive-group-permissions.md rename to changelog/3.0.0_2023-06-06/drive-group-permissions.md diff --git a/changelog/3.0.0_2023-05-22/enhancement-add-configurable-group-members-patch-limit.md b/changelog/3.0.0_2023-06-06/enhancement-add-configurable-group-members-patch-limit.md similarity index 100% rename from changelog/3.0.0_2023-05-22/enhancement-add-configurable-group-members-patch-limit.md rename to changelog/3.0.0_2023-06-06/enhancement-add-configurable-group-members-patch-limit.md diff --git a/changelog/3.0.0_2023-05-22/enhancement-change-username.md b/changelog/3.0.0_2023-06-06/enhancement-change-username.md similarity index 100% rename from changelog/3.0.0_2023-05-22/enhancement-change-username.md rename to changelog/3.0.0_2023-06-06/enhancement-change-username.md diff --git a/changelog/3.0.0_2023-05-22/enhancement-identity-set-display-name.md b/changelog/3.0.0_2023-06-06/enhancement-identity-set-display-name.md similarity index 100% rename from changelog/3.0.0_2023-05-22/enhancement-identity-set-display-name.md rename to changelog/3.0.0_2023-06-06/enhancement-identity-set-display-name.md diff --git a/changelog/3.0.0_2023-05-22/enhancement-ldap-group-create-basedn.md b/changelog/3.0.0_2023-06-06/enhancement-ldap-group-create-basedn.md similarity index 100% rename from changelog/3.0.0_2023-05-22/enhancement-ldap-group-create-basedn.md rename to changelog/3.0.0_2023-06-06/enhancement-ldap-group-create-basedn.md diff --git a/changelog/3.0.0_2023-05-22/enhancement-memlimit.md b/changelog/3.0.0_2023-06-06/enhancement-memlimit.md similarity index 100% rename from changelog/3.0.0_2023-05-22/enhancement-memlimit.md rename to changelog/3.0.0_2023-06-06/enhancement-memlimit.md diff --git a/changelog/3.0.0_2023-05-22/enhancement-name-attributes.md b/changelog/3.0.0_2023-06-06/enhancement-name-attributes.md similarity index 100% rename from changelog/3.0.0_2023-05-22/enhancement-name-attributes.md rename to changelog/3.0.0_2023-06-06/enhancement-name-attributes.md diff --git a/changelog/3.0.0_2023-05-22/enhancement-search.md b/changelog/3.0.0_2023-06-06/enhancement-search.md similarity index 100% rename from changelog/3.0.0_2023-05-22/enhancement-search.md rename to changelog/3.0.0_2023-06-06/enhancement-search.md diff --git a/changelog/3.0.0_2023-05-22/enhancement-tags.md b/changelog/3.0.0_2023-06-06/enhancement-tags.md similarity index 100% rename from changelog/3.0.0_2023-05-22/enhancement-tags.md rename to changelog/3.0.0_2023-06-06/enhancement-tags.md diff --git a/changelog/3.0.0_2023-05-22/enhancement-user-disable.md b/changelog/3.0.0_2023-06-06/enhancement-user-disable.md similarity index 100% rename from changelog/3.0.0_2023-05-22/enhancement-user-disable.md rename to changelog/3.0.0_2023-06-06/enhancement-user-disable.md diff --git a/changelog/3.0.0_2023-05-22/enhancement-web-config-additions.md b/changelog/3.0.0_2023-06-06/enhancement-web-config-additions.md similarity index 100% rename from changelog/3.0.0_2023-05-22/enhancement-web-config-additions.md rename to changelog/3.0.0_2023-06-06/enhancement-web-config-additions.md diff --git a/changelog/3.0.0_2023-05-22/event-history.md b/changelog/3.0.0_2023-06-06/event-history.md similarity index 100% rename from changelog/3.0.0_2023-05-22/event-history.md rename to changelog/3.0.0_2023-06-06/event-history.md diff --git a/changelog/3.0.0_2023-05-22/expiration-notifications.md b/changelog/3.0.0_2023-06-06/expiration-notifications.md similarity index 100% rename from changelog/3.0.0_2023-05-22/expiration-notifications.md rename to changelog/3.0.0_2023-06-06/expiration-notifications.md diff --git a/changelog/3.0.0_2023-06-06/fix-disable-user.md b/changelog/3.0.0_2023-06-06/fix-disable-user.md new file mode 100644 index 000000000..fa8631581 --- /dev/null +++ b/changelog/3.0.0_2023-06-06/fix-disable-user.md @@ -0,0 +1,5 @@ +Bugfix: Fix error message when disabling users + +When we disable users by adding them to a group we do not need to update the user entry. + +https://github.com/owncloud/ocis/pull/6435 diff --git a/changelog/3.0.0_2023-05-22/fix-duplicated-demouser-roles.md b/changelog/3.0.0_2023-06-06/fix-duplicated-demouser-roles.md similarity index 100% rename from changelog/3.0.0_2023-05-22/fix-duplicated-demouser-roles.md rename to changelog/3.0.0_2023-06-06/fix-duplicated-demouser-roles.md diff --git a/changelog/3.0.0_2023-06-06/fix-email-xsite-scripting.md b/changelog/3.0.0_2023-06-06/fix-email-xsite-scripting.md new file mode 100644 index 000000000..74f3f163e --- /dev/null +++ b/changelog/3.0.0_2023-06-06/fix-email-xsite-scripting.md @@ -0,0 +1,6 @@ +Enhancement: Fix to prevent the email X-Site scripting + +Fix to prevent the email notification X-Site scripting + +https://github.com/owncloud/ocis/pull/6429 +https://github.com/owncloud/ocis/issues/6411 diff --git a/changelog/3.0.0_2023-06-06/fix-empty-exact-list.md b/changelog/3.0.0_2023-06-06/fix-empty-exact-list.md new file mode 100644 index 000000000..e80474d97 --- /dev/null +++ b/changelog/3.0.0_2023-06-06/fix-empty-exact-list.md @@ -0,0 +1,7 @@ +Bugfix: Empty exact list while searching for a sharee + +We fixed a bug in the sharing api, it always returns an empty exact list while searching for a sharee + +https://github.com/owncloud/ocis/pull/6398 +https://github.com/cs3org/reva/pull/3877 +https://github.com/owncloud/ocis/issues/4265 diff --git a/changelog/3.0.0_2023-05-22/fix-graph-ttl-cache.md b/changelog/3.0.0_2023-06-06/fix-graph-ttl-cache.md similarity index 100% rename from changelog/3.0.0_2023-05-22/fix-graph-ttl-cache.md rename to changelog/3.0.0_2023-06-06/fix-graph-ttl-cache.md diff --git a/changelog/3.0.0_2023-05-22/fix-group-name-changes.md b/changelog/3.0.0_2023-06-06/fix-group-name-changes.md similarity index 100% rename from changelog/3.0.0_2023-05-22/fix-group-name-changes.md rename to changelog/3.0.0_2023-06-06/fix-group-name-changes.md diff --git a/changelog/3.0.0_2023-05-22/fix-idp-sub-recreation.md b/changelog/3.0.0_2023-06-06/fix-idp-sub-recreation.md similarity index 65% rename from changelog/3.0.0_2023-05-22/fix-idp-sub-recreation.md rename to changelog/3.0.0_2023-06-06/fix-idp-sub-recreation.md index da1d954e1..8f2df658f 100644 --- a/changelog/3.0.0_2023-05-22/fix-idp-sub-recreation.md +++ b/changelog/3.0.0_2023-06-06/fix-idp-sub-recreation.md @@ -7,12 +7,7 @@ use the user's unique id (`owncloudUUID` by default) for computing the `sub` claim. So that user's recreated with the same name will be treated as different users by the IDP. -This changed also the default values of the 'PROXY_USER_OIDC_CLAIM' and -'PROXY_USER_CS3_CLAIM' settings map the IDP users to CS3 user by the userid in -the default setup. The old behavior of matching by username can be achieved -byt setting 'PROXY_USER_OIDC_CLAIM=preferred_username' and -'PROXY_USER_CS3_CLAIM=username'. - https://github.com/owncloud/ocis/issues/904 https://github.com/owncloud/ocis/pull/6326 https://github.com/owncloud/ocis/pull/6338 +https://github.com/owncloud/ocis/pull/6420 diff --git a/changelog/3.0.0_2023-05-22/fix-leaks-existence.md b/changelog/3.0.0_2023-06-06/fix-leaks-existence.md similarity index 100% rename from changelog/3.0.0_2023-05-22/fix-leaks-existence.md rename to changelog/3.0.0_2023-06-06/fix-leaks-existence.md diff --git a/changelog/3.0.0_2023-05-22/fix-oidc-auth-cache.md b/changelog/3.0.0_2023-06-06/fix-oidc-auth-cache.md similarity index 100% rename from changelog/3.0.0_2023-05-22/fix-oidc-auth-cache.md rename to changelog/3.0.0_2023-06-06/fix-oidc-auth-cache.md diff --git a/changelog/3.0.0_2023-05-22/fix-omitempty-givenName.md b/changelog/3.0.0_2023-06-06/fix-omitempty-givenName.md similarity index 100% rename from changelog/3.0.0_2023-05-22/fix-omitempty-givenName.md rename to changelog/3.0.0_2023-06-06/fix-omitempty-givenName.md diff --git a/changelog/3.0.0_2023-05-22/fix-postprocessing.md b/changelog/3.0.0_2023-06-06/fix-postprocessing.md similarity index 100% rename from changelog/3.0.0_2023-05-22/fix-postprocessing.md rename to changelog/3.0.0_2023-06-06/fix-postprocessing.md diff --git a/changelog/unreleased/fix-preview-gif.md b/changelog/3.0.0_2023-06-06/fix-preview-gif.md similarity index 100% rename from changelog/unreleased/fix-preview-gif.md rename to changelog/3.0.0_2023-06-06/fix-preview-gif.md diff --git a/changelog/3.0.0_2023-05-22/fix-search-reindexing-performance-regression.md b/changelog/3.0.0_2023-06-06/fix-search-reindexing-performance-regression.md similarity index 100% rename from changelog/3.0.0_2023-05-22/fix-search-reindexing-performance-regression.md rename to changelog/3.0.0_2023-06-06/fix-search-reindexing-performance-regression.md diff --git a/changelog/3.0.0_2023-05-22/fix-search-tags.md b/changelog/3.0.0_2023-06-06/fix-search-tags.md similarity index 100% rename from changelog/3.0.0_2023-05-22/fix-search-tags.md rename to changelog/3.0.0_2023-06-06/fix-search-tags.md diff --git a/changelog/unreleased/fix-share-locked-file.md b/changelog/3.0.0_2023-06-06/fix-share-locked-file.md similarity index 79% rename from changelog/unreleased/fix-share-locked-file.md rename to changelog/3.0.0_2023-06-06/fix-share-locked-file.md index 015ae3f92..f1a27137e 100644 --- a/changelog/unreleased/fix-share-locked-file.md +++ b/changelog/3.0.0_2023-06-06/fix-share-locked-file.md @@ -2,5 +2,5 @@ Enhancement: Fix err when the user share the locked file Fix unexpected behavior when the user try to share the locked file -https://github.com/owncloud/ocis/pull/6357 +https://github.com/owncloud/ocis/pull/6358 https://github.com/owncloud/ocis/issues/6197 diff --git a/changelog/3.0.0_2023-05-22/fix-status-code-appRoleAssignments.md b/changelog/3.0.0_2023-06-06/fix-status-code-appRoleAssignments.md similarity index 100% rename from changelog/3.0.0_2023-05-22/fix-status-code-appRoleAssignments.md rename to changelog/3.0.0_2023-06-06/fix-status-code-appRoleAssignments.md diff --git a/changelog/3.0.0_2023-05-22/fix-user-type-config.md b/changelog/3.0.0_2023-06-06/fix-user-type-config.md similarity index 100% rename from changelog/3.0.0_2023-05-22/fix-user-type-config.md rename to changelog/3.0.0_2023-06-06/fix-user-type-config.md diff --git a/changelog/3.0.0_2023-05-22/fix-userlog-panic.md b/changelog/3.0.0_2023-06-06/fix-userlog-panic.md similarity index 100% rename from changelog/3.0.0_2023-05-22/fix-userlog-panic.md rename to changelog/3.0.0_2023-06-06/fix-userlog-panic.md diff --git a/changelog/3.0.0_2023-05-22/fix-web-config.md b/changelog/3.0.0_2023-06-06/fix-web-config.md similarity index 100% rename from changelog/3.0.0_2023-05-22/fix-web-config.md rename to changelog/3.0.0_2023-06-06/fix-web-config.md diff --git a/changelog/unreleased/fulltextsearch-capability.md b/changelog/3.0.0_2023-06-06/fulltextsearch-capability.md similarity index 100% rename from changelog/unreleased/fulltextsearch-capability.md rename to changelog/3.0.0_2023-06-06/fulltextsearch-capability.md diff --git a/changelog/3.0.0_2023-05-22/gdpr-export.md b/changelog/3.0.0_2023-06-06/gdpr-export.md similarity index 100% rename from changelog/3.0.0_2023-05-22/gdpr-export.md rename to changelog/3.0.0_2023-06-06/gdpr-export.md diff --git a/changelog/3.0.0_2023-05-22/graph-education-errors.md b/changelog/3.0.0_2023-06-06/graph-education-errors.md similarity index 100% rename from changelog/3.0.0_2023-05-22/graph-education-errors.md rename to changelog/3.0.0_2023-06-06/graph-education-errors.md diff --git a/changelog/3.0.0_2023-05-22/graph-user-capabilities.md b/changelog/3.0.0_2023-06-06/graph-user-capabilities.md similarity index 100% rename from changelog/3.0.0_2023-05-22/graph-user-capabilities.md rename to changelog/3.0.0_2023-06-06/graph-user-capabilities.md diff --git a/changelog/unreleased/id-cache.md b/changelog/3.0.0_2023-06-06/id-cache.md similarity index 100% rename from changelog/unreleased/id-cache.md rename to changelog/3.0.0_2023-06-06/id-cache.md diff --git a/changelog/3.0.0_2023-05-22/list-permissions.md b/changelog/3.0.0_2023-06-06/list-permissions.md similarity index 100% rename from changelog/3.0.0_2023-05-22/list-permissions.md rename to changelog/3.0.0_2023-06-06/list-permissions.md diff --git a/changelog/3.0.0_2023-05-22/make-emails-translatable-via-transifex.md b/changelog/3.0.0_2023-06-06/make-emails-translatable-via-transifex.md similarity index 100% rename from changelog/3.0.0_2023-05-22/make-emails-translatable-via-transifex.md rename to changelog/3.0.0_2023-06-06/make-emails-translatable-via-transifex.md diff --git a/changelog/3.0.0_2023-05-22/more-cors-config.md b/changelog/3.0.0_2023-06-06/more-cors-config.md similarity index 100% rename from changelog/3.0.0_2023-05-22/more-cors-config.md rename to changelog/3.0.0_2023-06-06/more-cors-config.md diff --git a/changelog/3.0.0_2023-05-22/nats-debug.md b/changelog/3.0.0_2023-06-06/nats-debug.md similarity index 100% rename from changelog/3.0.0_2023-05-22/nats-debug.md rename to changelog/3.0.0_2023-06-06/nats-debug.md diff --git a/changelog/3.0.0_2023-05-22/no-notification-own-action.md b/changelog/3.0.0_2023-06-06/no-notification-own-action.md similarity index 100% rename from changelog/3.0.0_2023-05-22/no-notification-own-action.md rename to changelog/3.0.0_2023-06-06/no-notification-own-action.md diff --git a/changelog/3.0.0_2023-05-22/notify-about-policies.md b/changelog/3.0.0_2023-06-06/notify-about-policies.md similarity index 100% rename from changelog/3.0.0_2023-05-22/notify-about-policies.md rename to changelog/3.0.0_2023-06-06/notify-about-policies.md diff --git a/changelog/unreleased/otlp-tracing.md b/changelog/3.0.0_2023-06-06/otlp-tracing.md similarity index 100% rename from changelog/unreleased/otlp-tracing.md rename to changelog/3.0.0_2023-06-06/otlp-tracing.md diff --git a/changelog/3.0.0_2023-05-22/personal-data-export-capability.md b/changelog/3.0.0_2023-06-06/personal-data-export-capability.md similarity index 100% rename from changelog/3.0.0_2023-05-22/personal-data-export-capability.md rename to changelog/3.0.0_2023-06-06/personal-data-export-capability.md diff --git a/changelog/3.0.0_2023-05-22/policies-response-body.md b/changelog/3.0.0_2023-06-06/policies-response-body.md similarity index 100% rename from changelog/3.0.0_2023-05-22/policies-response-body.md rename to changelog/3.0.0_2023-06-06/policies-response-body.md diff --git a/changelog/3.0.0_2023-05-22/policies-service.md b/changelog/3.0.0_2023-06-06/policies-service.md similarity index 100% rename from changelog/3.0.0_2023-05-22/policies-service.md rename to changelog/3.0.0_2023-06-06/policies-service.md diff --git a/changelog/3.0.0_2023-05-22/populate-expanded-properties.md b/changelog/3.0.0_2023-06-06/populate-expanded-properties.md similarity index 100% rename from changelog/3.0.0_2023-05-22/populate-expanded-properties.md rename to changelog/3.0.0_2023-06-06/populate-expanded-properties.md diff --git a/changelog/3.0.0_2023-05-22/portrait-thumbnails.md b/changelog/3.0.0_2023-06-06/portrait-thumbnails.md similarity index 100% rename from changelog/3.0.0_2023-05-22/portrait-thumbnails.md rename to changelog/3.0.0_2023-06-06/portrait-thumbnails.md diff --git a/changelog/3.0.0_2023-05-22/postprocessing-config.md b/changelog/3.0.0_2023-06-06/postprocessing-config.md similarity index 100% rename from changelog/3.0.0_2023-05-22/postprocessing-config.md rename to changelog/3.0.0_2023-06-06/postprocessing-config.md diff --git a/changelog/3.0.0_2023-05-22/postprocessing-store.md b/changelog/3.0.0_2023-06-06/postprocessing-store.md similarity index 100% rename from changelog/3.0.0_2023-05-22/postprocessing-store.md rename to changelog/3.0.0_2023-06-06/postprocessing-store.md diff --git a/changelog/3.0.0_2023-05-22/public-link-password.md b/changelog/3.0.0_2023-06-06/public-link-password.md similarity index 100% rename from changelog/3.0.0_2023-05-22/public-link-password.md rename to changelog/3.0.0_2023-06-06/public-link-password.md diff --git a/changelog/3.0.0_2023-05-22/public-link-permission.md b/changelog/3.0.0_2023-06-06/public-link-permission.md similarity index 100% rename from changelog/3.0.0_2023-05-22/public-link-permission.md rename to changelog/3.0.0_2023-06-06/public-link-permission.md diff --git a/changelog/unreleased/remove-email-logo.md b/changelog/3.0.0_2023-06-06/remove-email-logo.md similarity index 100% rename from changelog/unreleased/remove-email-logo.md rename to changelog/3.0.0_2023-06-06/remove-email-logo.md diff --git a/changelog/3.0.0_2023-05-22/remove-quota-from-api-for-shares.md b/changelog/3.0.0_2023-06-06/remove-quota-from-api-for-shares.md similarity index 100% rename from changelog/3.0.0_2023-05-22/remove-quota-from-api-for-shares.md rename to changelog/3.0.0_2023-06-06/remove-quota-from-api-for-shares.md diff --git a/changelog/3.0.0_2023-06-06/rename-guest-role.md b/changelog/3.0.0_2023-06-06/rename-guest-role.md new file mode 100644 index 000000000..23053effd --- /dev/null +++ b/changelog/3.0.0_2023-06-06/rename-guest-role.md @@ -0,0 +1,6 @@ +Change: We renamed the guest role to user light + +We needed to rename the "Guest" role to "User Light" because the naming was creating confusions. The roles are not bound to a user type. + +https://github.com/owncloud/ocis/pull/6456 +https://github.com/owncloud/ocis/issues/6058 diff --git a/changelog/3.0.0_2023-06-06/rename-permissions.md b/changelog/3.0.0_2023-06-06/rename-permissions.md new file mode 100644 index 000000000..3519343be --- /dev/null +++ b/changelog/3.0.0_2023-06-06/rename-permissions.md @@ -0,0 +1,6 @@ +Enhancement: Rename permissions + +Rename permissions to be consistent and future proof + +https://github.com/cs3org/reva/pull/3922 +https://github.com/owncloud/ocis/pull/6418 diff --git a/changelog/3.0.0_2023-05-22/role-assignment-from-oidc.md b/changelog/3.0.0_2023-06-06/role-assignment-from-oidc.md similarity index 100% rename from changelog/3.0.0_2023-05-22/role-assignment-from-oidc.md rename to changelog/3.0.0_2023-06-06/role-assignment-from-oidc.md diff --git a/changelog/3.0.0_2023-05-22/role-quota.md b/changelog/3.0.0_2023-06-06/role-quota.md similarity index 100% rename from changelog/3.0.0_2023-05-22/role-quota.md rename to changelog/3.0.0_2023-06-06/role-quota.md diff --git a/changelog/3.0.0_2023-05-22/runtime.md b/changelog/3.0.0_2023-06-06/runtime.md similarity index 100% rename from changelog/3.0.0_2023-05-22/runtime.md rename to changelog/3.0.0_2023-06-06/runtime.md diff --git a/changelog/3.0.0_2023-05-22/set-project-space-quota-permission.md b/changelog/3.0.0_2023-06-06/set-project-space-quota-permission.md similarity index 100% rename from changelog/3.0.0_2023-05-22/set-project-space-quota-permission.md rename to changelog/3.0.0_2023-06-06/set-project-space-quota-permission.md diff --git a/changelog/3.0.0_2023-05-22/share-expiration.md b/changelog/3.0.0_2023-06-06/share-expiration.md similarity index 100% rename from changelog/3.0.0_2023-05-22/share-expiration.md rename to changelog/3.0.0_2023-06-06/share-expiration.md diff --git a/changelog/3.0.0_2023-05-22/space-management-permissions.md b/changelog/3.0.0_2023-06-06/space-management-permissions.md similarity index 100% rename from changelog/3.0.0_2023-05-22/space-management-permissions.md rename to changelog/3.0.0_2023-06-06/space-management-permissions.md diff --git a/changelog/3.0.0_2023-05-22/space-trashbin-purge-cli.md b/changelog/3.0.0_2023-06-06/space-trashbin-purge-cli.md similarity index 100% rename from changelog/3.0.0_2023-05-22/space-trashbin-purge-cli.md rename to changelog/3.0.0_2023-06-06/space-trashbin-purge-cli.md diff --git a/changelog/unreleased/trace-proxy-middlewares.md b/changelog/3.0.0_2023-06-06/trace-proxy-middlewares.md similarity index 100% rename from changelog/unreleased/trace-proxy-middlewares.md rename to changelog/3.0.0_2023-06-06/trace-proxy-middlewares.md diff --git a/changelog/unreleased/unify-cacert-envvars.md b/changelog/3.0.0_2023-06-06/unify-cacert-envvars.md similarity index 100% rename from changelog/unreleased/unify-cacert-envvars.md rename to changelog/3.0.0_2023-06-06/unify-cacert-envvars.md diff --git a/changelog/3.0.0_2023-05-22/update-roles.md b/changelog/3.0.0_2023-06-06/update-roles.md similarity index 100% rename from changelog/3.0.0_2023-05-22/update-roles.md rename to changelog/3.0.0_2023-06-06/update-roles.md diff --git a/changelog/3.0.0_2023-05-22/update-web-7.0.0-rc.37.md b/changelog/3.0.0_2023-06-06/update-web-7.0.0-rc.37.md similarity index 100% rename from changelog/3.0.0_2023-05-22/update-web-7.0.0-rc.37.md rename to changelog/3.0.0_2023-06-06/update-web-7.0.0-rc.37.md diff --git a/changelog/unreleased/update-web-7.0.0-rc.38.md b/changelog/3.0.0_2023-06-06/update-web-7.0.0-rc.38.md similarity index 100% rename from changelog/unreleased/update-web-7.0.0-rc.38.md rename to changelog/3.0.0_2023-06-06/update-web-7.0.0-rc.38.md diff --git a/changelog/3.0.0_2023-06-06/update-web-7.0.0.md b/changelog/3.0.0_2023-06-06/update-web-7.0.0.md new file mode 100644 index 000000000..7e0458162 --- /dev/null +++ b/changelog/3.0.0_2023-06-06/update-web-7.0.0.md @@ -0,0 +1,172 @@ +Enhancement: Update web to v7.0.0 + +Tags: web + +We updated ownCloud Web to v7.0.0. Please refer to the changelog (linked) for details on the web release. + +## Breaking changes +* BREAKING CHANGE for developers and admins in [owncloud/web#7948](https://github.com/owncloud/web/issues/7948): we've updated Vue.js to version 3. Existing apps that have not been updated to Vue.js version 3 will not be compatible anymore. +* BREAKING CHANGE for admins in [owncloud/web#8563](https://github.com/owncloud/web/pull/8563): we've introduced contrast colors in our theming. In case you have created a custom `theme.json` it needs to be adjusted accordingly: `-contrast` color values need to be added to all `swatches`, e.g. to `swatch-brand-contrast`. See https://owncloud.dev/clients/web/theming/#colors + +## Summary +* Bugfix [owncloud/web#6423](https://github.com/owncloud/web/issues/6423): Archiver in protected public links +* Bugfix [owncloud/web#6434](https://github.com/owncloud/web/issues/6434): Endless lazy loading indicator after sorting file table +* Bugfix [owncloud/web#6731](https://github.com/owncloud/web/issues/6731): Layout with long breadcrumb +* Bugfix [owncloud/web#6768](https://github.com/owncloud/web/issues/6768): Pagination after increasing items per page +* Bugfix [owncloud/web#7513](https://github.com/owncloud/web/issues/7513): Calendar popup position in right sidebar +* Bugfix [owncloud/web#7655](https://github.com/owncloud/web/issues/7655): Loading shares in deep nested folders +* Bugfix [owncloud/web#7925](https://github.com/owncloud/web/pull/7925): "Paste"-action without write permissions +* Bugfix [owncloud/web#7926](https://github.com/owncloud/web/pull/7926): Include spaces in the list info +* Bugfix [owncloud/web#7958](https://github.com/owncloud/web/pull/7958): Prevent deletion of own account +* Bugfix [owncloud/web#7966](https://github.com/owncloud/web/pull/7966): UI fixes for sorting and quickactions +* Bugfix [owncloud/web#7969](https://github.com/owncloud/web/pull/7969): Space quota not displayed after creation +* Bugfix [owncloud/web#8026](https://github.com/owncloud/web/pull/8026): Text editor appearance +* Bugfix [owncloud/web#8040](https://github.com/owncloud/web/pull/8040): Reverting versions for read-only shares +* Bugfix [owncloud/web#8045](https://github.com/owncloud/web/pull/8045): Resolving drives in search +* Bugfix [owncloud/web#8054](https://github.com/owncloud/web/issues/8054): Search repeating no results message +* Bugfix [owncloud/web#8058](https://github.com/owncloud/web/pull/8058): Current year selection in the date picker +* Bugfix [owncloud/web#8061](https://github.com/owncloud/web/pull/8061): Omit "page"-query in breadcrumb navigation +* Bugfix [owncloud/web#8080](https://github.com/owncloud/web/pull/8080): Left sidebar navigation item text flickers on transition +* Bugfix [owncloud/web#8081](https://github.com/owncloud/web/issues/8081): Space member disappearing +* Bugfix [owncloud/web#8083](https://github.com/owncloud/web/issues/8083): Re-using space images +* Bugfix [owncloud/web#8148](https://github.com/owncloud/web/issues/8148): Show space members despite deleted entries +* Bugfix [owncloud/web#8158](https://github.com/owncloud/web/issues/8158): Search bar input appearance +* Bugfix [owncloud/web#8265](https://github.com/owncloud/web/pull/8265): Application menu active display on hover +* Bugfix [owncloud/web#8276](https://github.com/owncloud/web/pull/8276): Loading additional user data +* Bugfix [owncloud/web#8300](https://github.com/owncloud/web/pull/8300): Re-loading space members panel +* Bugfix [owncloud/web#8326](https://github.com/owncloud/web/pull/8326): Editing users who never logged in +* Bugfix [owncloud/web#8340](https://github.com/owncloud/web/pull/8340): Cancel custom permissions +* Bugfix [owncloud/web#8411](https://github.com/owncloud/web/issues/8411): Drop menus with limited vertical screen space +* Bugfix [owncloud/web#8420](https://github.com/owncloud/web/issues/8420): Token renewal in vue router hash mode +* Bugfix [owncloud/web#8434](https://github.com/owncloud/web/issues/8434): Accessing route in admin-settings with insufficient permissions +* Bugfix [owncloud/web#8479](https://github.com/owncloud/web/issues/8479): "Show more"-action in shares panel +* Bugfix [owncloud/web#8480](https://github.com/owncloud/web/pull/8480): Paste action conflict dialog broken +* Bugfix [owncloud/web#8498](https://github.com/owncloud/web/pull/8498): PDF display issue - Update CSP object-src policy +* Bugfix [owncloud/web#8508](https://github.com/owncloud/web/pull/8508): Remove fuzzy search results +* Bugfix [owncloud/web#8523](https://github.com/owncloud/web/issues/8523): Space image upload +* Bugfix [owncloud/web#8549](https://github.com/owncloud/web/issues/8549): Batch context actions in admin settings +* Bugfix [owncloud/web#8554](https://github.com/owncloud/web/pull/8554): Height of dropdown no-option +* Bugfix [owncloud/web#8576](https://github.com/owncloud/web/pull/8576): De-duplicate event handling to prevent errors on Draw-io +* Bugfix [owncloud/web#8585](https://github.com/owncloud/web/issues/8585): Users without role assignment +* Bugfix [owncloud/web#8587](https://github.com/owncloud/web/issues/8587): Password enforced check for public links +* Bugfix [owncloud/web#8592](https://github.com/owncloud/web/issues/8592): Group members sorting +* Bugfix [owncloud/web#8694](https://github.com/owncloud/web/pull/8694): Broken re-login after logout +* Bugfix [owncloud/web#8695](https://github.com/owncloud/web/issues/8695): Open files in external app +* Bugfix [owncloud/web#8756](https://github.com/owncloud/web/pull/8756): Copy link to clipboard text +* Bugfix [owncloud/web#8758](https://github.com/owncloud/web/pull/8758): Preview controls colors +* Bugfix [owncloud/web#8776](https://github.com/owncloud/web/issues/8776): Selection reset on action click +* Bugfix [owncloud/web#8814](https://github.com/owncloud/web/pull/8814): Share recipient container exceed +* Bugfix [owncloud/web#8825](https://github.com/owncloud/web/pull/8825): Remove drop target in read-only folders +* Bugfix [owncloud/web#8827](https://github.com/owncloud/web/pull/8827): Opening context menu via keyboard +* Bugfix [owncloud/web#8834](https://github.com/owncloud/web/issues/8834): Hide upload hint in empty read-only folders +* Bugfix [owncloud/web#8864](https://github.com/owncloud/web/pull/8864): Public link empty password stays forever +* Bugfix [owncloud/web#8880](https://github.com/owncloud/web/issues/8880): Sidebar header after deleting resource +* Bugfix [owncloud/web#8928](https://github.com/owncloud/web/issues/8928): Infinite login redirect +* Bugfix [owncloud/web#8987](https://github.com/owncloud/web/pull/8987): Limit amount of concurrent tus requests +* Bugfix [owncloud/web#8992](https://github.com/owncloud/web/pull/8992): Personal space name after language change +* Bugfix [owncloud/web#9004](https://github.com/owncloud/web/issues/9004): Endless loading when encountering a public link error +* Bugfix [owncloud/web#9009](https://github.com/owncloud/web/pull/9009): Public link file previews +* Bugfix [owncloud/web#9014](https://github.com/owncloud/web/issues/9014): Empty file list after deleting resources +* Bugfix [owncloud/web#9015](https://github.com/owncloud/web/pull/9015): Prevent "virtual" spaces from being displayed in the UI +* Bugfix [owncloud/web#9020](https://github.com/owncloud/web/issues/9020): Sidebar for spaces on "Shared via link"-page +* Bugfix [owncloud/web#9022](https://github.com/owncloud/web/issues/9022): Spaces in search results +* Bugfix [owncloud/web#9030](https://github.com/owncloud/web/issues/9030): Share indicator loading after pasting resources +* Bugfix [owncloud/web#9050](https://github.com/owncloud/web/issues/9050): Preview app mime type detection +* Bugfix [owncloud/web#9061](https://github.com/owncloud/web/issues/9061): Resource not found and No content message at the same time +* Bugfix [owncloud/web#9080](https://github.com/owncloud/web/issues/9080): Incorrect pause state in upload info +* Bugfix [owncloud/web#9131](https://github.com/owncloud/web/pull/9131): Select all checkbox +* Bugfix [owncloud/web#9144](https://github.com/owncloud/web/pull/9144): Notifications link overflow +* Change [owncloud/web#6661](https://github.com/owncloud/web/issues/6661): Streamline new tab handling in extensions +* Change [owncloud/web#7948](https://github.com/owncloud/web/issues/7948): Update Vue to v3.2 +* Change [owncloud/web#8431](https://github.com/owncloud/web/pull/8431): Remove permission manager +* Change [owncloud/web#8455](https://github.com/owncloud/web/pull/8455): Configurable extension autosave +* Change [owncloud/web#8563](https://github.com/owncloud/web/pull/8563): Theme colors +* Enhancement [owncloud/web#6183](https://github.com/owncloud/web/issues/6183): Global loading indicator +* Enhancement [owncloud/web#7388](https://github.com/owncloud/web/pull/7388): Add tag support +* Enhancement [owncloud/web#7721](https://github.com/owncloud/web/issues/7721): Improve performance when loading folders and share indicators +* Enhancement [owncloud/web#7942](https://github.com/owncloud/web/pull/7942): Warn users when using unsupported browsers +* Enhancement [owncloud/web#7965](https://github.com/owncloud/web/pull/7965): Optional Contributor role and configurable resharing permissions +* Enhancement [owncloud/web#7968](https://github.com/owncloud/web/pull/7968): Group and user creation forms submit on enter +* Enhancement [owncloud/web#7976](https://github.com/owncloud/web/pull/7976): Add switch to enable condensed resource table +* Enhancement [owncloud/web#7977](https://github.com/owncloud/web/pull/7977): Introduce zoom and rotate to the preview app +* Enhancement [owncloud/web#7983](https://github.com/owncloud/web/pull/7983): Conflict dialog UX +* Enhancement [owncloud/web#7991](https://github.com/owncloud/web/pull/7991): Add tiles view for resource display +* Enhancement [owncloud/web#7994](https://github.com/owncloud/web/pull/7994): Introduce full screen mode to the preview app +* Enhancement [owncloud/web#7995](https://github.com/owncloud/web/pull/7995): Enable autoplay in the preview app +* Enhancement [owncloud/web#8008](https://github.com/owncloud/web/issues/8008): Don't open sidebar when copying quicklink +* Enhancement [owncloud/web#8021](https://github.com/owncloud/web/pull/8021): Access right sidebar panels via URL +* Enhancement [owncloud/web#8051](https://github.com/owncloud/web/pull/8051): Introduce image preloading to the preview app +* Enhancement [owncloud/web#8055](https://github.com/owncloud/web/pull/8055): Retry failed uploads on re-upload +* Enhancement [owncloud/web#8056](https://github.com/owncloud/web/pull/8056): Increase Searchbar height +* Enhancement [owncloud/web#8057](https://github.com/owncloud/web/pull/8057): Show text file icon for empty text files +* Enhancement [owncloud/web#8132](https://github.com/owncloud/web/pull/8132): Update libre-graph-api to v1.0 +* Enhancement [owncloud/web#8136](https://github.com/owncloud/web/pull/8136): Make clipboard copy available to more browsers +* Enhancement [owncloud/web#8161](https://github.com/owncloud/web/pull/8161): Space group members +* Enhancement [owncloud/web#8161](https://github.com/owncloud/web/pull/8161): Space group shares +* Enhancement [owncloud/web#8166](https://github.com/owncloud/web/issues/8166): Show upload speed +* Enhancement [owncloud/web#8175](https://github.com/owncloud/web/pull/8175): Rename "user management" app +* Enhancement [owncloud/web#8178](https://github.com/owncloud/web/pull/8178): Spaces list in admin settings +* Enhancement [owncloud/web#8261](https://github.com/owncloud/web/pull/8261): Admin settings users section uses graph api for role assignments +* Enhancement [owncloud/web#8279](https://github.com/owncloud/web/pull/8279): Move user group select to edit panel +* Enhancement [owncloud/web#8280](https://github.com/owncloud/web/pull/8280): Add support for multiple clients in `theme.json` +* Enhancement [owncloud/web#8294](https://github.com/owncloud/web/pull/8294): Move language selection to user account page +* Enhancement [owncloud/web#8306](https://github.com/owncloud/web/pull/8306): Show selectable groups only +* Enhancement [owncloud/web#8317](https://github.com/owncloud/web/pull/8317): Add context menu to groups +* Enhancement [owncloud/web#8320](https://github.com/owncloud/web/pull/8320): Space member expiration +* Enhancement [owncloud/web#8320](https://github.com/owncloud/web/pull/8320): Update SDK to v3.1.0-alpha.3 +* Enhancement [owncloud/web#8324](https://github.com/owncloud/web/pull/8324): Add context menu to users +* Enhancement [owncloud/web#8331](https://github.com/owncloud/web/pull/8331): Admin settings users section details improvement +* Enhancement [owncloud/web#8354](https://github.com/owncloud/web/issues/8354): Add `ItemFilter` component +* Enhancement [owncloud/web#8356](https://github.com/owncloud/web/pull/8356): Slight improvement of key up/down performance +* Enhancement [owncloud/web#8363](https://github.com/owncloud/web/issues/8363): Admin settings general section +* Enhancement [owncloud/web#8375](https://github.com/owncloud/web/pull/8375): Add appearance section in general settings +* Enhancement [owncloud/web#8377](https://github.com/owncloud/web/issues/8377): User group filter +* Enhancement [owncloud/web#8387](https://github.com/owncloud/web/pull/8387): Batch edit quota in admin panel +* Enhancement [owncloud/web#8398](https://github.com/owncloud/web/pull/8398): Use standardized layout for file/space action list +* Enhancement [owncloud/web#8425](https://github.com/owncloud/web/issues/8425): Add dark ownCloud logo +* Enhancement [owncloud/web#8432](https://github.com/owncloud/web/pull/8432): Inject customizations +* Enhancement [owncloud/web#8433](https://github.com/owncloud/web/pull/8433): User settings login field +* Enhancement [owncloud/web#8441](https://github.com/owncloud/web/pull/8441): Skeleton App +* Enhancement [owncloud/web#8449](https://github.com/owncloud/web/pull/8449): Configurable top bar +* Enhancement [owncloud/web#8450](https://github.com/owncloud/web/pull/8450): Rework notification bell +* Enhancement [owncloud/web#8455](https://github.com/owncloud/web/pull/8455): Autosave content changes in text editor +* Enhancement [owncloud/web#8473](https://github.com/owncloud/web/pull/8473): Update CERN links +* Enhancement [owncloud/web#8489](https://github.com/owncloud/web/pull/8489): Respect max quota +* Enhancement [owncloud/web#8492](https://github.com/owncloud/web/pull/8492): User role filter +* Enhancement [owncloud/web#8503](https://github.com/owncloud/web/issues/8503): Beautify file version list +* Enhancement [owncloud/web#8515](https://github.com/owncloud/web/pull/8515): Introduce trashbin overview +* Enhancement [owncloud/web#8518](https://github.com/owncloud/web/pull/8518): Make notifications work with oCIS +* Enhancement [owncloud/web#8541](https://github.com/owncloud/web/pull/8541): Public link permission `PublicLink.Write.all` +* Enhancement [owncloud/web#8553](https://github.com/owncloud/web/pull/8553): Add and remove users from groups batch actions +* Enhancement [owncloud/web#8554](https://github.com/owncloud/web/pull/8554): Beautify form inputs +* Enhancement [owncloud/web#8557](https://github.com/owncloud/web/issues/8557): Rework mobile navigation +* Enhancement [owncloud/web#8566](https://github.com/owncloud/web/pull/8566): QuickActions role configurable +* Enhancement [owncloud/web#8612](https://github.com/owncloud/web/issues/8612): Add `Accept-Language` header to all outgoing requests +* Enhancement [owncloud/web#8630](https://github.com/owncloud/web/pull/8630): Add logout url +* Enhancement [owncloud/web#8652](https://github.com/owncloud/web/pull/8652): Enable guest users +* Enhancement [owncloud/web#8711](https://github.com/owncloud/web/pull/8711): Remove placeholder, add customizable label +* Enhancement [owncloud/web#8713](https://github.com/owncloud/web/pull/8713): Context helper read more link configurable +* Enhancement [owncloud/web#8715](https://github.com/owncloud/web/pull/8715): Enable rename groups +* Enhancement [owncloud/web#8730](https://github.com/owncloud/web/pull/8730): Create Space from selection +* Enhancement [owncloud/web#8738](https://github.com/owncloud/web/issues/8738): GDPR export +* Enhancement [owncloud/web#8762](https://github.com/owncloud/web/pull/8762): Stop bootstrapping application earlier in anonymous contexts +* Enhancement [owncloud/web#8766](https://github.com/owncloud/web/pull/8766): Add support for read-only groups +* Enhancement [owncloud/web#8790](https://github.com/owncloud/web/pull/8790): Custom translations +* Enhancement [owncloud/web#8797](https://github.com/owncloud/web/pull/8797): Font family in theming +* Enhancement [owncloud/web#8806](https://github.com/owncloud/web/pull/8806): Preview app sorting +* Enhancement [owncloud/web#8820](https://github.com/owncloud/web/pull/8820): Adjust missing reshare permissions message +* Enhancement [owncloud/web#8822](https://github.com/owncloud/web/pull/8822): Fix quicklink icon alignment +* Enhancement [owncloud/web#8826](https://github.com/owncloud/web/pull/8826): Admin settings groups members panel +* Enhancement [owncloud/web#8868](https://github.com/owncloud/web/pull/8868): Respect user read-only configuration by the server +* Enhancement [owncloud/web#8876](https://github.com/owncloud/web/pull/8876): Update roles and permissions names, labels, texts and icons +* Enhancement [owncloud/web#8882](https://github.com/owncloud/web/pull/8882): Layout of Share role and expiration date dropdown +* Enhancement [owncloud/web#8883](https://github.com/owncloud/web/issues/8883): Webfinger redirect app +* Enhancement [owncloud/web#8898](https://github.com/owncloud/web/pull/8898): Rename "Quicklink" to "link" +* Enhancement [owncloud/web#8911](https://github.com/owncloud/web/pull/8911): Add notification setting to account page +* Enhancement [owncloud/web#9048](https://github.com/owncloud/web/issues/9048): Support pagination in admin settings app +* Enhancement [owncloud/web#9070](https://github.com/owncloud/web/pull/9070): Disable change password capability +* Enhancement [owncloud/web#9070](https://github.com/owncloud/web/pull/9070): Disable create user and delete user via capabilities +* Enhancement [owncloud/web#9076](https://github.com/owncloud/web/pull/9076): Show detailed error messages while upload fails + +https://github.com/owncloud/ocis/pull/6438 +https://github.com/owncloud/web/releases/tag/v7.0.0 diff --git a/changelog/3.0.0_2023-05-22/use-accept-language-header.md b/changelog/3.0.0_2023-06-06/use-accept-language-header.md similarity index 100% rename from changelog/3.0.0_2023-05-22/use-accept-language-header.md rename to changelog/3.0.0_2023-06-06/use-accept-language-header.md diff --git a/changelog/3.0.0_2023-05-22/use-gotext-master.md b/changelog/3.0.0_2023-06-06/use-gotext-master.md similarity index 100% rename from changelog/3.0.0_2023-05-22/use-gotext-master.md rename to changelog/3.0.0_2023-06-06/use-gotext-master.md diff --git a/changelog/3.0.0_2023-05-22/userlog-improvements.md b/changelog/3.0.0_2023-06-06/userlog-improvements.md similarity index 100% rename from changelog/3.0.0_2023-05-22/userlog-improvements.md rename to changelog/3.0.0_2023-06-06/userlog-improvements.md diff --git a/changelog/3.0.0_2023-05-22/userlog-service.md b/changelog/3.0.0_2023-06-06/userlog-service.md similarity index 100% rename from changelog/3.0.0_2023-05-22/userlog-service.md rename to changelog/3.0.0_2023-06-06/userlog-service.md diff --git a/changelog/3.0.0_2023-05-22/users-preferred-language-email.md b/changelog/3.0.0_2023-06-06/users-preferred-language-email.md similarity index 100% rename from changelog/3.0.0_2023-05-22/users-preferred-language-email.md rename to changelog/3.0.0_2023-06-06/users-preferred-language-email.md diff --git a/changelog/3.0.0_2023-05-22/web-config.md b/changelog/3.0.0_2023-06-06/web-config.md similarity index 100% rename from changelog/3.0.0_2023-05-22/web-config.md rename to changelog/3.0.0_2023-06-06/web-config.md diff --git a/changelog/3.0.0_2023-05-22/www-authenticate-header.md b/changelog/3.0.0_2023-06-06/www-authenticate-header.md similarity index 100% rename from changelog/3.0.0_2023-05-22/www-authenticate-header.md rename to changelog/3.0.0_2023-06-06/www-authenticate-header.md diff --git a/changelog/unreleased/add-decomposedfs-check-treesize-command.md b/changelog/unreleased/add-decomposedfs-check-treesize-command.md new file mode 100644 index 000000000..d26c9a237 --- /dev/null +++ b/changelog/unreleased/add-decomposedfs-check-treesize-command.md @@ -0,0 +1,6 @@ +Enhancement: add 'ocis decomposedfs check-treesize' command + +We added a 'ocis decomposedfs check-treesize' command for checking (and reparing) +the treesize metadata of a storage space. + +https://github.com/owncloud/ocis/pull/6556 diff --git a/changelog/unreleased/add-default-store-to-postprocessing.md b/changelog/unreleased/add-default-store-to-postprocessing.md new file mode 100644 index 000000000..b28a123dc --- /dev/null +++ b/changelog/unreleased/add-default-store-to-postprocessing.md @@ -0,0 +1,5 @@ +Bugfix: Add default store to postprocessing + +Postprocessing did not have a default store especially `database` and `table` are needed to talk to nats-js + +https://github.com/owncloud/ocis/pull/6578 diff --git a/changelog/unreleased/add-ids-to-graph-education-logging.md b/changelog/unreleased/add-ids-to-graph-education-logging.md new file mode 100644 index 000000000..d628aa845 --- /dev/null +++ b/changelog/unreleased/add-ids-to-graph-education-logging.md @@ -0,0 +1,5 @@ +Enhancement: Add IDs to graph resource logging + +Graph access logs were unsuable as they didn't contain IDs to match them to a request + +https://github.com/owncloud/ocis/pull/6593 diff --git a/changelog/unreleased/add-permissions-to-report.md b/changelog/unreleased/add-permissions-to-report.md new file mode 100644 index 000000000..8e7345f7c --- /dev/null +++ b/changelog/unreleased/add-permissions-to-report.md @@ -0,0 +1,5 @@ +Enhancement: Add permissions to report + +The webdav REPORT endpoint only returned permissions for personal spaces and shares. Now also for project spaces. + +https://github.com/owncloud/ocis/pull/6528 diff --git a/changelog/unreleased/add-remote-item-metadata.md b/changelog/unreleased/add-remote-item-metadata.md new file mode 100644 index 000000000..5d18cc879 --- /dev/null +++ b/changelog/unreleased/add-remote-item-metadata.md @@ -0,0 +1,5 @@ +Enhancement: Add more metadata to the remote item + +We added the drive alias, the space name and the relative path to the remote item. This is needed to resolve shared files directly on the source space. + +https://github.com/owncloud/ocis/pull/6300 diff --git a/changelog/unreleased/add-sse-endpoint.md b/changelog/unreleased/add-sse-endpoint.md new file mode 100644 index 000000000..6986359a4 --- /dev/null +++ b/changelog/unreleased/add-sse-endpoint.md @@ -0,0 +1,5 @@ +Enhancement: Add SSE Endpoint + +Add a server-sent events (sse) endpoint for the userlog service + +https://github.com/owncloud/ocis/pull/5998 diff --git a/changelog/unreleased/add-storage-id-to-audit-log.md b/changelog/unreleased/add-storage-id-to-audit-log.md new file mode 100644 index 000000000..dd9eb887a --- /dev/null +++ b/changelog/unreleased/add-storage-id-to-audit-log.md @@ -0,0 +1,6 @@ +Enhancement: We added the storage id to the audit log for spaces + +We added the storage id to the audit log for spaces + +https://github.com/owncloud/ocis/pull/6548 +https://github.com/owncloud/ocis/issues/3753 \ No newline at end of file diff --git a/changelog/unreleased/add-token-to-linkAccessed-event.md b/changelog/unreleased/add-token-to-linkAccessed-event.md new file mode 100644 index 000000000..9a4018e64 --- /dev/null +++ b/changelog/unreleased/add-token-to-linkAccessed-event.md @@ -0,0 +1,7 @@ +Bugfix: Add token to LinkAccessedEvent + +We added the link token to the LinkAccessedEvent + +https://github.com/owncloud/ocis/pull/6554 +https://github.com/cs3org/reva/pull/3993 +https://github.com/owncloud/ocis/issues/3753 \ No newline at end of file diff --git a/changelog/unreleased/add-web-oidc-post-logout-redirect-uri.md b/changelog/unreleased/add-web-oidc-post-logout-redirect-uri.md new file mode 100644 index 000000000..1cb9a8c9c --- /dev/null +++ b/changelog/unreleased/add-web-oidc-post-logout-redirect-uri.md @@ -0,0 +1,6 @@ +Enhancement: Make the post logout redirect uri configurable + +We added a config option to change the redirect uri after the logout action of the web client. + +https://github.com/owncloud/ocis/pull/6583 +https://github.com/owncloud/ocis/issues/6536 diff --git a/changelog/unreleased/app-provider-service-name.md b/changelog/unreleased/app-provider-service-name.md new file mode 100644 index 000000000..33cdf18bb --- /dev/null +++ b/changelog/unreleased/app-provider-service-name.md @@ -0,0 +1,5 @@ +Enhancement: Make the app provider service name configurable + +We needed to make the service name of the app provider configurable. This needs to be changed when using more than one app provider. Each of them needs be found by a unique service name. Possible examples are: `app-provider-collabora`, `app-provider-onlyoffice`, `app-provider-office365`. + +https://github.com/owncloud/ocis/pull/6482 diff --git a/changelog/unreleased/audit-events.md b/changelog/unreleased/audit-events.md new file mode 100644 index 000000000..b1bbef4f5 --- /dev/null +++ b/changelog/unreleased/audit-events.md @@ -0,0 +1,6 @@ +Enhancement: Add old & new values to audit logs + +We have added old & new values to the audit logs +We have added the missing events for role changes + +https://github.com/owncloud/ocis/pull/6537 \ No newline at end of file diff --git a/changelog/unreleased/audit-timestamps.md b/changelog/unreleased/audit-timestamps.md new file mode 100644 index 000000000..8da66d74a --- /dev/null +++ b/changelog/unreleased/audit-timestamps.md @@ -0,0 +1,6 @@ +Bugfix: Add missing timestamps + +We have added missing timestamps to the audit service + +https://github.com/owncloud/ocis/pull/6515 +https://github.com/owncloud/ocis/issues/3753 \ No newline at end of file diff --git a/changelog/unreleased/bump-reva.md b/changelog/unreleased/bump-reva.md index a8bbfd69b..b66dff365 100644 --- a/changelog/unreleased/bump-reva.md +++ b/changelog/unreleased/bump-reva.md @@ -1,5 +1,31 @@ -Enhancement: Bump Reva +Enhancement: Update reva -bumps reva +Update reva to latest edge -https://github.com/owncloud/ocis/pull/6381 +* Bugfix [cs3org/reva#3963](https://github.com/cs3org/reva/pull/3963): Treesize interger overflows +* Bugfix [cs3org/reva#3943](https://github.com/cs3org/reva/pull/3943): When removing metadata always use correct database and table +* Bugfix [cs3org/reva#3978](https://github.com/cs3org/reva/pull/3978): Decomposedfs no longer os.Stats when reading node metadata +* Bugfix [cs3org/reva#3959](https://github.com/cs3org/reva/pull/3959): Drop unnecessary stat +* Bugfix [cs3org/reva#3948](https://github.com/cs3org/reva/pull/3948): Handle the bad request status +* Bugfix [cs3org/reva#3955](https://github.com/cs3org/reva/pull/3955): Fix panic +* Bugfix [cs3org/reva#3977](https://github.com/cs3org/reva/pull/3977): Prevent direct access to trash items +* Bugfix [cs3org/reva#3933](https://github.com/cs3org/reva/pull/3933): Concurrently invalidate mtime cache in jsoncs3 share manager +* Bugfix [cs3org/reva#3985](https://github.com/cs3org/reva/pull/3985): Reduce jsoncs3 lock congestion +* Bugfix [cs3org/reva#3960](https://github.com/cs3org/reva/pull/3960): Add trace span details +* Bugfix [cs3org/reva#3951](https://github.com/cs3org/reva/pull/3951): Link context in metadata client +* Bugfix [cs3org/reva#3950](https://github.com/cs3org/reva/pull/3950): Use plain otel tracing in metadata client +* Bugfix [cs3org/reva#3975](https://github.com/cs3org/reva/pull/3975): Decomposedfs now resolves the parent without an os.Stat +* Change [cs3org/reva#3947](https://github.com/cs3org/reva/pull/3947): Bump golangci-lint to 1.51.2 +* Change [cs3org/reva#3945](https://github.com/cs3org/reva/pull/3945): Revert golangci-lint back to 1.50.1 +* Enhancement [cs3org/reva#3966](https://github.com/cs3org/reva/pull/3966): Add space metadata to ocs shares list +* Enhancement [cs3org/reva#3953](https://github.com/cs3org/reva/pull/3953): Client selector pool +* Enhancement [cs3org/reva#3941](https://github.com/cs3org/reva/pull/3941): Adding tracing for jsoncs3 +* Enhancement [cs3org/reva#3965](https://github.com/cs3org/reva/pull/3965): ResumePostprocessing Event +* Enhancement [cs3org/reva#3981](https://github.com/cs3org/reva/pull/3981): We have updated the UserFeatureChangedEvent to reflect value changes +* Enhancement [cs3org/reva#3986](https://github.com/cs3org/reva/pull/3986): Allow disabling wopi chat + +https://github.com/owncloud/ocis/pull/6529 +https://github.com/owncloud/ocis/pull/6544 +https://github.com/owncloud/ocis/pull/6507 +https://github.com/owncloud/ocis/pull/6572 +https://github.com/owncloud/ocis/pull/6590 diff --git a/changelog/unreleased/client-selector-pool.md b/changelog/unreleased/client-selector-pool.md new file mode 100644 index 000000000..975c9928e --- /dev/null +++ b/changelog/unreleased/client-selector-pool.md @@ -0,0 +1,8 @@ +Enhancement: Use reva client selectors + +Use reva client selectors instead of the static clients, this introduces the ocis service registry in reva. +The service discovery now resolves reva services by name and the client selectors pick a random registered service node. + +https://github.com/owncloud/ocis/pull/6452 +https://github.com/cs3org/reva/pull/3939 +https://github.com/cs3org/reva/pull/3953 diff --git a/changelog/unreleased/config-yaml-1-2.md b/changelog/unreleased/config-yaml-1-2.md new file mode 100644 index 000000000..17078f87c --- /dev/null +++ b/changelog/unreleased/config-yaml-1-2.md @@ -0,0 +1,13 @@ +Change: YAML configuration files are restricted to yaml-1.2 + +For parsing YAML based configuration files we utilize the gookit/config module. +That module has dropped support for older variants of the YAML format. It now +only supports the YAML 1.2 syntax. +If you're using yaml configuration files, please make sure to update your files +accordingly. The most significant change likely is that only the string `true` +and `false` (including `TRUE`,`True`, `FALSE` and `False`) are now parsed as +booleans. `Yes`, `On` and other values are not longer considered valid values +for booleans. + +https://github.com/owncloud/ocis/issues/6510 +https://github.com/owncloud/ocis/pull/6493 diff --git a/changelog/unreleased/disable-thumbnailer.md b/changelog/unreleased/disable-thumbnailer.md new file mode 100644 index 000000000..0d420494e --- /dev/null +++ b/changelog/unreleased/disable-thumbnailer.md @@ -0,0 +1,8 @@ +Enhancement: Thumbnails can be disabled for webdav & web now + +We added an env var `OCIS_DISABLE_PREVIEWS` to disable the thumbnails for web & webdav via a global setting. +For each service this behaviour can be disabled using the local env vars `WEB_OPTION_DISABLE_PREVIEWS` (old) +and `WEBDAV_DISABLE_PREVIEWS` (new). + +https://github.com/owncloud/ocis/pull/6577 +https://github.com/owncloud/ocis/issues/192 \ No newline at end of file diff --git a/changelog/unreleased/dont-connect-ldap-on-startup.md b/changelog/unreleased/dont-connect-ldap-on-startup.md new file mode 100644 index 000000000..52dcefa7f --- /dev/null +++ b/changelog/unreleased/dont-connect-ldap-on-startup.md @@ -0,0 +1,5 @@ +Bugfix: Don't connect to ldap on startup + +This leads to misleading error messages. Instead we connect on first request + +https://github.com/owncloud/ocis/pull/6565 diff --git a/changelog/unreleased/enhancement-add-companion-url-config.md b/changelog/unreleased/enhancement-add-companion-url-config.md new file mode 100644 index 000000000..1ea52cc9e --- /dev/null +++ b/changelog/unreleased/enhancement-add-companion-url-config.md @@ -0,0 +1,5 @@ +Enhancement: Add companion URL config + +Introduce a config to set the Uppy Companion URL via `WEB_OPTION_UPLOAD_COMPANION_URL`. + +https://github.com/owncloud/ocis/pull/6453 diff --git a/changelog/unreleased/enhancement-add-imprint-privacy-url-config.md b/changelog/unreleased/enhancement-add-imprint-privacy-url-config.md new file mode 100644 index 000000000..5b991e87b --- /dev/null +++ b/changelog/unreleased/enhancement-add-imprint-privacy-url-config.md @@ -0,0 +1,5 @@ +Enhancement: Add imprint and privacy url config + +Introduce a config to set the imprint and privacy url via `WEB_OPTION_IMPRINT_URL` and `WEB_OPTION_PRIVACY_URL`. + +https://github.com/owncloud/ocis/pull/6462 diff --git a/changelog/unreleased/enhancement-add-logged-out-url-config b/changelog/unreleased/enhancement-add-logged-out-url-config new file mode 100644 index 000000000..01cf4d816 --- /dev/null +++ b/changelog/unreleased/enhancement-add-logged-out-url-config @@ -0,0 +1,5 @@ +Enhancement: Add logged out url config + +Introduce a config to set the more button url on the access denied page in web via `WEB_OPTION_ACCESS_DENIED_HELP_URL`. + +https://github.com/owncloud/ocis/pull/6549 diff --git a/changelog/unreleased/enhancement-add-search-result-preview.md b/changelog/unreleased/enhancement-add-search-result-preview.md new file mode 100644 index 000000000..03d588892 --- /dev/null +++ b/changelog/unreleased/enhancement-add-search-result-preview.md @@ -0,0 +1,7 @@ +Enhancement: Add search result content preview and term highlighting + +The search result REPORT response now contains a content preview which highlights the search term. +The feature is only available if content extraction (e.g. apache tika) is configured + +https://github.com/owncloud/ocis/pull/6634 +https://github.com/owncloud/ocis/issues/6426 diff --git a/changelog/unreleased/fix-defaults-OCIS_LDAP_DISABLE_USER_MECHANISM.md b/changelog/unreleased/fix-defaults-OCIS_LDAP_DISABLE_USER_MECHANISM.md new file mode 100644 index 000000000..cc02630d6 --- /dev/null +++ b/changelog/unreleased/fix-defaults-OCIS_LDAP_DISABLE_USER_MECHANISM.md @@ -0,0 +1,6 @@ +Enhancement: Fix envvar defaults + +Defaults for the envvar OCIS_LDAP_DISABLE_USER_MECHANISM were not used consistently, correct is `attribute`. + +https://github.com/owncloud/ocis/pull/6516 +https://github.com/owncloud/ocis/issues/6513 diff --git a/changelog/unreleased/fix-dir-check.md b/changelog/unreleased/fix-dir-check.md new file mode 100644 index 000000000..2dcbb0cc0 --- /dev/null +++ b/changelog/unreleased/fix-dir-check.md @@ -0,0 +1,6 @@ +Enhancement: Skip if the simulink is a directory + +Skip the error if the simulink is pointed to a directory + +https://github.com/owncloud/ocis/pull/6574 +https://github.com/owncloud/ocis/issues/6567 diff --git a/changelog/unreleased/fix-drive-response.md b/changelog/unreleased/fix-drive-response.md new file mode 100644 index 000000000..67d28b621 --- /dev/null +++ b/changelog/unreleased/fix-drive-response.md @@ -0,0 +1,7 @@ +Bugfix: Handle the bad request status + +Handle the bad request status for the CreateStorageSpace function + +https://github.com/owncloud/ocis/pull/6469 +https://github.com/cs3org/reva/pull/3948 +https://github.com/owncloud/ocis/issues/6414 diff --git a/changelog/unreleased/fix-groupname-validation.md b/changelog/unreleased/fix-groupname-validation.md new file mode 100644 index 000000000..2a07a098a --- /dev/null +++ b/changelog/unreleased/fix-groupname-validation.md @@ -0,0 +1,6 @@ +Enhancement: Fix the groupname validation + +Fixed the ability to create a group with an empty name + +https://github.com/owncloud/ocis/pull/6490 +https://github.com/owncloud/ocis/issues/5050 diff --git a/changelog/unreleased/fix-oidc-role-assigner.md b/changelog/unreleased/fix-oidc-role-assigner.md new file mode 100644 index 000000000..3850abc1f --- /dev/null +++ b/changelog/unreleased/fix-oidc-role-assigner.md @@ -0,0 +1,8 @@ +Bugfix: Fix the oidc role assigner + +The update role method did not allow to set a role when the user already has two roles. +This makes no sense as the user is supposed to have only one and the update will fix that. +We still log an error level log to make the admin aware of that. + +https://github.com/owncloud/ocis/pull/6605 +https://github.com/owncloud/ocis/pull/6618 diff --git a/changelog/unreleased/fix-username-validation.md b/changelog/unreleased/fix-username-validation.md new file mode 100644 index 000000000..f7aed77a9 --- /dev/null +++ b/changelog/unreleased/fix-username-validation.md @@ -0,0 +1,6 @@ +Enhancement: Fix the username validation + +Fix the username validation when an admin update the user + +https://github.com/owncloud/ocis/pull/6437 +https://github.com/owncloud/ocis/issues/6436 diff --git a/changelog/unreleased/move-proxy-to-service-tracerprovider.md b/changelog/unreleased/move-proxy-to-service-tracerprovider.md new file mode 100644 index 000000000..f14979cb6 --- /dev/null +++ b/changelog/unreleased/move-proxy-to-service-tracerprovider.md @@ -0,0 +1,6 @@ +Enhancement: Move proxy to service tracerprovider + +This moves the proxy to initialise a service tracer provider at service initialisation time, +instead of using a package global tracer provider. + +https://github.com/owncloud/ocis/pull/6591 diff --git a/changelog/unreleased/retry-postprocessing.md b/changelog/unreleased/retry-postprocessing.md new file mode 100644 index 000000000..47e9c6bab --- /dev/null +++ b/changelog/unreleased/retry-postprocessing.md @@ -0,0 +1,5 @@ +Enhancement: Add functionality to retry postprocessing + +Adds a ctl command to manually retry failed postprocessing on uploads + +https://github.com/owncloud/ocis/pull/6500 diff --git a/changelog/unreleased/update-go-micro-kubernetes-registry-to-a179a6b8.md b/changelog/unreleased/update-go-micro-kubernetes-registry-to-a179a6b8.md new file mode 100644 index 000000000..cda54f6ee --- /dev/null +++ b/changelog/unreleased/update-go-micro-kubernetes-registry-to-a179a6b8.md @@ -0,0 +1,5 @@ +Enhancement: Update go-micro kubernetes registry + +https://github.com/owncloud/ocis/pull/6457 +https://github.com/go-micro/plugins/pull/114 +https://github.com/go-micro/plugins/pull/113 diff --git a/changelog/unreleased/update-web-7.0.1.md b/changelog/unreleased/update-web-7.0.1.md new file mode 100644 index 000000000..2f0e60849 --- /dev/null +++ b/changelog/unreleased/update-web-7.0.1.md @@ -0,0 +1,11 @@ +Enhancement: Update web to v7.0.1 + +Tags: web + +We updated ownCloud Web to v7.0.1. Please refer to the changelog (linked) for details on the web release. + +## Summary +* Bugfix [owncloud/web#9153](https://github.com/owncloud/web/pull/9153): Reduce space preloading + +https://github.com/owncloud/ocis/pull/6470 +https://github.com/owncloud/web/releases/tag/v7.0.1 diff --git a/changelog/unreleased/wopi-disable-chat.md b/changelog/unreleased/wopi-disable-chat.md new file mode 100644 index 000000000..a01575227 --- /dev/null +++ b/changelog/unreleased/wopi-disable-chat.md @@ -0,0 +1,5 @@ +Enhancement: Allow disabling wopi chat + +Add a configreva for the new reva disable-chat feature + +https://github.com/owncloud/ocis/pull/6544 diff --git a/composer.json b/composer.json index ddff8e347..cde884246 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "owncloud/ocis", "config" : { "platform": { - "php": "7.2" + "php": "7.4" }, "vendor-dir": "./vendor-php", "allow-plugins": { @@ -11,7 +11,7 @@ }, "require-dev": { "ext-simplexml": "*", - "bamarni/composer-bin-plugin": "^1.4" + "bamarni/composer-bin-plugin": "^1.8" }, "extra": { "bamarni-bin": { diff --git a/deployments/continuous-deployment-config/ocis_keycloak/released.yml b/deployments/continuous-deployment-config/ocis_keycloak/released.yml index 521f980e3..74764e9d6 100644 --- a/deployments/continuous-deployment-config/ocis_keycloak/released.yml +++ b/deployments/continuous-deployment-config/ocis_keycloak/released.yml @@ -31,7 +31,7 @@ env: INSECURE: "false" TRAEFIK_ACME_MAIL: wkloucek@owncloud.com - OCIS_DOCKER_TAG: 3.0.0-rc.4 + OCIS_DOCKER_TAG: 3.0.0 OCIS_DOMAIN: ocis.ocis-keycloak.released.owncloud.works KEYCLOAK_DOMAIN: keycloak.ocis-keycloak.released.owncloud.works COMPOSE_FILE: docker-compose.yml:monitoring_tracing/docker-compose-additions.yml diff --git a/deployments/continuous-deployment-config/ocis_ldap/released.yml b/deployments/continuous-deployment-config/ocis_ldap/released.yml index 259c06460..4d8fe5ead 100644 --- a/deployments/continuous-deployment-config/ocis_ldap/released.yml +++ b/deployments/continuous-deployment-config/ocis_ldap/released.yml @@ -31,7 +31,7 @@ env: INSECURE: "false" TRAEFIK_ACME_MAIL: wkloucek@owncloud.com - OCIS_DOCKER_TAG: 3.0.0-rc.4 + OCIS_DOCKER_TAG: 3.0.0 OCIS_DOMAIN: ocis.ocis-ldap.released.owncloud.works LDAP_MANAGER_DOMAIN: ldap.ocis-ldap.released.owncloud.works COMPOSE_FILE: docker-compose.yml:monitoring_tracing/docker-compose-additions.yml diff --git a/deployments/continuous-deployment-config/ocis_traefik/released.yml b/deployments/continuous-deployment-config/ocis_traefik/released.yml index 7a78691c5..08c71ef10 100644 --- a/deployments/continuous-deployment-config/ocis_traefik/released.yml +++ b/deployments/continuous-deployment-config/ocis_traefik/released.yml @@ -31,7 +31,7 @@ env: INSECURE: "false" TRAEFIK_ACME_MAIL: wkloucek@owncloud.com - OCIS_DOCKER_TAG: 3.0.0-rc.4 + OCIS_DOCKER_TAG: 3.0.0 OCIS_DOMAIN: ocis.ocis-traefik.released.owncloud.works DEMO_USERS: "true" INBUCKET_DOMAIN: mail.ocis-traefik.released.owncloud.works diff --git a/deployments/continuous-deployment-config/ocis_wopi/released.yml b/deployments/continuous-deployment-config/ocis_wopi/released.yml index 33da29c53..495168d7f 100644 --- a/deployments/continuous-deployment-config/ocis_wopi/released.yml +++ b/deployments/continuous-deployment-config/ocis_wopi/released.yml @@ -31,7 +31,7 @@ env: INSECURE: "false" TRAEFIK_ACME_MAIL: wkloucek@owncloud.com - OCIS_DOCKER_TAG: 3.0.0-rc.4 + OCIS_DOCKER_TAG: 3.0.0 OCIS_DOMAIN: ocis.ocis-wopi.released.owncloud.works WOPISERVER_DOMAIN: wopiserver.ocis-wopi.released.owncloud.works COLLABORA_DOMAIN: collabora.ocis-wopi.released.owncloud.works diff --git a/deployments/examples/oc10_ocis_parallel/config/keycloak/clients/ios_app.json b/deployments/examples/oc10_ocis_parallel/config/keycloak/clients/ios_app.json index 50991c9a4..3a1672dd9 100644 --- a/deployments/examples/oc10_ocis_parallel/config/keycloak/clients/ios_app.json +++ b/deployments/examples/oc10_ocis_parallel/config/keycloak/clients/ios_app.json @@ -7,8 +7,7 @@ "clientAuthenticatorType": "client-secret", "secret" : "KFeFWWEZO9TkisIQzR3fo7hfiMXlOpaqP8CFuTbSHzV1TUuGECglPxpiVKJfOXIx", "redirectUris": [ - "oc://ios.owncloud.com", - "oc.ios://ios.owncloud.com" + "oc://ios.owncloud.com" ], "webOrigins": [], "notBefore": 0, diff --git a/deployments/examples/oc10_ocis_parallel/config/keycloak/owncloud-realm.dist.json b/deployments/examples/oc10_ocis_parallel/config/keycloak/owncloud-realm.dist.json index 608b58bb3..d0214d531 100644 --- a/deployments/examples/oc10_ocis_parallel/config/keycloak/owncloud-realm.dist.json +++ b/deployments/examples/oc10_ocis_parallel/config/keycloak/owncloud-realm.dist.json @@ -579,7 +579,7 @@ "alwaysDisplayInConsole" : false, "clientAuthenticatorType" : "client-secret", "secret" : "KFeFWWEZO9TkisIQzR3fo7hfiMXlOpaqP8CFuTbSHzV1TUuGECglPxpiVKJfOXIx", - "redirectUris" : [ "oc://ios.owncloud.com", "oc.ios://ios.owncloud.com" ], + "redirectUris" : [ "oc://ios.owncloud.com" ], "webOrigins" : [ ], "notBefore" : 0, "bearerOnly" : false, diff --git a/deployments/examples/ocis_keycloak/config/keycloak/clients/ios_app.json b/deployments/examples/ocis_keycloak/config/keycloak/clients/ios_app.json index a0a5ce7db..8e753f6cc 100644 --- a/deployments/examples/ocis_keycloak/config/keycloak/clients/ios_app.json +++ b/deployments/examples/ocis_keycloak/config/keycloak/clients/ios_app.json @@ -7,8 +7,7 @@ "clientAuthenticatorType": "client-secret", "secret" : "KFeFWWEZO9TkisIQzR3fo7hfiMXlOpaqP8CFuTbSHzV1TUuGECglPxpiVKJfOXIx", "redirectUris": [ - "oc://ios.owncloud.com", - "oc.ios://ios.owncloud.com" + "oc://ios.owncloud.com" ], "webOrigins": [], "notBefore": 0, diff --git a/deployments/examples/ocis_keycloak/config/keycloak/ocis-realm.dist.json b/deployments/examples/ocis_keycloak/config/keycloak/ocis-realm.dist.json index db538df7d..cc71b8d91 100644 --- a/deployments/examples/ocis_keycloak/config/keycloak/ocis-realm.dist.json +++ b/deployments/examples/ocis_keycloak/config/keycloak/ocis-realm.dist.json @@ -980,8 +980,7 @@ "clientAuthenticatorType": "client-secret", "secret": "KFeFWWEZO9TkisIQzR3fo7hfiMXlOpaqP8CFuTbSHzV1TUuGECglPxpiVKJfOXIx", "redirectUris": [ - "oc://ios.owncloud.com", - "oc.ios://ios.owncloud.com" + "oc://ios.owncloud.com" ], "webOrigins": [], "notBefore": 0, diff --git a/deployments/examples/ocis_wopi/docker-compose.yml b/deployments/examples/ocis_wopi/docker-compose.yml index d528251cb..31bb0818b 100644 --- a/deployments/examples/ocis_wopi/docker-compose.yml +++ b/deployments/examples/ocis_wopi/docker-compose.yml @@ -82,6 +82,8 @@ services: NOTIFICATIONS_SMTP_SENDER: oCIS notifications NOTIFICATIONS_SMTP_USERNAME: notifications@${OCIS_DOMAIN:-ocis.owncloud.test} NOTIFICATIONS_SMTP_INSECURE: "true" # the mail catcher uses self signed certificates + # make the registry available to the app provider containers + MICRO_REGISTRY: "mdns" volumes: - ./config/ocis/app-registry.yaml:/etc/ocis/app-registry.yaml - ocis-config:/etc/ocis @@ -103,9 +105,13 @@ services: ocis-net: command: app-provider server environment: - REVA_GATEWAY: ${REVA_GATEWAY:-ocis:9142} + # use the internal service name of the gateway + REVA_GATEWAY: ${REVA_GATEWAY:-com.owncloud.api.gateway} APP_PROVIDER_GRPC_ADDR: 0.0.0.0:9164 - APP_PROVIDER_EXTERNAL_ADDR: ocis-appprovider-collabora:9164 + # configure the service name to avoid collision with onlyoffice + APP_PROVIDER_SERVICE_NAME: app-provider-collabora + # use the internal service name + APP_PROVIDER_EXTERNAL_ADDR: com.owncloud.api.app-provider-collabora APP_PROVIDER_DRIVER: wopi APP_PROVIDER_WOPI_APP_NAME: Collabora APP_PROVIDER_WOPI_APP_ICON_URI: https://${COLLABORA_DOMAIN:-collabora.owncloud.test}/favicon.ico @@ -113,6 +119,8 @@ services: APP_PROVIDER_WOPI_INSECURE: "${INSECURE:-false}" APP_PROVIDER_WOPI_WOPI_SERVER_EXTERNAL_URL: https://${WOPISERVER_DOMAIN:-wopiserver.owncloud.test} APP_PROVIDER_WOPI_FOLDER_URL_BASE_URL: https://${OCIS_DOMAIN:-ocis.owncloud.test} + # share the registry with the ocis container + MICRO_REGISTRY: "mdns" volumes: - ocis-config:/etc/ocis logging: @@ -129,9 +137,13 @@ services: - /entrypoint-override.sh #command: app-provider server environment: - REVA_GATEWAY: ${REVA_GATEWAY:-ocis:9142} + # use the internal service name of the gateway + REVA_GATEWAY: ${REVA_GATEWAY:-com.owncloud.api.gateway} APP_PROVIDER_GRPC_ADDR: 0.0.0.0:9164 - APP_PROVIDER_EXTERNAL_ADDR: ocis-appprovider-onlyoffice:9164 + # configure the service name to avoid collision with collabora + APP_PROVIDER_SERVICE_NAME: app-provider-onlyoffice + # use the internal service name + APP_PROVIDER_EXTERNAL_ADDR: com.owncloud.api.app-provider-onlyoffice APP_PROVIDER_DRIVER: wopi APP_PROVIDER_WOPI_APP_NAME: OnlyOffice APP_PROVIDER_WOPI_APP_ICON_URI: https://${ONLYOFFICE_DOMAIN:-onlyoffice.owncloud.test}/web-apps/apps/documenteditor/main/resources/img/favicon.ico @@ -139,6 +151,8 @@ services: APP_PROVIDER_WOPI_INSECURE: "${INSECURE:-false}" APP_PROVIDER_WOPI_WOPI_SERVER_EXTERNAL_URL: https://${WOPISERVER_DOMAIN:-wopiserver.owncloud.test} APP_PROVIDER_WOPI_FOLDER_URL_BASE_URL: https://${OCIS_DOMAIN:-ocis.owncloud.test} + # share the registry with the ocis container + MICRO_REGISTRY: "mdns" volumes: - ./config/ocis-appprovider-onlyoffice/entrypoint-override.sh:/entrypoint-override.sh - ocis-config:/etc/ocis diff --git a/docs/clients/rclone/webdav-sync-oidc.md b/docs/clients/rclone/webdav-sync-oidc.md index 1e1af3fe7..a64b32e48 100644 --- a/docs/clients/rclone/webdav-sync-oidc.md +++ b/docs/clients/rclone/webdav-sync-oidc.md @@ -37,7 +37,7 @@ If you have dynamic client registration enabled on your OpenID Connect identity If you're using a dedicated OpenID Connect client for the OIDC-agent, we recommend a public one with the following two redirect URIs: `http://127.0.0.1:*` and `http://localhost:*`. Alternatively you also may use the already existing OIDC client of the ownCloud Desktop Client (`--client-id=xdXOt13JKxym1B1QcEncf2XDkLAexMBFwiT9j6EfhhHFJhs2KM9jbjTmf8JBXE69` and `--client-secret=UBntmLjC2yYCeHwsyj73Uwo9TAaecAetRwMw0xYcvNL9yRdLSUi0hUAHfvCHFeFh`, no `--pub` set, request specific scope for offline access), e.g.: ``` bash -oidc-gen / +oidc-gen \ --client-id=xdXOt13JKxym1B1QcEncf2XDkLAexMBFwiT9j6EfhhHFJhs2KM9jbjTmf8JBXE69 \ --client-secret=UBntmLjC2yYCeHwsyj73Uwo9TAaecAetRwMw0xYcvNL9yRdLSUi0hUAHfvCHFeFh \ --issuer https://cloud.ocis.test \ diff --git a/docs/ocis/adr/0020-file-search-query-language.md b/docs/ocis/adr/0020-file-search-query-language.md index e897c6b25..d5156057f 100644 --- a/docs/ocis/adr/0020-file-search-query-language.md +++ b/docs/ocis/adr/0020-file-search-query-language.md @@ -1,15 +1,15 @@ --- title: "20. File Search Query Language" -date: 2022-03-18T09:00:00+01:00 +date: 2022-06-23T09:00:00+01:00 weight: 20 geekdocRepo: https://github.com/owncloud/ocis geekdocEditPath: edit/master/docs/ocis/adr geekdocFilePath: 0020-file-search-query-language.md --- -* Status: proposed -* Deciders: @butonic, @micbar, @dragotin, @C0rby -* Date: 2022-03-18 +* Status: accepted +* Deciders: @butonic, @micbar, @dragotin, @C0rby, @kulmann, @felix-schwarz, @JammingBen +* Date: 2023-06-23 ## Context and Problem Statement @@ -28,6 +28,7 @@ From the users perspective, the interface to search is just a single form field ## Considered Options +* [KQL - Keyword Query Language](#keyword-query-language) * [Simple Query](#simplified-query) * [Lucene Query Language](#lucene-query-language) * [Solr Query Language](#solr-query-language) @@ -35,19 +36,31 @@ From the users perspective, the interface to search is just a single form field ## Decision Outcome -Chosen option: [Simple Query](#simplified-query), because it is a suitable MVP. +Chosen option: [KQL - Keyword Query Language](#keyword-query-language), because it enables advanced search across all platforms. ### Positive Consequences -* We can do a quick implementation +* We can use the same query language in all clients ### Negative consequences -* We cannot specify terms or other search criteria -* We will need to find a good point in time to use a query language before we start working around it +* We need to build and maintain a backend connector ## Pros and Cons of the Options +### Keyword Query Language + +The Keyword Query Language (KQL) is used by Microsoft Share Point and other Microsoft Services. It uses very simple query elements, property restrictions and operators. + +* Good, because we can fulfill all our current needs +* Good, because it is very similar to the used query language in iOS +* Good, because it supports date time keywords like "today", "this week" and more +* Good, because it can be easily extended to use "shortcuts" for eg. document types like `:presentation` which combine multiple mime types. +* Good, because it is successfully implemented and used in similar use cases +* Good, because it gives our clients the freedom to always use the same query language across all platforms +* Good, because Microsoft Graph API is using it, we will have an easy transition in the future +* Bad, because we need to build and maintain a connector to different search backends (bleve, elasticsearch or others) + ### Simplified Query Implement a very simple search approach: Return all files which contain at least one of the keywords in their name, path, alias or selected metadata. @@ -90,6 +103,7 @@ Elasticsearch provides a full Query DSL (Domain Specific Language) based on JSON * [Search API](0018-file-search-api.md) * [Search Indexing](0019-file-search-index.md) +* [KQL](https://learn.microsoft.com/en-us/sharepoint/dev/general-development/keyword-query-language-kql-syntax-reference) * [Apache Lucene](https://lucene.apache.org/) * [Apache Solr](https://solr.apache.org/) * [Elastic Search](https://solr.apache.org/) diff --git a/docs/ocis/deployment/ocis_hello.md b/docs/ocis/deployment/ocis_hello.md index 68f625786..16f8bd782 100644 --- a/docs/ocis/deployment/ocis_hello.md +++ b/docs/ocis/deployment/ocis_hello.md @@ -9,9 +9,6 @@ geekdocFilePath: ocis_hello.md {{< toc >}} -**IMPORTANT:** -The `ocis-hello` example is currently under revision and does not work due to latest changes in master. - ## Overview - oCIS running behind Traefik as reverse proxy diff --git a/docs/ocis/deployment/ocis_s3.md b/docs/ocis/deployment/ocis_s3.md index 0c596882e..77abee61c 100644 --- a/docs/ocis/deployment/ocis_s3.md +++ b/docs/ocis/deployment/ocis_s3.md @@ -45,7 +45,7 @@ See also [example server setup]({{< ref "preparing_server" >}}) * Go to the deployment example - `cd ocis/deployments/examples/ocis_s3` + `cd deployments/examples/ocis_s3` * Open the `.env` file in a text editor. diff --git a/docs/ocis/development/testing.md b/docs/ocis/development/testing.md index 3e9660095..4b3c9e6d9 100644 --- a/docs/ocis/development/testing.md +++ b/docs/ocis/development/testing.md @@ -74,7 +74,21 @@ To run the tests that require an email server (tests tagged with `@email`), you ```bash START_EMAIL=true \ -BEHAT_FEATURE='tests/acceptance/features/apiEmailNotification/emailNotification.feature' \ +BEHAT_FEATURE='tests/acceptance/features/apiNotification/emailNotification.feature' \ +make -C tests/acceptance/docker test-ocis-feature-ocis-storage +``` + +{{< /hint >}} + +{{< hint info >}} +To run the tests that require an antivirus service (tests tagged with `@antivirus`), you need to provide the following environment variables while running the tests. + +```bash +START_ANTIVIRUS=true \ +OCIS_ASYNC_UPLOADS=true \ +OCIS_ADD_RUN_SERVICES=antivirus \ +POSTPROCESSING_STEPS=virusscan \ +BEHAT_FEATURE='tests/acceptance/features/apiAntivirus/antivirus.feature' \ make -C tests/acceptance/docker test-ocis-feature-ocis-storage ``` @@ -313,7 +327,7 @@ While writing tests for a new oCIS ENV configuration, please make sure to follow 2. Use `OcisConfigHelper.php` for helper functions - provides functions to reconfigure the running oCIS instance. 3. Recommended: add the new step implementations in `OcisConfigContext.php` -## Running Test Suite With Email Service (@Email) +## Running Test Suite With Email Service (@email) Test suites that are tagged with `@email` require an email service. We use inbucket as the email service in our tests. @@ -406,3 +420,76 @@ make test-paralleldeployment-api \ ... \ BEHAT_FEATURE="tests/parallelDeployAcceptance/features/apiShareManagement/acceptShares.feature" ``` + +## Running Test Suite With Antivirus Service (@antivirus) +Test suites that are tagged with `@antivirus` require antivirus service. The available antivirus and the configuration related to them can be found [here](https://doc.owncloud.com/ocis/next/deployment/services/s-list/antivirus.html). This documentation is only going to use `clamAv` as antivirus. + +### Setup clamAV +#### 1. Setup Locally + +##### Linux OS user +Run the following command to set up calmAV and clamAV daemon +```bash +sudo apt install clamav clamav-daemon -y +``` + +Make sure that the clamAV daemon is up and running + +```bash +sudo service clamav-daemon status +``` +{{< hint info >}} +The commands are ubuntu specific and may differ according to your system. You can find information related to installation of clamAV in their official documentation [here](https://docs.clamav.net/manual/Installing/Packages.html). +{{< /hint>}} + +##### Mac OS user +Install ClamAV using [here](https://gist.github.com/mendozao/3ea393b91f23a813650baab9964425b9) +Start ClamAV daemon +```bash +/your/location/to/brew/Cellar/clamav/1.1.0/sbin/clamd +``` +#### 2. Setup clamAV With Docker +##### Linux OS user +Run `clamAV` through docker +```bash +docker run -d -p 3310:3310 owncloudci/clamavd +``` + +##### Mac OS user +```bash +docker run -d -p 3310:3310 -v /your/local/filesystem/path/to/clamav/:/var/lib/clamav mkodockx/docker-clamav:alpine +``` + +### Run oCIS + +As `antivirus` service is not enabled by default we need to enable the service while running oCIS server. We also need to enable `async upload` and as virus scan is performed in post-processing step, we need to set it as well. Documentation for environment variables related to antivirus is available [here](https://owncloud.dev/services/antivirus/#environment-variables) + +```bash +# run oCIS +PROXY_ENABLE_BASIC_AUTH=true \ +ANTIVIRUS_SCANNER_TYPE="clamav" \ +ANTIVIRUS_CLAMAV_SOCKET="tcp://host.docker.internal:3310" \ +POSTPROCESSING_STEPS="virusscan" \ +OCIS_ASYNC_UPLOADS=true \ +OCIS_ADD_RUN_SERVICES="antivirus" +ocis/bin/ocis server +``` +{{< hint info >}} +The value for `ANTIVIRUS_CLAMAV_SOCKET` is an example which needs adaption according your OS. + +For antivirus running localy on Linux OS, use `ANTIVIRUS_CLAMAV_SOCKET= "/var/run/clamav/clamd.ctl"`. +For antivirus running localy on Mac OS, use `ANTIVIRUS_CLAMAV_SOCKET= "/tmp/clamd.socket"`. +For antivirus running with docker, use `ANTIVIRUS_CLAMAV_SOCKET= "tcp://host.docker.internal:3310"` +{{< /hint>}} + +#### Run the Acceptance Test + +Run the acceptance test with the following command: + +```bash +TEST_WITH_GRAPH_API=true \ +TEST_OCIS=true \ +TEST_SERVER_URL="https://localhost:9200" \ +BEHAT_FEATURE="tests/acceptance/features/apiAntivirus/antivirus.feature" \ +make test-acceptance-api +``` diff --git a/docs/services/ocdav/_index.md b/docs/services/ocdav/_index.md old mode 100644 new mode 100755 index a675ccf84..6e4523318 --- a/docs/services/ocdav/_index.md +++ b/docs/services/ocdav/_index.md @@ -1,17 +1,26 @@ --- title: ocDAV -date: 2022-03-02T00:00:00+00:00 +date: 2023-05-31T13:33:51.029749931+02:00 weight: 20 geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/services/ocdav -geekdocFilePath: _index.md +geekdocEditPath: edit/master/services/ocdav +geekdocFilePath: README.md geekdocCollapseSection: true --- + + ## Abstract -This service provides the WebDAV API which is required by ownCloud clients. Previews (thumbnails) are provided by the [WebDAV service]({{ ../../webdav }}). + +The ocdav service provides the WebDAV API which is required by ownCloud clients. Previews (thumbnails) are provided by the [WebDAV service](../webdav). ## Table of Contents -{{< toc-tree >}} +* [Example Yaml Config](#example-yaml-config) + +## Example Yaml Config +{{< include file="services/_includes/ocdav-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/ocdav_configvars.md" >}} + diff --git a/docs/templates/ADOC_global.tmpl b/docs/templates/ADOC_global.tmpl index cbc43b850..4625df18a 100644 --- a/docs/templates/ADOC_global.tmpl +++ b/docs/templates/ADOC_global.tmpl @@ -1,5 +1,12 @@ // collected through docs/helpers/adoc-generator.go.tmpl +Note that some global environment variables have been deprecated and replaced by a new one starting with `OCIS_` for naming consistency: + +* All envvars starting with `LDAP_` +* All envvars starting with `IDM_` except `IDM_CREATE_DEMO_USERS` +* The following envvars: `REVA_GATEWAY`, `STORAGE_TRANSFER_SECRET`, `STORAGE_USERS_OCIS_ASYNC_UPLOADS`, `USERLOG_MACHINE_AUTH_API_KEY`. +* Note that `WEB_UI_CONFIG_FILE` is not a global envar and will dropped from the list in a later release. + [.landscape] [caption=] .Environment variables with global scope available in multiple services diff --git a/go.mod b/go.mod index b2e5abed3..cdd4a4396 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/coreos/go-oidc v2.2.1+incompatible github.com/coreos/go-oidc/v3 v3.6.0 github.com/cs3org/go-cs3apis v0.0.0-20230516150832-730ac860c71d - github.com/cs3org/reva/v2 v2.13.4-0.20230526111208-d62d1497abb9 + github.com/cs3org/reva/v2 v2.14.1-0.20230623085734-919a9585f147 github.com/disintegration/imaging v1.6.2 github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e github.com/egirna/icap-client v0.1.1 @@ -29,7 +29,7 @@ require ( github.com/go-micro/plugins/v4/logger/zerolog v1.2.0 github.com/go-micro/plugins/v4/registry/consul v1.2.0 github.com/go-micro/plugins/v4/registry/etcd v1.2.0 - github.com/go-micro/plugins/v4/registry/kubernetes v1.1.1 + github.com/go-micro/plugins/v4/registry/kubernetes v1.1.2-0.20230605104008-a179a6b8f8e6 github.com/go-micro/plugins/v4/registry/mdns v1.2.0 github.com/go-micro/plugins/v4/registry/memory v1.2.0 github.com/go-micro/plugins/v4/registry/nats v1.2.1 @@ -44,7 +44,7 @@ require ( github.com/golang/protobuf v1.5.3 github.com/google/go-tika v0.3.0 github.com/google/uuid v1.3.0 - github.com/gookit/config/v2 v2.1.8 + github.com/gookit/config/v2 v2.2.2 github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.13.0 github.com/jellydator/ttlcache/v2 v2.11.1 @@ -55,7 +55,7 @@ require ( github.com/libregraph/lico v0.60.1-0.20230516115351-f904ff5fd200 github.com/mitchellh/mapstructure v1.5.0 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 - github.com/nats-io/nats-server/v2 v2.9.17 + github.com/nats-io/nats-server/v2 v2.9.19 github.com/oklog/run v1.1.0 github.com/olekukonko/tablewriter v0.0.5 github.com/onsi/ginkgo v1.16.5 @@ -66,36 +66,37 @@ require ( github.com/owncloud/libre-graph-api-go v1.0.5-0.20230512172639-d458ad6b300b github.com/pkg/errors v0.9.1 github.com/pkg/xattr v0.4.9 - github.com/prometheus/client_golang v1.15.1 + github.com/prometheus/client_golang v1.16.0 + github.com/riandyrn/otelchi v0.5.1 github.com/rogpeppe/go-internal v1.10.0 - github.com/rs/zerolog v1.29.0 + github.com/rs/zerolog v1.29.1 github.com/shamaton/msgpack/v2 v2.1.1 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.7.0 - github.com/stretchr/testify v1.8.2 - github.com/test-go/testify v1.1.4 + github.com/stretchr/testify v1.8.4 github.com/thejerf/suture/v4 v4.0.2 github.com/tus/tusd v1.10.1 - github.com/urfave/cli/v2 v2.25.1 + github.com/urfave/cli/v2 v2.25.7 github.com/xhit/go-simple-mail/v2 v2.13.0 go-micro.dev/v4 v4.9.0 go.etcd.io/bbolt v1.3.7 - go.opentelemetry.io/contrib/zpages v0.41.1 - go.opentelemetry.io/otel v1.15.1 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.1-0.20230608065455-5cc3715df180 + go.opentelemetry.io/contrib/zpages v0.42.0 + go.opentelemetry.io/otel v1.16.0 go.opentelemetry.io/otel/exporters/jaeger v1.15.1 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.15.1 - go.opentelemetry.io/otel/sdk v1.15.1 - go.opentelemetry.io/otel/trace v1.15.1 - golang.org/x/crypto v0.9.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0 + go.opentelemetry.io/otel/sdk v1.16.0 + go.opentelemetry.io/otel/trace v1.16.0 + golang.org/x/crypto v0.10.0 golang.org/x/exp v0.0.0-20221026004748-78e5e7837ae6 golang.org/x/image v0.6.0 - golang.org/x/net v0.10.0 + golang.org/x/net v0.11.0 golang.org/x/oauth2 v0.8.0 - golang.org/x/sync v0.1.0 - golang.org/x/term v0.8.0 - golang.org/x/text v0.9.0 - google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f - google.golang.org/grpc v1.54.0 + golang.org/x/sync v0.2.0 + golang.org/x/term v0.9.0 + golang.org/x/text v0.10.0 + google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 + google.golang.org/grpc v1.55.0 google.golang.org/protobuf v1.30.0 gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/yaml.v2 v2.4.0 @@ -107,7 +108,7 @@ require ( require ( contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect - github.com/BurntSushi/toml v1.2.1 // indirect + github.com/BurntSushi/toml v1.3.2 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/sprig v2.22.0+incompatible // indirect github.com/Microsoft/go-winio v0.6.0 // indirect @@ -171,7 +172,7 @@ require ( github.com/emirpasic/gods v1.18.1 // indirect github.com/emvi/iso-639-1 v1.0.1 // indirect github.com/evanphx/json-patch/v5 v5.5.0 // indirect - github.com/fatih/color v1.13.0 // indirect + github.com/fatih/color v1.14.1 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gdexlab/go-render v1.0.1 // indirect @@ -192,24 +193,26 @@ require ( github.com/go-resty/resty/v2 v2.7.0 // indirect github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect + github.com/go-test/deep v1.1.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.0.4 // indirect + github.com/goccy/go-yaml v1.11.0 // indirect github.com/godbus/dbus/v5 v5.0.6 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect - github.com/golang/glog v1.0.0 // indirect + github.com/golang/glog v1.1.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect github.com/gomodule/redigo v1.8.9 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect - github.com/gookit/color v1.5.2 // indirect - github.com/gookit/goutil v0.6.0 // indirect + github.com/gookit/color v1.5.3 // indirect + github.com/gookit/goutil v0.6.9 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/schema v1.2.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect @@ -226,7 +229,7 @@ require ( github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/iancoleman/strcase v0.2.0 // indirect - github.com/imdario/mergo v0.3.13 // indirect + github.com/imdario/mergo v0.3.15 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -241,7 +244,7 @@ require ( github.com/longsleep/rndm v1.2.0 // indirect github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect @@ -262,7 +265,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/nats-io/jwt/v2 v2.4.1 // indirect - github.com/nats-io/nats.go v1.24.0 // indirect + github.com/nats-io/nats.go v1.27.0 // indirect github.com/nats-io/nkeys v0.4.4 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/nxadm/tail v1.4.8 // indirect @@ -275,8 +278,9 @@ require ( github.com/prometheus/alertmanager v0.24.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect github.com/prometheus/statsd_exporter v0.22.8 // indirect + github.com/r3labs/sse/v2 v2.10.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect github.com/rivo/uniseg v0.4.2 // indirect github.com/rs/cors v1.9.0 // indirect @@ -300,25 +304,29 @@ require ( github.com/xanzy/ssh-agent v0.3.2 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/yashtewari/glob-intersection v0.1.0 // indirect go.etcd.io/etcd/api/v3 v3.5.7 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect go.etcd.io/etcd/client/v3 v3.5.7 // indirect go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib v1.0.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.4 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.15.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 // indirect + go.opentelemetry.io/otel/metric v1.16.0 // indirect go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.23.0 // indirect golang.org/x/mod v0.9.0 // indirect - golang.org/x/sys v0.8.0 // indirect + golang.org/x/sys v0.9.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.7.0 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect + gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect @@ -327,3 +335,5 @@ require ( ) replace github.com/cs3org/go-cs3apis => github.com/2403905/go-cs3apis v0.0.0-20230517122726-727045414fd1 + +replace github.com/cs3org/reva/v2 => github.com/micbar/reva/v2 v2.0.0-20230626125956-c381fe19a108 diff --git a/go.sum b/go.sum index dc33f5e69..ce0224a8a 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w9 cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y= cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= @@ -115,7 +115,7 @@ cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= -cloud.google.com/go/compute v1.15.1 h1:7UGq3QknM33pw5xATlpzeoomNxsacIVvTqTTvbfajmE= +cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY= cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= @@ -416,8 +416,8 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CiscoM31/godata v1.0.7 h1:y3FHdICAU9j+IkK6E66ezCghaQSamFbYoj/YEHig0kY= github.com/CiscoM31/godata v1.0.7/go.mod h1:ZMiT6JuD3Rm83HEtiTx4JEChsd25YCrxchKGag/sdTc= @@ -454,7 +454,6 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= -github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= @@ -477,9 +476,6 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= -github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= -github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -615,7 +611,6 @@ github.com/coreos/go-oidc/v3 v3.6.0/go.mod h1:ZpHUsHBucTUj6WOkrP4E20UPynbLZzhTQ1 github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= @@ -629,8 +624,6 @@ github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= github.com/crewjam/saml v0.4.13 h1:TYHggH/hwP7eArqiXSJUvtOPNzQDyQ7vwmwEqlFWhMc= github.com/crewjam/saml v0.4.13/go.mod h1:igEejV+fihTIlHXYP8zOec3V5A8y3lws5bQBFsTm4gA= -github.com/cs3org/reva/v2 v2.13.4-0.20230526111208-d62d1497abb9 h1:JuZPMzFl6Yr2PIlT7mIYJ+S++AS3PQoRcYyA1tsH2DI= -github.com/cs3org/reva/v2 v2.13.4-0.20230526111208-d62d1497abb9/go.mod h1:vMQqSn30fEPHO/GKC2WmGimlOPqvfSy4gdhRSpbvrWc= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8/go.mod h1:4abs/jPXcmJzYoYGF91JF9Uq9s/KL5n1jvFDix8KcqY= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= @@ -695,10 +688,12 @@ github.com/exoscale/egoscale v0.46.0/go.mod h1:mpEXBpROAa/2i5GC0r33rfxG+TxSEka11 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -767,9 +762,12 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-micro/plugins/v4/client/grpc v1.2.0 h1:Z8BB6jqslXM2aMMhjZ+QfNuzR+msCMtGd83DGlsQQG0= @@ -782,8 +780,8 @@ github.com/go-micro/plugins/v4/registry/consul v1.2.0 h1:nqrTzfWUTWKAy+M+i2FazbH github.com/go-micro/plugins/v4/registry/consul v1.2.0/go.mod h1:wTat7/K9XQ+i64VbbcMYFcEwipYfSgJM51HcA/sgsM4= github.com/go-micro/plugins/v4/registry/etcd v1.2.0 h1:tcHlU1GzvX3oZa8WQH8ylMCGie5qD5g98YWTESJjeqQ= github.com/go-micro/plugins/v4/registry/etcd v1.2.0/go.mod h1:CQeTHkjN3xMtIQsynaTTquMz2sHEdsTfRIfFzrX7aug= -github.com/go-micro/plugins/v4/registry/kubernetes v1.1.1 h1:nzwyyK2JKJtmJ8w0CRn9zsn4+kwJitx8CYfAWH99eiI= -github.com/go-micro/plugins/v4/registry/kubernetes v1.1.1/go.mod h1:u78+qWLUq8jxu/CF4UW+1UUtNgBz67x27ar2kV5Dd/o= +github.com/go-micro/plugins/v4/registry/kubernetes v1.1.2-0.20230605104008-a179a6b8f8e6 h1:ekJ70Hq3N+Enz71GCubzhzcVuA9jfd1zWuqTXZF+UlU= +github.com/go-micro/plugins/v4/registry/kubernetes v1.1.2-0.20230605104008-a179a6b8f8e6/go.mod h1:u78+qWLUq8jxu/CF4UW+1UUtNgBz67x27ar2kV5Dd/o= github.com/go-micro/plugins/v4/registry/mdns v1.2.0 h1:BsGnco+PgycvSX+HS0XbeUQEPoPT3a+dDiHWV6dbVDs= github.com/go-micro/plugins/v4/registry/mdns v1.2.0/go.mod h1:re0JvO5F56n59WEDaAKj2jtboKa2dklAd6iWyz5xa54= github.com/go-micro/plugins/v4/registry/memory v1.2.0 h1:R0G2tltffuG+fQnk+/JuAdgEJX4J+LuOafZDoNd8ow0= @@ -825,8 +823,11 @@ github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUri github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= @@ -839,8 +840,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= -github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= +github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= @@ -875,6 +876,8 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs= github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= +github.com/goccy/go-yaml v1.11.0 h1:n7Z+zx8S9f9KgzG6KtQKf+kwqXZlLNR2F6018Dgau54= +github.com/goccy/go-yaml v1.11.0/go.mod h1:H+mJrWtjPTJAHvRbV09MCK9xYwODM+wRTVFFTWckfng= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -899,8 +902,9 @@ github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -918,7 +922,6 @@ github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3K github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v0.0.0-20170622202551-6a1fa9404c0a/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -1013,17 +1016,13 @@ github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqE github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= -github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI= -github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg= -github.com/gookit/config/v2 v2.1.8 h1:fH8NKzOLlQ2Ud65Czy5Tq+t1I8U7M9BmIPgVFQ4T9UQ= -github.com/gookit/config/v2 v2.1.8/go.mod h1:AP8Obh1AKx1pFkBNE2EghDb1ei2/fG33ULfkzuw4s0c= -github.com/gookit/goutil v0.5.14/go.mod h1:ozPE16eJS9f89aVbVk05ocEJsia3KPrYUqPTs8GvUTw= -github.com/gookit/goutil v0.5.15/go.mod h1:ozPE16eJS9f89aVbVk05ocEJsia3KPrYUqPTs8GvUTw= -github.com/gookit/goutil v0.6.0 h1:uGne/hUNe2xiJZB77QkeIsKsdPRaPyXFv9mUdDqq/Bw= -github.com/gookit/goutil v0.6.0/go.mod h1:DI6e4Waos7Yzjhoz75YFMpGl08m92cxNu0Tep36D/d0= -github.com/gookit/ini/v2 v2.1.3 h1:wQPpTWbuo5GuevQnuiZMVRtALfldNuBW0XvRFgF0EEk= -github.com/gookit/ini/v2 v2.1.3/go.mod h1:Mor4+c0wdx5UK660FBLAkmc6Yr2oBHLAUjydLQ+WgYg= -github.com/gookit/properties v0.2.1/go.mod h1:hEmnTl5DLbGKfodoIIS698l8hqHpbhWvIznY/WAyUHc= +github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE= +github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE= +github.com/gookit/config/v2 v2.2.2 h1:/iVW3H/5oPdNulrjSI370kf14Hs6D4Gc5E15u0AYxX8= +github.com/gookit/config/v2 v2.2.2/go.mod h1:9wXrsGnOc9nLTr4mU+tuaR0ORzFyZdf3q5DuqsTyodU= +github.com/gookit/goutil v0.6.9 h1:NrbGKt3cnWoqxjZonC5bTr0FrltTCRdzGReGQwnwhUk= +github.com/gookit/goutil v0.6.9/go.mod h1:ZYNl/t+EIMo0rCRe1mwfC7jKyF/q1FdOEOgrXzJpr5o= +github.com/gookit/ini/v2 v2.2.2 h1:3B8abZJrVH1vi/7TU4STuTBxdhiAq1ORSt6NJZCahaI= github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4= github.com/gophercloud/gophercloud v0.16.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4= github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae/go.mod h1:wx8HMD8oQD0Ryhz6+6ykq75PJ79iPyEqYHfwZ4l7OsA= @@ -1100,7 +1099,6 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/hcl/v2 v2.14.1/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= @@ -1124,8 +1122,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -1200,12 +1198,12 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA= github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leonelquinteros/gotext v1.5.3-0.20230317130943-71a59c05b2c1 h1:k56sFOOJ0CYuQtGoRSeAMhP1R692+iNH+S1dC/CEz0w= github.com/leonelquinteros/gotext v1.5.3-0.20230317130943-71a59c05b2c1/go.mod h1:AT4NpQrOmyj1L/+hLja6aR0lk81yYYL4ePnj2kp7d6M= @@ -1255,8 +1253,9 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -1273,6 +1272,8 @@ github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b h1:Q53idHrTuQD github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b/go.mod h1:KirJrATYGbTyUwVR26xIkaipRqRcMRXBf8N5dacvGus= github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103 h1:Z/i1e+gTZrmcGeZyWckaLfucYG6KYOXLWo4co8pZYNY= github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103/go.mod h1:o9YPB5aGP8ob35Vy6+vyq3P3bWe7NQWzf+JLiXCiMaE= +github.com/micbar/reva/v2 v2.0.0-20230626125956-c381fe19a108 h1:nb3VTDiZ8AHDdaRjNAsKgbFH93SSq/en2Y6hbMRpX38= +github.com/micbar/reva/v2 v2.0.0-20230626125956-c381fe19a108/go.mod h1:E32krZG159YflDSjDWfx/QGIC2529PS5LiPnGNHu3d0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= @@ -1300,7 +1301,6 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0= -github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0= @@ -1332,10 +1332,10 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4= github.com/nats-io/jwt/v2 v2.4.1/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI= -github.com/nats-io/nats-server/v2 v2.9.17 h1:gFpUQ3hqIDJrnqog+Bl5vaXg+RhhYEZIElasEuRn2tw= -github.com/nats-io/nats-server/v2 v2.9.17/go.mod h1:eQysm3xDZmIjfkjr7DuD9DjRFpnxQc2vKVxtEg0Dp6s= -github.com/nats-io/nats.go v1.24.0 h1:CRiD8L5GOQu/DcfkmgBcTTIQORMwizF+rPk6T0RaHVQ= -github.com/nats-io/nats.go v1.24.0/go.mod h1:dVQF+BK3SzUZpwyzHedXsvH3EO38aVKuOPkkHlv5hXA= +github.com/nats-io/nats-server/v2 v2.9.19 h1:OF9jSKZGo425C/FcVVIvNgpd36CUe7aVTTXEZRJk6kA= +github.com/nats-io/nats-server/v2 v2.9.19/go.mod h1:aTb/xtLCGKhfTFLxP591CMWfkdgBmcUUSkiSOe5A3gw= +github.com/nats-io/nats.go v1.27.0 h1:3o9fsPhmoKm+yK7rekH2GtWoE+D9jFbw8N3/ayI1C00= +github.com/nats-io/nats.go v1.27.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc= github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA= github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= @@ -1425,8 +1425,8 @@ github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrb github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= -github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.0.0-20170216185247-6f3806018612/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -1463,16 +1463,20 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -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/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= github.com/prometheus/statsd_exporter v0.22.8 h1:Qo2D9ZzaQG+id9i5NYNGmbf1aa/KxKbB9aKfMS+Yib0= github.com/prometheus/statsd_exporter v0.22.8/go.mod h1:/DzwbTEaFTE0Ojz5PqcSk6+PFHOPWGxdXVr6yC8eFOM= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/r3labs/sse/v2 v2.10.0 h1:hFEkLLFY4LDifoHdiCN/LlGBAdVJYsANaLqNYa1l/v0= +github.com/r3labs/sse/v2 v2.10.0/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktEmkNJ7I= github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/riandyrn/otelchi v0.5.1 h1:0/45omeqpP7f/cvdL16GddQBfAEmZvUyl2QzLSE6uYo= +github.com/riandyrn/otelchi v0.5.1/go.mod h1:ZxVxNEl+jQ9uHseRYIxKWRb3OY8YXFEu+EkNiiSNUEA= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -1491,8 +1495,8 @@ github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= -github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= +github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= +github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/russellhaering/goxmldsig v1.4.0 h1:8UcDh/xGyQiyrW+Fq5t8f+l2DLB1+zlhYzkPUJ7Qhys= github.com/russellhaering/goxmldsig v1.4.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= @@ -1509,7 +1513,6 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUt github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -1554,7 +1557,6 @@ github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -1576,14 +1578,13 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes= github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= -github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo= github.com/thejerf/suture/v4 v4.0.2 h1:VxIH/J8uYvqJY1+9fxi5GBfGRkRZ/jlSOP6x9HijFQc= github.com/thejerf/suture/v4 v4.0.2/go.mod h1:g0e8vwskm9tI0jRjxrnA6lSr0q6OfPdWJVX7G5bVWRs= @@ -1600,16 +1601,13 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqri github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/urfave/cli/v2 v2.25.1 h1:zw8dSP7ghX0Gmm8vugrs6q9Ku0wzweqPyshy+syu9Gw= -github.com/urfave/cli/v2 v2.25.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= +github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= +github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/vimeo/go-util v1.4.1/go.mod h1:r+yspV//C48HeMXV8nEvtUeNiIiGfVv3bbEHzOgudwE= github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14/go.mod h1:RWc47jtnVuQv6+lY3c768WtXCas/Xi+U5UFc5xULmYg= -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/vultr/govultr/v2 v2.0.0/go.mod h1:2PsEeg+gs3p/Fo5Pw8F9mv+DUBEOlrNZ8GmCTGmhOhs= github.com/wk8/go-ordered-map v1.0.0 h1:BV7z+2PaK8LTSd/mWgY12HyMAo5CEgkHqbkVq2thqr8= github.com/wk8/go-ordered-map v1.0.0/go.mod h1:9ZIbRunKbuvfPKyBP1SIKLcXNlv74YCOZ3t3VTS6gRk= @@ -1629,13 +1627,12 @@ github.com/xhit/go-simple-mail/v2 v2.13.0 h1:OANWU9jHZrVfBkNkvLf8Ww0fexwpQVF/v/5 github.com/xhit/go-simple-mail/v2 v2.13.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= -github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yashtewari/glob-intersection v0.1.0 h1:6gJvMYQlTDOL3dMsPF6J0+26vwX9MB8/1q3uAdhmTrg= github.com/yashtewari/glob-intersection v0.1.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= -github.com/yosuke-furukawa/json5 v0.1.1/go.mod h1:sw49aWDqNdRJ6DYUtIQiaA3xyj2IL9tjeNYmX2ixwcU= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1644,9 +1641,6 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= -github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= -github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go-micro.dev/v4 v4.9.0 h1:pd1CpqMT9hA47jSmX8mfdGK865PkMh95Rwj5RdfqPqE= go-micro.dev/v4 v4.9.0/go.mod h1:Ju8HrZ5hQSF+QguZ2QUs9Kbe42MHP1tJa/fpP5g07Cs= @@ -1673,24 +1667,33 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib v1.0.0 h1:khwDCxdSspjOLmFnvMuSHd/5rPzbTx0+l6aURwtQdfE= +go.opentelemetry.io/contrib v1.0.0/go.mod h1:EH4yDYeNoaTqn/8yCWQmfNB78VHfGX2Jt2bvnvzBlGM= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.4 h1:PRXhsszxTt5bbPriTjmaweWUsAnJYeWBhUMLRetUgBU= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.4/go.mod h1:05eWWy6ZWzmpeImD3UowLTB3VjDMU1yxQ+ENuVWDM3c= -go.opentelemetry.io/contrib/zpages v0.41.1 h1:FReY8OWFNtYm4mWleTRxTUyD3r02uGcwS6ZeElahs00= -go.opentelemetry.io/contrib/zpages v0.41.1/go.mod h1:C3iy146ccMyv1+gEaxVDDHuoT7yXAKKmbg+twudDpeg= -go.opentelemetry.io/otel v1.15.1 h1:3Iwq3lfRByPaws0f6bU3naAqOR1n5IeDWd9390kWHa8= -go.opentelemetry.io/otel v1.15.1/go.mod h1:mHHGEHVDLal6YrKMmk9LqC4a3sF5g+fHfrttQIB1NTc= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.1-0.20230608065455-5cc3715df180 h1:vx3BgH4QDw5zqcu8MPC43KPznuLJt7Ly6so2cUUwhu8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.1-0.20230608065455-5cc3715df180/go.mod h1:/Q7SZ3KqNy3qTGotS1MKC8lO5z0CFSeacGbjfgMa4sk= +go.opentelemetry.io/contrib/zpages v0.42.0 h1:hFscXKQ9PTjyIVmAr6zIV8cMoiEeR9lPIwPVqHi8+5Q= +go.opentelemetry.io/contrib/zpages v0.42.0/go.mod h1:qRJBEfB0iwRKrYImq5qfwTolmY8HXvZBRucvhuTVQZw= +go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= +go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= +go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= go.opentelemetry.io/otel/exporters/jaeger v1.15.1 h1:x3SLvwli0OyAJapNcOIzf1xXBRBA+HD3elrMQmFfmXo= go.opentelemetry.io/otel/exporters/jaeger v1.15.1/go.mod h1:0Ck9b5oLL/bFZvfAEEqtrb1U0jZXjm5fWXMCOCG3vvM= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.15.1 h1:XYDQtNzdb2T4uM1pku2m76eSMDJgqhJ+6KzkqgQBALc= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.15.1/go.mod h1:uOTV75+LOzV+ODmL8ahRLWkFA3eQcSC2aAsbxIu4duk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.1 h1:tyoeaUh8REKay72DVYsSEBYV18+fGONe+YYPaOxgLoE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.1/go.mod h1:HUSnrjQQ19KX9ECjpQxufsF+3ioD3zISPMlauTPZu2g= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.15.1 h1:pIfoG5IAZFzp9EUlJzdSkpUwpaUAAnD+Ru1nBLTACIQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.15.1/go.mod h1:poNKBqF5+nR/6ke2oGTDjHfksrsHDOHXAl2g4+9ONsY= -go.opentelemetry.io/otel/sdk v1.15.1 h1:5FKR+skgpzvhPQHIEfcwMYjCBr14LWzs3uSqKiQzETI= -go.opentelemetry.io/otel/sdk v1.15.1/go.mod h1:8rVtxQfrbmbHKfqzpQkT5EzZMcbMBwTzNAggbEAM0KA= -go.opentelemetry.io/otel/trace v1.15.1 h1:uXLo6iHJEzDfrNC0L0mNjItIp06SyaBQxu5t3xMlngY= -go.opentelemetry.io/otel/trace v1.15.1/go.mod h1:IWdQG/5N1x7f6YUlmdLeJvH9yxtuJAfc4VW5Agv9r/8= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 h1:t4ZwRPU+emrcvM2e9DHd0Fsf0JTPVcbfa/BhTDF03d0= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0/go.mod h1:vLarbg68dH2Wa77g71zmKQqlQ8+8Rq3GRG31uc0WcWI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 h1:cbsD4cUcviQGXdw8+bo5x2wazq10SKz8hEbtCRPcU78= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0/go.mod h1:JgXSGah17croqhJfhByOLVY719k1emAXC8MVhCIJlRs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0 h1:TVQp/bboR4mhZSav+MdgXB8FaRho1RC8UwVn3T0vjVc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0/go.mod h1:I33vtIe0sR96wfrUcilIzLoA3mLHhRmz9S9Te0S3gDo= +go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= +go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= +go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= +go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE= +go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4= +go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= +go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= +go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -1737,12 +1740,10 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1789,7 +1790,6 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1810,6 +1810,7 @@ golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1860,8 +1861,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= +golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1905,8 +1906,9 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/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-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1984,6 +1986,7 @@ golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2021,25 +2024,23 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= +golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2054,8 +2055,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2146,6 +2147,7 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -2319,8 +2321,8 @@ google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZV google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -2361,8 +2363,8 @@ google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCD google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= -google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/examples v0.0.0-20211102180624-670c133e568e h1:m7aQHHqd0q89mRwhwS9Bx2rjyl/hsFAeta+uGrHsQaU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -2384,6 +2386,8 @@ google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cn google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/Acconut/lockfile.v1 v1.1.0/go.mod h1:6UCz3wJ8tSFUsPR6uP/j8uegEtDuEEqFxlpi0JI4Umw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y= +gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -2426,7 +2430,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= diff --git a/ocis-pkg/registry/register.go b/ocis-pkg/registry/register.go new file mode 100644 index 000000000..e4e55837f --- /dev/null +++ b/ocis-pkg/registry/register.go @@ -0,0 +1,47 @@ +package registry + +import ( + "context" + "time" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + mRegistry "go-micro.dev/v4/registry" +) + +// RegisterService publishes an arbitrary endpoint to the service-registry. This allows querying nodes of +// non-micro services like reva. No health-checks are done, thus the caller is responsible for canceling. +func RegisterService(ctx context.Context, service *mRegistry.Service, logger log.Logger) error { + registry := GetRegistry() + node := service.Nodes[0] + + logger.Info().Msgf("registering external service %v@%v", node.Id, node.Address) + + rOpts := []mRegistry.RegisterOption{mRegistry.RegisterTTL(time.Minute)} + if err := registry.Register(service, rOpts...); err != nil { + logger.Fatal().Err(err).Msgf("Registration error for external service %v", service.Name) + } + + t := time.NewTicker(time.Second * 30) + + go func() { + for { + select { + case <-t.C: + logger.Debug().Interface("service", service).Msg("refreshing external service-registration") + err := registry.Register(service, rOpts...) + if err != nil { + logger.Error().Err(err).Msgf("registration error for external service %v", service.Name) + } + case <-ctx.Done(): + logger.Debug().Interface("service", service).Msg("unregistering") + t.Stop() + err := registry.Deregister(service) + if err != nil { + logger.Err(err).Msgf("Error unregistering external service %v", service.Name) + } + } + } + }() + + return nil +} diff --git a/ocis-pkg/service/external/external_test.go b/ocis-pkg/registry/register_test.go similarity index 99% rename from ocis-pkg/service/external/external_test.go rename to ocis-pkg/registry/register_test.go index 637d8601c..291670042 100644 --- a/ocis-pkg/service/external/external_test.go +++ b/ocis-pkg/registry/register_test.go @@ -1,4 +1,4 @@ -package external +package registry // //import ( diff --git a/ocis-pkg/registry/registry.go b/ocis-pkg/registry/registry.go index 6f048e448..6b4ccda01 100644 --- a/ocis-pkg/registry/registry.go +++ b/ocis-pkg/registry/registry.go @@ -6,14 +6,14 @@ import ( "sync" "time" + rRegistry "github.com/cs3org/reva/v2/pkg/registry" consulr "github.com/go-micro/plugins/v4/registry/consul" etcdr "github.com/go-micro/plugins/v4/registry/etcd" kubernetesr "github.com/go-micro/plugins/v4/registry/kubernetes" mdnsr "github.com/go-micro/plugins/v4/registry/mdns" memr "github.com/go-micro/plugins/v4/registry/memory" natsr "github.com/go-micro/plugins/v4/registry/nats" - - "go-micro.dev/v4/registry" + mRegistry "go-micro.dev/v4/registry" "go-micro.dev/v4/registry/cache" ) @@ -25,7 +25,7 @@ const ( var ( once sync.Once regPlugin string - reg registry.Registry + reg mRegistry.Registry ) func Configure(plugin string) { @@ -37,7 +37,7 @@ func Configure(plugin string) { // GetRegistry returns a configured micro registry based on Micro env vars. // It defaults to mDNS, so mind that systems with mDNS disabled by default (i.e SUSE) will have a hard time // and it needs to explicitly use etcd. Os awareness for providing a working registry out of the box should be done. -func GetRegistry() registry.Registry { +func GetRegistry() mRegistry.Registry { once.Do(func() { addresses := strings.Split(os.Getenv(registryAddressEnv), ",") // prefer env of setting from Configure() @@ -49,29 +49,33 @@ func GetRegistry() registry.Registry { switch plugin { case "nats": reg = natsr.NewRegistry( - registry.Addrs(addresses...), + mRegistry.Addrs(addresses...), ) case "kubernetes": reg = kubernetesr.NewRegistry( - registry.Addrs(addresses...), + mRegistry.Addrs(addresses...), ) case "etcd": reg = etcdr.NewRegistry( - registry.Addrs(addresses...), + mRegistry.Addrs(addresses...), ) case "consul": reg = consulr.NewRegistry( - registry.Addrs(addresses...), + mRegistry.Addrs(addresses...), ) case "memory": reg = memr.NewRegistry() default: reg = mdnsr.NewRegistry() } + // No cache needed for in-memory registry if plugin != "memory" { - reg = cache.New(reg, cache.WithTTL(20*time.Second)) + reg = cache.New(reg, cache.WithTTL(30*time.Second)) } + + // fixme: lazy initialization of reva registry, needs refactor to a explicit call per service + _ = rRegistry.Init(reg) }) // always use cached registry to prevent registry // lookup for every request diff --git a/ocis-pkg/registry/service.go b/ocis-pkg/registry/service.go new file mode 100644 index 000000000..da80a8912 --- /dev/null +++ b/ocis-pkg/registry/service.go @@ -0,0 +1,83 @@ +package registry + +import ( + "fmt" + "net" + "strconv" + "strings" + + mRegistry "go-micro.dev/v4/registry" + "go-micro.dev/v4/util/addr" +) + +func BuildGRPCService(serviceID, uuid, address string, version string) *mRegistry.Service { + var host string + var port int + + parts := strings.Split(address, ":") + if len(parts) > 1 { + host = strings.Join(parts[:len(parts)-1], ":") + port, _ = strconv.Atoi(parts[len(parts)-1]) + } else { + host = parts[0] + } + + addr, err := addr.Extract(host) + if err != nil { + addr = host + } + + node := &mRegistry.Node{ + Id: serviceID + "-" + uuid, + Address: net.JoinHostPort(addr, fmt.Sprint(port)), + Metadata: make(map[string]string), + } + + node.Metadata["registry"] = GetRegistry().String() + node.Metadata["server"] = "grpc" + node.Metadata["transport"] = "grpc" + node.Metadata["protocol"] = "grpc" + + return &mRegistry.Service{ + Name: serviceID, + Version: version, + Nodes: []*mRegistry.Node{node}, + Endpoints: make([]*mRegistry.Endpoint, 0), + } +} + +func BuildHTTPService(serviceID, uuid, address string, version string) *mRegistry.Service { + var host string + var port int + + parts := strings.Split(address, ":") + if len(parts) > 1 { + host = strings.Join(parts[:len(parts)-1], ":") + port, _ = strconv.Atoi(parts[len(parts)-1]) + } else { + host = parts[0] + } + + addr, err := addr.Extract(host) + if err != nil { + addr = host + } + + node := &mRegistry.Node{ + Id: serviceID + "-" + uuid, + Address: net.JoinHostPort(addr, fmt.Sprint(port)), + Metadata: make(map[string]string), + } + + node.Metadata["registry"] = GetRegistry().String() + node.Metadata["server"] = "http" + node.Metadata["transport"] = "http" + node.Metadata["protocol"] = "http" + + return &mRegistry.Service{ + Name: serviceID, + Version: version, + Nodes: []*mRegistry.Node{node}, + Endpoints: make([]*mRegistry.Endpoint, 0), + } +} diff --git a/ocis-pkg/service/external/external.go b/ocis-pkg/service/external/external.go deleted file mode 100644 index 7b90fe7fb..000000000 --- a/ocis-pkg/service/external/external.go +++ /dev/null @@ -1,120 +0,0 @@ -package external - -import ( - "context" - "time" - - "github.com/owncloud/ocis/v2/ocis-pkg/log" - oregistry "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "go-micro.dev/v4/registry" -) - -// RegisterGRPCEndpoint publishes an arbitrary endpoint to the service-registry. This allows querying nodes of -// non-micro GRPC-services like reva. No health-checks are done, thus the caller is responsible for canceling. -func RegisterGRPCEndpoint(ctx context.Context, serviceID, uuid, addr string, version string, logger log.Logger) error { - node := ®istry.Node{ - Id: serviceID + "-" + uuid, - Address: addr, - Metadata: make(map[string]string), - } - ocisRegistry := oregistry.GetRegistry() - - node.Metadata["registry"] = ocisRegistry.String() - node.Metadata["server"] = "grpc" - node.Metadata["transport"] = "grpc" - node.Metadata["protocol"] = "grpc" - - service := ®istry.Service{ - Name: serviceID, - Version: version, - Nodes: []*registry.Node{node}, - Endpoints: make([]*registry.Endpoint, 0), - } - - logger.Info().Msgf("registering external service %v@%v", node.Id, node.Address) - - rOpts := []registry.RegisterOption{registry.RegisterTTL(time.Minute)} - if err := ocisRegistry.Register(service, rOpts...); err != nil { - logger.Fatal().Err(err).Msgf("Registration error for external service %v", serviceID) - } - - t := time.NewTicker(time.Second * 30) - - go func() { - for { - select { - case <-t.C: - logger.Debug().Interface("service", service).Msg("refreshing external service-registration") - err := ocisRegistry.Register(service, rOpts...) - if err != nil { - logger.Error().Err(err).Msgf("registration error for external service %v", serviceID) - } - case <-ctx.Done(): - logger.Debug().Interface("service", service).Msg("unregistering") - t.Stop() - err := ocisRegistry.Deregister(service) - if err != nil { - logger.Err(err).Msgf("Error unregistering external service %v", serviceID) - } - - } - } - }() - - return nil -} - -// RegisterHTTPEndpoint publishes an arbitrary endpoint to the service-registry. This allows querying nodes of -// non-micro HTTP-services like reva. No health-checks are done, thus the caller is responsible for canceling. -func RegisterHTTPEndpoint(ctx context.Context, serviceID, uuid, addr string, version string, logger log.Logger) error { - node := ®istry.Node{ - Id: serviceID + "-" + uuid, - Address: addr, - Metadata: make(map[string]string), - } - ocisRegistry := oregistry.GetRegistry() - - node.Metadata["registry"] = ocisRegistry.String() - node.Metadata["server"] = "http" - node.Metadata["transport"] = "http" - node.Metadata["protocol"] = "http" - - service := ®istry.Service{ - Name: serviceID, - Version: version, - Nodes: []*registry.Node{node}, - Endpoints: make([]*registry.Endpoint, 0), - } - - logger.Info().Msgf("registering external service %v@%v", node.Id, node.Address) - - rOpts := []registry.RegisterOption{registry.RegisterTTL(time.Minute)} - if err := ocisRegistry.Register(service, rOpts...); err != nil { - logger.Fatal().Err(err).Msgf("Registration error for external service %v", serviceID) - } - - t := time.NewTicker(time.Second * 30) - - go func() { - for { - select { - case <-t.C: - logger.Debug().Interface("service", service).Msg("refreshing external service-registration") - err := ocisRegistry.Register(service, rOpts...) - if err != nil { - logger.Error().Err(err).Msgf("registration error for external service %v", serviceID) - } - case <-ctx.Done(): - logger.Debug().Interface("service", service).Msg("unregistering") - t.Stop() - err := ocisRegistry.Deregister(service) - if err != nil { - logger.Err(err).Msgf("Error unregistering external service %v", serviceID) - } - - } - } - }() - - return nil -} diff --git a/ocis-pkg/service/grpc/option.go b/ocis-pkg/service/grpc/option.go index 24abeb861..57361e564 100644 --- a/ocis-pkg/service/grpc/option.go +++ b/ocis-pkg/service/grpc/option.go @@ -5,6 +5,7 @@ import ( "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/urfave/cli/v2" + "go.opentelemetry.io/otel/trace" ) // Option defines a single option function. @@ -12,16 +13,17 @@ type Option func(o *Options) // Options defines the available options for this package. type Options struct { - Logger log.Logger - Namespace string - Name string - Version string - Address string - TLSEnabled bool - TLSCert string - TLSKey string - Context context.Context - Flags []cli.Flag + Logger log.Logger + Namespace string + Name string + Version string + Address string + TLSEnabled bool + TLSCert string + TLSKey string + Context context.Context + Flags []cli.Flag + TraceProvider trace.TracerProvider } // newOptions initializes the available default options. @@ -100,3 +102,10 @@ func Flags(flags ...cli.Flag) Option { o.Flags = append(o.Flags, flags...) } } + +// TraceProvider provides a function to set the trace provider option. +func TraceProvider(tp trace.TracerProvider) Option { + return func(o *Options) { + o.TraceProvider = tp + } +} diff --git a/ocis-pkg/service/grpc/service.go b/ocis-pkg/service/grpc/service.go index d507213ef..920d9abc9 100644 --- a/ocis-pkg/service/grpc/service.go +++ b/ocis-pkg/service/grpc/service.go @@ -62,9 +62,15 @@ func NewService(opts ...Option) (Service, error) { micro.RegisterTTL(time.Second * 30), micro.RegisterInterval(time.Second * 10), micro.WrapHandler(prometheus.NewHandlerWrapper()), - micro.WrapClient(mtracer.NewClientWrapper()), - micro.WrapHandler(mtracer.NewHandlerWrapper()), - micro.WrapSubscriber(mtracer.NewSubscriberWrapper()), + micro.WrapClient(mtracer.NewClientWrapper( + mtracer.WithTraceProvider(sopts.TraceProvider), + )), + micro.WrapHandler(mtracer.NewHandlerWrapper( + mtracer.WithTraceProvider(sopts.TraceProvider), + )), + micro.WrapSubscriber(mtracer.NewSubscriberWrapper( + mtracer.WithTraceProvider(sopts.TraceProvider), + )), } return Service{micro.NewService(mopts...)}, nil diff --git a/ocis-pkg/shared/reva.go b/ocis-pkg/shared/reva.go index cc3837090..a4a9f5555 100644 --- a/ocis-pkg/shared/reva.go +++ b/ocis-pkg/shared/reva.go @@ -1,9 +1,11 @@ package shared -import "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" +import ( + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" +) var defaultRevaConfig = Reva{ - Address: "127.0.0.1:9142", + Address: "com.owncloud.api.gateway", } func DefaultRevaConfig() *Reva { diff --git a/ocis-pkg/tracing/config.go b/ocis-pkg/tracing/config.go new file mode 100644 index 000000000..a4b299c94 --- /dev/null +++ b/ocis-pkg/tracing/config.go @@ -0,0 +1,14 @@ +package tracing + +// ConfigConverter is the interface for external configuration. +type ConfigConverter interface { + Convert() Config +} + +// Tracing defines the available tracing configuration. +type Config struct { + Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED" desc:"Activates tracing."` + Type string `yaml:"type" env:"OCIS_TRACING_TYPE" desc:"The type of tracing. Defaults to \"\", which is the same as \"jaeger\". Allowed tracing types are \"jaeger\" and \"\" as of now."` + Endpoint string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT" desc:"The endpoint of the tracing agent."` + Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR" desc:"The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset."` +} diff --git a/ocis-pkg/tracing/tracing.go b/ocis-pkg/tracing/tracing.go index 157a22454..96dd798a0 100644 --- a/ocis-pkg/tracing/tracing.go +++ b/ocis-pkg/tracing/tracing.go @@ -16,6 +16,7 @@ import ( "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" + "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) @@ -26,6 +27,23 @@ var Propagator = propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, ) +// GetServiceTraceProvider returns a configured open-telemetry trace provider. +func GetServiceTraceProvider(c ConfigConverter, serviceName string) (trace.TracerProvider, error) { + cfg := c.Convert() + if cfg.Enabled { + return GetTraceProvider(cfg.Endpoint, cfg.Collector, serviceName, cfg.Type) + } + return trace.NewNoopTracerProvider(), nil +} + +// GetPropagator gets a configured propagator. +func GetPropagator() propagation.TextMapPropagator { + return propagation.NewCompositeTextMapPropagator( + propagation.Baggage{}, + propagation.TraceContext{}, + ) +} + // GetTraceProvider returns a configured open-telemetry trace provider. func GetTraceProvider(endpoint, collector, serviceName, traceType string) (*sdktrace.TracerProvider, error) { switch t := traceType; t { @@ -92,7 +110,6 @@ func GetTraceProvider(endpoint, collector, serviceName, traceType string) (*sdkt context.Background(), otlptracegrpc.WithGRPCConn(conn), ) - if err != nil { return nil, err } @@ -184,7 +201,6 @@ func Configure(enabled bool, tracingType string, logger log.Logger) { Str("type", tracingType). Msg("Unknown tracing exporter") } - } else { logger.Debug(). Msg("Tracing is not enabled") diff --git a/ocis-pkg/version/version.go b/ocis-pkg/version/version.go index c6bcb2e56..fa035fb7f 100644 --- a/ocis-pkg/version/version.go +++ b/ocis-pkg/version/version.go @@ -16,7 +16,7 @@ var ( // LatestTag is the latest released version plus the dev meta version. // Will be overwritten by the release pipeline // Needs a manual change for every tagged release - LatestTag = "3.0.0-rc.4+dev" + LatestTag = "3.1.0-next.2+dev" // Date indicates the build date. // This has been removed, it looks like you can only replace static strings with recent go versions diff --git a/ocis/pkg/command/antivirus.go b/ocis/pkg/command/antivirus.go deleted file mode 100644 index 24cd36efb..000000000 --- a/ocis/pkg/command/antivirus.go +++ /dev/null @@ -1,29 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/antivirus/pkg/command" - "github.com/urfave/cli/v2" -) - -// AntivirusCommand is the entrypoint for the antivirus command. -func AntivirusCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Antivirus.Service.Name, - Usage: helper.SubcommandDescription(cfg.Antivirus.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - return nil - }, - Subcommands: command.GetCommands(cfg.Antivirus), - } -} - -func init() { - register.AddCommand(AntivirusCommand) -} diff --git a/ocis/pkg/command/app-provider.go b/ocis/pkg/command/app-provider.go deleted file mode 100644 index 50f20ce76..000000000 --- a/ocis/pkg/command/app-provider.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/app-provider/pkg/command" - "github.com/urfave/cli/v2" -) - -// AppProviderCommand is the entrypoint for the app provider command. -func AppProviderCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.AppProvider.Service.Name, - Usage: helper.SubcommandDescription(cfg.AppProvider.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.AppProvider.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.AppProvider), - } -} - -func init() { - register.AddCommand(AppProviderCommand) -} diff --git a/ocis/pkg/command/app-registry.go b/ocis/pkg/command/app-registry.go deleted file mode 100644 index d35fcabb4..000000000 --- a/ocis/pkg/command/app-registry.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/app-registry/pkg/command" - "github.com/urfave/cli/v2" -) - -// AppRegistryCommand is the entrypoint for the AppRegistry command. -func AppRegistryCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.AppRegistry.Service.Name, - Usage: helper.SubcommandDescription(cfg.AppRegistry.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.AppRegistry.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.AppRegistry), - } -} - -func init() { - register.AddCommand(AppRegistryCommand) -} diff --git a/ocis/pkg/command/audit.go b/ocis/pkg/command/audit.go deleted file mode 100644 index 7299a71a8..000000000 --- a/ocis/pkg/command/audit.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/audit/pkg/command" - "github.com/urfave/cli/v2" -) - -// AuditCommand is the entrypoint for the Audit command. -func AuditCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Audit.Service.Name, - Usage: helper.SubcommandDescription(cfg.Audit.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Audit.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Audit), - } -} - -func init() { - register.AddCommand(AuditCommand) -} diff --git a/ocis/pkg/command/auth-basic.go b/ocis/pkg/command/auth-basic.go deleted file mode 100644 index 48a026647..000000000 --- a/ocis/pkg/command/auth-basic.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/auth-basic/pkg/command" - "github.com/urfave/cli/v2" -) - -// AuthBasicCommand is the entrypoint for the AuthBasic command. -func AuthBasicCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.AuthBasic.Service.Name, - Usage: helper.SubcommandDescription(cfg.AuthBasic.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.AuthBasic.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.AuthBasic), - } -} - -func init() { - register.AddCommand(AuthBasicCommand) -} diff --git a/ocis/pkg/command/auth-bearer.go b/ocis/pkg/command/auth-bearer.go deleted file mode 100644 index 413e15e9e..000000000 --- a/ocis/pkg/command/auth-bearer.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/command" - "github.com/urfave/cli/v2" -) - -// AuthBearerCommand is the entrypoint for the AuthBearer command. -func AuthBearerCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.AuthBearer.Service.Name, - Usage: helper.SubcommandDescription(cfg.AuthBearer.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.AuthBearer.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.AuthBearer), - } -} - -func init() { - register.AddCommand(AuthBearerCommand) -} diff --git a/ocis/pkg/command/auth-machine.go b/ocis/pkg/command/auth-machine.go deleted file mode 100644 index 17c23b632..000000000 --- a/ocis/pkg/command/auth-machine.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/auth-machine/pkg/command" - "github.com/urfave/cli/v2" -) - -// AuthMachineCommand is the entrypoint for the AuthMachine command. -func AuthMachineCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.AuthMachine.Service.Name, - Usage: helper.SubcommandDescription(cfg.AuthMachine.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.AuthMachine.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.AuthMachine), - } -} - -func init() { - register.AddCommand(AuthMachineCommand) -} diff --git a/ocis/pkg/command/decomposedfs.go b/ocis/pkg/command/decomposedfs.go index 06b78b939..16a431e54 100644 --- a/ocis/pkg/command/decomposedfs.go +++ b/ocis/pkg/command/decomposedfs.go @@ -4,15 +4,23 @@ import ( "context" "encoding/base64" "encoding/hex" + "errors" "fmt" "sort" "strings" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/storage/cache" + "github.com/cs3org/reva/v2/pkg/storage/fs/ocis/blobstore" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree" "github.com/cs3org/reva/v2/pkg/storagespace" + "github.com/cs3org/reva/v2/pkg/store" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis/pkg/register" "github.com/urfave/cli/v2" @@ -21,10 +29,13 @@ import ( // DecomposedfsCommand is the entrypoint for the groups command. func DecomposedfsCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "decomposedfs", - Usage: `cli tools to inspect and manipulate a decomposedfs storage.`, - Category: "maintenance", - Subcommands: []*cli.Command{metadataCmd(cfg)}, + Name: "decomposedfs", + Usage: `cli tools to inspect and manipulate a decomposedfs storage.`, + Category: "maintenance", + Subcommands: []*cli.Command{ + metadataCmd(cfg), + checkCmd(cfg), + }, } } @@ -32,6 +43,145 @@ func init() { register.AddCommand(DecomposedfsCommand) } +func checkCmd(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "check-treesize", + Usage: `cli tool to check the treesize metadata of a Space`, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "root", + Aliases: []string{"r"}, + Required: true, + Usage: "Path to the root directory of the decomposedfs", + }, + &cli.StringFlag{ + Name: "node", + Required: true, + Aliases: []string{"n"}, + Usage: "Space ID of the Space to inspect", + }, + &cli.BoolFlag{ + Name: "repair", + Usage: "Try to repair nodes with incorrect treesize metadata. IMPORTANT: Only use this while ownCloud Infinite Scale is not running.", + }, + &cli.BoolFlag{ + Name: "force", + Usage: "Do not prompt for confirmation when running in repair mode.", + }, + }, + Action: check, + } +} + +func check(c *cli.Context) error { + rootFlag := c.String("root") + repairFlag := c.Bool("repair") + + if repairFlag && !c.Bool("force") { + answer := strings.ToLower(stringPrompt("IMPORTANT: Only use '--repair' when ownCloud Infinite Scale is not running. Do you want to continue? [yes | no = default]")) + if answer != "yes" && answer != "y" { + return nil + } + } + + lu, backend := getBackend(c) + o := &options.Options{ + MetadataBackend: backend.Name(), + MaxConcurrency: 100, + } + bs, err := blobstore.New(rootFlag) + if err != nil { + fmt.Println("Failed to init blobstore") + return err + } + + tree := tree.New(lu, bs, o, store.Create()) + + nId := c.String("node") + n, err := lu.NodeFromSpaceID(context.Background(), nId) + if err != nil || !n.Exists { + fmt.Println("Can not find node '" + nId + "'") + return err + } + fmt.Printf("Checking treesizes in space: %s (id: %s)\n", n.Name, n.ID) + ctx := revactx.ContextSetUser(context.Background(), + &userpb.User{ + Id: &userpb.UserId{ + OpaqueId: "00000000-0000-0000-0000-000000000000", + }, + Username: "offline", + }) + + treeSize, err := walkTree(ctx, tree, lu, n, repairFlag) + treesizeFromMetadata, err := n.GetTreeSize() + if err != nil { + fmt.Printf("failed to read treesize of node: %s: %s\n", n.ID, err) + } + if treesizeFromMetadata != treeSize { + fmt.Printf("Tree sizes mismatch for space: %s\n\tNodeId: %s\n\tInternalPath: %s\n\tcalculated treesize: %d\n\ttreesize in metadata: %d\n", + n.Name, n.ID, n.InternalPath(), treeSize, treesizeFromMetadata) + if repairFlag { + fmt.Printf("Fixing tree size for node: %s. Calculated treesize: %d\n", + n.ID, treeSize) + n.SetTreeSize(treeSize) + } + } + return nil +} + +func walkTree(ctx context.Context, tree *tree.Tree, lu *lookup.Lookup, root *node.Node, repair bool) (uint64, error) { + if root.Type() != provider.ResourceType_RESOURCE_TYPE_CONTAINER { + return 0, errors.New("can't travers non-container nodes") + } + children, err := tree.ListFolder(ctx, root) + if err != nil { + fmt.Println("Can not list children for space'" + root.ID + "'") + return 0, err + } + + var treesize uint64 + for _, child := range children { + switch child.Type() { + case provider.ResourceType_RESOURCE_TYPE_CONTAINER: + subtreesize, err := walkTree(ctx, tree, lu, child, repair) + if err != nil { + fmt.Printf("error calculating tree size of node: %s: %s\n", child.ID, err) + return 0, err + } + treesizeFromMetadata, err := child.GetTreeSize() + if err != nil { + fmt.Printf("failed to read tree size of node: %s: %s\n", child.ID, err) + return 0, err + } + if treesizeFromMetadata != subtreesize { + origin, err := lu.Path(ctx, child, node.NoCheck) + if err != nil { + fmt.Printf("error get path: %s\n", err) + } + fmt.Printf("Tree sizes mismatch for node: %s\n\tNodeId: %s\n\tInternalPath: %s\n\tcalculated treesize: %d\n\ttreesize in metadata: %d\n", + origin, child.ID, child.InternalPath(), subtreesize, treesizeFromMetadata) + if repair { + fmt.Printf("Fixing tree size for node: %s. Calculated treesize: %d\n", + child.ID, subtreesize) + child.SetTreeSize(subtreesize) + } + } + treesize += subtreesize + case provider.ResourceType_RESOURCE_TYPE_FILE: + blobsize, err := child.GetBlobSize() + if err != nil { + fmt.Printf("error reading blobsize of node: %s: %s\n", child.ID, err) + return 0, err + } + treesize += blobsize + default: + fmt.Printf("Ignoring type: %v, node: %s %s\n", child.Type(), child.Name, child.ID) + } + } + + return treesize, nil +} + func metadataCmd(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "metadata", @@ -135,15 +285,19 @@ func setCmd(cfg *config.Config) *cli.Command { b64, err := base64.StdEncoding.DecodeString(v[2:]) if err == nil { v = string(b64) + } else { + fmt.Printf("Error decoding base64 string: '%s'. Using as raw string.\n", err) } } else if strings.HasPrefix(v, "0x") { - h, err := hex.DecodeString(v) + h, err := hex.DecodeString(v[2:]) if err == nil { v = string(h) + } else { + fmt.Printf("Error decoding base64 string: '%s'. Using as raw string.\n", err) } } - err = backend.Set(path, c.String("attribute"), []byte(v[2:])) + err = backend.Set(path, c.String("attribute"), []byte(v)) if err != nil { fmt.Println("Error setting attribute") return err @@ -188,7 +342,7 @@ func getPath(c *cli.Context, lu *lookup.Lookup) (string, error) { fmt.Println("Invalid node id.") return "", err } - n, _ := lu.NodeFromID(context.Background(), &id) + n, err := lu.NodeFromID(context.Background(), &id) if err != nil || !n.Exists { fmt.Println("Can not find node '" + nId + "'") return "", err diff --git a/ocis/pkg/command/eventhistory.go b/ocis/pkg/command/eventhistory.go deleted file mode 100644 index a75122210..000000000 --- a/ocis/pkg/command/eventhistory.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/eventhistory/pkg/command" - "github.com/urfave/cli/v2" -) - -// EventHistoryCommand is the entrypoint for the eventhistory command. -func EventHistoryCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.EventHistory.Service.Name, - Usage: helper.SubcommandDescription(cfg.EventHistory.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.EventHistory.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.EventHistory), - } -} - -func init() { - register.AddCommand(EventHistoryCommand) -} diff --git a/ocis/pkg/command/frontend.go b/ocis/pkg/command/frontend.go deleted file mode 100644 index ae28f4105..000000000 --- a/ocis/pkg/command/frontend.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/frontend/pkg/command" - "github.com/urfave/cli/v2" -) - -// FrontendCommand is the entrypoint for the Frontend command. -func FrontendCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Frontend.Service.Name, - Usage: helper.SubcommandDescription(cfg.Frontend.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Frontend.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Frontend), - } -} - -func init() { - register.AddCommand(FrontendCommand) -} diff --git a/ocis/pkg/command/gateway.go b/ocis/pkg/command/gateway.go deleted file mode 100644 index 3562ffade..000000000 --- a/ocis/pkg/command/gateway.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/gateway/pkg/command" - "github.com/urfave/cli/v2" -) - -// GatewayCommand is the entrypoint for the Gateway command. -func GatewayCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Gateway.Service.Name, - Usage: helper.SubcommandDescription(cfg.Gateway.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Gateway.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Gateway), - } -} - -func init() { - register.AddCommand(GatewayCommand) -} diff --git a/ocis/pkg/command/graph.go b/ocis/pkg/command/graph.go deleted file mode 100644 index 4ff45e231..000000000 --- a/ocis/pkg/command/graph.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/graph/pkg/command" - "github.com/urfave/cli/v2" -) - -// GraphCommand is the entrypoint for the graph command. -func GraphCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Graph.Service.Name, - Usage: helper.SubcommandDescription(cfg.Graph.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Graph.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Graph), - } -} - -func init() { - register.AddCommand(GraphCommand) -} diff --git a/ocis/pkg/command/groups.go b/ocis/pkg/command/groups.go deleted file mode 100644 index 630f1060e..000000000 --- a/ocis/pkg/command/groups.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/groups/pkg/command" - "github.com/urfave/cli/v2" -) - -// GroupsCommand is the entrypoint for the groups command. -func GroupsCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Groups.Service.Name, - Usage: helper.SubcommandDescription(cfg.Groups.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Groups.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Groups), - } -} - -func init() { - register.AddCommand(GroupsCommand) -} diff --git a/ocis/pkg/command/idm.go b/ocis/pkg/command/idm.go deleted file mode 100644 index 3adc83738..000000000 --- a/ocis/pkg/command/idm.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/idm/pkg/command" - "github.com/urfave/cli/v2" -) - -// IDMCommand is the entrypoint for the idm command. -func IDMCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.IDM.Service.Name, - Usage: helper.SubcommandDescription(cfg.IDM.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.IDM.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.IDM), - } -} - -func init() { - register.AddCommand(IDMCommand) -} diff --git a/ocis/pkg/command/idp.go b/ocis/pkg/command/idp.go deleted file mode 100644 index b8f0449c7..000000000 --- a/ocis/pkg/command/idp.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/idp/pkg/command" - "github.com/urfave/cli/v2" -) - -// IDPCommand is the entrypoint for the idp command. -func IDPCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.IDP.Service.Name, - Usage: helper.SubcommandDescription(cfg.IDP.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.IDP.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.IDP), - } -} - -func init() { - register.AddCommand(IDPCommand) -} diff --git a/ocis/pkg/command/invitations.go b/ocis/pkg/command/invitations.go deleted file mode 100644 index 984d8adf8..000000000 --- a/ocis/pkg/command/invitations.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/invitations/pkg/command" - "github.com/urfave/cli/v2" -) - -// InvitationsCommand is the entrypoint for the invitations command. -func InvitationsCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Invitations.Service.Name, - Usage: helper.SubcommandDescription(cfg.Invitations.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Invitations.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Invitations), - } -} - -func init() { - register.AddCommand(InvitationsCommand) -} diff --git a/ocis/pkg/command/nats.go b/ocis/pkg/command/nats.go deleted file mode 100644 index 989286c0a..000000000 --- a/ocis/pkg/command/nats.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/nats/pkg/command" - "github.com/urfave/cli/v2" -) - -// NatsCommand is the entrypoint for the Nats command. -func NatsCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Nats.Service.Name, - Usage: helper.SubcommandDescription(cfg.Nats.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Nats.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Nats), - } -} - -func init() { - register.AddCommand(NatsCommand) -} diff --git a/ocis/pkg/command/notifications.go b/ocis/pkg/command/notifications.go deleted file mode 100644 index 0fc591f14..000000000 --- a/ocis/pkg/command/notifications.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/notifications/pkg/command" - "github.com/urfave/cli/v2" -) - -// NotificationsCommand is the entrypoint for the notifications command. -func NotificationsCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Notifications.Service.Name, - Usage: helper.SubcommandDescription(cfg.Notifications.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Notifications.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Notifications), - } -} - -func init() { - register.AddCommand(NotificationsCommand) -} diff --git a/ocis/pkg/command/ocdav.go b/ocis/pkg/command/ocdav.go deleted file mode 100644 index a46cd3ecf..000000000 --- a/ocis/pkg/command/ocdav.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/ocdav/pkg/command" - "github.com/urfave/cli/v2" -) - -// OCDavCommand is the entrypoint for the OCDav command. -func OCDavCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.OCDav.Service.Name, - Usage: helper.SubcommandDescription(cfg.OCDav.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.OCDav.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.OCDav), - } -} - -func init() { - register.AddCommand(OCDavCommand) -} diff --git a/ocis/pkg/command/ocs.go b/ocis/pkg/command/ocs.go deleted file mode 100644 index 89091138c..000000000 --- a/ocis/pkg/command/ocs.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/ocs/pkg/command" - "github.com/urfave/cli/v2" -) - -// OCSCommand is the entrypoint for the ocs command. -func OCSCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.OCS.Service.Name, - Usage: helper.SubcommandDescription(cfg.OCS.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.OCS.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.OCS), - } -} - -func init() { - register.AddCommand(OCSCommand) -} diff --git a/ocis/pkg/command/policies.go b/ocis/pkg/command/policies.go deleted file mode 100644 index 69540d50b..000000000 --- a/ocis/pkg/command/policies.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/policies/pkg/command" - "github.com/urfave/cli/v2" -) - -// PoliciesCommand is the entrypoint for the policies service. -func PoliciesCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Policies.Service.Name, - Usage: helper.SubcommandDescription(cfg.Policies.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Policies.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Policies), - } -} - -func init() { - register.AddCommand(PoliciesCommand) -} diff --git a/ocis/pkg/command/postprocessing.go b/ocis/pkg/command/postprocessing.go deleted file mode 100644 index 0d35a35ac..000000000 --- a/ocis/pkg/command/postprocessing.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/postprocessing/pkg/command" - "github.com/urfave/cli/v2" -) - -// PostprocessingCommand is the entrypoint for the postprocessing command. -func PostprocessingCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Postprocessing.Service.Name, - Usage: helper.SubcommandDescription(cfg.Postprocessing.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Postprocessing.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Postprocessing), - } -} - -func init() { - register.AddCommand(PostprocessingCommand) -} diff --git a/ocis/pkg/command/proxy.go b/ocis/pkg/command/proxy.go deleted file mode 100644 index 0bdbf71e2..000000000 --- a/ocis/pkg/command/proxy.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/proxy/pkg/command" - "github.com/urfave/cli/v2" -) - -// ProxyCommand is the entry point for the proxy command. -func ProxyCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Proxy.Service.Name, - Usage: helper.SubcommandDescription(cfg.Proxy.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Proxy.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Proxy), - } -} - -func init() { - register.AddCommand(ProxyCommand) -} diff --git a/ocis/pkg/command/search.go b/ocis/pkg/command/search.go deleted file mode 100644 index 59cb691e9..000000000 --- a/ocis/pkg/command/search.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/search/pkg/command" - "github.com/urfave/cli/v2" -) - -// SearchCommand is the entry point for the search command. -func SearchCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Search.Service.Name, - Usage: helper.SubcommandDescription(cfg.Search.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Search.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Search), - } -} - -func init() { - register.AddCommand(SearchCommand) -} diff --git a/ocis/pkg/command/services.go b/ocis/pkg/command/services.go new file mode 100644 index 000000000..960abd452 --- /dev/null +++ b/ocis/pkg/command/services.go @@ -0,0 +1,251 @@ +package command + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" + "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" + "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" + "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/urfave/cli/v2" + + antivirus "github.com/owncloud/ocis/v2/services/antivirus/pkg/command" + appprovider "github.com/owncloud/ocis/v2/services/app-provider/pkg/command" + appregistry "github.com/owncloud/ocis/v2/services/app-registry/pkg/command" + audit "github.com/owncloud/ocis/v2/services/audit/pkg/command" + authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/command" + authbearer "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/command" + authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/command" + eventhistory "github.com/owncloud/ocis/v2/services/eventhistory/pkg/command" + frontend "github.com/owncloud/ocis/v2/services/frontend/pkg/command" + gateway "github.com/owncloud/ocis/v2/services/gateway/pkg/command" + graph "github.com/owncloud/ocis/v2/services/graph/pkg/command" + groups "github.com/owncloud/ocis/v2/services/groups/pkg/command" + idm "github.com/owncloud/ocis/v2/services/idm/pkg/command" + idp "github.com/owncloud/ocis/v2/services/idp/pkg/command" + invitations "github.com/owncloud/ocis/v2/services/invitations/pkg/command" + nats "github.com/owncloud/ocis/v2/services/nats/pkg/command" + notifications "github.com/owncloud/ocis/v2/services/notifications/pkg/command" + ocdav "github.com/owncloud/ocis/v2/services/ocdav/pkg/command" + ocs "github.com/owncloud/ocis/v2/services/ocs/pkg/command" + policies "github.com/owncloud/ocis/v2/services/policies/pkg/command" + postprocessing "github.com/owncloud/ocis/v2/services/postprocessing/pkg/command" + proxy "github.com/owncloud/ocis/v2/services/proxy/pkg/command" + search "github.com/owncloud/ocis/v2/services/search/pkg/command" + settings "github.com/owncloud/ocis/v2/services/settings/pkg/command" + sharing "github.com/owncloud/ocis/v2/services/sharing/pkg/command" + storagepubliclink "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/command" + storageshares "github.com/owncloud/ocis/v2/services/storage-shares/pkg/command" + storagesystem "github.com/owncloud/ocis/v2/services/storage-system/pkg/command" + storageusers "github.com/owncloud/ocis/v2/services/storage-users/pkg/command" + store "github.com/owncloud/ocis/v2/services/store/pkg/command" + thumbnails "github.com/owncloud/ocis/v2/services/thumbnails/pkg/command" + userlog "github.com/owncloud/ocis/v2/services/userlog/pkg/command" + users "github.com/owncloud/ocis/v2/services/users/pkg/command" + web "github.com/owncloud/ocis/v2/services/web/pkg/command" + webdav "github.com/owncloud/ocis/v2/services/webdav/pkg/command" + webfinger "github.com/owncloud/ocis/v2/services/webfinger/pkg/command" +) + +var svccmds = []register.Command{ + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Antivirus.Service.Name, antivirus.GetCommands(cfg.Antivirus), func(c *config.Config) { + // cfg.Antivirus.Commons = cfg.Commons // antivirus needs no commons atm + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.AppProvider.Service.Name, appprovider.GetCommands(cfg.AppProvider), func(c *config.Config) { + cfg.AppProvider.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.AppRegistry.Service.Name, appregistry.GetCommands(cfg.AppRegistry), func(c *config.Config) { + cfg.AppRegistry.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Audit.Service.Name, audit.GetCommands(cfg.Audit), func(c *config.Config) { + cfg.Audit.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.AuthBasic.Service.Name, authbasic.GetCommands(cfg.AuthBasic), func(c *config.Config) { + cfg.AuthBasic.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.AuthBearer.Service.Name, authbearer.GetCommands(cfg.AuthBearer), func(c *config.Config) { + cfg.AuthBearer.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.AuthMachine.Service.Name, authmachine.GetCommands(cfg.AuthMachine), func(c *config.Config) { + cfg.AuthMachine.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.EventHistory.Service.Name, eventhistory.GetCommands(cfg.EventHistory), func(c *config.Config) { + cfg.EventHistory.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Frontend.Service.Name, frontend.GetCommands(cfg.Frontend), func(c *config.Config) { + cfg.Frontend.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Gateway.Service.Name, gateway.GetCommands(cfg.Gateway), func(c *config.Config) { + cfg.Gateway.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Graph.Service.Name, graph.GetCommands(cfg.Graph), func(c *config.Config) { + cfg.Graph.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Groups.Service.Name, groups.GetCommands(cfg.Groups), func(c *config.Config) { + cfg.Groups.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.IDM.Service.Name, idm.GetCommands(cfg.IDM), func(c *config.Config) { + cfg.IDM.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.IDP.Service.Name, idp.GetCommands(cfg.IDP), func(c *config.Config) { + cfg.IDP.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Invitations.Service.Name, invitations.GetCommands(cfg.Invitations), func(c *config.Config) { + cfg.Invitations.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Nats.Service.Name, nats.GetCommands(cfg.Nats), func(c *config.Config) { + cfg.Nats.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Notifications.Service.Name, notifications.GetCommands(cfg.Notifications), func(c *config.Config) { + cfg.Notifications.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.OCDav.Service.Name, ocdav.GetCommands(cfg.OCDav), func(c *config.Config) { + cfg.OCDav.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.OCS.Service.Name, ocs.GetCommands(cfg.OCS), func(c *config.Config) { + cfg.OCS.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Policies.Service.Name, policies.GetCommands(cfg.Policies), func(c *config.Config) { + cfg.Policies.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Postprocessing.Service.Name, postprocessing.GetCommands(cfg.Postprocessing), func(c *config.Config) { + cfg.Postprocessing.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Proxy.Service.Name, proxy.GetCommands(cfg.Proxy), func(c *config.Config) { + cfg.Proxy.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Search.Service.Name, search.GetCommands(cfg.Search), func(c *config.Config) { + cfg.Search.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Settings.Service.Name, settings.GetCommands(cfg.Settings), func(c *config.Config) { + cfg.Settings.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Sharing.Service.Name, sharing.GetCommands(cfg.Sharing), func(c *config.Config) { + cfg.Sharing.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.StoragePublicLink.Service.Name, storagepubliclink.GetCommands(cfg.StoragePublicLink), func(c *config.Config) { + cfg.StoragePublicLink.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.StorageShares.Service.Name, storageshares.GetCommands(cfg.StorageShares), func(c *config.Config) { + cfg.StorageShares.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.StorageSystem.Service.Name, storagesystem.GetCommands(cfg.StorageSystem), func(c *config.Config) { + cfg.StorageSystem.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.StorageUsers.Service.Name, storageusers.GetCommands(cfg.StorageUsers), func(c *config.Config) { + cfg.StorageUsers.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Store.Service.Name, store.GetCommands(cfg.Store), func(c *config.Config) { + cfg.Store.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Thumbnails.Service.Name, thumbnails.GetCommands(cfg.Thumbnails), func(c *config.Config) { + cfg.Thumbnails.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Userlog.Service.Name, userlog.GetCommands(cfg.Userlog), func(c *config.Config) { + cfg.Userlog.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Users.Service.Name, users.GetCommands(cfg.Users), func(c *config.Config) { + cfg.Users.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Web.Service.Name, web.GetCommands(cfg.Web), func(c *config.Config) { + cfg.Web.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.WebDAV.Service.Name, webdav.GetCommands(cfg.WebDAV), func(c *config.Config) { + cfg.WebDAV.Commons = cfg.Commons + }) + }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.Webfinger.Service.Name, webfinger.GetCommands(cfg.Webfinger), func(c *config.Config) { + cfg.Webfinger.Commons = cfg.Commons + }) + }, +} + +// ServiceCommand is the entry point for the all service commands. +func ServiceCommand(cfg *config.Config, servicename string, subcommands []*cli.Command, f func(*config.Config)) *cli.Command { + return &cli.Command{ + Name: servicename, + Usage: helper.SubcommandDescription(servicename), + Category: "services", + Before: func(c *cli.Context) error { + configlog.Error(parser.ParseConfig(cfg, true)) + f(cfg) + return nil + }, + Subcommands: subcommands, + } +} + +func init() { + for _, c := range svccmds { + register.AddCommand(c) + } +} diff --git a/ocis/pkg/command/settings.go b/ocis/pkg/command/settings.go deleted file mode 100644 index d5950b209..000000000 --- a/ocis/pkg/command/settings.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/settings/pkg/command" - "github.com/urfave/cli/v2" -) - -// SettingsCommand is the entry point for the settings command. -func SettingsCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Settings.Service.Name, - Usage: helper.SubcommandDescription(cfg.Settings.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Settings.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Settings), - } -} - -func init() { - register.AddCommand(SettingsCommand) -} diff --git a/ocis/pkg/command/sharing.go b/ocis/pkg/command/sharing.go deleted file mode 100644 index 31ccc40ca..000000000 --- a/ocis/pkg/command/sharing.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/sharing/pkg/command" - "github.com/urfave/cli/v2" -) - -// SharingCommand is the entrypoint for the Sharing command. -func SharingCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Sharing.Service.Name, - Usage: helper.SubcommandDescription(cfg.Sharing.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Sharing.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Sharing), - } -} - -func init() { - register.AddCommand(SharingCommand) -} diff --git a/ocis/pkg/command/storage-publiclink.go b/ocis/pkg/command/storage-publiclink.go deleted file mode 100644 index 181908924..000000000 --- a/ocis/pkg/command/storage-publiclink.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/command" - "github.com/urfave/cli/v2" -) - -// StoragePublicLinkCommand is the entrypoint for the StoragePublicLink command. -func StoragePublicLinkCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.StoragePublicLink.Service.Name, - Usage: helper.SubcommandDescription(cfg.StoragePublicLink.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.StoragePublicLink.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.StoragePublicLink), - } -} - -func init() { - register.AddCommand(StoragePublicLinkCommand) -} diff --git a/ocis/pkg/command/storage-shares.go b/ocis/pkg/command/storage-shares.go deleted file mode 100644 index 6def89269..000000000 --- a/ocis/pkg/command/storage-shares.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/storage-shares/pkg/command" - "github.com/urfave/cli/v2" -) - -// StorageSharesCommand is the entrypoint for the StorageShares command. -func StorageSharesCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.StorageShares.Service.Name, - Usage: helper.SubcommandDescription(cfg.StorageShares.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.StorageShares.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.StorageShares), - } -} - -func init() { - register.AddCommand(StorageSharesCommand) -} diff --git a/ocis/pkg/command/storage-system.go b/ocis/pkg/command/storage-system.go deleted file mode 100644 index cd933eeea..000000000 --- a/ocis/pkg/command/storage-system.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/storage-system/pkg/command" - "github.com/urfave/cli/v2" -) - -// StorageSystemCommand is the entrypoint for the StorageSystem command. -func StorageSystemCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.StorageSystem.Service.Name, - Usage: helper.SubcommandDescription(cfg.StorageSystem.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.StorageSystem.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.StorageSystem), - } -} - -func init() { - register.AddCommand(StorageSystemCommand) -} diff --git a/ocis/pkg/command/storage-users.go b/ocis/pkg/command/storage-users.go deleted file mode 100644 index 980cc953d..000000000 --- a/ocis/pkg/command/storage-users.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/storage-users/pkg/command" - "github.com/urfave/cli/v2" -) - -// StorageUsersCommand is the entrypoint for the StorageUsers command. -func StorageUsersCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.StorageUsers.Service.Name, - Usage: helper.SubcommandDescription(cfg.StorageUsers.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.StorageUsers.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.StorageUsers), - } -} - -func init() { - register.AddCommand(StorageUsersCommand) -} diff --git a/ocis/pkg/command/store.go b/ocis/pkg/command/store.go deleted file mode 100644 index 947779d71..000000000 --- a/ocis/pkg/command/store.go +++ /dev/null @@ -1,31 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/store/pkg/command" - "github.com/urfave/cli/v2" -) - -// StoreCommand is the entrypoint for the ocs command. -func StoreCommand(cfg *config.Config) *cli.Command { - - return &cli.Command{ - Name: cfg.Store.Service.Name, - Usage: helper.SubcommandDescription(cfg.Store.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Store.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Store), - } -} - -func init() { - register.AddCommand(StoreCommand) -} diff --git a/ocis/pkg/command/thumbnails.go b/ocis/pkg/command/thumbnails.go deleted file mode 100644 index 235edfafb..000000000 --- a/ocis/pkg/command/thumbnails.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/thumbnails/pkg/command" - "github.com/urfave/cli/v2" -) - -// ThumbnailsCommand is the entrypoint for the thumbnails command. -func ThumbnailsCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Thumbnails.Service.Name, - Usage: helper.SubcommandDescription(cfg.Thumbnails.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Thumbnails.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Thumbnails), - } -} - -func init() { - register.AddCommand(ThumbnailsCommand) -} diff --git a/ocis/pkg/command/userlog.go b/ocis/pkg/command/userlog.go deleted file mode 100644 index 363b7d30b..000000000 --- a/ocis/pkg/command/userlog.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/userlog/pkg/command" - "github.com/urfave/cli/v2" -) - -// UserlogCommand is the entrypoint for the userlog command. -func UserlogCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Userlog.Service.Name, - Usage: helper.SubcommandDescription(cfg.Userlog.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Userlog.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Userlog), - } -} - -func init() { - register.AddCommand(UserlogCommand) -} diff --git a/ocis/pkg/command/users.go b/ocis/pkg/command/users.go deleted file mode 100644 index 905c75718..000000000 --- a/ocis/pkg/command/users.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/users/pkg/command" - "github.com/urfave/cli/v2" -) - -// UsersCommand is the entrypoint for the users command. -func UsersCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Users.Service.Name, - Usage: helper.SubcommandDescription(cfg.Users.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Users.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Users), - } -} - -func init() { - register.AddCommand(UsersCommand) -} diff --git a/ocis/pkg/command/web.go b/ocis/pkg/command/web.go deleted file mode 100644 index e7b1f001d..000000000 --- a/ocis/pkg/command/web.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/web/pkg/command" - "github.com/urfave/cli/v2" -) - -// WebCommand is the entrypoint for the web command. -func WebCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: cfg.Web.Service.Name, - Usage: helper.SubcommandDescription(cfg.Web.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Web.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Web), - } -} - -func init() { - register.AddCommand(WebCommand) -} diff --git a/ocis/pkg/command/webdav.go b/ocis/pkg/command/webdav.go deleted file mode 100644 index 6acd8ec59..000000000 --- a/ocis/pkg/command/webdav.go +++ /dev/null @@ -1,31 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/webdav/pkg/command" - "github.com/urfave/cli/v2" -) - -// WebDAVCommand is the entrypoint for the webdav command. -func WebDAVCommand(cfg *config.Config) *cli.Command { - - return &cli.Command{ - Name: cfg.WebDAV.Service.Name, - Usage: helper.SubcommandDescription(cfg.WebDAV.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.WebDAV.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.WebDAV), - } -} - -func init() { - register.AddCommand(WebDAVCommand) -} diff --git a/ocis/pkg/command/webfinger.go b/ocis/pkg/command/webfinger.go deleted file mode 100644 index e01fde864..000000000 --- a/ocis/pkg/command/webfinger.go +++ /dev/null @@ -1,31 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" - "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/owncloud/ocis/v2/services/webfinger/pkg/command" - "github.com/urfave/cli/v2" -) - -// WebfingerCommand is the entrypoint for the webfinger command. -func WebfingerCommand(cfg *config.Config) *cli.Command { - - return &cli.Command{ - Name: cfg.Webfinger.Service.Name, - Usage: helper.SubcommandDescription(cfg.Webfinger.Service.Name), - Category: "services", - Before: func(c *cli.Context) error { - configlog.Error(parser.ParseConfig(cfg, true)) - cfg.Webfinger.Commons = cfg.Commons - return nil - }, - Subcommands: command.GetCommands(cfg.Webfinger), - } -} - -func init() { - register.AddCommand(WebfingerCommand) -} diff --git a/ocis/pkg/runtime/service/service.go b/ocis/pkg/runtime/service/service.go index eec8b87e9..958d2d167 100644 --- a/ocis/pkg/runtime/service/service.go +++ b/ocis/pkg/runtime/service/service.go @@ -109,45 +109,195 @@ func NewService(options ...Option) (*Service, error) { cfg: opts.Config, } - s.ServicesRegistry[opts.Config.Settings.Service.Name] = settings.NewSutureService - s.ServicesRegistry[opts.Config.Nats.Service.Name] = nats.NewSutureService - s.ServicesRegistry[opts.Config.StorageSystem.Service.Name] = storageSystem.NewSutureService - s.ServicesRegistry[opts.Config.Graph.Service.Name] = graph.NewSutureService - s.ServicesRegistry[opts.Config.IDM.Service.Name] = idm.NewSutureService - s.ServicesRegistry[opts.Config.Invitations.Service.Name] = invitations.NewSutureService - s.ServicesRegistry[opts.Config.OCS.Service.Name] = ocs.NewSutureService - s.ServicesRegistry[opts.Config.Store.Service.Name] = store.NewSutureService - s.ServicesRegistry[opts.Config.Thumbnails.Service.Name] = thumbnails.NewSutureService - s.ServicesRegistry[opts.Config.Web.Service.Name] = web.NewSutureService - s.ServicesRegistry[opts.Config.WebDAV.Service.Name] = webdav.NewSutureService - s.ServicesRegistry[opts.Config.Webfinger.Service.Name] = webfinger.NewSutureService - s.ServicesRegistry[opts.Config.Frontend.Service.Name] = frontend.NewSutureService - s.ServicesRegistry[opts.Config.OCDav.Service.Name] = ocdav.NewSutureService - s.ServicesRegistry[opts.Config.Gateway.Service.Name] = gateway.NewSutureService - s.ServicesRegistry[opts.Config.AppRegistry.Service.Name] = appRegistry.NewSutureService - s.ServicesRegistry[opts.Config.Users.Service.Name] = users.NewSutureService - s.ServicesRegistry[opts.Config.Groups.Service.Name] = groups.NewSutureService - s.ServicesRegistry[opts.Config.AuthBasic.Service.Name] = authbasic.NewSutureService - s.ServicesRegistry[opts.Config.AuthMachine.Service.Name] = authmachine.NewSutureService - s.ServicesRegistry[opts.Config.StorageUsers.Service.Name] = storageusers.NewSutureService - s.ServicesRegistry[opts.Config.StorageShares.Service.Name] = storageshares.NewSutureService - s.ServicesRegistry[opts.Config.StoragePublicLink.Service.Name] = storagepublic.NewSutureService - s.ServicesRegistry[opts.Config.AppProvider.Service.Name] = appProvider.NewSutureService - s.ServicesRegistry[opts.Config.Notifications.Service.Name] = notifications.NewSutureService - s.ServicesRegistry[opts.Config.Search.Service.Name] = search.NewSutureService - s.ServicesRegistry[opts.Config.Postprocessing.Service.Name] = postprocessing.NewSutureService - s.ServicesRegistry[opts.Config.EventHistory.Service.Name] = eventhistory.NewSutureService - s.ServicesRegistry[opts.Config.Userlog.Service.Name] = userlog.NewSutureService + // populate services + reg := func(name string, exec func(context.Context, *ociscfg.Config) error) { + s.ServicesRegistry[name] = NewSutureServiceBuilder(exec) + } + reg(opts.Config.AppProvider.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.AppProvider.Context = ctx + cfg.AppProvider.Commons = cfg.Commons + return appProvider.Execute(cfg.AppProvider) + }) + reg(opts.Config.AppRegistry.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.AppRegistry.Context = ctx + cfg.AppRegistry.Commons = cfg.Commons + return appRegistry.Execute(cfg.AppRegistry) + }) + reg(opts.Config.AuthBasic.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.AuthBasic.Context = ctx + cfg.AuthBasic.Commons = cfg.Commons + return authbasic.Execute(cfg.AuthBasic) + }) + reg(opts.Config.AuthMachine.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.AuthMachine.Context = ctx + cfg.AuthMachine.Commons = cfg.Commons + return authmachine.Execute(cfg.AuthMachine) + }) + reg(opts.Config.EventHistory.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.EventHistory.Context = ctx + cfg.EventHistory.Commons = cfg.Commons + return eventhistory.Execute(cfg.EventHistory) + }) + reg(opts.Config.Frontend.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Frontend.Context = ctx + cfg.Frontend.Commons = cfg.Commons + return frontend.Execute(cfg.Frontend) + }) + reg(opts.Config.Gateway.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Gateway.Context = ctx + cfg.Gateway.Commons = cfg.Commons + return gateway.Execute(cfg.Gateway) + }) + reg(opts.Config.Graph.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Graph.Context = ctx + cfg.Graph.Commons = cfg.Commons + return graph.Execute(cfg.Graph) + }) + reg(opts.Config.Groups.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Groups.Context = ctx + cfg.Groups.Commons = cfg.Commons + return groups.Execute(cfg.Groups) + }) + reg(opts.Config.IDM.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.IDM.Context = ctx + cfg.IDM.Commons = cfg.Commons + return idm.Execute(cfg.IDM) + }) + reg(opts.Config.Invitations.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Invitations.Context = ctx + cfg.Invitations.Commons = cfg.Commons + return invitations.Execute(cfg.Invitations) + }) + reg(opts.Config.Nats.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Nats.Context = ctx + cfg.Nats.Commons = cfg.Commons + return nats.Execute(cfg.Nats) + }) + reg(opts.Config.Notifications.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Notifications.Context = ctx + cfg.Notifications.Commons = cfg.Commons + return notifications.Execute(cfg.Notifications) + }) + reg(opts.Config.OCDav.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.OCDav.Context = ctx + cfg.OCDav.Commons = cfg.Commons + return ocdav.Execute(cfg.OCDav) + }) + reg(opts.Config.OCS.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.OCS.Context = ctx + cfg.OCS.Commons = cfg.Commons + return ocs.Execute(cfg.OCS) + }) + reg(opts.Config.Postprocessing.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Postprocessing.Context = ctx + cfg.Postprocessing.Commons = cfg.Commons + return postprocessing.Execute(cfg.Postprocessing) + }) + reg(opts.Config.Search.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Search.Context = ctx + cfg.Search.Commons = cfg.Commons + return search.Execute(cfg.Search) + }) + reg(opts.Config.Settings.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Settings.Context = ctx + cfg.Settings.Commons = cfg.Commons + return settings.Execute(cfg.Settings) + }) + reg(opts.Config.StoragePublicLink.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.StoragePublicLink.Context = ctx + cfg.StoragePublicLink.Commons = cfg.Commons + return storagepublic.Execute(cfg.StoragePublicLink) + }) + reg(opts.Config.StorageShares.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.StorageShares.Context = ctx + cfg.StorageShares.Commons = cfg.Commons + return storageshares.Execute(cfg.StorageShares) + }) + reg(opts.Config.StorageSystem.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.StorageSystem.Context = ctx + cfg.StorageSystem.Commons = cfg.Commons + return storageSystem.Execute(cfg.StorageSystem) + }) + reg(opts.Config.StorageUsers.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.StorageUsers.Context = ctx + cfg.StorageUsers.Commons = cfg.Commons + return storageusers.Execute(cfg.StorageUsers) + }) + reg(opts.Config.Store.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Store.Context = ctx + cfg.Store.Commons = cfg.Commons + return store.Execute(cfg.Store) + }) + reg(opts.Config.Thumbnails.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Thumbnails.Context = ctx + cfg.Thumbnails.Commons = cfg.Commons + return thumbnails.Execute(cfg.Thumbnails) + }) + reg(opts.Config.Userlog.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Userlog.Context = ctx + cfg.Userlog.Commons = cfg.Commons + return userlog.Execute(cfg.Userlog) + }) + reg(opts.Config.Users.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Users.Context = ctx + cfg.Users.Commons = cfg.Commons + return users.Execute(cfg.Users) + }) + reg(opts.Config.Web.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Web.Context = ctx + cfg.Web.Commons = cfg.Commons + return web.Execute(cfg.Web) + }) + reg(opts.Config.WebDAV.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.WebDAV.Context = ctx + cfg.WebDAV.Commons = cfg.Commons + return webdav.Execute(cfg.WebDAV) + }) + reg(opts.Config.Webfinger.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Webfinger.Context = ctx + cfg.Webfinger.Commons = cfg.Commons + return webfinger.Execute(cfg.Webfinger) + }) // populate optional services - s.Additional[opts.Config.Antivirus.Service.Name] = antivirus.NewSutureService - s.Additional[opts.Config.Audit.Service.Name] = audit.NewSutureService - s.Additional[opts.Config.Policies.Service.Name] = policies.NewSutureService + areg := func(name string, exec func(context.Context, *ociscfg.Config) error) { + s.Additional[name] = NewSutureServiceBuilder(exec) + } + areg(opts.Config.Antivirus.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Antivirus.Context = ctx + // cfg.Antivirus.Commons = cfg.Commons // antivirus holds no Commons atm + return antivirus.Execute(cfg.Antivirus) + }) + areg(opts.Config.Audit.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Audit.Context = ctx + cfg.Audit.Commons = cfg.Commons + return audit.Execute(cfg.Audit) + }) + areg(opts.Config.Policies.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Policies.Context = ctx + cfg.Policies.Commons = cfg.Commons + return policies.Execute(cfg.Policies) + }) // populate delayed services - s.Delayed[opts.Config.Sharing.Service.Name] = sharing.NewSutureService - s.Delayed[opts.Config.Proxy.Service.Name] = proxy.NewSutureService - s.Delayed[opts.Config.IDP.Service.Name] = idp.NewSutureService + dreg := func(name string, exec func(context.Context, *ociscfg.Config) error) { + s.Delayed[name] = NewSutureServiceBuilder(exec) + } + dreg(opts.Config.IDP.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.IDP.Context = ctx + cfg.IDP.Commons = cfg.Commons + return idp.Execute(cfg.IDP) + }) + dreg(opts.Config.Proxy.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Proxy.Context = ctx + cfg.Proxy.Commons = cfg.Commons + return proxy.Execute(cfg.Proxy) + }) + dreg(opts.Config.Sharing.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Sharing.Context = ctx + cfg.Sharing.Commons = cfg.Commons + return sharing.Execute(cfg.Sharing) + }) return s, nil } @@ -282,7 +432,6 @@ func (s *Service) generateRunSet(cfg *ociscfg.Config) { for _, name := range cfg.Runtime.Disabled { delete(runset, name) } - } // List running processes for the Service Controller. diff --git a/ocis/pkg/runtime/service/sutureservice.go b/ocis/pkg/runtime/service/sutureservice.go new file mode 100644 index 000000000..d0bc3e3bc --- /dev/null +++ b/ocis/pkg/runtime/service/sutureservice.go @@ -0,0 +1,29 @@ +package service + +import ( + "context" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/thejerf/suture/v4" +) + +// SutureService allows for the settings command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + exec func(ctx context.Context) error +} + +// NewSutureServiceBuilder creates a new suture service +func NewSutureServiceBuilder(f func(context.Context, *ociscfg.Config) error) func(*ociscfg.Config) suture.Service { + return func(cfg *ociscfg.Config) suture.Service { + return SutureService{ + exec: func(ctx context.Context) error { + return f(ctx, cfg) + }, + } + } +} + +// Serve to fullfil Server interface +func (s SutureService) Serve(ctx context.Context) error { + return s.exec(ctx) +} diff --git a/protogen/gen/ocis/messages/search/v0/search.pb.go b/protogen/gen/ocis/messages/search/v0/search.pb.go index 68cc77dec..e9eabf8a7 100644 --- a/protogen/gen/ocis/messages/search/v0/search.pb.go +++ b/protogen/gen/ocis/messages/search/v0/search.pb.go @@ -157,6 +157,7 @@ type Entity struct { ShareRootName string `protobuf:"bytes,11,opt,name=shareRootName,proto3" json:"shareRootName,omitempty"` ParentId *ResourceID `protobuf:"bytes,12,opt,name=parent_id,json=parentId,proto3" json:"parent_id,omitempty"` Tags []string `protobuf:"bytes,13,rep,name=tags,proto3" json:"tags,omitempty"` + Highlights string `protobuf:"bytes,14,opt,name=highlights,proto3" json:"highlights,omitempty"` } func (x *Entity) Reset() { @@ -282,6 +283,13 @@ func (x *Entity) GetTags() []string { return nil } +func (x *Entity) GetHighlights() string { + if x != nil { + return x.Highlights + } + return "" +} + type Match struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -360,7 +368,7 @@ var file_ocis_messages_search_v0_search_proto_rawDesc = []byte{ 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x44, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0xe2, 0x03, 0x0a, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x82, 0x04, 0x0a, 0x06, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x34, 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x52, @@ -391,6 +399,8 @@ var file_ocis_messages_search_v0_search_proto_rawDesc = []byte{ 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x44, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x74, 0x61, 0x67, + 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x73, 0x18, + 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x73, 0x22, 0x56, 0x0a, 0x05, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x37, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, diff --git a/protogen/gen/ocis/services/search/v0/search.swagger.json b/protogen/gen/ocis/services/search/v0/search.swagger.json index 38e9809e8..267235414 100644 --- a/protogen/gen/ocis/services/search/v0/search.swagger.json +++ b/protogen/gen/ocis/services/search/v0/search.swagger.json @@ -204,6 +204,9 @@ "items": { "type": "string" } + }, + "highlights": { + "type": "string" } } }, diff --git a/protogen/proto/ocis/messages/search/v0/search.proto b/protogen/proto/ocis/messages/search/v0/search.proto index dbba26769..442eb502d 100644 --- a/protogen/proto/ocis/messages/search/v0/search.proto +++ b/protogen/proto/ocis/messages/search/v0/search.proto @@ -31,6 +31,7 @@ message Entity { string shareRootName = 11; ResourceID parent_id = 12; repeated string tags = 13; + string highlights = 14; } message Match { diff --git a/services/antivirus/pkg/command/root.go b/services/antivirus/pkg/command/root.go index 5f35285c7..288448274 100644 --- a/services/antivirus/pkg/command/root.go +++ b/services/antivirus/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/antivirus/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -30,25 +27,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the web command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new web.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Policies.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Antivirus, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/antivirus/pkg/service/service.go b/services/antivirus/pkg/service/service.go index 09952d903..5b3fac146 100644 --- a/services/antivirus/pkg/service/service.go +++ b/services/antivirus/pkg/service/service.go @@ -121,6 +121,7 @@ func (av Antivirus) Run() error { av.l.Fatal().Err(err).Str("uploadid", ev.UploadID).Interface("resourceID", ev.ResourceID).Msg("cannot publish events - exiting") return err } + continue } av.l.Debug().Str("uploadid", ev.UploadID).Str("filename", ev.Filename).Msg("Starting virus scan.") diff --git a/services/app-provider/pkg/command/root.go b/services/app-provider/pkg/command/root.go index b981f0d82..d7bbe2bb4 100644 --- a/services/app-provider/pkg/command/root.go +++ b/services/app-provider/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/app-provider/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the app-provider command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new app-provider.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.AppProvider.Commons = cfg.Commons - return SutureService{ - cfg: cfg.AppProvider, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/app-provider/pkg/command/server.go b/services/app-provider/pkg/command/server.go index a7fc216d0..902c29386 100644 --- a/services/app-provider/pkg/command/server.go +++ b/services/app-provider/pkg/command/server.go @@ -10,7 +10,7 @@ import ( "github.com/gofrs/uuid" "github.com/oklog/run" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/sync" "github.com/owncloud/ocis/v2/ocis-pkg/version" "github.com/owncloud/ocis/v2/services/app-provider/pkg/config" @@ -42,12 +42,16 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") - - rcfg := revaconfig.AppProviderConfigFromStruct(cfg) - gr.Add(func() error { - runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + rCfg := revaconfig.AppProviderConfigFromStruct(cfg) + reg := registry.GetRegistry() + + runtime.RunWithOptions(rCfg, pidFile, + runtime.WithLogger(&logger.Logger), + runtime.WithRegistry(reg), + ) + return nil }, func(err error) { logger.Error(). @@ -77,15 +81,9 @@ func Server(cfg *config.Config) *cli.Command { sync.Trap(&gr, cancel) } - if err := external.RegisterGRPCEndpoint( - ctx, - cfg.GRPC.Namespace+"."+cfg.Service.Name, - uuid.Must(uuid.NewV4()).String(), - cfg.GRPC.Addr, - version.GetString(), - logger, - ); err != nil { - logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, version.GetString()) + if err := registry.RegisterService(ctx, grpcSvc, logger); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc service") } return gr.Run() diff --git a/services/app-provider/pkg/config/config.go b/services/app-provider/pkg/config/config.go index abeeda0fa..5b0086945 100644 --- a/services/app-provider/pkg/config/config.go +++ b/services/app-provider/pkg/config/config.go @@ -40,10 +40,6 @@ type Log struct { File string `yaml:"file" env:"OCIS_LOG_FILE;APP_PROVIDER_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set."` } -type Service struct { - Name string `yaml:"-"` -} - type Debug struct { Addr string `yaml:"addr" env:"APP_PROVIDER_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."` Token string `yaml:"token" env:"APP_PROVIDER_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"` @@ -51,6 +47,10 @@ type Debug struct { Zpages bool `yaml:"zpages" env:"APP_PROVIDER_DEBUG_ZPAGES" desc:"Enables zpages, which can be used for collecting and viewing traces in-memory."` } +type Service struct { + Name string `yaml:"name" env:"APP_PROVIDER_SERVICE_NAME" desc:"The name of the service. This needs to be changed when using more than one app provider. Each app provider configured needs to be identified by a unique service name. Possible examples are: 'app-provider-collabora', 'app-provider-onlyoffice', 'app-provider-office365'."` +} + type GRPCConfig struct { Addr string `yaml:"addr" env:"APP_PROVIDER_GRPC_ADDR" desc:"The bind address of the GRPC service."` TLS *shared.GRPCServiceTLS `yaml:"tls"` @@ -69,6 +69,7 @@ type WOPIDriver struct { AppInternalURL string `yaml:"app_internal_url" env:"APP_PROVIDER_WOPI_APP_INTERNAL_URL" desc:"Internal URL to the app, like in your DMZ."` AppName string `yaml:"app_name" env:"APP_PROVIDER_WOPI_APP_NAME" desc:"Human readable app name."` AppURL string `yaml:"app_url" env:"APP_PROVIDER_WOPI_APP_URL" desc:"URL for end users to access the app."` + AppDisableChat bool `yaml:"app_disable_chat" env:"APP_PROVIDER_WOPI_DISABLE_CHAT" desc:"Disable the chat functionality of the office app."` Insecure bool `yaml:"insecure" env:"APP_PROVIDER_WOPI_INSECURE" desc:"Disable TLS certificate validation for requests to the WOPI server and the web office application. Do not set this in production environments."` IopSecret string `yaml:"wopi_server_iop_secret" env:"APP_PROVIDER_WOPI_WOPI_SERVER_IOP_SECRET" desc:"Shared secret of the CS3org WOPI server."` WopiURL string `yaml:"wopi_server_external_url" env:"APP_PROVIDER_WOPI_WOPI_SERVER_EXTERNAL_URL" desc:"External url of the CS3org WOPI server."` diff --git a/services/app-provider/pkg/revaconfig/config.go b/services/app-provider/pkg/revaconfig/config.go index d15b825b8..84e7c8144 100644 --- a/services/app-provider/pkg/revaconfig/config.go +++ b/services/app-provider/pkg/revaconfig/config.go @@ -40,6 +40,7 @@ func AppProviderConfigFromStruct(cfg *config.Config) map[string]interface{} { "app_int_url": cfg.Drivers.WOPI.AppInternalURL, "app_name": cfg.Drivers.WOPI.AppName, "app_url": cfg.Drivers.WOPI.AppURL, + "app_disable_chat": cfg.Drivers.WOPI.AppDisableChat, "insecure_connections": cfg.Drivers.WOPI.Insecure, "iop_secret": cfg.Drivers.WOPI.IopSecret, "jwt_secret": cfg.TokenManager.JWTSecret, diff --git a/services/app-registry/pkg/command/root.go b/services/app-registry/pkg/command/root.go index 3d20b9fd4..9f20d6051 100644 --- a/services/app-registry/pkg/command/root.go +++ b/services/app-registry/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/app-registry/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the app-registry command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new app-registry.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.AppRegistry.Commons = cfg.Commons - return SutureService{ - cfg: cfg.AppRegistry, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/app-registry/pkg/command/server.go b/services/app-registry/pkg/command/server.go index aa230570f..3fae646d8 100644 --- a/services/app-registry/pkg/command/server.go +++ b/services/app-registry/pkg/command/server.go @@ -10,7 +10,7 @@ import ( "github.com/gofrs/uuid" "github.com/oklog/run" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/version" "github.com/owncloud/ocis/v2/services/app-registry/pkg/config" "github.com/owncloud/ocis/v2/services/app-registry/pkg/config/parser" @@ -41,12 +41,16 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") - - rcfg := revaconfig.AppRegistryConfigFromStruct(cfg, logger) - gr.Add(func() error { - runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + rCfg := revaconfig.AppRegistryConfigFromStruct(cfg, logger) + reg := registry.GetRegistry() + + runtime.RunWithOptions(rCfg, pidFile, + runtime.WithLogger(&logger.Logger), + runtime.WithRegistry(reg), + ) + return nil }, func(err error) { logger.Error(). @@ -72,15 +76,9 @@ func Server(cfg *config.Config) *cli.Command { cancel() }) - if err := external.RegisterGRPCEndpoint( - ctx, - cfg.GRPC.Namespace+"."+cfg.Service.Name, - uuid.Must(uuid.NewV4()).String(), - cfg.GRPC.Addr, - version.GetString(), - logger, - ); err != nil { - logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, version.GetString()) + if err := registry.RegisterService(ctx, grpcSvc, logger); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc service") } return gr.Run() diff --git a/services/app-registry/pkg/revaconfig/config.go b/services/app-registry/pkg/revaconfig/config.go index 8698f2fb7..68215a328 100644 --- a/services/app-registry/pkg/revaconfig/config.go +++ b/services/app-registry/pkg/revaconfig/config.go @@ -1,9 +1,8 @@ package revaconfig import ( - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/mitchellh/mapstructure" + "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/services/app-registry/pkg/config" ) diff --git a/services/audit/pkg/command/root.go b/services/audit/pkg/command/root.go index cfe85a709..abe6054a5 100644 --- a/services/audit/pkg/command/root.go +++ b/services/audit/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/audit/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,26 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the audit command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new audit.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Audit.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Audit, - } -} - -// Serve implements Server interface -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/audit/pkg/service/service_test.go b/services/audit/pkg/service/service_test.go index 563ab1e39..c8cdd723a 100644 --- a/services/audit/pkg/service/service_test.go +++ b/services/audit/pkg/service/service_test.go @@ -8,7 +8,7 @@ import ( "github.com/cs3org/reva/v2/pkg/events" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/services/audit/pkg/types" - "github.com/test-go/testify/require" + "github.com/stretchr/testify/require" group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -161,7 +161,7 @@ var testCases = []struct { require.NoError(t, json.Unmarshal(b, &ev)) // AuditEvent fields - checkBaseAuditEvent(t, ev.AuditEvent, "", "", "share id:'shareid' uid:'' item-id:'' was removed", "file_unshared") + checkBaseAuditEvent(t, ev.AuditEvent, "", "0001-01-01T00:00:00Z", "share id:'shareid' uid:'' item-id:'' was removed", "file_unshared") // AuditEventSharing fields checkSharingAuditEvent(t, ev.AuditEventSharing, "", "", "shareid") // AuditEventShareUpdated fields @@ -287,7 +287,7 @@ var testCases = []struct { require.NoError(t, json.Unmarshal(b, &ev)) // AuditEvent fields - checkBaseAuditEvent(t, ev.AuditEvent, "sharing-userid", "2001-09-09T01:46:40Z", "link 'shareid' was accessed. Success: true", "public_link_accessed") + checkBaseAuditEvent(t, ev.AuditEvent, "sharing-userid", "2001-09-09T01:46:40Z", "link with token 'token-123' was accessed. Success: true", "public_link_accessed") // AuditEventSharing fields checkSharingAuditEvent(t, ev.AuditEventSharing, "itemid-1", "sharing-userid", "shareid") // AuditEventShareUpdated fields @@ -310,7 +310,7 @@ var testCases = []struct { require.NoError(t, json.Unmarshal(b, &ev)) // AuditEvent fields - checkBaseAuditEvent(t, ev.AuditEvent, "", "", "link 'shareid' was accessed. Success: false", "public_link_accessed") + checkBaseAuditEvent(t, ev.AuditEvent, "", "", "link with token 'token-123' was accessed. Success: false", "public_link_accessed") // AuditEventSharing fields checkSharingAuditEvent(t, ev.AuditEventSharing, "", "", "shareid") // AuditEventShareUpdated fields @@ -462,7 +462,7 @@ var testCases = []struct { SystemEvent: events.Event{ Event: events.SpaceCreated{ Executant: userID("uid-123"), - ID: &provider.StorageSpaceId{OpaqueId: "space-123"}, + ID: &provider.StorageSpaceId{OpaqueId: "storage-1$space-123"}, Owner: userID("uid-123"), Root: resourceID("pro-1", "sto-123", "iid-123"), Name: "test-space", @@ -476,9 +476,9 @@ var testCases = []struct { require.NoError(t, json.Unmarshal(b, &ev)) // AuditEvent fields - checkBaseAuditEvent(t, ev.AuditEvent, "", "2286-11-20T17:46:40Z", "user 'uid-123' created a space 'space-123' with name 'test-space'", "space_created") + checkBaseAuditEvent(t, ev.AuditEvent, "", "2286-11-20T17:46:40Z", "user 'uid-123' created a space 'space-123' with name 'test-space' (storage: 'storage-1')", "space_created") // AuditEventSpaces fields - checkSpacesAuditEvent(t, ev.AuditEventSpaces, "space-123") + checkSpacesAuditEvent(t, ev.AuditEventSpaces, "storage-1$space-123") // AuditEventFileRestored fields require.Equal(t, "uid-123", ev.Owner) require.Equal(t, "pro-1$sto-123!iid-123", ev.RootItem) @@ -490,7 +490,7 @@ var testCases = []struct { SystemEvent: events.Event{ Event: events.SpaceRenamed{ Executant: userID("uid-123"), - ID: &provider.StorageSpaceId{OpaqueId: "space-123"}, + ID: &provider.StorageSpaceId{OpaqueId: "storage-1$space-123"}, Owner: userID("uid-123"), Name: "new-name", }, @@ -500,9 +500,9 @@ var testCases = []struct { require.NoError(t, json.Unmarshal(b, &ev)) // AuditEvent fields - checkBaseAuditEvent(t, ev.AuditEvent, "", "", "user 'uid-123' renamed space 'space-123' to 'new-name'", "space_renamed") + checkBaseAuditEvent(t, ev.AuditEvent, "", "", "user 'uid-123' renamed space 'space-123' to 'new-name' (storage: 'storage-1')", "space_renamed") // AuditEventSpaces fields - checkSpacesAuditEvent(t, ev.AuditEventSpaces, "space-123") + checkSpacesAuditEvent(t, ev.AuditEventSpaces, "storage-1$space-123") // AuditEventSpaceRenamed fields require.Equal(t, "new-name", ev.NewName) }, @@ -511,7 +511,7 @@ var testCases = []struct { SystemEvent: events.Event{ Event: events.SpaceDisabled{ Executant: userID("uid-123"), - ID: &provider.StorageSpaceId{OpaqueId: "space-123"}, + ID: &provider.StorageSpaceId{OpaqueId: "storage-1$space-123"}, }, }, CheckAuditEvent: func(t *testing.T, b []byte) { @@ -519,16 +519,16 @@ var testCases = []struct { require.NoError(t, json.Unmarshal(b, &ev)) // AuditEvent fields - checkBaseAuditEvent(t, ev.AuditEvent, "", "", "user 'uid-123' disabled the space 'space-123'", "space_disabled") + checkBaseAuditEvent(t, ev.AuditEvent, "", "0001-01-01T00:00:00Z", "user 'uid-123' disabled the space 'space-123' (storage: 'storage-1')", "space_disabled") // AuditEventSpaces fields - checkSpacesAuditEvent(t, ev.AuditEventSpaces, "space-123") + checkSpacesAuditEvent(t, ev.AuditEventSpaces, "storage-1$space-123") }, }, { Alias: "Space enabled", SystemEvent: events.Event{ Event: events.SpaceEnabled{ Executant: userID("uid-123"), - ID: &provider.StorageSpaceId{OpaqueId: "space-123"}, + ID: &provider.StorageSpaceId{OpaqueId: "storage-1$space-123"}, }, }, CheckAuditEvent: func(t *testing.T, b []byte) { @@ -536,16 +536,16 @@ var testCases = []struct { require.NoError(t, json.Unmarshal(b, &ev)) // AuditEvent fields - checkBaseAuditEvent(t, ev.AuditEvent, "", "", "user 'uid-123' (re-) enabled the space 'space-123'", "space_enabled") + checkBaseAuditEvent(t, ev.AuditEvent, "", "", "user 'uid-123' (re-) enabled the space 'space-123' (storage: 'storage-1')", "space_enabled") // AuditEventSpaces fields - checkSpacesAuditEvent(t, ev.AuditEventSpaces, "space-123") + checkSpacesAuditEvent(t, ev.AuditEventSpaces, "storage-1$space-123") }, }, { Alias: "Space deleted", SystemEvent: events.Event{ Event: events.SpaceDeleted{ Executant: userID("uid-123"), - ID: &provider.StorageSpaceId{OpaqueId: "space-123"}, + ID: &provider.StorageSpaceId{OpaqueId: "storage-1$space-123"}, }, }, CheckAuditEvent: func(t *testing.T, b []byte) { @@ -553,9 +553,9 @@ var testCases = []struct { require.NoError(t, json.Unmarshal(b, &ev)) // AuditEvent fields - checkBaseAuditEvent(t, ev.AuditEvent, "", "", "user 'uid-123' deleted the space 'space-123'", "space_deleted") + checkBaseAuditEvent(t, ev.AuditEvent, "", "0001-01-01T00:00:00Z", "user 'uid-123' deleted the space 'space-123' (storage: 'storage-1')", "space_deleted") // AuditEventSpaces fields - checkSpacesAuditEvent(t, ev.AuditEventSpaces, "space-123") + checkSpacesAuditEvent(t, ev.AuditEventSpaces, "storage-1$space-123") }, }, } diff --git a/services/audit/pkg/types/conversion.go b/services/audit/pkg/types/conversion.go index dc433d569..079456927 100644 --- a/services/audit/pkg/types/conversion.go +++ b/services/audit/pkg/types/conversion.go @@ -6,6 +6,7 @@ import ( "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/storagespace" + "github.com/cs3org/reva/v2/pkg/utils" group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -140,7 +141,7 @@ func ShareRemoved(ev events.ShareRemoved) AuditEventShareRemoved { iid = ev.ShareKey.GetResourceId().GetOpaqueId() with, typ = extractGrantee(ev.ShareKey.GetGrantee().GetUserId(), ev.ShareKey.GetGrantee().GetGroupId()) } - base := BasicAuditEvent(uid, "", MessageShareRemoved(uid, sid, iid), ActionShareRemoved) + base := BasicAuditEvent(uid, formatTime(utils.TimeToTS(ev.Timestamp)), MessageShareRemoved(uid, sid, iid), ActionShareRemoved) return AuditEventShareRemoved{ AuditEventSharing: SharingAuditEvent(sid, iid, uid, base), ShareWith: with, @@ -160,7 +161,7 @@ func LinkRemoved(ev events.LinkRemoved) AuditEventShareRemoved { sid = ev.ShareToken } - base := BasicAuditEvent(uid, "", MessageLinkRemoved(uid, sid), ActionShareRemoved) + base := BasicAuditEvent(uid, formatTime(ev.Timestamp), MessageLinkRemoved(uid, sid), ActionShareRemoved) return AuditEventShareRemoved{ AuditEventSharing: SharingAuditEvent(sid, "", uid, base), ShareWith: "", @@ -201,7 +202,7 @@ func ReceivedShareUpdated(ev events.ReceivedShareUpdated) AuditEventReceivedShar // LinkAccessed converts a LinkAccessed event to an AuditEventLinkAccessed func LinkAccessed(ev events.LinkAccessed) AuditEventLinkAccessed { uid := ev.Sharer.OpaqueId - base := BasicAuditEvent(uid, formatTime(ev.CTime), MessageLinkAccessed(ev.ShareID.GetOpaqueId(), true), ActionLinkAccessed) + base := BasicAuditEvent(uid, formatTime(ev.CTime), MessageLinkAccessed(ev.Token, true), ActionLinkAccessed) return AuditEventLinkAccessed{ AuditEventSharing: SharingAuditEvent(ev.ShareID.GetOpaqueId(), ev.ItemID.OpaqueId, uid, base), ShareToken: ev.Token, @@ -214,7 +215,7 @@ func LinkAccessed(ev events.LinkAccessed) AuditEventLinkAccessed { // LinkAccessFailed converts a LinkAccessFailed event to an AuditEventLinkAccessed func LinkAccessFailed(ev events.LinkAccessFailed) AuditEventLinkAccessed { - base := BasicAuditEvent("", "", MessageLinkAccessed(ev.ShareID.GetOpaqueId(), false), ActionLinkAccessed) + base := BasicAuditEvent("", formatTime(ev.Timestamp), MessageLinkAccessed(ev.Token, false), ActionLinkAccessed) return AuditEventLinkAccessed{ AuditEventSharing: SharingAuditEvent(ev.ShareID.GetOpaqueId(), "", "", base), ShareToken: ev.Token, @@ -238,7 +239,7 @@ func FilesAuditEvent(base AuditEvent, itemid, owner, path string) AuditEventFile // ContainerCreated converts a ContainerCreated event to an AuditEventContainerCreated func ContainerCreated(ev events.ContainerCreated) AuditEventContainerCreated { iid, path, uid := extractFileDetails(ev.Ref, ev.Owner) - base := BasicAuditEvent(uid, "", MessageContainerCreated(ev.Executant.GetOpaqueId(), iid), ActionContainerCreated) + base := BasicAuditEvent(uid, formatTime(ev.Timestamp), MessageContainerCreated(ev.Executant.GetOpaqueId(), iid), ActionContainerCreated) return AuditEventContainerCreated{ AuditEventFiles: FilesAuditEvent(base, iid, uid, path), } @@ -247,7 +248,7 @@ func ContainerCreated(ev events.ContainerCreated) AuditEventContainerCreated { // FileUploaded converts a FileUploaded event to an AuditEventFileCreated func FileUploaded(ev events.FileUploaded) AuditEventFileCreated { iid, path, uid := extractFileDetails(ev.Ref, ev.Owner) - base := BasicAuditEvent(uid, "", MessageFileCreated(ev.Executant.GetOpaqueId(), iid), ActionFileCreated) + base := BasicAuditEvent(uid, formatTime(ev.Timestamp), MessageFileCreated(ev.Executant.GetOpaqueId(), iid), ActionFileCreated) return AuditEventFileCreated{ AuditEventFiles: FilesAuditEvent(base, iid, uid, path), } @@ -256,7 +257,7 @@ func FileUploaded(ev events.FileUploaded) AuditEventFileCreated { // FileDownloaded converts a FileDownloaded event to an AuditEventFileRead func FileDownloaded(ev events.FileDownloaded) AuditEventFileRead { iid, path, uid := extractFileDetails(ev.Ref, ev.Owner) - base := BasicAuditEvent(uid, "", MessageFileRead(ev.Executant.GetOpaqueId(), iid), ActionFileRead) + base := BasicAuditEvent(uid, formatTime(ev.Timestamp), MessageFileRead(ev.Executant.GetOpaqueId(), iid), ActionFileRead) return AuditEventFileRead{ AuditEventFiles: FilesAuditEvent(base, iid, uid, path), } @@ -271,7 +272,7 @@ func ItemMoved(ev events.ItemMoved) AuditEventFileRenamed { oldpath = ev.OldReference.GetPath() } - base := BasicAuditEvent(uid, "", MessageFileRenamed(ev.Executant.GetOpaqueId(), iid, oldpath, path), ActionFileRenamed) + base := BasicAuditEvent(uid, formatTime(ev.Timestamp), MessageFileRenamed(ev.Executant.GetOpaqueId(), iid, oldpath, path), ActionFileRenamed) return AuditEventFileRenamed{ AuditEventFiles: FilesAuditEvent(base, iid, uid, path), OldPath: oldpath, @@ -281,7 +282,7 @@ func ItemMoved(ev events.ItemMoved) AuditEventFileRenamed { // ItemTrashed converts a ItemTrashed event to an AuditEventFileDeleted func ItemTrashed(ev events.ItemTrashed) AuditEventFileDeleted { iid, path, uid := extractFileDetails(ev.Ref, ev.Owner) - base := BasicAuditEvent(uid, "", MessageFileTrashed(ev.Executant.GetOpaqueId(), iid), ActionFileTrashed) + base := BasicAuditEvent(uid, formatTime(ev.Timestamp), MessageFileTrashed(ev.Executant.GetOpaqueId(), iid), ActionFileTrashed) return AuditEventFileDeleted{ AuditEventFiles: FilesAuditEvent(base, iid, uid, path), } @@ -290,7 +291,7 @@ func ItemTrashed(ev events.ItemTrashed) AuditEventFileDeleted { // ItemPurged converts a ItemPurged event to an AuditEventFilePurged func ItemPurged(ev events.ItemPurged) AuditEventFilePurged { iid, path, uid := extractFileDetails(ev.Ref, ev.Owner) - base := BasicAuditEvent(uid, "", MessageFilePurged(ev.Executant.GetOpaqueId(), iid), ActionFilePurged) + base := BasicAuditEvent(uid, formatTime(ev.Timestamp), MessageFilePurged(ev.Executant.GetOpaqueId(), iid), ActionFilePurged) return AuditEventFilePurged{ AuditEventFiles: FilesAuditEvent(base, iid, uid, path), } @@ -305,7 +306,7 @@ func ItemRestored(ev events.ItemRestored) AuditEventFileRestored { oldpath = ev.OldReference.GetPath() } - base := BasicAuditEvent(uid, "", MessageFileRestored(ev.Executant.GetOpaqueId(), iid, path), ActionFileRestored) + base := BasicAuditEvent(uid, formatTime(ev.Timestamp), MessageFileRestored(ev.Executant.GetOpaqueId(), iid, path), ActionFileRestored) return AuditEventFileRestored{ AuditEventFiles: FilesAuditEvent(base, iid, uid, path), OldPath: oldpath, @@ -315,7 +316,7 @@ func ItemRestored(ev events.ItemRestored) AuditEventFileRestored { // FileVersionRestored converts a FileVersionRestored event to an AuditEventFileVersionRestored func FileVersionRestored(ev events.FileVersionRestored) AuditEventFileVersionRestored { iid, path, uid := extractFileDetails(ev.Ref, ev.Owner) - base := BasicAuditEvent(uid, "", MessageFileVersionRestored(ev.Executant.GetOpaqueId(), iid, ev.Key), ActionFileVersionRestored) + base := BasicAuditEvent(uid, formatTime(ev.Timestamp), MessageFileVersionRestored(ev.Executant.GetOpaqueId(), iid, ev.Key), ActionFileVersionRestored) return AuditEventFileVersionRestored{ AuditEventFiles: FilesAuditEvent(base, iid, uid, path), Key: ev.Key, @@ -347,7 +348,7 @@ func SpaceCreated(ev events.SpaceCreated) AuditEventSpaceCreated { // SpaceRenamed converts a SpaceRenamed event to an AuditEventSpaceRenamed func SpaceRenamed(ev events.SpaceRenamed) AuditEventSpaceRenamed { sid := ev.ID.GetOpaqueId() - base := BasicAuditEvent("", "", MessageSpaceRenamed(ev.Executant.GetOpaqueId(), sid, ev.Name), ActionSpaceRenamed) + base := BasicAuditEvent("", formatTime(ev.Timestamp), MessageSpaceRenamed(ev.Executant.GetOpaqueId(), sid, ev.Name), ActionSpaceRenamed) return AuditEventSpaceRenamed{ AuditEventSpaces: SpacesAuditEvent(base, sid), NewName: ev.Name, @@ -357,7 +358,7 @@ func SpaceRenamed(ev events.SpaceRenamed) AuditEventSpaceRenamed { // SpaceDisabled converts a SpaceDisabled event to an AuditEventSpaceDisabled func SpaceDisabled(ev events.SpaceDisabled) AuditEventSpaceDisabled { sid := ev.ID.GetOpaqueId() - base := BasicAuditEvent("", "", MessageSpaceDisabled(ev.Executant.GetOpaqueId(), sid), ActionSpaceDisabled) + base := BasicAuditEvent("", formatTime(utils.TimeToTS(ev.Timestamp)), MessageSpaceDisabled(ev.Executant.GetOpaqueId(), sid), ActionSpaceDisabled) return AuditEventSpaceDisabled{ AuditEventSpaces: SpacesAuditEvent(base, sid), } @@ -366,7 +367,7 @@ func SpaceDisabled(ev events.SpaceDisabled) AuditEventSpaceDisabled { // SpaceEnabled converts a SpaceEnabled event to an AuditEventSpaceEnabled func SpaceEnabled(ev events.SpaceEnabled) AuditEventSpaceEnabled { sid := ev.ID.GetOpaqueId() - base := BasicAuditEvent("", "", MessageSpaceEnabled(ev.Executant.GetOpaqueId(), sid), ActionSpaceEnabled) + base := BasicAuditEvent("", formatTime(ev.Timestamp), MessageSpaceEnabled(ev.Executant.GetOpaqueId(), sid), ActionSpaceEnabled) return AuditEventSpaceEnabled{ AuditEventSpaces: SpacesAuditEvent(base, sid), } @@ -375,7 +376,7 @@ func SpaceEnabled(ev events.SpaceEnabled) AuditEventSpaceEnabled { // SpaceDeleted converts a SpaceDeleted event to an AuditEventSpaceDeleted func SpaceDeleted(ev events.SpaceDeleted) AuditEventSpaceDeleted { sid := ev.ID.GetOpaqueId() - base := BasicAuditEvent("", "", MessageSpaceDeleted(ev.Executant.GetOpaqueId(), sid), ActionSpaceDeleted) + base := BasicAuditEvent("", formatTime(utils.TimeToTS(ev.Timestamp)), MessageSpaceDeleted(ev.Executant.GetOpaqueId(), sid), ActionSpaceDeleted) return AuditEventSpaceDeleted{ AuditEventSpaces: SpacesAuditEvent(base, sid), } @@ -413,7 +414,7 @@ func SpaceUnshared(ev events.SpaceUnshared) AuditEventSpaceUnshared { sue.GranteeGroupID = ev.GranteeGroupID.OpaqueId grantee = "group:" + ev.GranteeGroupID.OpaqueId } - base := BasicAuditEvent("", "", MessageSpaceUnshared(ev.Executant.GetOpaqueId(), sid, grantee), ActionSpaceUnshared) + base := BasicAuditEvent("", formatTime(utils.TimeToTS(ev.Timestamp)), MessageSpaceUnshared(ev.Executant.GetOpaqueId(), sid, grantee), ActionSpaceUnshared) sue.AuditEventSpaces = SpacesAuditEvent(base, sid) return sue @@ -428,7 +429,7 @@ func SpaceUpdated(ev events.SpaceUpdated) AuditEventSpaceUpdated { Opaque: opaqueMap, } - base := BasicAuditEvent("", "", MessageSpaceUpdated(ev.Executant.GetOpaqueId(), sid, ev.Space.Name, ev.Space.Quota.QuotaMaxBytes, opaqueMap), ActionSpaceUpdated) + base := BasicAuditEvent("", formatTime(ev.Timestamp), MessageSpaceUpdated(ev.Executant.GetOpaqueId(), sid, ev.Space.Name, ev.Space.Quota.QuotaMaxBytes, opaqueMap), ActionSpaceUpdated) sue.AuditEventSpaces = SpacesAuditEvent(base, sid) return sue @@ -436,7 +437,7 @@ func SpaceUpdated(ev events.SpaceUpdated) AuditEventSpaceUpdated { // UserCreated converts a UserCreated event to an AuditEventUserCreated func UserCreated(ev events.UserCreated) AuditEventUserCreated { - base := BasicAuditEvent("", "", MessageUserCreated(ev.Executant.GetOpaqueId(), ev.UserID), ActionUserCreated) + base := BasicAuditEvent("", formatTime(ev.Timestamp), MessageUserCreated(ev.Executant.GetOpaqueId(), ev.UserID), ActionUserCreated) return AuditEventUserCreated{ AuditEvent: base, UserID: ev.UserID, @@ -445,7 +446,7 @@ func UserCreated(ev events.UserCreated) AuditEventUserCreated { // UserDeleted converts a UserDeleted event to an AuditEventUserDeleted func UserDeleted(ev events.UserDeleted) AuditEventUserDeleted { - base := BasicAuditEvent("", "", MessageUserDeleted(ev.Executant.GetOpaqueId(), ev.UserID), ActionUserDeleted) + base := BasicAuditEvent("", formatTime(ev.Timestamp), MessageUserDeleted(ev.Executant.GetOpaqueId(), ev.UserID), ActionUserDeleted) return AuditEventUserDeleted{ AuditEvent: base, UserID: ev.UserID, @@ -455,7 +456,7 @@ func UserDeleted(ev events.UserDeleted) AuditEventUserDeleted { // UserFeatureChanged converts a UserFeatureChanged event to an AuditEventUserFeatureChanged func UserFeatureChanged(ev events.UserFeatureChanged) AuditEventUserFeatureChanged { msg := MessageUserFeatureChanged(ev.Executant.GetOpaqueId(), ev.UserID, ev.Features) - base := BasicAuditEvent("", "", msg, ActionUserFeatureChanged) + base := BasicAuditEvent("", formatTime(ev.Timestamp), msg, ActionUserFeatureChanged) return AuditEventUserFeatureChanged{ AuditEvent: base, UserID: ev.UserID, @@ -465,7 +466,7 @@ func UserFeatureChanged(ev events.UserFeatureChanged) AuditEventUserFeatureChang // GroupCreated converts a GroupCreated event to an AuditEventGroupCreated func GroupCreated(ev events.GroupCreated) AuditEventGroupCreated { - base := BasicAuditEvent("", "", MessageGroupCreated(ev.Executant.GetOpaqueId(), ev.GroupID), ActionGroupCreated) + base := BasicAuditEvent("", formatTime(ev.Timestamp), MessageGroupCreated(ev.Executant.GetOpaqueId(), ev.GroupID), ActionGroupCreated) return AuditEventGroupCreated{ AuditEvent: base, GroupID: ev.GroupID, @@ -474,7 +475,7 @@ func GroupCreated(ev events.GroupCreated) AuditEventGroupCreated { // GroupDeleted converts a GroupDeleted event to an AuditEventGroupDeleted func GroupDeleted(ev events.GroupDeleted) AuditEventGroupDeleted { - base := BasicAuditEvent("", "", MessageGroupDeleted(ev.Executant.GetOpaqueId(), ev.GroupID), ActionGroupDeleted) + base := BasicAuditEvent("", formatTime(ev.Timestamp), MessageGroupDeleted(ev.Executant.GetOpaqueId(), ev.GroupID), ActionGroupDeleted) return AuditEventGroupDeleted{ AuditEvent: base, GroupID: ev.GroupID, @@ -484,7 +485,7 @@ func GroupDeleted(ev events.GroupDeleted) AuditEventGroupDeleted { // GroupMemberAdded converts a GroupMemberAdded event to an AuditEventGroupMemberAdded func GroupMemberAdded(ev events.GroupMemberAdded) AuditEventGroupMemberAdded { msg := MessageGroupMemberAdded(ev.Executant.GetOpaqueId(), ev.GroupID, ev.UserID) - base := BasicAuditEvent("", "", msg, ActionGroupMemberAdded) + base := BasicAuditEvent("", formatTime(ev.Timestamp), msg, ActionGroupMemberAdded) return AuditEventGroupMemberAdded{ AuditEvent: base, GroupID: ev.GroupID, @@ -495,7 +496,7 @@ func GroupMemberAdded(ev events.GroupMemberAdded) AuditEventGroupMemberAdded { // GroupMemberRemoved converts a GroupMemberRemoved event to an AuditEventGroupMemberRemove func GroupMemberRemoved(ev events.GroupMemberRemoved) AuditEventGroupMemberRemoved { msg := MessageGroupMemberRemoved(ev.Executant.GetOpaqueId(), ev.GroupID, ev.UserID) - base := BasicAuditEvent("", "", msg, ActionGroupMemberRemoved) + base := BasicAuditEvent("", formatTime(ev.Timestamp), msg, ActionGroupMemberRemoved) return AuditEventGroupMemberRemoved{ AuditEvent: base, GroupID: ev.GroupID, diff --git a/services/audit/pkg/types/constants.go b/services/audit/pkg/types/messages.go similarity index 86% rename from services/audit/pkg/types/constants.go rename to services/audit/pkg/types/messages.go index e41f53ba6..d8cb503d9 100644 --- a/services/audit/pkg/types/constants.go +++ b/services/audit/pkg/types/messages.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/storagespace" ) // short identifiers for audit actions @@ -93,8 +94,8 @@ func MessageShareDeclined(userid, shareid, sharerid string) string { } // MessageLinkAccessed returns the human readable string that describes the action -func MessageLinkAccessed(linkid string, success bool) string { - return fmt.Sprintf("link '%s' was accessed. Success: %v", linkid, success) +func MessageLinkAccessed(token string, success bool) string { + return fmt.Sprintf("link with token '%s' was accessed. Success: %v", token, success) } // MessageContainerCreated returns the human readable string that describes the action @@ -139,43 +140,51 @@ func MessageFileVersionRestored(executant, item, version string) string { // MessageSpaceCreated returns the human readable string that describes the action func MessageSpaceCreated(executant, spaceID, name string) string { - return fmt.Sprintf("user '%s' created a space '%s' with name '%s'", executant, spaceID, name) + storagID, spaceID := storagespace.SplitStorageID(spaceID) + return fmt.Sprintf("user '%s' created a space '%s' with name '%s' (storage: '%s')", executant, spaceID, name, storagID) } // MessageSpaceRenamed returns the human readable string that describes the action func MessageSpaceRenamed(executant, spaceID, name string) string { - return fmt.Sprintf("user '%s' renamed space '%s' to '%s'", executant, spaceID, name) + storagID, spaceID := storagespace.SplitStorageID(spaceID) + return fmt.Sprintf("user '%s' renamed space '%s' to '%s' (storage: '%s')", executant, spaceID, name, storagID) } // MessageSpaceDisabled returns the human readable string that describes the action func MessageSpaceDisabled(executant, spaceID string) string { - return fmt.Sprintf("user '%s' disabled the space '%s'", executant, spaceID) + storagID, spaceID := storagespace.SplitStorageID(spaceID) + return fmt.Sprintf("user '%s' disabled the space '%s' (storage: '%s')", executant, spaceID, storagID) } // MessageSpaceEnabled returns the human readable string that describes the action func MessageSpaceEnabled(executant, spaceID string) string { - return fmt.Sprintf("user '%s' (re-) enabled the space '%s'", executant, spaceID) + storagID, spaceID := storagespace.SplitStorageID(spaceID) + return fmt.Sprintf("user '%s' (re-) enabled the space '%s' (storage: '%s')", executant, spaceID, storagID) } // MessageSpaceDeleted returns the human readable string that describes the action func MessageSpaceDeleted(executant, spaceID string) string { - return fmt.Sprintf("user '%s' deleted the space '%s'", executant, spaceID) + storagID, spaceID := storagespace.SplitStorageID(spaceID) + return fmt.Sprintf("user '%s' deleted the space '%s' (storage: '%s')", executant, spaceID, storagID) } // MessageSpaceShared returns the human readable string that describes the action func MessageSpaceShared(executant, spaceID, grantee string) string { - return fmt.Sprintf("user '%s' shared the space '%s' with '%s'", executant, spaceID, grantee) + storagID, spaceID := storagespace.SplitStorageID(spaceID) + return fmt.Sprintf("user '%s' shared the space '%s' with '%s' (storage: '%s')", executant, spaceID, grantee, storagID) } // MessageSpaceUnshared returns the human readable string that describes the action func MessageSpaceUnshared(executant, spaceID, grantee string) string { - return fmt.Sprintf("user '%s' unshared the space '%s' with '%s'", executant, spaceID, grantee) + storagID, spaceID := storagespace.SplitStorageID(spaceID) + return fmt.Sprintf("user '%s' unshared the space '%s' with '%s' (storage: '%s')", executant, spaceID, grantee, storagID) } // MessageSpaceUpdated returns the human readable string that describes the action func MessageSpaceUpdated(executant, spaceID, name string, quota uint64, opaque map[string]string) string { - return fmt.Sprintf("user '%s' updated space '%s'. name: '%s', quota: '%d', opaque: '%s'", - executant, spaceID, name, quota, opaque) + storagID, spaceID := storagespace.SplitStorageID(spaceID) + return fmt.Sprintf("user '%s' updated space '%s'. name: '%s', quota: '%d', opaque: '%s' (storage: '%s')", + executant, spaceID, name, quota, opaque, storagID) } // MessageUserCreated returns the human readable string that describes the action diff --git a/services/auth-basic/pkg/command/root.go b/services/auth-basic/pkg/command/root.go index d7ba172de..cb53a0a66 100644 --- a/services/auth-basic/pkg/command/root.go +++ b/services/auth-basic/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the auth-basic command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new auth-basic.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.AuthBasic.Commons = cfg.Commons - return SutureService{ - cfg: cfg.AuthBasic, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/auth-basic/pkg/command/server.go b/services/auth-basic/pkg/command/server.go index 4dc4ffba2..f137ae5b5 100644 --- a/services/auth-basic/pkg/command/server.go +++ b/services/auth-basic/pkg/command/server.go @@ -11,7 +11,7 @@ import ( "github.com/oklog/run" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" "github.com/owncloud/ocis/v2/ocis-pkg/ldap" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/sync" "github.com/owncloud/ocis/v2/ocis-pkg/version" "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config" @@ -43,10 +43,6 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") - - rcfg := revaconfig.AuthBasicConfigFromStruct(cfg) - // the reva runtime calls os.Exit in the case of a failure and there is no way for the oCIS // runtime to catch it and restart a reva service. Therefore we need to ensure the service has // everything it needs, before starting the service. @@ -60,7 +56,15 @@ func Server(cfg *config.Config) *cli.Command { } gr.Add(func() error { - runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + rCfg := revaconfig.AuthBasicConfigFromStruct(cfg) + reg := registry.GetRegistry() + + runtime.RunWithOptions(rCfg, pidFile, + runtime.WithLogger(&logger.Logger), + runtime.WithRegistry(reg), + ) + return nil }, func(err error) { logger.Error(). @@ -90,15 +94,9 @@ func Server(cfg *config.Config) *cli.Command { sync.Trap(&gr, cancel) } - if err := external.RegisterGRPCEndpoint( - ctx, - cfg.GRPC.Namespace+"."+cfg.Service.Name, - uuid.Must(uuid.NewV4()).String(), - cfg.GRPC.Addr, - version.GetString(), - logger, - ); err != nil { - logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, version.GetString()) + if err := registry.RegisterService(ctx, grpcSvc, logger); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc service") } return gr.Run() diff --git a/services/auth-basic/pkg/config/config.go b/services/auth-basic/pkg/config/config.go index 7bb4dd161..41a4b3539 100644 --- a/services/auth-basic/pkg/config/config.go +++ b/services/auth-basic/pkg/config/config.go @@ -90,8 +90,8 @@ type LDAPProvider struct { } type LDAPUserSchema struct { - ID string `yaml:"id" env:"OCIS_LDAP_USER_SCHEMA_ID;LDAP_USER_SCHEMA_ID;AUTH_BASIC_LDAP_USER_SCHEMA_ID" desc:"LDAP Attribute to use as the unique id for users. This should be a stable globally unique ID like a UUID." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_ID changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_ID"` - IDIsOctetString bool `yaml:"id_is_octet_string" env:"OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;AUTH_BASIC_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING" desc:"Set this to true if the defined 'id' attribute for users is of the 'OCTETSTRING' syntax. This is e.g. required when using the 'objectGUID' attribute of Active Directory for the user IDs." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_ID_IS_OCTETSTRING changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING"` + ID string `yaml:"id" env:"OCIS_LDAP_USER_SCHEMA_ID;LDAP_USER_SCHEMA_ID;AUTH_BASIC_LDAP_USER_SCHEMA_ID" desc:"LDAP Attribute to use as the unique ID for users. This should be a stable globally unique ID like a UUID." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_ID changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_ID"` + IDIsOctetString bool `yaml:"id_is_octet_string" env:"OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;AUTH_BASIC_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING" desc:"Set this to true if the defined 'ID' attribute for users is of the 'OCTETSTRING' syntax. This is e.g. required when using the 'objectGUID' attribute of Active Directory for the user IDs." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_ID_IS_OCTETSTRING changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING"` Mail string `yaml:"mail" env:"OCIS_LDAP_USER_SCHEMA_MAIL;LDAP_USER_SCHEMA_MAIL;AUTH_BASIC_LDAP_USER_SCHEMA_MAIL" desc:"LDAP Attribute to use for the email address of users." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_MAIL changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_MAIL"` DisplayName string `yaml:"display_name" env:"OCIS_LDAP_USER_SCHEMA_DISPLAYNAME;LDAP_USER_SCHEMA_DISPLAYNAME;AUTH_BASIC_LDAP_USER_SCHEMA_DISPLAYNAME" desc:"LDAP Attribute to use for the displayname of users." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_DISPLAYNAME changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_DISPLAYNAME"` Username string `yaml:"user_name" env:"OCIS_LDAP_USER_SCHEMA_USERNAME;LDAP_USER_SCHEMA_USERNAME;AUTH_BASIC_LDAP_USER_SCHEMA_USERNAME" desc:"LDAP Attribute to use for username of users." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_USERNAME changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_USERNAME"` diff --git a/services/auth-basic/pkg/config/defaults/defaultconfig.go b/services/auth-basic/pkg/config/defaults/defaultconfig.go index 3cdd5fd86..b21153fc3 100644 --- a/services/auth-basic/pkg/config/defaults/defaultconfig.go +++ b/services/auth-basic/pkg/config/defaults/defaultconfig.go @@ -51,7 +51,7 @@ func DefaultConfig() *config.Config { UserObjectClass: "inetOrgPerson", GroupObjectClass: "groupOfNames", BindDN: "uid=reva,ou=sysusers,o=libregraph-idm", - DisableUserMechanism: "none", + DisableUserMechanism: "attribute", LdapDisabledUsersGroupDN: "cn=DisabledUsersGroup,ou=groups,o=libregraph-idm", IDP: "https://localhost:9200", UserSchema: config.LDAPUserSchema{ diff --git a/services/auth-basic/pkg/revaconfig/config.go b/services/auth-basic/pkg/revaconfig/config.go index 7fe28fd67..ad3932694 100644 --- a/services/auth-basic/pkg/revaconfig/config.go +++ b/services/auth-basic/pkg/revaconfig/config.go @@ -1,6 +1,8 @@ package revaconfig -import "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config" +import ( + "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config" +) // AuthBasicConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. func AuthBasicConfigFromStruct(cfg *config.Config) map[string]interface{} { diff --git a/services/auth-bearer/pkg/command/server.go b/services/auth-bearer/pkg/command/server.go index 4c181bfab..50d97b785 100644 --- a/services/auth-bearer/pkg/command/server.go +++ b/services/auth-bearer/pkg/command/server.go @@ -10,7 +10,7 @@ import ( "github.com/gofrs/uuid" "github.com/oklog/run" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/sync" "github.com/owncloud/ocis/v2/ocis-pkg/version" "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config" @@ -42,12 +42,16 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") - - rcfg := revaconfig.AuthBearerConfigFromStruct(cfg) - gr.Add(func() error { - runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + rCfg := revaconfig.AuthBearerConfigFromStruct(cfg) + reg := registry.GetRegistry() + + runtime.RunWithOptions(rCfg, pidFile, + runtime.WithLogger(&logger.Logger), + runtime.WithRegistry(reg), + ) + return nil }, func(err error) { logger.Error(). @@ -77,15 +81,9 @@ func Server(cfg *config.Config) *cli.Command { sync.Trap(&gr, cancel) } - if err := external.RegisterGRPCEndpoint( - ctx, - cfg.GRPC.Namespace+"."+cfg.Service.Name, - uuid.Must(uuid.NewV4()).String(), - cfg.GRPC.Addr, - version.GetString(), - logger, - ); err != nil { - logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, version.GetString()) + if err := registry.RegisterService(ctx, grpcSvc, logger); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc service") } return gr.Run() diff --git a/services/auth-bearer/pkg/revaconfig/config.go b/services/auth-bearer/pkg/revaconfig/config.go index 6c19622e6..82cd79f1b 100644 --- a/services/auth-bearer/pkg/revaconfig/config.go +++ b/services/auth-bearer/pkg/revaconfig/config.go @@ -1,6 +1,9 @@ +// Package revaconfig transfers the config struct to reva config map package revaconfig -import "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config" +import ( + "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config" +) // AuthBearerConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. func AuthBearerConfigFromStruct(cfg *config.Config) map[string]interface{} { diff --git a/services/auth-machine/pkg/command/root.go b/services/auth-machine/pkg/command/root.go index fb01026af..fe153cd78 100644 --- a/services/auth-machine/pkg/command/root.go +++ b/services/auth-machine/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the auth-machine command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new auth-machine.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.AuthMachine.Commons = cfg.Commons - return SutureService{ - cfg: cfg.AuthMachine, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/auth-machine/pkg/command/server.go b/services/auth-machine/pkg/command/server.go index bf2ad19cc..168947664 100644 --- a/services/auth-machine/pkg/command/server.go +++ b/services/auth-machine/pkg/command/server.go @@ -10,7 +10,7 @@ import ( "github.com/gofrs/uuid" "github.com/oklog/run" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/sync" "github.com/owncloud/ocis/v2/ocis-pkg/version" "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config" @@ -42,12 +42,16 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") - - rcfg := revaconfig.AuthMachineConfigFromStruct(cfg) - gr.Add(func() error { - runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + rCfg := revaconfig.AuthMachineConfigFromStruct(cfg) + reg := registry.GetRegistry() + + runtime.RunWithOptions(rCfg, pidFile, + runtime.WithLogger(&logger.Logger), + runtime.WithRegistry(reg), + ) + return nil }, func(err error) { logger.Error(). @@ -77,15 +81,9 @@ func Server(cfg *config.Config) *cli.Command { sync.Trap(&gr, cancel) } - if err := external.RegisterGRPCEndpoint( - ctx, - cfg.GRPC.Namespace+"."+cfg.Service.Name, - uuid.Must(uuid.NewV4()).String(), - cfg.GRPC.Addr, - version.GetString(), - logger, - ); err != nil { - logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, version.GetString()) + if err := registry.RegisterService(ctx, grpcSvc, logger); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc service") } return gr.Run() diff --git a/services/eventhistory/pkg/command/root.go b/services/eventhistory/pkg/command/root.go index f2cf66389..683c716a3 100644 --- a/services/eventhistory/pkg/command/root.go +++ b/services/eventhistory/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/eventhistory/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the eventhistory command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new eventhistory.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.EventHistory.Commons = cfg.Commons - return SutureService{ - cfg: cfg.EventHistory, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/eventhistory/pkg/service/service_test.go b/services/eventhistory/pkg/service/service_test.go index 2591b8c38..882727317 100644 --- a/services/eventhistory/pkg/service/service_test.go +++ b/services/eventhistory/pkg/service/service_test.go @@ -10,6 +10,7 @@ import ( userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/store" + "github.com/cs3org/reva/v2/pkg/utils" "github.com/google/uuid" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -66,7 +67,7 @@ var _ = Describe("EventHistoryService", func() { }, }, Failed: false, - Timestamp: time.Time{}, + Timestamp: utils.TimeToTS(time.Time{}), }) ids[1] = bus.Publish(events.UserCreated{ UserID: "another-id", diff --git a/services/frontend/pkg/command/root.go b/services/frontend/pkg/command/root.go index 4feac04e0..ab4fd0452 100644 --- a/services/frontend/pkg/command/root.go +++ b/services/frontend/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/frontend/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the frontend command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new frontend.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Frontend.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Frontend, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/frontend/pkg/command/server.go b/services/frontend/pkg/command/server.go index 650879969..d899fa027 100644 --- a/services/frontend/pkg/command/server.go +++ b/services/frontend/pkg/command/server.go @@ -10,7 +10,7 @@ import ( "github.com/gofrs/uuid" "github.com/oklog/run" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/sync" "github.com/owncloud/ocis/v2/ocis-pkg/version" "github.com/owncloud/ocis/v2/services/frontend/pkg/config" @@ -42,15 +42,20 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") - - rcfg, err := revaconfig.FrontendConfigFromStruct(cfg) + rCfg, err := revaconfig.FrontendConfigFromStruct(cfg) if err != nil { return err } gr.Add(func() error { - runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + reg := registry.GetRegistry() + + runtime.RunWithOptions(rCfg, pidFile, + runtime.WithLogger(&logger.Logger), + runtime.WithRegistry(reg), + ) + return nil }, func(err error) { logger.Error(). @@ -80,15 +85,9 @@ func Server(cfg *config.Config) *cli.Command { sync.Trap(&gr, cancel) } - if err := external.RegisterHTTPEndpoint( - ctx, - cfg.HTTP.Namespace+"."+cfg.Service.Name, - uuid.Must(uuid.NewV4()).String(), - cfg.HTTP.Addr, - version.GetString(), - logger, - ); err != nil { - logger.Fatal().Err(err).Msg("failed to register the http endpoint") + httpSvc := registry.BuildHTTPService(cfg.HTTP.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.HTTP.Addr, version.GetString()) + if err := registry.RegisterService(ctx, httpSvc, logger); err != nil { + logger.Fatal().Err(err).Msg("failed to register the http service") } return gr.Run() diff --git a/services/frontend/pkg/config/config.go b/services/frontend/pkg/config/config.go index de43bbe8c..ae61890e1 100644 --- a/services/frontend/pkg/config/config.go +++ b/services/frontend/pkg/config/config.go @@ -92,7 +92,7 @@ type HTTPConfig struct { type CORS struct { AllowedOrigins []string `yaml:"allow_origins" env:"OCIS_CORS_ALLOW_ORIGINS;FRONTEND_CORS_ALLOW_ORIGINS" desc:"A comma-separated list of allowed CORS origins. See following chapter for more details: *Access-Control-Allow-Origin* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin"` AllowedMethods []string `yaml:"allow_methods" env:"OCIS_CORS_ALLOW_METHODS;FRONTEND_CORS_ALLOW_METHODS" desc:"A comma-separated list of allowed CORS methods. See following chapter for more details: *Access-Control-Request-Method* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method"` - AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;FRONTEND_CORS_ALLOW_HEADERS" desc:"A comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` + AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;FRONTEND_CORS_ALLOW_HEADERS" desc:"A blank or comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` AllowCredentials bool `yaml:"allow_credentials" env:"OCIS_CORS_ALLOW_CREDENTIALS;FRONTEND_CORS_ALLOW_CREDENTIALS" desc:"Allow credentials for CORS.See following chapter for more details: *Access-Control-Allow-Credentials* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials."` } diff --git a/services/gateway/pkg/command/root.go b/services/gateway/pkg/command/root.go index fe058b917..fcf6db18d 100644 --- a/services/gateway/pkg/command/root.go +++ b/services/gateway/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/gateway/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the gateway command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new gateway.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Gateway.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Gateway, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/gateway/pkg/command/server.go b/services/gateway/pkg/command/server.go index f2ddf03af..37c34c976 100644 --- a/services/gateway/pkg/command/server.go +++ b/services/gateway/pkg/command/server.go @@ -10,7 +10,7 @@ import ( "github.com/gofrs/uuid" "github.com/oklog/run" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/version" "github.com/owncloud/ocis/v2/services/gateway/pkg/config" "github.com/owncloud/ocis/v2/services/gateway/pkg/config/parser" @@ -41,12 +41,16 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") - - rcfg := revaconfig.GatewayConfigFromStruct(cfg, logger) - gr.Add(func() error { - runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + rCfg := revaconfig.GatewayConfigFromStruct(cfg, logger) + reg := registry.GetRegistry() + + runtime.RunWithOptions(rCfg, pidFile, + runtime.WithLogger(&logger.Logger), + runtime.WithRegistry(reg), + ) + return nil }, func(err error) { logger.Error(). @@ -72,15 +76,9 @@ func Server(cfg *config.Config) *cli.Command { cancel() }) - if err := external.RegisterGRPCEndpoint( - ctx, - cfg.GRPC.Namespace+"."+cfg.Service.Name, - uuid.Must(uuid.NewV4()).String(), - cfg.GRPC.Addr, - version.GetString(), - logger, - ); err != nil { - logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, version.GetString()) + if err := registry.RegisterService(ctx, grpcSvc, logger); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc service") } return gr.Run() diff --git a/services/gateway/pkg/config/defaults/defaultconfig.go b/services/gateway/pkg/config/defaults/defaultconfig.go index f776c55a2..3e0fd026e 100644 --- a/services/gateway/pkg/config/defaults/defaultconfig.go +++ b/services/gateway/pkg/config/defaults/defaultconfig.go @@ -52,16 +52,16 @@ func DefaultConfig() *config.Config { FrontendPublicURL: "https://localhost:9200", - AppRegistryEndpoint: "localhost:9242", - AuthBasicEndpoint: "localhost:9146", - AuthMachineEndpoint: "localhost:9166", - GroupsEndpoint: "localhost:9160", - PermissionsEndpoint: "localhost:9191", - SharingEndpoint: "localhost:9150", - StoragePublicLinkEndpoint: "localhost:9178", - StorageSharesEndpoint: "localhost:9154", - StorageUsersEndpoint: "localhost:9157", - UsersEndpoint: "localhost:9144", + AppRegistryEndpoint: "com.owncloud.api.app-registry", + AuthBasicEndpoint: "com.owncloud.api.auth-basic", + AuthMachineEndpoint: "com.owncloud.api.auth-machine", + GroupsEndpoint: "com.owncloud.api.groups", + PermissionsEndpoint: "com.owncloud.api.settings", + SharingEndpoint: "com.owncloud.api.sharing", + StoragePublicLinkEndpoint: "com.owncloud.api.storage-publiclink", + StorageSharesEndpoint: "com.owncloud.api.storage-shares", + StorageUsersEndpoint: "com.owncloud.api.storage-users", + UsersEndpoint: "com.owncloud.api.users", StorageRegistry: config.StorageRegistry{ Driver: "spaces", diff --git a/services/gateway/pkg/revaconfig/config.go b/services/gateway/pkg/revaconfig/config.go index 7654d3ea9..d9c5df0d9 100644 --- a/services/gateway/pkg/revaconfig/config.go +++ b/services/gateway/pkg/revaconfig/config.go @@ -6,9 +6,8 @@ import ( "strings" "time" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/cs3org/reva/v2/pkg/utils" + "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/services/gateway/pkg/config" ) diff --git a/services/graph/pkg/command/root.go b/services/graph/pkg/command/root.go index 73b42c6d1..5c9fae289 100644 --- a/services/graph/pkg/command/root.go +++ b/services/graph/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - "github.com/thejerf/suture/v4" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/graph/pkg/config" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { }) return app.Run(os.Args) } - -// SutureService allows for the graph command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new graph.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Graph.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Graph, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/graph/pkg/config/application.go b/services/graph/pkg/config/application.go index c33af2865..447c0dbb0 100644 --- a/services/graph/pkg/config/application.go +++ b/services/graph/pkg/config/application.go @@ -2,6 +2,6 @@ package config // Application defines the available graph application configuration. type Application struct { - ID string `yaml:"id" env:"GRAPH_APPLICATION_ID" desc:"The ocis application id shown in the graph. All app roles are tied to this."` + ID string `yaml:"id" env:"GRAPH_APPLICATION_ID" desc:"The ocis application ID shown in the graph. All app roles are tied to this ID."` DisplayName string `yaml:"displayname" env:"GRAPH_APPLICATION_DISPLAYNAME" desc:"The oCIS application name"` } diff --git a/services/graph/pkg/config/config.go b/services/graph/pkg/config/config.go index 091bde254..90602fbec 100644 --- a/services/graph/pkg/config/config.go +++ b/services/graph/pkg/config/config.go @@ -121,7 +121,7 @@ type Events struct { type CORS struct { AllowedOrigins []string `yaml:"allow_origins" env:"OCIS_CORS_ALLOW_ORIGINS;GRAPH_CORS_ALLOW_ORIGINS" desc:"A comma-separated list of allowed CORS origins. See following chapter for more details: *Access-Control-Allow-Origin* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin"` AllowedMethods []string `yaml:"allow_methods" env:"OCIS_CORS_ALLOW_METHODS;GRAPH_CORS_ALLOW_METHODS" desc:"A comma-separated list of allowed CORS methods. See following chapter for more details: *Access-Control-Request-Method* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method"` - AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;GRAPH_CORS_ALLOW_HEADERS" desc:"A comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` + AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;GRAPH_CORS_ALLOW_HEADERS" desc:"A blank or comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` AllowCredentials bool `yaml:"allow_credentials" env:"OCIS_CORS_ALLOW_CREDENTIALS;GRAPH_CORS_ALLOW_CREDENTIALS" desc:"Allow credentials for CORS.See following chapter for more details: *Access-Control-Allow-Credentials* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials."` } diff --git a/services/graph/pkg/identity/cs3.go b/services/graph/pkg/identity/cs3.go index 1f03170d1..a21ef4bbc 100644 --- a/services/graph/pkg/identity/cs3.go +++ b/services/graph/pkg/identity/cs3.go @@ -5,12 +5,12 @@ import ( "net/url" "github.com/CiscoM31/godata" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" cs3group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" cs3user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/shared" "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" @@ -21,8 +21,9 @@ var ( ) type CS3 struct { - Config *shared.Reva - Logger *log.Logger + Config *shared.Reva + Logger *log.Logger + GatewaySelector pool.Selectable[gateway.GatewayAPIClient] } // CreateUser implements the Backend Interface. It's currently not supported for the CS3 backend @@ -44,13 +45,13 @@ func (i *CS3) UpdateUser(ctx context.Context, nameOrID string, user libregraph.U func (i *CS3) GetUser(ctx context.Context, userID string, _ *godata.GoDataRequest) (*libregraph.User, error) { logger := i.Logger.SubloggerWithRequestID(ctx) logger.Debug().Str("backend", "cs3").Msg("GetUser") - client, err := pool.GetGatewayServiceClient(i.Config.Address, i.Config.GetRevaOptions()...) + gatewayClient, err := i.GatewaySelector.Next() if err != nil { - logger.Error().Str("backend", "cs3").Err(err).Msg("could not get client") + logger.Error().Str("backend", "cs3").Err(err).Msg("could not get gatewayClient") return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) } - res, err := client.GetUserByClaim(ctx, &cs3user.GetUserByClaimRequest{ + res, err := gatewayClient.GetUserByClaim(ctx, &cs3user.GetUserByClaimRequest{ Claim: "userid", // FIXME add consts to reva Value: userID, }) @@ -73,9 +74,9 @@ func (i *CS3) GetUser(ctx context.Context, userID string, _ *godata.GoDataReques func (i *CS3) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*libregraph.User, error) { logger := i.Logger.SubloggerWithRequestID(ctx) logger.Debug().Str("backend", "cs3").Msg("GetUsers") - client, err := pool.GetGatewayServiceClient(i.Config.Address, i.Config.GetRevaOptions()...) + gatewayClient, err := i.GatewaySelector.Next() if err != nil { - logger.Error().Str("backend", "cs3").Err(err).Msg("could not get client") + logger.Error().Str("backend", "cs3").Err(err).Msg("could not get gatewayClient") return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) } @@ -84,7 +85,7 @@ func (i *CS3) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*libr return nil, err } - res, err := client.FindUsers(ctx, &cs3user.FindUsersRequest{ + res, err := gatewayClient.FindUsers(ctx, &cs3user.FindUsersRequest{ // FIXME presence match is currently not implemented, an empty search currently leads to // Unwilling To Perform": Search Error: error parsing filter: (&(objectclass=posixAccount)(|(cn=*)(displayname=*)(mail=*))), error: Present filter match for cn not implemented Filter: search, @@ -114,9 +115,9 @@ func (i *CS3) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*libr func (i *CS3) GetGroups(ctx context.Context, queryParam url.Values) ([]*libregraph.Group, error) { logger := i.Logger.SubloggerWithRequestID(ctx) logger.Debug().Str("backend", "cs3").Msg("GetGroups") - client, err := pool.GetGatewayServiceClient(i.Config.Address, i.Config.GetRevaOptions()...) + gatewayClient, err := i.GatewaySelector.Next() if err != nil { - logger.Error().Str("backend", "cs3").Err(err).Msg("could not get client") + logger.Error().Str("backend", "cs3").Err(err).Msg("could not get gatewayClient") return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) } @@ -125,7 +126,7 @@ func (i *CS3) GetGroups(ctx context.Context, queryParam url.Values) ([]*libregra search = queryParam.Get("$search") } - res, err := client.FindGroups(ctx, &cs3group.FindGroupsRequest{ + res, err := gatewayClient.FindGroups(ctx, &cs3group.FindGroupsRequest{ // FIXME presence match is currently not implemented, an empty search currently leads to // Unwilling To Perform": Search Error: error parsing filter: (&(objectclass=posixAccount)(|(cn=*)(displayname=*)(mail=*))), error: Present filter match for cn not implemented Filter: search, @@ -161,13 +162,13 @@ func (i *CS3) CreateGroup(ctx context.Context, group libregraph.Group) (*libregr func (i *CS3) GetGroup(ctx context.Context, groupID string, queryParam url.Values) (*libregraph.Group, error) { logger := i.Logger.SubloggerWithRequestID(ctx) logger.Debug().Str("backend", "cs3").Msg("GetGroup") - client, err := pool.GetGatewayServiceClient(i.Config.Address, i.Config.GetRevaOptions()...) + gatewayClient, err := i.GatewaySelector.Next() if err != nil { - logger.Error().Str("backend", "cs3").Err(err).Msg("could not get client") + logger.Error().Str("backend", "cs3").Err(err).Msg("could not get gatewayClient") return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) } - res, err := client.GetGroupByClaim(ctx, &cs3group.GetGroupByClaimRequest{ + res, err := gatewayClient.GetGroupByClaim(ctx, &cs3group.GetGroupByClaimRequest{ Claim: "groupid", // FIXME add consts to reva Value: groupID, }) diff --git a/services/graph/pkg/identity/ldap.go b/services/graph/pkg/identity/ldap.go index c64eaab9a..7189d45b0 100644 --- a/services/graph/pkg/identity/ldap.go +++ b/services/graph/pkg/identity/ldap.go @@ -1178,7 +1178,7 @@ func (i *LDAP) updateAccountEnabledState(logger log.Logger, accountEnabled bool, } else { err = i.disableUser(logger, e.DN) } - updateNeeded = true + updateNeeded = false } return updateNeeded, err diff --git a/services/graph/pkg/identity/ldap/reconnect.go b/services/graph/pkg/identity/ldap/reconnect.go index 0a4e92474..31525eea1 100644 --- a/services/graph/pkg/identity/ldap/reconnect.go +++ b/services/graph/pkg/identity/ldap/reconnect.go @@ -205,10 +205,10 @@ func (c ConnWithReconnect) GetConnection() (*ldap.Conn, error) { } func (c ConnWithReconnect) ldapAutoConnect(config Config) { - l, err := c.ldapConnect(config) - if err != nil { - c.logger.Error().Err(err).Msg("autoconnect could not get ldap Connection") - } + var ( + l *ldap.Conn + err error + ) for { select { diff --git a/services/graph/pkg/identity/ldap_education_class_test.go b/services/graph/pkg/identity/ldap_education_class_test.go index 3f328a4cb..b8f481b14 100644 --- a/services/graph/pkg/identity/ldap_education_class_test.go +++ b/services/graph/pkg/identity/ldap_education_class_test.go @@ -8,8 +8,8 @@ import ( "github.com/go-ldap/ldap/v3" libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/services/graph/mocks" - "github.com/test-go/testify/assert" - "github.com/test-go/testify/mock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) var classEntry = ldap.NewEntry("ocEducationExternalId=Math0123", diff --git a/services/graph/pkg/identity/ldap_education_school_test.go b/services/graph/pkg/identity/ldap_education_school_test.go index bbd5439de..4ad31c509 100644 --- a/services/graph/pkg/identity/ldap_education_school_test.go +++ b/services/graph/pkg/identity/ldap_education_school_test.go @@ -8,8 +8,8 @@ import ( libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/services/graph/mocks" "github.com/owncloud/ocis/v2/services/graph/pkg/config" - "github.com/test-go/testify/assert" - "github.com/test-go/testify/mock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) var eduConfig = config.LDAP{ diff --git a/services/graph/pkg/identity/ldap_education_user_test.go b/services/graph/pkg/identity/ldap_education_user_test.go index fdd931d3d..8b0e9fff5 100644 --- a/services/graph/pkg/identity/ldap_education_user_test.go +++ b/services/graph/pkg/identity/ldap_education_user_test.go @@ -7,8 +7,8 @@ import ( "github.com/go-ldap/ldap/v3" libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/services/graph/mocks" - "github.com/test-go/testify/assert" - "github.com/test-go/testify/mock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) var eduUserAttrs = []string{ diff --git a/services/graph/pkg/identity/ldap_group.go b/services/graph/pkg/identity/ldap_group.go index f4c8e8506..f9749bbbc 100644 --- a/services/graph/pkg/identity/ldap_group.go +++ b/services/graph/pkg/identity/ldap_group.go @@ -186,6 +186,13 @@ func (i *LDAP) CreateGroup(ctx context.Context, group libregraph.Group) (*libreg } if err := i.conn.Add(ar); err != nil { + var lerr *ldap.Error + logger.Debug().Str("backend", "ldap").Str("dn", group.GetDisplayName()).Err(err).Msg("Failed to create group") + if errors.As(err, &lerr) { + if lerr.ResultCode == ldap.LDAPResultEntryAlreadyExists { + err = errorcode.New(errorcode.NameAlreadyExists, "group already exists") + } + } return nil, err } @@ -529,7 +536,7 @@ func (i *LDAP) getGroupByDN(dn string) (*ldap.Entry, error) { func (i *LDAP) getGroupsForUser(dn string) ([]*ldap.Entry, error) { groupFilter := fmt.Sprintf( "(%s=%s)", - i.groupAttributeMap.member, dn, + i.groupAttributeMap.member, ldap.EscapeFilter(dn), ) userGroups, err := i.getLDAPGroupsByFilter(groupFilter, false, false) if err != nil { diff --git a/services/graph/pkg/identity/ldap_group_test.go b/services/graph/pkg/identity/ldap_group_test.go index e823444c4..e3613967b 100644 --- a/services/graph/pkg/identity/ldap_group_test.go +++ b/services/graph/pkg/identity/ldap_group_test.go @@ -9,7 +9,7 @@ import ( "github.com/go-ldap/ldap/v3" "github.com/owncloud/ocis/v2/services/graph/mocks" "github.com/stretchr/testify/assert" - "github.com/test-go/testify/mock" + "github.com/stretchr/testify/mock" ) var groupEntry = ldap.NewEntry("cn=group", diff --git a/services/graph/pkg/identity/ldap_test.go b/services/graph/pkg/identity/ldap_test.go index 3db4efc84..f3a18560a 100644 --- a/services/graph/pkg/identity/ldap_test.go +++ b/services/graph/pkg/identity/ldap_test.go @@ -14,7 +14,7 @@ import ( "github.com/owncloud/ocis/v2/services/graph/mocks" "github.com/owncloud/ocis/v2/services/graph/pkg/config" "github.com/stretchr/testify/assert" - "github.com/test-go/testify/mock" + "github.com/stretchr/testify/mock" ) func getMockedBackend(l ldap.Client, lc config.LDAP, logger *log.Logger) (*LDAP, error) { diff --git a/services/graph/pkg/server/http/server.go b/services/graph/pkg/server/http/server.go index 213cc7577..203ac1ee7 100644 --- a/services/graph/pkg/server/http/server.go +++ b/services/graph/pkg/server/http/server.go @@ -17,6 +17,7 @@ import ( ociscrypto "github.com/owncloud/ocis/v2/ocis-pkg/crypto" "github.com/owncloud/ocis/v2/ocis-pkg/keycloak" "github.com/owncloud/ocis/v2/ocis-pkg/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" "github.com/owncloud/ocis/v2/ocis-pkg/service/http" "github.com/owncloud/ocis/v2/ocis-pkg/version" @@ -114,7 +115,7 @@ func Server(opts ...Option) (http.Service, error) { // how do we secure the api? var requireAdminMiddleware func(stdhttp.Handler) stdhttp.Handler var roleService svc.RoleService - var gatewayClient gateway.GatewayAPIClient + var gatewaySelector pool.Selectable[gateway.GatewayAPIClient] grpcClient, err := grpc.NewClient(append(grpc.GetClientOptions(options.Config.GRPCClientTLS), grpc.WithTraceProvider(tracing.TraceProvider))...) if err != nil { return http.Service{}, err @@ -126,9 +127,9 @@ func Server(opts ...Option) (http.Service, error) { account.JWTSecret(options.Config.TokenManager.JWTSecret), )) roleService = settingssvc.NewRoleService("com.owncloud.api.settings", grpcClient) - gatewayClient, err = pool.GetGatewayServiceClient(options.Config.Reva.Address, options.Config.Reva.GetRevaOptions()...) + gatewaySelector, err = pool.GatewaySelector(options.Config.Reva.Address, append(options.Config.Reva.GetRevaOptions(), pool.WithRegistry(registry.GetRegistry()))...) if err != nil { - return http.Service{}, errors.Wrap(err, "could not initialize gateway client") + return http.Service{}, errors.Wrap(err, "could not initialize gateway selector") } } else { middlewares = append(middlewares, graphMiddleware.Token(options.Config.HTTP.APIToken)) @@ -159,7 +160,7 @@ func Server(opts ...Option) (http.Service, error) { svc.EventsPublisher(publisher), svc.WithRoleService(roleService), svc.WithRequireAdminMiddleware(requireAdminMiddleware), - svc.WithGatewayClient(gatewayClient), + svc.WithGatewaySelector(gatewaySelector), svc.WithSearchService(searchsvc.NewSearchProviderService("com.owncloud.api.search", grpcClient)), svc.KeycloakClient(keyCloakClient), svc.EventHistoryClient(hClient), diff --git a/services/graph/pkg/service/v0/application_test.go b/services/graph/pkg/service/v0/application_test.go index 851e2ebed..dc136709c 100644 --- a/services/graph/pkg/service/v0/application_test.go +++ b/services/graph/pkg/service/v0/application_test.go @@ -7,13 +7,13 @@ import ( "net/http" "net/http/httptest" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" "github.com/go-chi/chi/v5" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/stretchr/testify/mock" - - cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" "github.com/owncloud/ocis/v2/ocis-pkg/shared" settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" @@ -23,6 +23,8 @@ import ( "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks" service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" + "github.com/stretchr/testify/mock" + "google.golang.org/grpc" ) type applicationList struct { @@ -35,6 +37,7 @@ var _ = Describe("Applications", func() { ctx context.Context cfg *config.Config gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] eventsPublisher mocks.Publisher roleService *mocks.RoleService identityBackend *identitymocks.Backend @@ -47,7 +50,16 @@ var _ = Describe("Applications", func() { identityBackend = &identitymocks.Backend{} roleService = &mocks.RoleService{} + + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") gatewayClient = &cs3mocks.GatewayAPIClient{} + gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return gatewayClient + }, + ) rr = httptest.NewRecorder() ctx = context.Background() @@ -62,7 +74,7 @@ var _ = Describe("Applications", func() { _ = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...) svc, _ = service.NewService( service.Config(cfg), - service.WithGatewayClient(gatewayClient), + service.WithGatewaySelector(gatewaySelector), service.EventsPublisher(&eventsPublisher), service.WithIdentityBackend(identityBackend), service.WithRoleService(roleService), diff --git a/services/graph/pkg/service/v0/approleassignments.go b/services/graph/pkg/service/v0/approleassignments.go index 81fb693f3..1927d05ae 100644 --- a/services/graph/pkg/service/v0/approleassignments.go +++ b/services/graph/pkg/service/v0/approleassignments.go @@ -4,6 +4,9 @@ import ( "fmt" "net/http" + revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/utils" "github.com/go-chi/chi/v5" "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" @@ -54,6 +57,11 @@ func (g Graph) CreateAppRoleAssignment(w http.ResponseWriter, r *http.Request) { userID := chi.URLParam(r, "userID") + // We can ignore the error, in the worst case the old role will be empty + oldRoles, _ := g.roleService.ListRoleAssignments(r.Context(), &settingssvc.ListRoleAssignmentsRequest{ + AccountUuid: userID, + }) + if appRoleAssignment.GetPrincipalId() != userID { errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, fmt.Sprintf("user id %s does not match principal id %v", userID, appRoleAssignment.GetPrincipalId())) return @@ -76,6 +84,27 @@ func (g Graph) CreateAppRoleAssignment(w http.ResponseWriter, r *http.Request) { return } + var role string + roles := oldRoles.GetAssignments() + if len(roles) > 0 { + role = roles[0].GetRoleId() + } + + e := events.UserFeatureChanged{ + UserID: userID, + Features: []events.UserFeature{ + { + Name: "roleChanged", + Value: appRoleAssignment.AppRoleId, + OldValue: &role, + }, + }, + Timestamp: utils.TSNow(), + } + if currentUser, ok := revactx.ContextGetUser(r.Context()); ok { + e.Executant = currentUser.GetId() + } + g.publishEvent(e) render.Status(r, http.StatusCreated) render.JSON(w, r, g.assignmentToAppRoleAssignment(artur.GetAssignment())) } diff --git a/services/graph/pkg/service/v0/approleassignments_test.go b/services/graph/pkg/service/v0/approleassignments_test.go index 7f37975a5..7315df703 100644 --- a/services/graph/pkg/service/v0/approleassignments_test.go +++ b/services/graph/pkg/service/v0/approleassignments_test.go @@ -8,15 +8,15 @@ import ( "net/http" "net/http/httptest" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" "github.com/go-chi/chi/v5" "github.com/golang/protobuf/ptypes/empty" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/stretchr/testify/mock" - libregraph "github.com/owncloud/libre-graph-api-go" ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" "github.com/owncloud/ocis/v2/ocis-pkg/shared" @@ -27,6 +27,8 @@ import ( "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks" service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" + "github.com/stretchr/testify/mock" + "google.golang.org/grpc" ) type assignmentList struct { @@ -39,6 +41,7 @@ var _ = Describe("AppRoleAssignments", func() { ctx context.Context cfg *config.Config gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] eventsPublisher mocks.Publisher roleService *mocks.RoleService identityBackend *identitymocks.Backend @@ -57,7 +60,16 @@ var _ = Describe("AppRoleAssignments", func() { identityBackend = &identitymocks.Backend{} roleService = &mocks.RoleService{} + + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") gatewayClient = &cs3mocks.GatewayAPIClient{} + gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return gatewayClient + }, + ) rr = httptest.NewRecorder() ctx = context.Background() @@ -72,7 +84,7 @@ var _ = Describe("AppRoleAssignments", func() { _ = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...) svc, _ = service.NewService( service.Config(cfg), - service.WithGatewayClient(gatewayClient), + service.WithGatewaySelector(gatewaySelector), service.EventsPublisher(&eventsPublisher), service.WithIdentityBackend(identityBackend), service.WithRoleService(roleService), @@ -127,6 +139,12 @@ var _ = Describe("AppRoleAssignments", func() { AccountUuid: user.GetId(), RoleId: "some-appRole-ID", } + roleService.On("ListRoleAssignments", mock.Anything, mock.Anything, mock.Anything).Return(&settings.ListRoleAssignmentsResponse{ + Assignments: []*settingsmsg.UserRoleAssignment{ + userRoleAssignment, + }, + }, nil) + roleService.On("AssignRoleToUser", mock.Anything, mock.Anything, mock.Anything).Return(&settings.AssignRoleToUserResponse{Assignment: userRoleAssignment}, nil) ara := libregraph.NewAppRoleAssignmentWithDefaults() diff --git a/services/graph/pkg/service/v0/driveitems.go b/services/graph/pkg/service/v0/driveitems.go index 3c9d412b6..16387914c 100644 --- a/services/graph/pkg/service/v0/driveitems.go +++ b/services/graph/pkg/service/v0/driveitems.go @@ -25,9 +25,14 @@ func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) { g.logger.Info().Msg("Calling GetRootDriveChildren") ctx := r.Context() - client := g.GetGatewayClient() + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + g.logger.Error().Err(err).Msg("could not select next gateway client") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "could not select next gateway client, aborting") + return + } - res, err := client.GetHome(ctx, &storageprovider.GetHomeRequest{}) + res, err := gatewayClient.GetHome(ctx, &storageprovider.GetHomeRequest{}) switch { case err != nil: g.logger.Error().Err(err).Msg("error sending get home grpc request") @@ -43,7 +48,7 @@ func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) { return } - lRes, err := client.ListContainer(ctx, &storageprovider.ListContainerRequest{ + lRes, err := gatewayClient.ListContainer(ctx, &storageprovider.ListContainerRequest{ Ref: &storageprovider.Reference{ Path: res.Path, }, @@ -80,9 +85,12 @@ func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) { } func (g Graph) getDriveItem(ctx context.Context, ref storageprovider.Reference) (*libregraph.DriveItem, error) { - client := g.GetGatewayClient() + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + return nil, err + } - res, err := client.Stat(ctx, &storageprovider.StatRequest{Ref: &ref}) + res, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &ref}) if err != nil { return nil, err } @@ -94,12 +102,15 @@ func (g Graph) getDriveItem(ctx context.Context, ref storageprovider.Reference) } func (g Graph) getRemoteItem(ctx context.Context, root *storageprovider.ResourceId, baseURL *url.URL) (*libregraph.RemoteItem, error) { - client := g.GetGatewayClient() + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + return nil, err + } ref := &storageprovider.Reference{ ResourceId: root, } - res, err := client.Stat(ctx, &storageprovider.StatRequest{Ref: ref}) + res, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: ref}) if err != nil { return nil, err } @@ -113,13 +124,17 @@ func (g Graph) getRemoteItem(ctx context.Context, root *storageprovider.Resource return nil, err } - if baseURL != nil { + if baseURL != nil && res.GetInfo() != nil && res.GetInfo().GetSpace() != nil { // TODO read from StorageSpace ... needs Opaque for now // TODO how do we build the url? // for now: read from request - webDavURL := *baseURL - webDavURL.Path = path.Join(webDavURL.Path, storagespace.FormatResourceID(*root)) - item.WebDavUrl = libregraph.PtrString(webDavURL.String()) + item.Name = libregraph.PtrString(res.GetInfo().GetName()) + if res.GetInfo().GetSpace().GetRoot() != nil { + webDavURL := *baseURL + relativePath := res.GetInfo().GetPath() + webDavURL.Path = path.Join(webDavURL.Path, storagespace.FormatResourceID(*res.GetInfo().GetSpace().GetRoot()), relativePath) + item.WebDavUrl = libregraph.PtrString(webDavURL.String()) + } } return item, nil } @@ -181,8 +196,8 @@ func cs3ResourceToRemoteItem(res *storageprovider.ResourceInfo) (*libregraph.Rem Size: size, } - if name := path.Base(res.Path); name != "" { - remoteItem.Name = &name + if res.GetPath() != "" { + remoteItem.Path = libregraph.PtrString(path.Clean(res.GetPath())) } if res.Etag != "" { remoteItem.ETag = &res.Etag @@ -200,12 +215,23 @@ func cs3ResourceToRemoteItem(res *storageprovider.ResourceInfo) (*libregraph.Rem if res.Type == storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER { remoteItem.Folder = &libregraph.Folder{} } + if res.GetSpace() != nil && res.GetSpace().GetRoot() != nil { + remoteItem.RootId = libregraph.PtrString(storagespace.FormatResourceID(*res.GetSpace().GetRoot())) + grantSpaceAlias := utils.ReadPlainFromOpaque(res.GetSpace().GetOpaque(), "spaceAlias") + if grantSpaceAlias != "" { + remoteItem.DriveAlias = libregraph.PtrString(grantSpaceAlias) + } + } return remoteItem, nil } func (g Graph) getPathForResource(ctx context.Context, id storageprovider.ResourceId) (string, error) { - client := g.GetGatewayClient() - res, err := client.GetPath(ctx, &storageprovider.GetPathRequest{ResourceId: &id}) + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + return "", err + } + + res, err := gatewayClient.GetPath(ctx, &storageprovider.GetPathRequest{ResourceId: &id}) if err != nil { return "", err } diff --git a/services/graph/pkg/service/v0/driveitems_test.go b/services/graph/pkg/service/v0/driveitems_test.go index b830cb14a..c907ec670 100644 --- a/services/graph/pkg/service/v0/driveitems_test.go +++ b/services/graph/pkg/service/v0/driveitems_test.go @@ -8,14 +8,14 @@ import ( "net/http/httptest" "time" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/rgrpc/status" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/utils" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/stretchr/testify/mock" - libregraph "github.com/owncloud/libre-graph-api-go" ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" "github.com/owncloud/ocis/v2/ocis-pkg/shared" @@ -24,6 +24,8 @@ import ( "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks" service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" + "github.com/stretchr/testify/mock" + "google.golang.org/grpc" ) type itemsList struct { @@ -36,6 +38,7 @@ var _ = Describe("Driveitems", func() { ctx context.Context cfg *config.Config gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] eventsPublisher mocks.Publisher identityBackend *identitymocks.Backend @@ -49,8 +52,17 @@ var _ = Describe("Driveitems", func() { rr = httptest.NewRecorder() - identityBackend = &identitymocks.Backend{} + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") gatewayClient = &cs3mocks.GatewayAPIClient{} + gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return gatewayClient + }, + ) + + identityBackend = &identitymocks.Backend{} newGroup = libregraph.NewGroup() newGroup.SetMembersodataBind([]string{"/users/user1"}) newGroup.SetId("group1") @@ -67,7 +79,7 @@ var _ = Describe("Driveitems", func() { _ = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...) svc, _ = service.NewService( service.Config(cfg), - service.WithGatewayClient(gatewayClient), + service.WithGatewaySelector(gatewaySelector), service.EventsPublisher(&eventsPublisher), service.WithIdentityBackend(identityBackend), ) diff --git a/services/graph/pkg/service/v0/drives.go b/services/graph/pkg/service/v0/drives.go index 5cb2a4662..f5832b275 100644 --- a/services/graph/pkg/service/v0/drives.go +++ b/services/graph/pkg/service/v0/drives.go @@ -261,7 +261,13 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) { return } - client := g.GetGatewayClient() + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + logger.Error().Err(err).Msg("could not select next gateway client") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "could not select next gateway client, aborting") + return + } + drive := libregraph.Drive{} if err := StrictJSONUnmarshal(r.Body, &drive); err != nil { logger.Debug().Err(err).Interface("body", r.Body).Msg("could not create drive: invalid body schema definition") @@ -306,7 +312,7 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) { csr.Owner = us } - resp, err := client.CreateStorageSpace(r.Context(), &csr) + resp, err := gatewayClient.CreateStorageSpace(r.Context(), &csr) if err != nil { logger.Error().Err(err).Msg("could not create drive: transport error") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) @@ -319,6 +325,11 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) { errorcode.NotAllowed.Render(w, r, http.StatusForbidden, "permission denied") return } + if resp.GetStatus().GetCode() == cs3rpc.Code_CODE_INVALID_ARGUMENT { + logger.Debug().Str("grpcmessage", resp.GetStatus().GetMessage()).Msg("could not create drive: bad request") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, resp.GetStatus().GetMessage()) + return + } logger.Debug().Interface("grpcmessage", csr).Str("grpc", resp.GetStatus().GetMessage()).Msg("could not create drive: grpc error") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, resp.GetStatus().GetMessage()) return @@ -374,7 +385,12 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) { } root := &rid - client := g.GetGatewayClient() + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + g.logger.Error().Err(err).Msg("could not select next gateway client") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "could not select next gateway client, aborting") + return + } updateSpaceRequest := &storageprovider.UpdateStorageSpaceRequest{ // Prepare the object to apply the diff from. The properties on StorageSpace will overwrite @@ -459,7 +475,7 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) { } logger.Debug().Interface("payload", updateSpaceRequest).Msg("calling update space on backend") - resp, err := client.UpdateStorageSpace(r.Context(), updateSpaceRequest) + resp, err := gatewayClient.UpdateStorageSpace(r.Context(), updateSpaceRequest) if err != nil { logger.Error().Err(err).Msg("could not update drive: transport error") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "transport error") @@ -581,7 +597,10 @@ func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, storageSpaces // ListStorageSpacesWithFilters List Storage Spaces using filters func (g Graph) ListStorageSpacesWithFilters(ctx context.Context, filters []*storageprovider.ListStorageSpacesRequest_Filter, unrestricted bool) (*storageprovider.ListStorageSpacesResponse, error) { - client := g.GetGatewayClient() + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + return nil, err + } grpcClient, err := grpc.NewClient(append(grpc.GetClientOptions(g.config.GRPCClientTLS), grpc.WithTraceProvider(gtracing.TraceProvider))...) if err != nil { @@ -615,7 +634,7 @@ func (g Graph) ListStorageSpacesWithFilters(ctx context.Context, filters []*stor }}, Filters: filters, } - res, err := client.ListStorageSpaces(ctx, lReq) + res, err := gatewayClient.ListStorageSpaces(ctx, lReq) return res, err } @@ -858,7 +877,10 @@ func (g Graph) getDriveQuota(ctx context.Context, space *storageprovider.Storage if noQuotaInOpaque { // we have to make a trip to the storage // TODO only if quota property was requested - client := g.GetGatewayClient() + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + return libregraph.Quota{}, err + } req := &gateway.GetQuotaRequest{ Ref: &storageprovider.Reference{ @@ -866,7 +888,8 @@ func (g Graph) getDriveQuota(ctx context.Context, space *storageprovider.Storage Path: ".", }, } - res, err := client.GetQuota(ctx, req) + + res, err := gatewayClient.GetQuota(ctx, req) switch { case err != nil: logger.Error().Err(err).Interface("ref", req.Ref).Msg("could not call GetQuota: transport error") @@ -1041,8 +1064,8 @@ func (g Graph) DeleteDrive(w http.ResponseWriter, r *http.Request) { }, } } - - dRes, err := g.gatewayClient.DeleteStorageSpace(r.Context(), &storageprovider.DeleteStorageSpaceRequest{ + gatewayClient, _ := g.gatewaySelector.Next() + dRes, err := gatewayClient.DeleteStorageSpace(r.Context(), &storageprovider.DeleteStorageSpaceRequest{ Opaque: opaque, Id: &storageprovider.StorageSpaceId{ OpaqueId: storagespace.FormatResourceID(rid), diff --git a/services/graph/pkg/service/v0/drives_test.go b/services/graph/pkg/service/v0/drives_test.go index 9abacf840..491dfd404 100644 --- a/services/graph/pkg/service/v0/drives_test.go +++ b/services/graph/pkg/service/v0/drives_test.go @@ -7,7 +7,7 @@ import ( "github.com/CiscoM31/godata" libregraph "github.com/owncloud/libre-graph-api-go" "github.com/stretchr/testify/assert" - "github.com/test-go/testify/require" + "github.com/stretchr/testify/require" ) type sortTest struct { diff --git a/services/graph/pkg/service/v0/educationclasses.go b/services/graph/pkg/service/v0/educationclasses.go index aa0fb98fa..61fb8c2e1 100644 --- a/services/graph/pkg/service/v0/educationclasses.go +++ b/services/graph/pkg/service/v0/educationclasses.go @@ -1,7 +1,6 @@ package svc import ( - "errors" "fmt" "net/http" "net/url" @@ -34,12 +33,7 @@ func (g Graph) GetEducationClasses(w http.ResponseWriter, r *http.Request) { classes, err := g.identityEducationBackend.GetEducationClasses(r.Context()) if err != nil { logger.Debug().Err(err).Msg("could not get classes: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -82,12 +76,7 @@ func (g Graph) PostEducationClass(w http.ResponseWriter, r *http.Request) { if class, err = g.identityEducationBackend.CreateEducationClass(r.Context(), *class); err != nil { logger.Debug().Interface("class", class).Msg("could not create class: backend error") - var eerr errorcode.Error - if errors.As(err, &eerr) { - eerr.Render(w, r) - return - } - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + errorcode.RenderError(w, r, err) return } @@ -104,8 +93,8 @@ func (g Graph) PostEducationClass(w http.ResponseWriter, r *http.Request) { // PatchEducationClass implements the Service interface. func (g Graph) PatchEducationClass(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling patch education class") classID := chi.URLParam(r, "classID") + logger.Info().Str("classID", classID).Msg("calling patch education class") classID, err := url.PathUnescape(classID) if err != nil { logger.Debug().Str("id", classID).Msg("could not change class: unescaping class id failed") @@ -183,12 +172,7 @@ func (g Graph) PatchEducationClass(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Debug().Err(err).Msg("could not change class: backend could not add members") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -212,8 +196,8 @@ func (g Graph) PatchEducationClass(w http.ResponseWriter, r *http.Request) { // GetEducationClass implements the Service interface. func (g Graph) GetEducationClass(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling get education class") classID := chi.URLParam(r, "classID") + logger.Info().Str("classID", classID).Msg("calling get education class") classID, err := url.PathUnescape(classID) if err != nil { logger.Debug().Str("id", classID).Msg("could not get class: unescaping class id failed") @@ -233,12 +217,8 @@ func (g Graph) GetEducationClass(w http.ResponseWriter, r *http.Request) { class, err := g.identityEducationBackend.GetEducationClass(r.Context(), classID) if err != nil { logger.Debug().Err(err).Msg("could not get class: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) + return } render.Status(r, http.StatusOK) @@ -248,8 +228,8 @@ func (g Graph) GetEducationClass(w http.ResponseWriter, r *http.Request) { // DeleteEducationClass implements the Service interface. func (g Graph) DeleteEducationClass(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling delete class") classID := chi.URLParam(r, "classID") + logger.Info().Str("classID", classID).Msg("calling delete class") classID, err := url.PathUnescape(classID) if err != nil { logger.Debug().Err(err).Str("id", classID).Msg("could not delete class: unescaping class id failed") @@ -268,12 +248,7 @@ func (g Graph) DeleteEducationClass(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Debug().Err(err).Msg("could not delete class: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -289,8 +264,8 @@ func (g Graph) DeleteEducationClass(w http.ResponseWriter, r *http.Request) { // GetEducationClassMembers implements the Service interface. func (g Graph) GetEducationClassMembers(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling get class members") classID := chi.URLParam(r, "classID") + logger.Info().Str("classID", classID).Msg("calling get class members") classID, err := url.PathUnescape(classID) if err != nil { logger.Debug().Str("id", classID).Msg("could not get class members: unescaping class id failed") @@ -308,12 +283,7 @@ func (g Graph) GetEducationClassMembers(w http.ResponseWriter, r *http.Request) members, err := g.identityEducationBackend.GetEducationClassMembers(r.Context(), classID) if err != nil { logger.Debug().Err(err).Msg("could not get class members: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -324,9 +294,9 @@ func (g Graph) GetEducationClassMembers(w http.ResponseWriter, r *http.Request) // PostEducationClassMember implements the Service interface. func (g Graph) PostEducationClassMember(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("Calling post class member") classID := chi.URLParam(r, "classID") + logger.Info().Str("classID", classID).Msg("Calling post class member") classID, err := url.PathUnescape(classID) if err != nil { logger.Debug(). @@ -377,12 +347,7 @@ func (g Graph) PostEducationClassMember(w http.ResponseWriter, r *http.Request) if err != nil { logger.Debug().Err(err).Msg("could not add class member: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -397,9 +362,10 @@ func (g Graph) PostEducationClassMember(w http.ResponseWriter, r *http.Request) // DeleteEducationClassMember implements the Service interface. func (g Graph) DeleteEducationClassMember(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling delete class member") classID := chi.URLParam(r, "classID") + memberID := chi.URLParam(r, "memberID") + logger.Info().Str("classID", classID).Str("memberID", memberID).Msg("calling delete class member") classID, err := url.PathUnescape(classID) if err != nil { logger.Debug().Err(err).Str("id", classID).Msg("could not delete class member: unescaping class id failed") @@ -413,7 +379,6 @@ func (g Graph) DeleteEducationClassMember(w http.ResponseWriter, r *http.Request return } - memberID := chi.URLParam(r, "memberID") memberID, err = url.PathUnescape(memberID) if err != nil { logger.Debug().Err(err).Str("id", memberID).Msg("could not delete class member: unescaping member id failed") @@ -431,12 +396,7 @@ func (g Graph) DeleteEducationClassMember(w http.ResponseWriter, r *http.Request if err != nil { logger.Debug().Err(err).Msg("could not delete class member: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } /* TODO requires reva changes @@ -450,8 +410,8 @@ func (g Graph) DeleteEducationClassMember(w http.ResponseWriter, r *http.Request // GetEducationClassTeachers implements the Service interface. func (g Graph) GetEducationClassTeachers(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling get class teachers") classID := chi.URLParam(r, "classID") + logger.Info().Str("classID", classID).Msg("calling get class teachers") classID, err := url.PathUnescape(classID) if err != nil { logger.Debug().Str("id", classID).Msg("could not get class teachers: unescaping class id failed") @@ -469,12 +429,7 @@ func (g Graph) GetEducationClassTeachers(w http.ResponseWriter, r *http.Request) teachers, err := g.identityEducationBackend.GetEducationClassTeachers(r.Context(), classID) if err != nil { logger.Debug().Err(err).Msg("could not get class teachers: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -485,9 +440,9 @@ func (g Graph) GetEducationClassTeachers(w http.ResponseWriter, r *http.Request) // PostEducationClassTeacher implements the Service interface. func (g Graph) PostEducationClassTeacher(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("Calling post class teacher") classID := chi.URLParam(r, "classID") + logger.Info().Str("classID", classID).Msg("Calling post class teacher") classID, err := url.PathUnescape(classID) if err != nil { logger.Debug(). @@ -538,12 +493,7 @@ func (g Graph) PostEducationClassTeacher(w http.ResponseWriter, r *http.Request) if err != nil { logger.Debug().Err(err).Msg("could not add class teacher: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -558,9 +508,10 @@ func (g Graph) PostEducationClassTeacher(w http.ResponseWriter, r *http.Request) // DeleteEducationClassTeacher implements the Service interface. func (g Graph) DeleteEducationClassTeacher(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling delete class teacher") classID := chi.URLParam(r, "classID") + teacherID := chi.URLParam(r, "teacherID") + logger.Info().Str("classID", classID).Str("teacherID", teacherID).Msg("calling delete class teacher") classID, err := url.PathUnescape(classID) if err != nil { logger.Debug().Err(err).Str("id", classID).Msg("could not delete class teacher: unescaping class id failed") @@ -574,7 +525,6 @@ func (g Graph) DeleteEducationClassTeacher(w http.ResponseWriter, r *http.Reques return } - teacherID := chi.URLParam(r, "teacherID") teacherID, err = url.PathUnescape(teacherID) if err != nil { logger.Debug().Err(err).Str("id", teacherID).Msg("could not delete class teacher: unescaping teacher id failed") @@ -592,12 +542,7 @@ func (g Graph) DeleteEducationClassTeacher(w http.ResponseWriter, r *http.Reques if err != nil { logger.Debug().Err(err).Msg("could not delete class teacher: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } /* TODO requires reva changes diff --git a/services/graph/pkg/service/v0/educationclasses_test.go b/services/graph/pkg/service/v0/educationclasses_test.go index 50564cf8e..530d31d6b 100644 --- a/services/graph/pkg/service/v0/educationclasses_test.go +++ b/services/graph/pkg/service/v0/educationclasses_test.go @@ -9,14 +9,14 @@ import ( "net/http" "net/http/httptest" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" "github.com/go-chi/chi/v5" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/test-go/testify/mock" - libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/ocis-pkg/log" ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" @@ -27,6 +27,8 @@ import ( identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks" service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/stretchr/testify/mock" + "google.golang.org/grpc" ) var _ = Describe("EducationClass", func() { @@ -35,6 +37,7 @@ var _ = Describe("EducationClass", func() { ctx context.Context cfg *config.Config gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] eventsPublisher mocks.Publisher identityBackend *identitymocks.Backend identityEducationBackend *identitymocks.EducationBackend @@ -52,9 +55,18 @@ var _ = Describe("EducationClass", func() { BeforeEach(func() { eventsPublisher.On("Publish", mock.Anything, mock.Anything, mock.Anything).Return(nil) + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") + gatewayClient = &cs3mocks.GatewayAPIClient{} + gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return gatewayClient + }, + ) + identityEducationBackend = &identitymocks.EducationBackend{} identityBackend = &identitymocks.Backend{} - gatewayClient = &cs3mocks.GatewayAPIClient{} newClass = libregraph.NewEducationClass("math", "course") newClass.SetMembersodataBind([]string{"/users/user1"}) newClass.SetId("math") @@ -71,7 +83,7 @@ var _ = Describe("EducationClass", func() { _ = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...) svc, _ = service.NewService( service.Config(cfg), - service.WithGatewayClient(gatewayClient), + service.WithGatewaySelector(gatewaySelector), service.EventsPublisher(&eventsPublisher), service.WithIdentityBackend(identityBackend), service.WithIdentityEducationBackend(identityEducationBackend), @@ -320,7 +332,7 @@ var _ = Describe("EducationClass", func() { cfg.API.GroupMembersPatchLimit = 21 svc, _ = service.NewService( service.Config(cfg), - service.WithGatewayClient(gatewayClient), + service.WithGatewaySelector(gatewaySelector), service.EventsPublisher(&eventsPublisher), service.WithIdentityBackend(identityBackend), service.WithIdentityEducationBackend(identityEducationBackend), diff --git a/services/graph/pkg/service/v0/educationschools.go b/services/graph/pkg/service/v0/educationschools.go index 821592757..e061a5c87 100644 --- a/services/graph/pkg/service/v0/educationschools.go +++ b/services/graph/pkg/service/v0/educationschools.go @@ -32,12 +32,7 @@ func (g Graph) GetEducationSchools(w http.ResponseWriter, r *http.Request) { schools, err := g.identityEducationBackend.GetEducationSchools(r.Context()) if err != nil { logger.Debug().Err(err).Msg("could not get schools: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -86,12 +81,7 @@ func (g Graph) PostEducationSchool(w http.ResponseWriter, r *http.Request) { if school, err = g.identityEducationBackend.CreateEducationSchool(r.Context(), *school); err != nil { logger.Debug().Err(err).Interface("school", school).Msg("could not create school: backend error") - var eerr errorcode.Error - if errors.As(err, &eerr) { - eerr.Render(w, r) - return - } - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + errorcode.RenderError(w, r, err) return } @@ -112,8 +102,8 @@ func (g Graph) PostEducationSchool(w http.ResponseWriter, r *http.Request) { // PatchEducationSchool implements the Service interface. func (g Graph) PatchEducationSchool(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling patch school") schoolID := chi.URLParam(r, "schoolID") + logger.Info().Str("schoolID", schoolID).Msg("calling patch school") schoolID, err := url.PathUnescape(schoolID) if err != nil { logger.Debug().Str("id", schoolID).Msg("could not update school: unescaping school id failed") @@ -156,8 +146,8 @@ func (g Graph) PatchEducationSchool(w http.ResponseWriter, r *http.Request) { // GetEducationSchool implements the Service interface. func (g Graph) GetEducationSchool(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling get school") schoolID := chi.URLParam(r, "schoolID") + logger.Info().Str("schoolID", schoolID).Msg("calling get school") schoolID, err := url.PathUnescape(schoolID) if err != nil { logger.Debug().Str("id", schoolID).Msg("could not get school: unescaping school id failed") @@ -177,12 +167,8 @@ func (g Graph) GetEducationSchool(w http.ResponseWriter, r *http.Request) { school, err := g.identityEducationBackend.GetEducationSchool(r.Context(), schoolID) if err != nil { logger.Debug().Err(err).Msg("could not get school: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) + return } render.Status(r, http.StatusOK) @@ -192,8 +178,8 @@ func (g Graph) GetEducationSchool(w http.ResponseWriter, r *http.Request) { // DeleteEducationSchool implements the Service interface. func (g Graph) DeleteEducationSchool(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling delete school") schoolID := chi.URLParam(r, "schoolID") + logger.Info().Str("schoolID", schoolID).Msg("calling delete school") schoolID, err := url.PathUnescape(schoolID) if err != nil { logger.Debug().Err(err).Str("id", schoolID).Msg("could not delete school: unescaping school id failed") @@ -211,7 +197,7 @@ func (g Graph) DeleteEducationSchool(w http.ResponseWriter, r *http.Request) { users, err := g.identityEducationBackend.GetEducationSchoolUsers(r.Context(), schoolID) if err != nil { logger.Debug().Err(err).Msg("could not get school users: backend error") - renderInternalServerError(w, r, err) + errorcode.RenderError(w, r, err) return } @@ -223,7 +209,8 @@ func (g Graph) DeleteEducationSchool(w http.ResponseWriter, r *http.Request) { continue } logger.Debug().Err(err).Msg("could not delete school member: backend error") - renderInternalServerError(w, r, err) + errorcode.RenderError(w, r, err) + // TODO Do we need return right hear? } } @@ -232,7 +219,7 @@ func (g Graph) DeleteEducationSchool(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Debug().Err(err).Msg("could not delete school: backend error") - renderInternalServerError(w, r, err) + errorcode.RenderError(w, r, err) return } @@ -248,20 +235,11 @@ func (g Graph) DeleteEducationSchool(w http.ResponseWriter, r *http.Request) { render.NoContent(w, r) } -func renderInternalServerError(w http.ResponseWriter, r *http.Request, err error) { - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } -} - // GetEducationSchoolUsers implements the Service interface. func (g Graph) GetEducationSchoolUsers(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling get school users") schoolID := chi.URLParam(r, "schoolID") + logger.Info().Str("schoolID", schoolID).Msg("calling get school users") schoolID, err := url.PathUnescape(schoolID) if err != nil { logger.Debug().Str("id", schoolID).Msg("could not get school users: unescaping school id failed") @@ -279,12 +257,7 @@ func (g Graph) GetEducationSchoolUsers(w http.ResponseWriter, r *http.Request) { users, err := g.identityEducationBackend.GetEducationSchoolUsers(r.Context(), schoolID) if err != nil { logger.Debug().Err(err).Msg("could not get school users: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -295,9 +268,9 @@ func (g Graph) GetEducationSchoolUsers(w http.ResponseWriter, r *http.Request) { // PostEducationSchoolUser implements the Service interface. func (g Graph) PostEducationSchoolUser(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("Calling post school user") schoolID := chi.URLParam(r, "schoolID") + logger.Info().Str("schoolID", schoolID).Msg("Calling post school user") schoolID, err := url.PathUnescape(schoolID) if err != nil { logger.Debug(). @@ -348,12 +321,7 @@ func (g Graph) PostEducationSchoolUser(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Debug().Err(err).Msg("could not add school user: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -372,9 +340,10 @@ func (g Graph) PostEducationSchoolUser(w http.ResponseWriter, r *http.Request) { // DeleteEducationSchoolUser implements the Service interface. func (g Graph) DeleteEducationSchoolUser(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling delete school member") schoolID := chi.URLParam(r, "schoolID") + userID := chi.URLParam(r, "userID") + logger.Info().Str("schoolID", schoolID).Str("userID", userID).Msg("calling delete school member") schoolID, err := url.PathUnescape(schoolID) if err != nil { logger.Debug().Err(err).Str("id", schoolID).Msg("could not delete school member: unescaping school id failed") @@ -388,7 +357,6 @@ func (g Graph) DeleteEducationSchoolUser(w http.ResponseWriter, r *http.Request) return } - userID := chi.URLParam(r, "userID") userID, err = url.PathUnescape(userID) if err != nil { logger.Debug().Err(err).Str("id", userID).Msg("could not delete school member: unescaping member id failed") @@ -406,12 +374,7 @@ func (g Graph) DeleteEducationSchoolUser(w http.ResponseWriter, r *http.Request) if err != nil { logger.Debug().Err(err).Msg("could not delete school member: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -430,8 +393,8 @@ func (g Graph) DeleteEducationSchoolUser(w http.ResponseWriter, r *http.Request) // GetEducationSchoolClasses implements the Service interface. func (g Graph) GetEducationSchoolClasses(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling get school classes") schoolID := chi.URLParam(r, "schoolID") + logger.Info().Str("schoolID", schoolID).Msg("calling get school classes") schoolID, err := url.PathUnescape(schoolID) if err != nil { logger.Debug().Str("id", schoolID).Msg("could not get school users: unescaping school id failed") @@ -449,12 +412,7 @@ func (g Graph) GetEducationSchoolClasses(w http.ResponseWriter, r *http.Request) classes, err := g.identityEducationBackend.GetEducationSchoolClasses(r.Context(), schoolID) if err != nil { logger.Debug().Err(err).Msg("could not get school classes: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -465,9 +423,9 @@ func (g Graph) GetEducationSchoolClasses(w http.ResponseWriter, r *http.Request) // PostEducationSchoolClass implements the Service interface. func (g Graph) PostEducationSchoolClass(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("Calling post school class") schoolID := chi.URLParam(r, "schoolID") + logger.Info().Str("schoolID", schoolID).Msg("Calling post school class") schoolID, err := url.PathUnescape(schoolID) if err != nil { logger.Debug(). @@ -518,12 +476,7 @@ func (g Graph) PostEducationSchoolClass(w http.ResponseWriter, r *http.Request) if err != nil { logger.Debug().Err(err).Msg("could not add school class: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -542,9 +495,10 @@ func (g Graph) PostEducationSchoolClass(w http.ResponseWriter, r *http.Request) // DeleteEducationSchoolClass implements the Service interface. func (g Graph) DeleteEducationSchoolClass(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling delete school class") schoolID := chi.URLParam(r, "schoolID") + classID := chi.URLParam(r, "classID") + logger.Info().Str("schoolID", schoolID).Str("classID", classID).Msg("calling delete school class") schoolID, err := url.PathUnescape(schoolID) if err != nil { logger.Debug().Err(err).Str("id", schoolID).Msg("could not delete school class: unescaping school id failed") @@ -558,7 +512,6 @@ func (g Graph) DeleteEducationSchoolClass(w http.ResponseWriter, r *http.Request return } - classID := chi.URLParam(r, "classID") classID, err = url.PathUnescape(classID) if err != nil { logger.Debug().Err(err).Str("id", classID).Msg("could not delete school class: unescaping class id failed") @@ -576,12 +529,7 @@ func (g Graph) DeleteEducationSchoolClass(w http.ResponseWriter, r *http.Request if err != nil { logger.Debug().Err(err).Msg("could not delete school class: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } diff --git a/services/graph/pkg/service/v0/educationschools_test.go b/services/graph/pkg/service/v0/educationschools_test.go index 3bf5157fa..6e755b957 100644 --- a/services/graph/pkg/service/v0/educationschools_test.go +++ b/services/graph/pkg/service/v0/educationschools_test.go @@ -9,13 +9,16 @@ import ( "net/http" "net/http/httptest" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" "github.com/go-chi/chi/v5" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/test-go/testify/mock" + "github.com/stretchr/testify/mock" + "google.golang.org/grpc" libregraph "github.com/owncloud/libre-graph-api-go" ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" @@ -37,6 +40,7 @@ var _ = Describe("Schools", func() { ctx context.Context cfg *config.Config gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] identityEducationBackend *identitymocks.EducationBackend rr *httptest.ResponseRecorder @@ -50,8 +54,17 @@ var _ = Describe("Schools", func() { ) BeforeEach(func() { - identityEducationBackend = &identitymocks.EducationBackend{} + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") gatewayClient = &cs3mocks.GatewayAPIClient{} + gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return gatewayClient + }, + ) + + identityEducationBackend = &identitymocks.EducationBackend{} newSchool = libregraph.NewEducationSchool() newSchool.SetId("school1") @@ -67,7 +80,7 @@ var _ = Describe("Schools", func() { _ = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...) svc, _ = service.NewService( service.Config(cfg), - service.WithGatewayClient(gatewayClient), + service.WithGatewaySelector(gatewaySelector), service.WithIdentityEducationBackend(identityEducationBackend), ) }) diff --git a/services/graph/pkg/service/v0/educationuser.go b/services/graph/pkg/service/v0/educationuser.go index 9ba3c4052..e7e003c12 100644 --- a/services/graph/pkg/service/v0/educationuser.go +++ b/services/graph/pkg/service/v0/educationuser.go @@ -1,7 +1,6 @@ package svc import ( - "errors" "fmt" "net/http" "net/url" @@ -35,12 +34,7 @@ func (g Graph) GetEducationUsers(w http.ResponseWriter, r *http.Request) { users, err := g.identityEducationBackend.GetEducationUsers(r.Context()) if err != nil { logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get education users from backend") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -144,12 +138,7 @@ func (g Graph) PostEducationUser(w http.ResponseWriter, r *http.Request) { logger.Debug().Interface("user", u).Msg("calling create education user on backend") if u, err = g.identityEducationBackend.CreateEducationUser(r.Context(), *u); err != nil { logger.Debug().Err(err).Msg("could not create education user: backend error") - var ecErr errorcode.Error - if errors.As(err, &ecErr) { - ecErr.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -166,8 +155,8 @@ func (g Graph) PostEducationUser(w http.ResponseWriter, r *http.Request) { // GetEducationUser implements the Service interface. func (g Graph) GetEducationUser(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling get education user") userID := chi.URLParam(r, "userID") + logger.Info().Str("userID", userID).Msg("calling get education user") userID, err := url.PathUnescape(userID) if err != nil { logger.Debug().Err(err).Str("id", userID).Msg("could not get education user: unescaping education user id failed") @@ -186,12 +175,7 @@ func (g Graph) GetEducationUser(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Debug().Err(err).Msg("could not get education user: error fetching education user from backend") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -202,8 +186,8 @@ func (g Graph) GetEducationUser(w http.ResponseWriter, r *http.Request) { // DeleteEducationUser implements the Service interface. func (g Graph) DeleteEducationUser(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling delete education user") userID := chi.URLParam(r, "userID") + logger.Info().Str("userID", userID).Msg("calling delete education user") userID, err := url.PathUnescape(userID) if err != nil { logger.Debug().Err(err).Str("id", userID).Msg("could not delete education user: unescaping education user id failed") @@ -220,12 +204,7 @@ func (g Graph) DeleteEducationUser(w http.ResponseWriter, r *http.Request) { user, err := g.identityEducationBackend.GetEducationUser(r.Context(), userID) if err != nil { logger.Debug().Err(err).Str("userID", userID).Msg("failed to get education user from backend") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -239,13 +218,19 @@ func (g Graph) DeleteEducationUser(w http.ResponseWriter, r *http.Request) { e.Executant = currentUser.GetId() } - if g.gatewayClient != nil { + if g.gatewaySelector != nil { logger.Debug(). Str("user", user.GetId()). Msg("calling list spaces with user filter to fetch the personal space for deletion") opaque := utils.AppendPlainToOpaque(nil, "unrestricted", "T") f := listStorageSpacesUserFilter(user.GetId()) - lspr, err := g.gatewayClient.ListStorageSpaces(r.Context(), &storageprovider.ListStorageSpacesRequest{ + client, err := g.gatewaySelector.Next() + if err != nil { + logger.Error().Err(err).Msg("could not select next gateway client") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "could not select next gateway client, aborting") + return + } + lspr, err := client.ListStorageSpaces(r.Context(), &storageprovider.ListStorageSpacesRequest{ Opaque: opaque, Filters: []*storageprovider.ListStorageSpacesRequest_Filter{f}, }) @@ -266,7 +251,7 @@ func (g Graph) DeleteEducationUser(w http.ResponseWriter, r *http.Request) { // Deleting a space a two step process (1. disabling/trashing, 2. purging) // Do the "disable/trash" step only if the space is not marked as trashed yet: if _, ok := sp.Opaque.Map["trashed"]; !ok { - _, err := g.gatewayClient.DeleteStorageSpace(r.Context(), &storageprovider.DeleteStorageSpaceRequest{ + _, err := client.DeleteStorageSpace(r.Context(), &storageprovider.DeleteStorageSpaceRequest{ Id: &storageprovider.StorageSpaceId{ OpaqueId: sp.Id.OpaqueId, }, @@ -278,7 +263,7 @@ func (g Graph) DeleteEducationUser(w http.ResponseWriter, r *http.Request) { } } purgeFlag := utils.AppendPlainToOpaque(nil, "purge", "") - _, err := g.gatewayClient.DeleteStorageSpace(r.Context(), &storageprovider.DeleteStorageSpaceRequest{ + _, err := client.DeleteStorageSpace(r.Context(), &storageprovider.DeleteStorageSpaceRequest{ Opaque: purgeFlag, Id: &storageprovider.StorageSpaceId{ OpaqueId: sp.Id.OpaqueId, @@ -299,13 +284,8 @@ func (g Graph) DeleteEducationUser(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Debug().Err(err).Msg("could not delete education user: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - return - } + errorcode.RenderError(w, r, err) + return } g.publishEvent(e) @@ -318,8 +298,8 @@ func (g Graph) DeleteEducationUser(w http.ResponseWriter, r *http.Request) { // ExistingUser func (g Graph) PatchEducationUser(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling patch education user") nameOrID := chi.URLParam(r, "userID") + logger.Info().Str("userID", nameOrID).Msg("calling patch education user") nameOrID, err := url.PathUnescape(nameOrID) if err != nil { logger.Debug().Err(err).Str("id", nameOrID).Msg("could not update education user: unescaping education user id failed") @@ -341,6 +321,14 @@ func (g Graph) PatchEducationUser(w http.ResponseWriter, r *http.Request) { return } + if accountName, ok := changes.GetOnPremisesSamAccountNameOk(); ok { + if !g.isValidUsername(*accountName) { + logger.Debug().Str("username", *accountName).Msg("could not update education user: username must be at least the local part of an email") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, fmt.Sprintf("username %s must be at least the local part of an email", *changes.OnPremisesSamAccountName)) + return + } + } + var features []events.UserFeature if mail, ok := changes.GetMailOk(); ok { if !isValidEmail(*mail) { @@ -368,18 +356,14 @@ func (g Graph) PatchEducationUser(w http.ResponseWriter, r *http.Request) { u, err := g.identityEducationBackend.UpdateEducationUser(r.Context(), nameOrID, *changes) if err != nil { logger.Debug().Err(err).Str("id", nameOrID).Msg("could not update education user: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } e := events.UserFeatureChanged{ - UserID: nameOrID, - Features: features, + UserID: nameOrID, + Features: features, + Timestamp: utils.TSNow(), } if currentUser, ok := revactx.ContextGetUser(r.Context()); ok { e.Executant = currentUser.GetId() diff --git a/services/graph/pkg/service/v0/educationuser_test.go b/services/graph/pkg/service/v0/educationuser_test.go index 1d4a10407..80e5ed41d 100644 --- a/services/graph/pkg/service/v0/educationuser_test.go +++ b/services/graph/pkg/service/v0/educationuser_test.go @@ -8,17 +8,17 @@ import ( "net/http" "net/http/httptest" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/rgrpc/status" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" "github.com/go-chi/chi/v5" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/stretchr/testify/mock" - libregraph "github.com/owncloud/libre-graph-api-go" ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" "github.com/owncloud/ocis/v2/ocis-pkg/shared" @@ -28,6 +28,8 @@ import ( "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks" service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" + "github.com/stretchr/testify/mock" + "google.golang.org/grpc" ) type educationUserList struct { @@ -40,6 +42,7 @@ var _ = Describe("EducationUsers", func() { ctx context.Context cfg *config.Config gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] eventsPublisher mocks.Publisher roleService *mocks.RoleService identityEducationBackend *identitymocks.EducationBackend @@ -56,9 +59,18 @@ var _ = Describe("EducationUsers", func() { BeforeEach(func() { eventsPublisher.On("Publish", mock.Anything, mock.Anything, mock.Anything).Return(nil) + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") + gatewayClient = &cs3mocks.GatewayAPIClient{} + gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return gatewayClient + }, + ) + identityEducationBackend = &identitymocks.EducationBackend{} roleService = &mocks.RoleService{} - gatewayClient = &cs3mocks.GatewayAPIClient{} rr = httptest.NewRecorder() ctx = context.Background() @@ -72,7 +84,7 @@ var _ = Describe("EducationUsers", func() { _ = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...) svc, _ = service.NewService( service.Config(cfg), - service.WithGatewayClient(gatewayClient), + service.WithGatewaySelector(gatewaySelector), service.EventsPublisher(&eventsPublisher), service.WithIdentityEducationBackend(identityEducationBackend), service.WithRoleService(roleService), diff --git a/services/graph/pkg/service/v0/errorcode/errorcode.go b/services/graph/pkg/service/v0/errorcode/errorcode.go index 911bb27f9..e15289f32 100644 --- a/services/graph/pkg/service/v0/errorcode/errorcode.go +++ b/services/graph/pkg/service/v0/errorcode/errorcode.go @@ -1,6 +1,7 @@ package errorcode import ( + "errors" "net/http" "time" @@ -121,3 +122,13 @@ func (e ErrorCode) String() string { func (e Error) Error() string { return errorCodes[e.errorCode] } + +// RenderError render the Graph Error based on a code or default one +func RenderError(w http.ResponseWriter, r *http.Request, err error) { + var errcode Error + if errors.As(err, &errcode) { + errcode.Render(w, r) + } else { + GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + } +} diff --git a/services/graph/pkg/service/v0/graph.go b/services/graph/pkg/service/v0/graph.go index eab4903d4..86045e00a 100644 --- a/services/graph/pkg/service/v0/graph.go +++ b/services/graph/pkg/service/v0/graph.go @@ -10,6 +10,7 @@ import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/go-chi/chi/v5" "github.com/jellydator/ttlcache/v3" libregraph "github.com/owncloud/libre-graph-api-go" @@ -62,7 +63,7 @@ type Graph struct { logger *log.Logger identityBackend identity.Backend identityEducationBackend identity.EducationBackend - gatewayClient gateway.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] roleService RoleService permissionsService Permissions specialDriveItemsCache *ttlcache.Cache[string, interface{}] @@ -86,11 +87,6 @@ func (g Graph) ServeHTTP(w http.ResponseWriter, r *http.Request) { g.mux.ServeHTTP(w, r) } -// GetGatewayClient returns a gateway client to talk to reva -func (g Graph) GetGatewayClient() gateway.GatewayAPIClient { - return g.gatewayClient -} - func (g Graph) publishEvent(ev interface{}) { if g.eventsPublisher != nil { if err := events.Publish(g.eventsPublisher, ev); err != nil { diff --git a/services/graph/pkg/service/v0/graph_suite_test.go b/services/graph/pkg/service/v0/graph_suite_test.go index 6b34ae063..7b6ed5b23 100644 --- a/services/graph/pkg/service/v0/graph_suite_test.go +++ b/services/graph/pkg/service/v0/graph_suite_test.go @@ -3,10 +3,24 @@ package svc_test import ( "testing" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + mRegistry "go-micro.dev/v4/registry" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) +func init() { + registry.Configure("memory") + r := registry.GetRegistry() + service := registry.BuildGRPCService("com.owncloud.api.gateway", "", "", "") + service.Nodes = []*mRegistry.Node{{ + Address: "any", + }} + + _ = r.Register(service) +} + func TestGraph(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Graph Suite") diff --git a/services/graph/pkg/service/v0/graph_test.go b/services/graph/pkg/service/v0/graph_test.go index 0b244d505..e7b6acdaa 100644 --- a/services/graph/pkg/service/v0/graph_test.go +++ b/services/graph/pkg/service/v0/graph_test.go @@ -16,13 +16,13 @@ import ( typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/rgrpc/status" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/utils" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" "github.com/go-chi/chi/v5" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" libregraph "github.com/owncloud/libre-graph-api-go" - ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" "github.com/owncloud/ocis/v2/ocis-pkg/shared" v0 "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" @@ -40,6 +40,7 @@ var _ = Describe("Graph", func() { var ( svc service.Service gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] eventsPublisher mocks.Publisher permissionService mocks.Permissions ctx context.Context @@ -63,13 +64,21 @@ var _ = Describe("Graph", func() { cfg.Commons = &shared.Commons{} cfg.GRPCClientTLS = &shared.GRPCClientTLS{} - _ = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...) + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") gatewayClient = &cs3mocks.GatewayAPIClient{} + gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return gatewayClient + }, + ) + eventsPublisher = mocks.Publisher{} permissionService = mocks.Permissions{} svc, _ = service.NewService( service.Config(cfg), - service.WithGatewayClient(gatewayClient), + service.WithGatewaySelector(gatewaySelector), service.EventsPublisher(&eventsPublisher), service.PermissionService(&permissionService), ) @@ -269,18 +278,28 @@ var _ = Describe("Graph", func() { "grantOpaqueID": {Decoder: "plain", Value: []byte("opaqueID")}, }, }, + RootInfo: &provider.ResourceInfo{Path: "New Folder", Name: "Project Mars"}, }, }, }, nil) + var opaque *typesv1beta1.Opaque + opaque = utils.AppendPlainToOpaque(opaque, "spaceAlias", "project/project-mars") gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(&provider.StatResponse{ Status: status.NewOK(ctx), Info: &provider.ResourceInfo{ Etag: "123456789", Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, Id: &provider.ResourceId{StorageId: "ownerStorageID", SpaceId: "spaceID", OpaqueId: "opaqueID"}, - Path: "New Folder", + Path: "Folder/New Folder", Mtime: &typesv1beta1.Timestamp{Seconds: 1648327606, Nanos: 0}, Size: uint64(1234), + Name: "New Folder", + Space: &provider.StorageSpace{ + Name: "Project Mars", + SpaceType: "project", + Opaque: opaque, + Root: &provider.ResourceId{StorageId: "ownerStorageID", SpaceId: "spaceID", OpaqueId: "opaqueID"}, + }, }, }, nil) gatewayClient.On("GetQuota", mock.Anything, mock.Anything).Return(&provider.GetQuotaResponse{ @@ -313,8 +332,9 @@ var _ = Describe("Graph", func() { Expect(value.Root.RemoteItem.LastModifiedDateTime.UTC()).To(Equal(time.Unix(1648327606, 0).UTC())) Expect(*value.Root.RemoteItem.Folder).To(Equal(libregraph.Folder{})) Expect(*value.Root.RemoteItem.Name).To(Equal("New Folder")) + Expect(*value.Root.RemoteItem.Path).To(Equal("Folder/New Folder")) Expect(*value.Root.RemoteItem.Size).To(Equal(int64(1234))) - Expect(*value.Root.RemoteItem.WebDavUrl).To(Equal("https://localhost:9200/dav/spaces/ownerStorageID$spaceID%21opaqueID")) + Expect(*value.Root.RemoteItem.WebDavUrl).To(Equal("https://localhost:9200/dav/spaces/ownerStorageID$spaceID%21opaqueID/Folder/New%20Folder")) }) It("can not list spaces with wrong sort parameter", func() { gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{ diff --git a/services/graph/pkg/service/v0/groups.go b/services/graph/pkg/service/v0/groups.go index cf465b5a0..f25d903f1 100644 --- a/services/graph/pkg/service/v0/groups.go +++ b/services/graph/pkg/service/v0/groups.go @@ -1,7 +1,6 @@ package svc import ( - "errors" "fmt" "net/http" "net/url" @@ -36,12 +35,7 @@ func (g Graph) GetGroups(w http.ResponseWriter, r *http.Request) { groups, err := g.identityBackend.GetGroups(r.Context(), r.URL.Query()) if err != nil { logger.Debug().Err(err).Msg("could not get groups: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -67,7 +61,13 @@ func (g Graph) PostGroup(w http.ResponseWriter, r *http.Request) { return } - if _, ok := grp.GetDisplayNameOk(); !ok { + if displayName, ok := grp.GetDisplayNameOk(); ok { + if !isValidGroupName(*displayName) { + logger.Info().Str("group", *displayName).Msg("could not create group: invalid displayName") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Invalid displayName") + return + } + } else { logger.Debug().Err(err).Interface("group", grp).Msg("could not create group: missing required attribute") errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Missing Required Attribute") return @@ -83,13 +83,8 @@ func (g Graph) PostGroup(w http.ResponseWriter, r *http.Request) { } if grp, err = g.identityBackend.CreateGroup(r.Context(), *grp); err != nil { - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - logger.Debug().Interface("group", grp).Msg("could not create group: backend error") - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + logger.Debug().Err(err).Interface("group", grp).Msg("could not create group: backend error") + errorcode.RenderError(w, r, err) return } @@ -139,8 +134,17 @@ func (g Graph) PatchGroup(w http.ResponseWriter, r *http.Request) { } if changes.HasDisplayName() { - groupName := changes.GetDisplayName() - err = g.identityBackend.UpdateGroupName(r.Context(), groupID, groupName) + displayName := changes.GetDisplayName() + if !isValidGroupName(displayName) { + logger.Info().Str("group", displayName).Msg("could not update group: invalid displayName") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Invalid displayName") + return + } + if err = g.identityBackend.UpdateGroupName(r.Context(), groupID, displayName); err != nil { + logger.Debug().Err(err).Msg("could not update group displayName") + errorcode.RenderError(w, r, err) + return + } } if memberRefs, ok := changes.GetMembersodataBindOk(); ok { @@ -176,19 +180,13 @@ func (g Graph) PatchGroup(w http.ResponseWriter, r *http.Request) { } memberIDs = append(memberIDs, id) } - err = g.identityBackend.AddMembersToGroup(r.Context(), groupID, memberIDs) + if err = g.identityBackend.AddMembersToGroup(r.Context(), groupID, memberIDs); err != nil { + logger.Debug().Err(err).Msg("could not change group: backend could not add members") + errorcode.RenderError(w, r, err) + return + } } - if err != nil { - logger.Debug().Err(err).Msg("could not change group: backend could not add members") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } - return - } render.Status(r, http.StatusNoContent) // TODO StatusNoContent when prefer=minimal is used, otherwise OK and the resource in the body render.NoContent(w, r) } @@ -217,12 +215,8 @@ func (g Graph) GetGroup(w http.ResponseWriter, r *http.Request) { group, err := g.identityBackend.GetGroup(r.Context(), groupID, r.URL.Query()) if err != nil { logger.Debug().Err(err).Msg("could not get group: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) + return } render.Status(r, http.StatusOK) @@ -252,12 +246,7 @@ func (g Graph) DeleteGroup(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Debug().Err(err).Msg("could not delete group: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -302,12 +291,7 @@ func (g Graph) GetGroupMembers(w http.ResponseWriter, r *http.Request) { members, err := g.identityBackend.GetGroupMembers(r.Context(), groupID, odataReq) if err != nil { logger.Debug().Err(err).Msg("could not get group members: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -371,12 +355,7 @@ func (g Graph) PostGroupMember(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Debug().Err(err).Msg("could not add group member: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -429,12 +408,7 @@ func (g Graph) DeleteGroupMember(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Debug().Err(err).Msg("could not delete group member: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } e := events.GroupMemberRemoved{ @@ -472,3 +446,8 @@ func sortGroups(req *godata.GoDataRequest, groups []*libregraph.Group) ([]*libre return groups, nil } + +func isValidGroupName(e string) bool { + str := strings.TrimSpace(e) + return len(str) > 0 && len(str) <= 256 +} diff --git a/services/graph/pkg/service/v0/groups_test.go b/services/graph/pkg/service/v0/groups_test.go index 2712641b6..b1dee30a4 100644 --- a/services/graph/pkg/service/v0/groups_test.go +++ b/services/graph/pkg/service/v0/groups_test.go @@ -9,14 +9,14 @@ import ( "net/http" "net/http/httptest" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" "github.com/go-chi/chi/v5" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/test-go/testify/mock" - libregraph "github.com/owncloud/libre-graph-api-go" ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" "github.com/owncloud/ocis/v2/ocis-pkg/shared" @@ -26,6 +26,8 @@ import ( identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks" service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/stretchr/testify/mock" + "google.golang.org/grpc" ) type groupList struct { @@ -38,6 +40,7 @@ var _ = Describe("Groups", func() { ctx context.Context cfg *config.Config gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] eventsPublisher mocks.Publisher identityBackend *identitymocks.Backend @@ -54,8 +57,17 @@ var _ = Describe("Groups", func() { BeforeEach(func() { eventsPublisher.On("Publish", mock.Anything, mock.Anything, mock.Anything).Return(nil) - identityBackend = &identitymocks.Backend{} + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") gatewayClient = &cs3mocks.GatewayAPIClient{} + gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return gatewayClient + }, + ) + + identityBackend = &identitymocks.Backend{} newGroup = libregraph.NewGroup() newGroup.SetMembersodataBind([]string{"/users/user1"}) newGroup.SetId("group1") @@ -72,7 +84,7 @@ var _ = Describe("Groups", func() { _ = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...) svc, _ = service.NewService( service.Config(cfg), - service.WithGatewayClient(gatewayClient), + service.WithGatewaySelector(gatewaySelector), service.EventsPublisher(&eventsPublisher), service.WithIdentityBackend(identityBackend), ) @@ -316,7 +328,7 @@ var _ = Describe("Groups", func() { cfg.API.GroupMembersPatchLimit = 21 svc, _ = service.NewService( service.Config(cfg), - service.WithGatewayClient(gatewayClient), + service.WithGatewaySelector(gatewaySelector), service.EventsPublisher(&eventsPublisher), service.WithIdentityBackend(identityBackend), ) diff --git a/services/graph/pkg/service/v0/option.go b/services/graph/pkg/service/v0/option.go index 51581ade7..a13714f54 100644 --- a/services/graph/pkg/service/v0/option.go +++ b/services/graph/pkg/service/v0/option.go @@ -5,6 +5,7 @@ import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/owncloud/ocis/v2/ocis-pkg/keycloak" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/roles" @@ -24,7 +25,7 @@ type Options struct { Config *config.Config Middleware []func(http.Handler) http.Handler RequireAdminMiddleware func(http.Handler) http.Handler - GatewayClient gateway.GatewayAPIClient + GatewaySelector pool.Selectable[gateway.GatewayAPIClient] IdentityBackend identity.Backend IdentityEducationBackend identity.EducationBackend RoleService RoleService @@ -75,10 +76,10 @@ func WithRequireAdminMiddleware(val func(http.Handler) http.Handler) Option { } } -// WithGatewayClient provides a function to set the gateway client option. -func WithGatewayClient(val gateway.GatewayAPIClient) Option { +// WithGatewaySelector provides a function to set the gateway client option. +func WithGatewaySelector(val pool.Selectable[gateway.GatewayAPIClient]) Option { return func(o *Options) { - o.GatewayClient = val + o.GatewaySelector = val } } diff --git a/services/graph/pkg/service/v0/password.go b/services/graph/pkg/service/v0/password.go index 551c0e29f..231204088 100644 --- a/services/graph/pkg/service/v0/password.go +++ b/services/graph/pkg/service/v0/password.go @@ -61,7 +61,12 @@ func (g Graph) ChangeOwnPassword(w http.ResponseWriter, r *http.Request) { ClientId: u.Username, ClientSecret: currentPw, } - authRes, err := g.gatewayClient.Authenticate(r.Context(), authReq) + client, err := g.gatewaySelector.Next() + if err != nil { + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "could not select next gateway client, aborting") + return + } + authRes, err := client.Authenticate(r.Context(), authReq) if err != nil { errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) return diff --git a/services/graph/pkg/service/v0/password_test.go b/services/graph/pkg/service/v0/password_test.go index 72bf4a07f..cb54ab6e5 100644 --- a/services/graph/pkg/service/v0/password_test.go +++ b/services/graph/pkg/service/v0/password_test.go @@ -12,6 +12,7 @@ import ( userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/rgrpc/status" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" "github.com/go-ldap/ldap/v3" . "github.com/onsi/ginkgo/v2" @@ -25,12 +26,14 @@ import ( "github.com/owncloud/ocis/v2/services/graph/pkg/identity" service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" "github.com/stretchr/testify/mock" + "google.golang.org/grpc" ) var _ = Describe("Users changing their own password", func() { var ( svc service.Service gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] ldapClient *mocks.Client ldapConfig config.LDAP identityBackend identity.Backend @@ -47,7 +50,16 @@ var _ = Describe("Users changing their own password", func() { cfg.TokenManager.JWTSecret = "loremipsum" cfg.GRPCClientTLS = &shared.GRPCClientTLS{} + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") gatewayClient = &cs3mocks.GatewayAPIClient{} + gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return gatewayClient + }, + ) + ldapClient = mockedLDAPClient() ldapConfig = config.LDAP{ @@ -68,7 +80,7 @@ var _ = Describe("Users changing their own password", func() { eventsPublisher = mocks.Publisher{} svc, _ = service.NewService( service.Config(cfg), - service.WithGatewayClient(gatewayClient), + service.WithGatewaySelector(gatewaySelector), service.WithIdentityBackend(identityBackend), service.EventsPublisher(&eventsPublisher), ) diff --git a/services/graph/pkg/service/v0/personaldata.go b/services/graph/pkg/service/v0/personaldata.go index ce7c10c2c..77bba3ab4 100644 --- a/services/graph/pkg/service/v0/personaldata.go +++ b/services/graph/pkg/service/v0/personaldata.go @@ -9,7 +9,8 @@ import ( "net/http" "path/filepath" "strconv" - "time" + + "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -69,8 +70,15 @@ func (g Graph) ExportPersonalData(w http.ResponseWriter, r *http.Request) { Path: loc, } + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + g.logger.Error().Err(err).Msg("could not select next gateway client") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "could not select next gateway client, aborting") + return + } + // touch file - if err := mustTouchFile(ctx, ref, g.GetGatewayClient()); err != nil { + if err := mustTouchFile(ctx, ref, gatewayClient); err != nil { g.logger.Error().Err(err).Msg("error touching file") w.WriteHeader(http.StatusInternalServerError) return @@ -84,8 +92,14 @@ func (g Graph) ExportPersonalData(w http.ResponseWriter, r *http.Request) { // GatherPersonalData will all gather all personal data of the user and save it to a file in the users personal space func (g Graph) GatherPersonalData(usr *user.User, ref *provider.Reference, token string, marsh Marshaller) { + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + g.logger.Error().Err(err).Msg("could not select next gateway client") + return + } + // the context might already be cancelled. We need to impersonate the acting user again - ctx, err := utils.ImpersonateUser(usr, g.gatewayClient, g.config.MachineAuthAPIKey) + ctx, err := utils.ImpersonateUser(usr, gatewayClient, g.config.MachineAuthAPIKey) if err != nil { g.logger.Error().Err(err).Str("userID", usr.GetId().GetOpaqueId()).Msg("cannot impersonate user") } @@ -129,7 +143,7 @@ func (g Graph) GatherPersonalData(usr *user.User, ref *provider.Reference, token if err := events.Publish(g.eventsPublisher, events.PersonalDataExtracted{ Executant: usr.GetId(), - Timestamp: time.Now(), + Timestamp: utils.TSNow(), ErrorMsg: errmsg, }); err != nil { g.logger.Error().Err(err).Msg("cannot publish event") @@ -142,13 +156,22 @@ func (g Graph) upload(u *user.User, data []byte, ref *provider.Reference, th str Opaque: utils.AppendPlainToOpaque(nil, "Upload-Length", strconv.FormatUint(uint64(len(data)), 10)), } - gwc := g.GetGatewayClient() - ctx, err := utils.ImpersonateUser(u, gwc, g.config.MachineAuthAPIKey) + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + g.logger.Error().Err(err).Msg("could not select next gateway client") + return err + } + + ctx, err := utils.ImpersonateUser(u, gatewayClient, g.config.MachineAuthAPIKey) + if err != nil { + return err + } + client, err := g.gatewaySelector.Next() if err != nil { return err } ctx = revactx.ContextSetToken(ctx, th) - uRes, err := gwc.InitiateFileUpload(ctx, uReq) + uRes, err := client.InitiateFileUpload(ctx, uReq) if err != nil { return err } diff --git a/services/graph/pkg/service/v0/service.go b/services/graph/pkg/service/v0/service.go index 86fe823dd..404e10a48 100644 --- a/services/graph/pkg/service/v0/service.go +++ b/services/graph/pkg/service/v0/service.go @@ -10,6 +10,7 @@ import ( "strconv" "time" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/store" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" @@ -17,6 +18,7 @@ import ( "github.com/jellydator/ttlcache/v3" libregraph "github.com/owncloud/libre-graph-api-go" ocisldap "github.com/owncloud/ocis/v2/ocis-pkg/ldap" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/roles" "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" @@ -144,7 +146,7 @@ func NewService(opts ...Option) (Graph, error) { usersCache: usersCache, groupsCache: groupsCache, eventsPublisher: options.EventsPublisher, - gatewayClient: options.GatewayClient, + gatewaySelector: options.GatewaySelector, searchService: options.SearchService, identityEducationBackend: options.IdentityEducationBackend, keycloakClient: options.KeycloakClient, @@ -316,9 +318,18 @@ func setIdentityBackends(options Options, svc *Graph) error { if options.IdentityBackend == nil { switch options.Config.Identity.Backend { case "cs3": + gatewaySelector, err := pool.GatewaySelector( + options.Config.Reva.Address, + append(options.Config.Reva.GetRevaOptions(), pool.WithRegistry(registry.GetRegistry()))..., + ) + if err != nil { + return err + } + svc.identityBackend = &identity.CS3{ - Config: options.Config.Reva, - Logger: &options.Logger, + Config: options.Config.Reva, + Logger: &options.Logger, + GatewaySelector: gatewaySelector, } case "ldap": var err error diff --git a/services/graph/pkg/service/v0/tags.go b/services/graph/pkg/service/v0/tags.go index b3e95ef5e..786eb0e49 100644 --- a/services/graph/pkg/service/v0/tags.go +++ b/services/graph/pkg/service/v0/tags.go @@ -66,7 +66,13 @@ func (g Graph) AssignTags(w http.ResponseWriter, r *http.Request) { return } - sres, err := g.gatewayClient.Stat(ctx, &provider.StatRequest{ + client, err := g.gatewaySelector.Next() + if err != nil { + g.logger.Error().Err(err).Msg("error selecting next gateway client") + w.WriteHeader(http.StatusInternalServerError) + return + } + sres, err := client.Stat(ctx, &provider.StatRequest{ Ref: &provider.Reference{ResourceId: &rid}, }) if err != nil { @@ -105,7 +111,7 @@ func (g Graph) AssignTags(w http.ResponseWriter, r *http.Request) { return } - resp, err := g.gatewayClient.SetArbitraryMetadata(ctx, &provider.SetArbitraryMetadataRequest{ + resp, err := client.SetArbitraryMetadata(ctx, &provider.SetArbitraryMetadataRequest{ Ref: &provider.Reference{ResourceId: &rid}, ArbitraryMetadata: &provider.ArbitraryMetadata{ Metadata: map[string]string{ @@ -155,7 +161,13 @@ func (g Graph) UnassignTags(w http.ResponseWriter, r *http.Request) { return } - sres, err := g.gatewayClient.Stat(ctx, &provider.StatRequest{ + client, err := g.gatewaySelector.Next() + if err != nil { + g.logger.Error().Err(err).Msg("error selecting next gateway client") + w.WriteHeader(http.StatusInternalServerError) + return + } + sres, err := client.Stat(ctx, &provider.StatRequest{ Ref: &provider.Reference{ResourceId: &rid}, }) if err != nil { @@ -194,7 +206,7 @@ func (g Graph) UnassignTags(w http.ResponseWriter, r *http.Request) { return } - resp, err := g.gatewayClient.SetArbitraryMetadata(ctx, &provider.SetArbitraryMetadataRequest{ + resp, err := client.SetArbitraryMetadata(ctx, &provider.SetArbitraryMetadataRequest{ Ref: &provider.Reference{ResourceId: &rid}, ArbitraryMetadata: &provider.ArbitraryMetadata{ Metadata: map[string]string{ diff --git a/services/graph/pkg/service/v0/users.go b/services/graph/pkg/service/v0/users.go index b77444caa..ed3b962a3 100644 --- a/services/graph/pkg/service/v0/users.go +++ b/services/graph/pkg/service/v0/users.go @@ -9,6 +9,7 @@ import ( "reflect" "regexp" "sort" + "strconv" "strings" "github.com/CiscoM31/godata" @@ -65,12 +66,7 @@ func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) { me, err = g.identityBackend.GetUser(r.Context(), u.GetId().GetOpaqueId(), odataReq) if err != nil { logger.Debug().Err(err).Interface("user", u).Msg("could not get user from backend") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } if me.MemberOf == nil { @@ -84,12 +80,7 @@ func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) { me.AppRoleAssignments, err = g.fetchAppRoleAssignments(r.Context(), me.GetId()) if err != nil { logger.Debug().Err(err).Str("userid", me.GetId()).Msg("could not get appRoleAssignments for self") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } } @@ -318,12 +309,7 @@ func (g Graph) PostUser(w http.ResponseWriter, r *http.Request) { logger.Debug().Interface("user", u).Msg("calling create user on backend") if u, err = g.identityBackend.CreateUser(r.Context(), *u); err != nil { logger.Error().Err(err).Msg("could not create user: backend error") - var ecErr errorcode.Error - if errors.As(err, &ecErr) { - ecErr.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -392,12 +378,7 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Debug().Err(err).Msg("could not get user: error fetching user from backend") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -427,10 +408,18 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { return } logger.Debug().Str("id", user.GetId()).Msg("calling list storage spaces with filter") + // use the unrestricted flag to get all possible spaces // users with the canListAllSpaces permission should see all spaces + + client, err := g.gatewaySelector.Next() + if err != nil { + logger.Error().Err(err).Msg("error selecting next gateway client") + render.Status(r, http.StatusInternalServerError) + return + } opaque := utils.AppendPlainToOpaque(nil, "unrestricted", "T") - lspr, err := g.gatewayClient.ListStorageSpaces(r.Context(), &storageprovider.ListStorageSpacesRequest{ + lspr, err := client.ListStorageSpaces(r.Context(), &storageprovider.ListStorageSpacesRequest{ Opaque: opaque, Filters: filters, }) @@ -482,12 +471,7 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { user.AppRoleAssignments, err = g.fetchAppRoleAssignments(r.Context(), user.GetId()) if err != nil { logger.Debug().Err(err).Str("userid", user.GetId()).Msg("could not get appRoleAssignments for user") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } } @@ -526,12 +510,7 @@ func (g Graph) DeleteUser(w http.ResponseWriter, r *http.Request) { user, err := g.identityBackend.GetUser(r.Context(), userID, odataReq) if err != nil { logger.Debug().Err(err).Str("userID", userID).Msg("failed to get user from backend") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } @@ -545,13 +524,19 @@ func (g Graph) DeleteUser(w http.ResponseWriter, r *http.Request) { e.Executant = currentUser.GetId() } - if g.gatewayClient != nil { + if g.gatewaySelector != nil { logger.Debug(). Str("user", user.GetId()). Msg("calling list spaces with user filter to fetch the personal space for deletion") opaque := utils.AppendPlainToOpaque(nil, "unrestricted", "T") f := listStorageSpacesUserFilter(user.GetId()) - lspr, err := g.gatewayClient.ListStorageSpaces(r.Context(), &storageprovider.ListStorageSpacesRequest{ + client, err := g.gatewaySelector.Next() + if err != nil { + logger.Error().Err(err).Msg("error selecting next gateway client") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "error selecting next gateway client, aborting") + return + } + lspr, err := client.ListStorageSpaces(r.Context(), &storageprovider.ListStorageSpacesRequest{ Opaque: opaque, Filters: []*storageprovider.ListStorageSpacesRequest_Filter{f}, }) @@ -572,7 +557,7 @@ func (g Graph) DeleteUser(w http.ResponseWriter, r *http.Request) { // Deleting a space a two step process (1. disabling/trashing, 2. purging) // Do the "disable/trash" step only if the space is not marked as trashed yet: if _, ok := sp.Opaque.Map["trashed"]; !ok { - _, err := g.gatewayClient.DeleteStorageSpace(r.Context(), &storageprovider.DeleteStorageSpaceRequest{ + _, err := client.DeleteStorageSpace(r.Context(), &storageprovider.DeleteStorageSpaceRequest{ Id: &storageprovider.StorageSpaceId{ OpaqueId: sp.Id.OpaqueId, }, @@ -584,7 +569,7 @@ func (g Graph) DeleteUser(w http.ResponseWriter, r *http.Request) { } } purgeFlag := utils.AppendPlainToOpaque(nil, "purge", "") - _, err := g.gatewayClient.DeleteStorageSpace(r.Context(), &storageprovider.DeleteStorageSpaceRequest{ + _, err := client.DeleteStorageSpace(r.Context(), &storageprovider.DeleteStorageSpaceRequest{ Opaque: purgeFlag, Id: &storageprovider.StorageSpaceId{ OpaqueId: sp.Id.OpaqueId, @@ -605,13 +590,8 @@ func (g Graph) DeleteUser(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Debug().Err(err).Msg("could not delete user: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - return - } + errorcode.RenderError(w, r, err) + return } g.publishEvent(e) @@ -633,6 +613,22 @@ func (g Graph) PatchUser(w http.ResponseWriter, r *http.Request) { return } + sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/") + + odataReq, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query()) + if err != nil { + logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users: query error") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) + return + } + + oldUserValues, err := g.identityBackend.GetUser(r.Context(), nameOrID, odataReq) + if err != nil { + logger.Debug().Err(err).Str("id", nameOrID).Msg("could not get user: backend error") + errorcode.RenderError(w, r, err) + return + } + if nameOrID == "" { logger.Debug().Msg("could not update user: missing user id") errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing user id") @@ -648,12 +644,20 @@ func (g Graph) PatchUser(w http.ResponseWriter, r *http.Request) { } if reflect.ValueOf(*changes).IsZero() { - logger.Debug().Interface("body", r.Body).Msg("ignoring empyt request body") + logger.Debug().Interface("body", r.Body).Msg("ignoring empty request body") render.Status(r, http.StatusNoContent) render.NoContent(w, r) return } + if accountName, ok := changes.GetOnPremisesSamAccountNameOk(); ok { + if !g.isValidUsername(*accountName) { + logger.Info().Str("username", *accountName).Msg("could not update user: invalid username") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Invalid username") + return + } + } + var features []events.UserFeature if mail, ok := changes.GetMailOk(); ok { if !isValidEmail(*mail) { @@ -662,37 +666,61 @@ func (g Graph) PatchUser(w http.ResponseWriter, r *http.Request) { fmt.Sprintf("'%s' is not a valid email address", *mail)) return } - features = append(features, events.UserFeature{Name: "email", Value: *mail}) + features = append(features, events.UserFeature{ + Name: "email", + Value: *mail, + OldValue: oldUserValues.Mail, + }) } if name, ok := changes.GetDisplayNameOk(); ok { - features = append(features, events.UserFeature{Name: "displayname", Value: *name}) + features = append(features, events.UserFeature{ + Name: "displayname", + Value: *name, + OldValue: oldUserValues.DisplayName, + }) } - if changes.HasUserType() { + if userType, ok := changes.GetUserTypeOk(); ok { if !isValidUserType(*changes.UserType) { logger.Debug().Interface("user", changes).Msg("invalid userType attribute") errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid userType attribute, valid options are 'Member' or 'Guest'") return } + features = append(features, events.UserFeature{ + Name: "changeUserType", + Value: *userType, + OldValue: oldUserValues.UserType, + }) + } + + if accEnabled, ok := changes.GetAccountEnabledOk(); ok { + oldAccVal := strconv.FormatBool(oldUserValues.GetAccountEnabled()) + features = append(features, events.UserFeature{ + Name: "accountEnabled", + Value: strconv.FormatBool(*accEnabled), + OldValue: &oldAccVal, + }) + } + + if changes.HasPasswordProfile() { + features = append(features, events.UserFeature{ + Name: "passwordChanged", + }) } logger.Debug().Str("nameid", nameOrID).Interface("changes", *changes).Msg("calling update user on backend") u, err := g.identityBackend.UpdateUser(r.Context(), nameOrID, *changes) if err != nil { logger.Debug().Err(err).Str("id", nameOrID).Msg("could not update user: backend error") - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } + errorcode.RenderError(w, r, err) return } e := events.UserFeatureChanged{ - UserID: nameOrID, - Features: features, + UserID: nameOrID, + Features: features, + Timestamp: utils.TSNow(), } if currentUser, ok := revactx.ContextGetUser(r.Context()); ok { e.Executant = currentUser.GetId() diff --git a/services/graph/pkg/service/v0/users_test.go b/services/graph/pkg/service/v0/users_test.go index 4719f366e..38ee84349 100644 --- a/services/graph/pkg/service/v0/users_test.go +++ b/services/graph/pkg/service/v0/users_test.go @@ -9,18 +9,17 @@ import ( "net/http/httptest" "net/url" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/rgrpc/status" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" "github.com/go-chi/chi/v5" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/stretchr/testify/mock" - "go-micro.dev/v4/client" - libregraph "github.com/owncloud/libre-graph-api-go" ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" "github.com/owncloud/ocis/v2/ocis-pkg/shared" @@ -31,6 +30,9 @@ import ( "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks" service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" + "github.com/stretchr/testify/mock" + "go-micro.dev/v4/client" + "google.golang.org/grpc" ) type userList struct { @@ -43,6 +45,7 @@ var _ = Describe("Users", func() { ctx context.Context cfg *config.Config gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] eventsPublisher mocks.Publisher roleService *mocks.RoleService identityBackend *identitymocks.Backend @@ -59,9 +62,18 @@ var _ = Describe("Users", func() { BeforeEach(func() { eventsPublisher.On("Publish", mock.Anything, mock.Anything, mock.Anything).Return(nil) + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") + gatewayClient = &cs3mocks.GatewayAPIClient{} + gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return gatewayClient + }, + ) + identityBackend = &identitymocks.Backend{} roleService = &mocks.RoleService{} - gatewayClient = &cs3mocks.GatewayAPIClient{} rr = httptest.NewRecorder() ctx = context.Background() @@ -76,7 +88,7 @@ var _ = Describe("Users", func() { _ = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...) svc, _ = service.NewService( service.Config(cfg), - service.WithGatewayClient(gatewayClient), + service.WithGatewaySelector(gatewaySelector), service.EventsPublisher(&eventsPublisher), service.WithIdentityBackend(identityBackend), service.WithRoleService(roleService), @@ -681,7 +693,7 @@ var _ = Describe("Users", func() { _ = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...) localSvc, _ := service.NewService( service.Config(localCfg), - service.WithGatewayClient(gatewayClient), + service.WithGatewaySelector(gatewaySelector), service.EventsPublisher(&eventsPublisher), service.WithIdentityBackend(identityBackend), service.WithRoleService(roleService), @@ -797,7 +809,7 @@ var _ = Describe("Users", func() { user.SetMail("user@example.com") user.SetId("/users/user") - identityBackend.On("GetUser", mock.Anything, mock.Anything, mock.Anything).Return(&user, nil) + identityBackend.On("GetUser", mock.Anything, mock.Anything, mock.Anything).Return(user, nil) }) It("handles missing userids", func() { @@ -854,7 +866,7 @@ var _ = Describe("Users", func() { data, err := json.Marshal(user) Expect(err).ToNot(HaveOccurred()) - r := httptest.NewRequest(http.MethodPost, "/graph/v1.0/users?$invalid=true", bytes.NewBuffer(data)) + r := httptest.NewRequest(http.MethodPost, "/graph/v1.0/users", bytes.NewBuffer(data)) rctx := chi.NewRouteContext() rctx.URLParams.Add("userID", user.GetId()) r = r.WithContext(context.WithValue(revactx.ContextSetUser(ctx, currentUser), chi.RouteCtxKey, rctx)) diff --git a/services/groups/pkg/command/root.go b/services/groups/pkg/command/root.go index 4a7578fc5..fdc14e685 100644 --- a/services/groups/pkg/command/root.go +++ b/services/groups/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/groups/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the group command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new group.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Groups.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Groups, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/groups/pkg/command/server.go b/services/groups/pkg/command/server.go index 6605c3dfd..55a69ddfa 100644 --- a/services/groups/pkg/command/server.go +++ b/services/groups/pkg/command/server.go @@ -11,7 +11,7 @@ import ( "github.com/oklog/run" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" "github.com/owncloud/ocis/v2/ocis-pkg/ldap" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/sync" "github.com/owncloud/ocis/v2/ocis-pkg/version" "github.com/owncloud/ocis/v2/services/groups/pkg/config" @@ -43,10 +43,6 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") - - rcfg := revaconfig.GroupsConfigFromStruct(cfg) - // the reva runtime calls os.Exit in the case of a failure and there is no way for the oCIS // runtime to catch it and restart a reva service. Therefore we need to ensure the service has // everything it needs, before starting the service. @@ -60,7 +56,15 @@ func Server(cfg *config.Config) *cli.Command { } gr.Add(func() error { - runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + rCfg := revaconfig.GroupsConfigFromStruct(cfg) + reg := registry.GetRegistry() + + runtime.RunWithOptions(rCfg, pidFile, + runtime.WithLogger(&logger.Logger), + runtime.WithRegistry(reg), + ) + return nil }, func(err error) { logger.Error(). @@ -90,15 +94,9 @@ func Server(cfg *config.Config) *cli.Command { sync.Trap(&gr, cancel) } - if err := external.RegisterGRPCEndpoint( - ctx, - cfg.GRPC.Namespace+"."+cfg.Service.Name, - uuid.Must(uuid.NewV4()).String(), - cfg.GRPC.Addr, - version.GetString(), - logger, - ); err != nil { - logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, version.GetString()) + if err := registry.RegisterService(ctx, grpcSvc, logger); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc service") } return gr.Run() diff --git a/services/groups/pkg/config/config.go b/services/groups/pkg/config/config.go index beb514999..41283ce45 100644 --- a/services/groups/pkg/config/config.go +++ b/services/groups/pkg/config/config.go @@ -88,7 +88,7 @@ type LDAPDriver struct { type LDAPUserSchema struct { ID string `yaml:"id" env:"OCIS_LDAP_USER_SCHEMA_ID;LDAP_USER_SCHEMA_ID;GROUPS_LDAP_USER_SCHEMA_ID" desc:"LDAP Attribute to use as the unique id for users. This should be a stable globally unique id like a UUID." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_ID changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_ID"` - IDIsOctetString bool `yaml:"id_is_octet_string" env:"OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;GROUPS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING" desc:"Set this to true if the defined 'id' attribute for users is of the 'OCTETSTRING' syntax. This is e.g. required when using the 'objectGUID' attribute of Active Directory for the user id's." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_ID_IS_OCTETSTRING changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING"` + IDIsOctetString bool `yaml:"id_is_octet_string" env:"OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;GROUPS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING" desc:"Set this to true if the defined 'ID' attribute for users is of the 'OCTETSTRING' syntax. This is e.g. required when using the 'objectGUID' attribute of Active Directory for the user ID's." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_ID_IS_OCTETSTRING changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING"` Mail string `yaml:"mail" env:"OCIS_LDAP_USER_SCHEMA_MAIL;LDAP_USER_SCHEMA_MAIL;GROUPS_LDAP_USER_SCHEMA_MAIL" desc:"LDAP Attribute to use for the email address of users." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_MAIL changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_MAIL"` DisplayName string `yaml:"display_name" env:"OCIS_LDAP_USER_SCHEMA_DISPLAYNAME;LDAP_USER_SCHEMA_DISPLAYNAME;GROUPS_LDAP_USER_SCHEMA_DISPLAYNAME" desc:"LDAP Attribute to use for the displayname of users." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_DISPLAYNAME changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_DISPLAYNAME"` Username string `yaml:"user_name" env:"OCIS_LDAP_USER_SCHEMA_USERNAME;LDAP_USER_SCHEMA_USERNAME;GROUPS_LDAP_USER_SCHEMA_USERNAME" desc:"LDAP Attribute to use for username of users." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_USERNAME changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_USERNAME"` diff --git a/services/idm/README.md b/services/idm/README.md index fe168f5ad..07988dd86 100644 --- a/services/idm/README.md +++ b/services/idm/README.md @@ -4,6 +4,6 @@ The IDM service provides a minimal LDAP Service, based on [Libregraph idm](https It is mainly targeted at small oCIS installations. For larger setups it is recommended to replace IDM with a “real” LDAP server or to switch to an external identity management solution. -IDM listens on port 9325 by default. In the default configuration it only accepts TLS-protected connections (LDAPS). The BaseDN of the LDAP tree is `o=libregraph-idm`. IDM gives LDAP write permissions to a single user (DN: `uid=libregraph,ou=sysusers,o=libregraph-idm`). Any other authenticated user has read-only access. IDM stores its data in a boltdb file `idm/ocis.boltdb` inside the oCIS base data directory. +IDM listens on port 9235 by default. In the default configuration it only accepts TLS-protected connections (LDAPS). The BaseDN of the LDAP tree is `o=libregraph-idm`. IDM gives LDAP write permissions to a single user (DN: `uid=libregraph,ou=sysusers,o=libregraph-idm`). Any other authenticated user has read-only access. IDM stores its data in a boltdb file `idm/ocis.boltdb` inside the oCIS base data directory. Note: IDM is limited in its functionality. It only supports a subset of the LDAP operations (namely `BIND`, `SEARCH`, `ADD`, `MODIFY`, `DELETE`). Also, IDM currently does not do any schema verification (like. structural vs. auxiliary object classes, require and option attributes, syntax checks, …). Therefore it is not meant as a general purpose LDAP server. diff --git a/services/idm/pkg/command/root.go b/services/idm/pkg/command/root.go index ee1ccb95d..85134a437 100644 --- a/services/idm/pkg/command/root.go +++ b/services/idm/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/idm/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -36,25 +33,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the idm command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new idm.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.IDM.Commons = cfg.Commons - return SutureService{ - cfg: cfg.IDM, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/idp/pkg/command/root.go b/services/idp/pkg/command/root.go index 9b3960579..724620d7d 100644 --- a/services/idp/pkg/command/root.go +++ b/services/idp/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/idp/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the idp command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new idp.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.IDP.Commons = cfg.Commons - return SutureService{ - cfg: cfg.IDP, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/idp/pkg/config/config.go b/services/idp/pkg/config/config.go index 6439d2e8a..ac6fb0643 100644 --- a/services/idp/pkg/config/config.go +++ b/services/idp/pkg/config/config.go @@ -44,7 +44,7 @@ type Ldap struct { LoginAttribute string `yaml:"login_attribute" env:"IDP_LDAP_LOGIN_ATTRIBUTE" desc:"LDAP User attribute to use for login like 'uid'."` EmailAttribute string `yaml:"email_attribute" env:"OCIS_LDAP_USER_SCHEMA_MAIL;LDAP_USER_SCHEMA_MAIL;IDP_LDAP_EMAIL_ATTRIBUTE" desc:"LDAP User email attribute like 'mail'." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_MAIL changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_MAIL"` NameAttribute string `yaml:"name_attribute" env:"OCIS_LDAP_USER_SCHEMA_USERNAME;LDAP_USER_SCHEMA_USERNAME;IDP_LDAP_NAME_ATTRIBUTE" desc:"LDAP User name attribute like 'displayName'." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_USERNAME changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_USERNAME"` - UUIDAttribute string `yaml:"uuid_attribute" env:"OCIS_LDAP_USER_SCHEMA_ID;LDAP_USER_SCHEMA_ID;IDP_LDAP_UUID_ATTRIBUTE" desc:"LDAP User uuid attribute like 'uid'." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_ID changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_ID"` + UUIDAttribute string `yaml:"uuid_attribute" env:"OCIS_LDAP_USER_SCHEMA_ID;LDAP_USER_SCHEMA_ID;IDP_LDAP_UUID_ATTRIBUTE" desc:"LDAP User UUID attribute like 'uid'." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_ID changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_ID"` UUIDAttributeType string `yaml:"uuid_attribute_type" env:"IDP_LDAP_UUID_ATTRIBUTE_TYPE" desc:"LDAP User uuid attribute type like 'text'."` UserEnabledAttribute string `yaml:"user_enabled_attribute" env:"OCIS_LDAP_USER_ENABLED_ATTRIBUTE;LDAP_USER_ENABLED_ATTRIBUTE;IDP_USER_ENABLED_ATTRIBUTE" desc:"LDAP Attribute to use as a flag telling if the user is enabled or disabled." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_ENABLED_ATTRIBUTE changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_ENABLED_ATTRIBUTE"` @@ -61,7 +61,6 @@ type Client struct { ID string `yaml:"id"` Name string `yaml:"name"` Trusted bool `yaml:"trusted"` - ImplicitScopes []string `yaml:"implicit_scopes"` Secret string `yaml:"secret"` RedirectURIs []string `yaml:"redirect_uris"` Origins []string `yaml:"origins"` diff --git a/services/idp/pkg/config/defaults/defaultconfig.go b/services/idp/pkg/config/defaults/defaultconfig.go index eb5f81db4..ac0d15bf5 100644 --- a/services/idp/pkg/config/defaults/defaultconfig.go +++ b/services/idp/pkg/config/defaults/defaultconfig.go @@ -71,10 +71,9 @@ func DefaultConfig() *config.Config { }, Clients: []config.Client{ { - ID: "web", - Name: "ownCloud Web app", - ImplicitScopes: []string{"LibgreGraph.UUID"}, - Trusted: true, + ID: "web", + Name: "ownCloud Web app", + Trusted: true, RedirectURIs: []string{ "{{OCIS_URL}}/", "{{OCIS_URL}}/oidc-callback.html", @@ -88,7 +87,6 @@ func DefaultConfig() *config.Config { ID: "xdXOt13JKxym1B1QcEncf2XDkLAexMBFwiT9j6EfhhHFJhs2KM9jbjTmf8JBXE69", Secret: "UBntmLjC2yYCeHwsyj73Uwo9TAaecAetRwMw0xYcvNL9yRdLSUi0hUAHfvCHFeFh", Name: "ownCloud desktop app", - ImplicitScopes: []string{"LibgreGraph.UUID"}, ApplicationType: "native", RedirectURIs: []string{ "http://127.0.0.1", @@ -99,7 +97,6 @@ func DefaultConfig() *config.Config { ID: "e4rAsNUSIUs0lF4nbv9FmCeUkTlV9GdgTLDH1b5uie7syb90SzEVrbN7HIpmWJeD", Secret: "dInFYGV33xKzhbRmpqQltYNdfLdJIfJ9L5ISoKhNoT9qZftpdWSP71VrpGR9pmoD", Name: "ownCloud Android app", - ImplicitScopes: []string{"LibgreGraph.UUID"}, ApplicationType: "native", RedirectURIs: []string{ "oc://android.owncloud.com", @@ -109,11 +106,9 @@ func DefaultConfig() *config.Config { ID: "mxd5OQDk6es5LzOzRvidJNfXLUZS2oN3oUFeXPP8LpPrhx3UroJFduGEYIBOxkY1", Secret: "KFeFWWEZO9TkisIQzR3fo7hfiMXlOpaqP8CFuTbSHzV1TUuGECglPxpiVKJfOXIx", Name: "ownCloud iOS app", - ImplicitScopes: []string{"LibgreGraph.UUID"}, ApplicationType: "native", RedirectURIs: []string{ "oc://ios.owncloud.com", - "oc.ios://ios.owncloud.com", }, }, }, diff --git a/services/idp/pkg/middleware/static.go b/services/idp/pkg/middleware/static.go index 11bbe1fa8..440219617 100644 --- a/services/idp/pkg/middleware/static.go +++ b/services/idp/pkg/middleware/static.go @@ -7,6 +7,7 @@ import ( idpTracing "github.com/owncloud/ocis/v2/services/idp/pkg/tracing" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" ) // Static is a middleware that serves static assets. @@ -24,7 +25,10 @@ func Static(root string, fs http.FileSystem) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx, span := idpTracing.TraceProvider.Tracer("idp").Start(r.Context(), "serve static asset") + spanOpts := []trace.SpanStartOption{ + trace.WithSpanKind(trace.SpanKindServer), + } + ctx, span := idpTracing.TraceProvider.Tracer("idp").Start(r.Context(), "serve static asset", spanOpts...) defer span.End() r = r.WithContext(ctx) diff --git a/services/invitations/md-sources/example-realm.json b/services/invitations/md-sources/example-realm.json index a164f29f3..614d80054 100644 --- a/services/invitations/md-sources/example-realm.json +++ b/services/invitations/md-sources/example-realm.json @@ -543,8 +543,7 @@ "clientAuthenticatorType": "client-secret", "secret": "**********", "redirectUris": [ - "oc://ios.owncloud.com", - "oc.ios://ios.owncloud.com" + "oc://ios.owncloud.com" ], "webOrigins": [], "notBefore": 0, diff --git a/services/invitations/pkg/backends/keycloak/backend_test.go b/services/invitations/pkg/backends/keycloak/backend_test.go index 019469b52..d1e8bc93d 100644 --- a/services/invitations/pkg/backends/keycloak/backend_test.go +++ b/services/invitations/pkg/backends/keycloak/backend_test.go @@ -13,7 +13,7 @@ import ( "github.com/owncloud/ocis/v2/services/invitations/pkg/invitations" "github.com/owncloud/ocis/v2/services/invitations/pkg/mocks" "github.com/stretchr/testify/assert" - "github.com/test-go/testify/mock" + "github.com/stretchr/testify/mock" ) const ( diff --git a/services/invitations/pkg/command/root.go b/services/invitations/pkg/command/root.go index dde703557..aa8c993ab 100644 --- a/services/invitations/pkg/command/root.go +++ b/services/invitations/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/invitations/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the webdav command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new webdav.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Invitations.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Invitations, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/invitations/pkg/config/config.go b/services/invitations/pkg/config/config.go index 27391684c..97a87e104 100644 --- a/services/invitations/pkg/config/config.go +++ b/services/invitations/pkg/config/config.go @@ -27,7 +27,7 @@ type Config struct { // Keycloak configuration type Keycloak struct { BasePath string `yaml:"base_path" env:"OCIS_KEYCLOAK_BASE_PATH;INVITATIONS_KEYCLOAK_BASE_PATH" desc:"The URL to access keycloak."` - ClientID string `yaml:"client_id" env:"OCIS_KEYCLOAK_CLIENT_ID;INVITATIONS_KEYCLOAK_CLIENT_ID" desc:"The client id to authenticate with keycloak."` + ClientID string `yaml:"client_id" env:"OCIS_KEYCLOAK_CLIENT_ID;INVITATIONS_KEYCLOAK_CLIENT_ID" desc:"The client ID to authenticate with keycloak."` ClientSecret string `yaml:"client_secret" env:"OCIS_KEYCLOAK_CLIENT_SECRET;INVITATIONS_KEYCLOAK_CLIENT_SECRET" desc:"The client secret to use in authentication."` ClientRealm string `yaml:"client_realm" env:"OCIS_KEYCLOAK_CLIENT_REALM;INVITATIONS_KEYCLOAK_CLIENT_REALM" desc:"The realm the client is defined in."` UserRealm string `yaml:"user_realm" env:"OCIS_KEYCLOAK_USER_REALM;INVITATIONS_KEYCLOAK_USER_REALM" desc:"The realm users are defined."` diff --git a/services/invitations/pkg/config/http.go b/services/invitations/pkg/config/http.go index 342b45fca..baac6d769 100644 --- a/services/invitations/pkg/config/http.go +++ b/services/invitations/pkg/config/http.go @@ -6,7 +6,7 @@ import "github.com/owncloud/ocis/v2/ocis-pkg/shared" type CORS struct { AllowedOrigins []string `yaml:"allow_origins" env:"OCIS_CORS_ALLOW_ORIGINS;INVITATIONS_CORS_ALLOW_ORIGINS" desc:"A comma-separated list of allowed CORS origins. See following chapter for more details: *Access-Control-Allow-Origin* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin"` AllowedMethods []string `yaml:"allow_methods" env:"OCIS_CORS_ALLOW_METHODS;INVITATIONS_CORS_ALLOW_METHODS" desc:"A comma-separated list of allowed CORS methods. See following chapter for more details: *Access-Control-Request-Method* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method"` - AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;INVITATIONS_CORS_ALLOW_HEADERS" desc:"A comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` + AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;INVITATIONS_CORS_ALLOW_HEADERS" desc:"A blank or comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` AllowCredentials bool `yaml:"allow_credentials" env:"OCIS_CORS_ALLOW_CREDENTIALS;INVITATIONS_CORS_ALLOW_CREDENTIALS" desc:"Allow credentials for CORS.See following chapter for more details: *Access-Control-Allow-Credentials* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials."` } diff --git a/services/invitations/pkg/service/v0/tracing.go b/services/invitations/pkg/service/v0/tracing.go index 2f5ea3b81..f7eb3ee56 100644 --- a/services/invitations/pkg/service/v0/tracing.go +++ b/services/invitations/pkg/service/v0/tracing.go @@ -22,9 +22,13 @@ type tracing struct { // Invite implements the Service interface. func (t tracing) Invite(ctx context.Context, invitation *invitations.Invitation) (*invitations.Invitation, error) { - ctx, span := invitationstracing.TraceProvider.Tracer("invitations").Start(ctx, "Invite", trace.WithAttributes( - attribute.KeyValue{Key: "invitation", Value: attribute.StringValue(invitation.InvitedUserEmailAddress)}, - )) + spanOpts := []trace.SpanStartOption{ + trace.WithSpanKind(trace.SpanKindServer), + trace.WithAttributes( + attribute.KeyValue{ + Key: "invitation", Value: attribute.StringValue(invitation.InvitedUserEmailAddress), + })} + ctx, span := invitationstracing.TraceProvider.Tracer("invitations").Start(ctx, "Invite", spanOpts...) defer span.End() return t.next.Invite(ctx, invitation) diff --git a/services/nats/pkg/command/root.go b/services/nats/pkg/command/root.go index 583d2658d..067bd39cd 100644 --- a/services/nats/pkg/command/root.go +++ b/services/nats/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/nats/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the nats command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new nats.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Nats.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Nats, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/notifications/pkg/command/root.go b/services/notifications/pkg/command/root.go index 72c7dfda2..aa048245a 100644 --- a/services/notifications/pkg/command/root.go +++ b/services/notifications/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/notifications/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the notifications command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new notifications.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Notifications.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Notifications, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/notifications/pkg/command/server.go b/services/notifications/pkg/command/server.go index f1b623c2b..f409ce8ce 100644 --- a/services/notifications/pkg/command/server.go +++ b/services/notifications/pkg/command/server.go @@ -15,6 +15,7 @@ import ( "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" "github.com/owncloud/ocis/v2/ocis-pkg/crypto" "github.com/owncloud/ocis/v2/ocis-pkg/handlers" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" "github.com/owncloud/ocis/v2/ocis-pkg/version" @@ -39,6 +40,11 @@ func Server(cfg *config.Config) *cli.Command { Action: func(c *cli.Context) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := grpc.Configure(grpc.GetClientOptions(&cfg.GRPCClientTLS)...) + if err != nil { + return err + } + gr := run.Group{} ctx, cancel := func() (context.Context, context.CancelFunc) { @@ -122,16 +128,17 @@ func Server(cfg *config.Config) *cli.Command { if err != nil { return err } - gwclient, err := pool.GetGatewayServiceClient( + gatewaySelector, err := pool.GatewaySelector( cfg.Notifications.RevaGateway, pool.WithTLSCACert(cfg.Notifications.GRPCClientTLS.CACert), pool.WithTLSMode(tm), + pool.WithRegistry(registry.GetRegistry()), ) if err != nil { - logger.Fatal().Err(err).Str("addr", cfg.Notifications.RevaGateway).Msg("could not get reva client") + logger.Fatal().Err(err).Str("addr", cfg.Notifications.RevaGateway).Msg("could not get reva gateway selector") } valueService := settingssvc.NewValueService("com.owncloud.api.settings", grpc.DefaultClient()) - svc := service.NewEventsNotifier(evts, channel, logger, gwclient, valueService, cfg.Notifications.MachineAuthAPIKey, cfg.Notifications.EmailTemplatePath, cfg.WebUIURL) + svc := service.NewEventsNotifier(evts, channel, logger, gatewaySelector, valueService, cfg.Notifications.MachineAuthAPIKey, cfg.Notifications.EmailTemplatePath, cfg.WebUIURL) gr.Add(svc.Run, func(error) { cancel() diff --git a/services/notifications/pkg/config/config.go b/services/notifications/pkg/config/config.go index 9c0a31981..83491eeeb 100644 --- a/services/notifications/pkg/config/config.go +++ b/services/notifications/pkg/config/config.go @@ -18,7 +18,8 @@ type Config struct { WebUIURL string `yaml:"ocis_url" env:"OCIS_URL;NOTIFICATIONS_WEB_UI_URL" desc:"The public facing URL of the oCIS Web UI, used e.g. when sending notification eMails"` - Notifications Notifications `yaml:"notifications"` + Notifications Notifications `yaml:"notifications"` + GRPCClientTLS shared.GRPCClientTLS `yaml:"grpc_client_tls"` Context context.Context `yaml:"-"` } diff --git a/services/notifications/pkg/email/composer.go b/services/notifications/pkg/email/composer.go index 3342cdbea..8a9c61bc5 100644 --- a/services/notifications/pkg/email/composer.go +++ b/services/notifications/pkg/email/composer.go @@ -9,7 +9,7 @@ import ( ) // NewTextTemplate replace the body message template placeholders with the translated template -func NewTextTemplate(mt MessageTemplate, locale string, translationPath string, vars map[string]interface{}) (MessageTemplate, error) { +func NewTextTemplate(mt MessageTemplate, locale string, translationPath string, vars map[string]string) (MessageTemplate, error) { var err error t := l10n.NewTranslator(locale, translationPath) mt.Subject, err = composeMessage(t.Translate(mt.Subject), vars) @@ -32,7 +32,7 @@ func NewTextTemplate(mt MessageTemplate, locale string, translationPath string, } // NewHTMLTemplate replace the body message template placeholders with the translated template -func NewHTMLTemplate(mt MessageTemplate, locale string, translationPath string, vars map[string]interface{}) (MessageTemplate, error) { +func NewHTMLTemplate(mt MessageTemplate, locale string, translationPath string, vars map[string]string) (MessageTemplate, error) { var err error t := l10n.NewTranslator(locale, translationPath) mt.Subject, err = composeMessage(t.Translate(mt.Subject), vars) @@ -55,7 +55,7 @@ func NewHTMLTemplate(mt MessageTemplate, locale string, translationPath string, } // composeMessage renders the message based on template -func composeMessage(tmpl string, vars map[string]interface{}) (string, error) { +func composeMessage(tmpl string, vars map[string]string) (string, error) { tpl, err := template.New("").Parse(replacePlaceholders(tmpl)) if err != nil { return "", err diff --git a/services/notifications/pkg/email/email.go b/services/notifications/pkg/email/email.go index e4779779f..03a37b499 100644 --- a/services/notifications/pkg/email/email.go +++ b/services/notifications/pkg/email/email.go @@ -6,11 +6,14 @@ package email import ( "bytes" "embed" + "errors" + "html" "html/template" "io/fs" "os" "path/filepath" "strings" + "syscall" "github.com/owncloud/ocis/v2/services/notifications/pkg/channels" ) @@ -23,7 +26,7 @@ var ( ) // RenderEmailTemplate renders the email template for a new share -func RenderEmailTemplate(mt MessageTemplate, locale string, emailTemplatePath string, translationPath string, vars map[string]interface{}) (*channels.Message, error) { +func RenderEmailTemplate(mt MessageTemplate, locale string, emailTemplatePath string, translationPath string, vars map[string]string) (*channels.Message, error) { textMt, err := NewTextTemplate(mt, locale, translationPath, vars) if err != nil { return nil, err @@ -36,8 +39,7 @@ func RenderEmailTemplate(mt MessageTemplate, locale string, emailTemplatePath st if err != nil { return nil, err } - - htmlMt, err := NewHTMLTemplate(mt, locale, translationPath, vars) + htmlMt, err := NewHTMLTemplate(mt, locale, translationPath, escapeStringMap(vars)) if err != nil { return nil, err } @@ -104,16 +106,21 @@ func readImages(emailTemplatePath string) (map[string][]byte, error) { func read(entries []fs.DirEntry, fsys fs.FS) (map[string][]byte, error) { list := make(map[string][]byte) for _, e := range entries { - if !e.IsDir() { - file, err := fs.ReadFile(fsys, filepath.Join(imgDir, e.Name())) - if err != nil { - return nil, err - } - if !validateMime(file) { + if e.IsDir() { + continue + } + file, err := fs.ReadFile(fsys, filepath.Join(imgDir, e.Name())) + if err != nil { + // skip if the symbolic is a directory + if errors.Is(err, syscall.EISDIR) { continue } - list[e.Name()] = file + return nil, err } + if !validateMime(file) { + continue + } + list[e.Name()] = file } return list, nil } @@ -135,3 +142,10 @@ func validateMime(incipit []byte) bool { } return false } + +func escapeStringMap(vars map[string]string) map[string]string { + for k := range vars { + vars[k] = html.EscapeString(vars[k]) + } + return vars +} diff --git a/services/notifications/pkg/email/l10n/locale/ca/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/ca/LC_MESSAGES/notifications.po index 489700a8f..842505756 100644 --- a/services/notifications/pkg/email/l10n/locale/ca/LC_MESSAGES/notifications.po +++ b/services/notifications/pkg/email/l10n/locale/ca/LC_MESSAGES/notifications.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-30 00:02+0000\n" +"POT-Creation-Date: 2023-06-19 00:15+0000\n" "PO-Revision-Date: 2023-04-19 11:11+0000\n" "Last-Translator: Arnau Morancho, 2023\n" "Language-Team: Catalan (https://app.transifex.com/owncloud-org/teams/6149/ca/)\n" diff --git a/services/notifications/pkg/email/l10n/locale/de/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/de/LC_MESSAGES/notifications.po index 7cc790c05..03a7c249f 100644 --- a/services/notifications/pkg/email/l10n/locale/de/LC_MESSAGES/notifications.po +++ b/services/notifications/pkg/email/l10n/locale/de/LC_MESSAGES/notifications.po @@ -6,15 +6,16 @@ # Translators: # Edith Parzefall, 2023 # hurradieweltgehtunter , 2023 +# Michael Barz , 2023 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-30 00:02+0000\n" +"POT-Creation-Date: 2023-06-28 10:06+0000\n" "PO-Revision-Date: 2023-04-19 11:11+0000\n" -"Last-Translator: hurradieweltgehtunter , 2023\n" +"Last-Translator: Michael Barz , 2023\n" "Language-Team: German (https://app.transifex.com/owncloud-org/teams/6149/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -49,7 +50,7 @@ msgstr "Hallo {ShareGrantee}," #: pkg/email/templates.go:42 pkg/email/templates.go:55 #: pkg/email/templates.go:70 msgid "Hello {SpaceGrantee}," -msgstr "Hallo {ShareGrantee}," +msgstr "Hallo {SpaceGrantee}," #. MembershipExpired email template, Subject field (resolves directly) #: pkg/email/templates.go:68 diff --git a/services/notifications/pkg/email/l10n/locale/de_DE/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/de_DE/LC_MESSAGES/notifications.po index 4100e2690..accb48ec6 100644 --- a/services/notifications/pkg/email/l10n/locale/de_DE/LC_MESSAGES/notifications.po +++ b/services/notifications/pkg/email/l10n/locale/de_DE/LC_MESSAGES/notifications.po @@ -5,15 +5,16 @@ # # Translators: # Edith Parzefall, 2023 +# Michael Barz , 2023 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-30 00:02+0000\n" +"POT-Creation-Date: 2023-06-28 10:06+0000\n" "PO-Revision-Date: 2023-04-19 11:11+0000\n" -"Last-Translator: Edith Parzefall, 2023\n" +"Last-Translator: Michael Barz , 2023\n" "Language-Team: German (Germany) (https://app.transifex.com/owncloud-org/teams/6149/de_DE/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -48,7 +49,7 @@ msgstr "Hallo {ShareGrantee}," #: pkg/email/templates.go:42 pkg/email/templates.go:55 #: pkg/email/templates.go:70 msgid "Hello {SpaceGrantee}," -msgstr "Hallo {ShareGrantee}," +msgstr "Hallo {SpaceGrantee}," #. MembershipExpired email template, Subject field (resolves directly) #: pkg/email/templates.go:68 diff --git a/services/notifications/pkg/email/l10n/locale/en_GB/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/en_GB/LC_MESSAGES/notifications.po index 075c8f570..0baeac0e0 100644 --- a/services/notifications/pkg/email/l10n/locale/en_GB/LC_MESSAGES/notifications.po +++ b/services/notifications/pkg/email/l10n/locale/en_GB/LC_MESSAGES/notifications.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-30 00:02+0000\n" +"POT-Creation-Date: 2023-06-19 00:15+0000\n" "PO-Revision-Date: 2023-04-19 11:11+0000\n" "Last-Translator: Andi Chandler , 2023\n" "Language-Team: English (United Kingdom) (https://app.transifex.com/owncloud-org/teams/6149/en_GB/)\n" diff --git a/services/notifications/pkg/email/l10n/locale/es/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/es/LC_MESSAGES/notifications.po index b91d64b32..be7896fc3 100644 --- a/services/notifications/pkg/email/l10n/locale/es/LC_MESSAGES/notifications.po +++ b/services/notifications/pkg/email/l10n/locale/es/LC_MESSAGES/notifications.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-30 00:02+0000\n" +"POT-Creation-Date: 2023-06-19 00:15+0000\n" "PO-Revision-Date: 2023-04-19 11:11+0000\n" "Last-Translator: Juan Carlos Garrote, 2023\n" "Language-Team: Spanish (https://app.transifex.com/owncloud-org/teams/6149/es/)\n" diff --git a/services/notifications/pkg/email/l10n/locale/ko/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/ko/LC_MESSAGES/notifications.po index fd0301d33..f606ee0ff 100644 --- a/services/notifications/pkg/email/l10n/locale/ko/LC_MESSAGES/notifications.po +++ b/services/notifications/pkg/email/l10n/locale/ko/LC_MESSAGES/notifications.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-30 00:02+0000\n" +"POT-Creation-Date: 2023-06-19 00:15+0000\n" "PO-Revision-Date: 2023-04-19 11:11+0000\n" "Last-Translator: Hugo Blackwood, 2023\n" "Language-Team: Korean (https://app.transifex.com/owncloud-org/teams/6149/ko/)\n" diff --git a/services/notifications/pkg/email/l10n/locale/pt_BR/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/pt_BR/LC_MESSAGES/notifications.po index f14753d32..196309969 100644 --- a/services/notifications/pkg/email/l10n/locale/pt_BR/LC_MESSAGES/notifications.po +++ b/services/notifications/pkg/email/l10n/locale/pt_BR/LC_MESSAGES/notifications.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-30 00:02+0000\n" +"POT-Creation-Date: 2023-06-19 00:15+0000\n" "PO-Revision-Date: 2023-04-19 11:11+0000\n" "Last-Translator: Flávio Veras , 2023\n" "Language-Team: Portuguese (Brazil) (https://app.transifex.com/owncloud-org/teams/6149/pt_BR/)\n" diff --git a/services/notifications/pkg/email/l10n/locale/ru/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/ru/LC_MESSAGES/notifications.po index f3732f544..f14d7cfb0 100644 --- a/services/notifications/pkg/email/l10n/locale/ru/LC_MESSAGES/notifications.po +++ b/services/notifications/pkg/email/l10n/locale/ru/LC_MESSAGES/notifications.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-30 00:02+0000\n" +"POT-Creation-Date: 2023-06-19 00:15+0000\n" "PO-Revision-Date: 2023-04-19 11:11+0000\n" "Last-Translator: Roman Perekhod, 2023\n" "Language-Team: Russian (https://app.transifex.com/owncloud-org/teams/6149/ru/)\n" diff --git a/services/notifications/pkg/email/l10n/locale/sq/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/sq/LC_MESSAGES/notifications.po index e9a274432..dc8532a1f 100644 --- a/services/notifications/pkg/email/l10n/locale/sq/LC_MESSAGES/notifications.po +++ b/services/notifications/pkg/email/l10n/locale/sq/LC_MESSAGES/notifications.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-30 00:02+0000\n" +"POT-Creation-Date: 2023-06-19 00:15+0000\n" "PO-Revision-Date: 2023-04-19 11:11+0000\n" "Last-Translator: Besnik Bleta , 2023\n" "Language-Team: Albanian (https://app.transifex.com/owncloud-org/teams/6149/sq/)\n" diff --git a/services/notifications/pkg/email/l10n/locale/zh-Hans/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/zh-Hans/LC_MESSAGES/notifications.po new file mode 100644 index 000000000..617e95ff8 --- /dev/null +++ b/services/notifications/pkg/email/l10n/locale/zh-Hans/LC_MESSAGES/notifications.po @@ -0,0 +1,119 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +# Translators: +# Shouyuan Shi, 2023 +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: EMAIL\n" +"POT-Creation-Date: 2023-06-28 04:01+0000\n" +"PO-Revision-Date: 2023-04-19 11:11+0000\n" +"Last-Translator: Shouyuan Shi, 2023\n" +"Language-Team: Chinese Simplified (https://app.transifex.com/owncloud-org/teams/6149/zh-Hans/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh-Hans\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#. UnsharedSpace email template, resolves via {{ .CallToAction }} +#: pkg/email/templates.go:61 +msgid "Click here to check it: {ShareLink}" +msgstr "单击此处检查:{ShareLink}" + +#. ShareCreated email template, resolves via {{ .CallToAction }} +#. SharedSpace email template, resolves via {{ .CallToAction }} +#: pkg/email/templates.go:19 pkg/email/templates.go:46 +msgid "Click here to view it: {ShareLink}" +msgstr "单击此处查看:{ShareLink}" + +#. ShareCreated email template, resolves via {{ .Greeting }} +#: pkg/email/templates.go:15 +msgid "Hello {ShareGrantee}" +msgstr "你好,{ShareGrantee}" + +#. ShareExpired email template, resolves via {{ .Greeting }} +#: pkg/email/templates.go:28 +msgid "Hello {ShareGrantee}," +msgstr "你好,{ShareGrantee}," + +#. SharedSpace email template, resolves via {{ .Greeting }} +#. UnsharedSpace email template, resolves via {{ .Greeting }} +#. MembershipExpired email template, resolves via {{ .Greeting }} +#: pkg/email/templates.go:42 pkg/email/templates.go:55 +#: pkg/email/templates.go:70 +msgid "Hello {SpaceGrantee}," +msgstr "你好,{SpaceGrantee}," + +#. MembershipExpired email template, Subject field (resolves directly) +#: pkg/email/templates.go:68 +msgid "Membership of '{SpaceName}' expired at {ExpiredAt}" +msgstr "在“{SpaceName}”中的成员身份已经在{ExpiredAt}过期" + +#. ShareExpired email template, Subject field (resolves directly) +#: pkg/email/templates.go:26 +msgid "Share to '{ShareFolder}' expired at {ExpiredAt}" +msgstr "对于“{ShareFolder}”的共享已经在{ExpiredAt}过期" + +#. MembershipExpired email template, resolves via {{ .MessageBody }} +#: pkg/email/templates.go:72 +msgid "" +"Your membership of space {SpaceName} has expired at {ExpiredAt}\n" +"\n" +"Even though this membership has expired you still might have access through other shares and/or space memberships" +msgstr "" +"你在空间 {SpaceName} 中的成员身份已经在 {ExpiredAt}过期\n" +"\n" +"即使该成员身份过期了你也仍有可能通过其他共享或空间成员身份对其进行访问" + +#. ShareExpired email template, resolves via {{ .MessageBody }} +#: pkg/email/templates.go:30 +msgid "" +"Your share to {ShareFolder} has expired at {ExpiredAt}\n" +"\n" +"Even though this share has been revoked you still might have access through other shares and/or space memberships." +msgstr "" +"你对{ShareFolder}的共享已经在{ExpiredAt}过期。\n" +"\n" +"即使这个共享已经被撤销,你仍然可以通过其他共享或空间成员身份进行访问。" + +#. ShareCreated email template, resolves via {{ .MessageBody }} +#: pkg/email/templates.go:17 +msgid "{ShareSharer} has shared \"{ShareFolder}\" with you." +msgstr "{ShareSharer} 向你共享了 “{ShareFolder}” 。" + +#. ShareCreated email template, Subject field (resolves directly) +#: pkg/email/templates.go:13 +msgid "{ShareSharer} shared '{ShareFolder}' with you" +msgstr "{ShareSharer} 向你共享了“{ShareFolder}”" + +#. SharedSpace email template, resolves via {{ .MessageBody }} +#: pkg/email/templates.go:44 +msgid "{SpaceSharer} has invited you to join \"{SpaceName}\"." +msgstr "{SpaceSharer}邀请你加入“{SpaceName}”。" + +#. UnsharedSpace email template, resolves via {{ .MessageBody }} +#: pkg/email/templates.go:57 +msgid "" +"{SpaceSharer} has removed you from \"{SpaceName}\".\n" +"\n" +"You might still have access through your other groups or direct membership." +msgstr "" +"{SpaceSharer}已经把你从“{SpaceName}”中移除。\n" +"\n" +"你仍有可能通过你其他的分组或者直接的成员身份访问它。" + +#. SharedSpace email template, Subject field (resolves directly) +#: pkg/email/templates.go:40 +msgid "{SpaceSharer} invited you to join {SpaceName}" +msgstr "{SpaceSharer}邀请你加入{SpaceName}" + +#. UnsharedSpace email template, Subject field (resolves directly) +#: pkg/email/templates.go:53 +msgid "{SpaceSharer} removed you from {SpaceName}" +msgstr "{SpaceSharer}已经把你从{SpaceName}中移除。" diff --git a/services/notifications/pkg/email/l10n/locale/zh_CN/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/zh_CN/LC_MESSAGES/notifications.po new file mode 100644 index 000000000..32400a6d6 --- /dev/null +++ b/services/notifications/pkg/email/l10n/locale/zh_CN/LC_MESSAGES/notifications.po @@ -0,0 +1,119 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +# Translators: +# Shouyuan Shi, 2023 +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: EMAIL\n" +"POT-Creation-Date: 2023-06-28 04:01+0000\n" +"PO-Revision-Date: 2023-04-19 11:11+0000\n" +"Last-Translator: Shouyuan Shi, 2023\n" +"Language-Team: Chinese (China) (https://app.transifex.com/owncloud-org/teams/6149/zh_CN/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_CN\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#. UnsharedSpace email template, resolves via {{ .CallToAction }} +#: pkg/email/templates.go:61 +msgid "Click here to check it: {ShareLink}" +msgstr "单击此处检查:{ShareLink}" + +#. ShareCreated email template, resolves via {{ .CallToAction }} +#. SharedSpace email template, resolves via {{ .CallToAction }} +#: pkg/email/templates.go:19 pkg/email/templates.go:46 +msgid "Click here to view it: {ShareLink}" +msgstr "单击此处查看:{ShareLink}" + +#. ShareCreated email template, resolves via {{ .Greeting }} +#: pkg/email/templates.go:15 +msgid "Hello {ShareGrantee}" +msgstr "你好,{ShareGrantee}" + +#. ShareExpired email template, resolves via {{ .Greeting }} +#: pkg/email/templates.go:28 +msgid "Hello {ShareGrantee}," +msgstr "你好,{ShareGrantee}," + +#. SharedSpace email template, resolves via {{ .Greeting }} +#. UnsharedSpace email template, resolves via {{ .Greeting }} +#. MembershipExpired email template, resolves via {{ .Greeting }} +#: pkg/email/templates.go:42 pkg/email/templates.go:55 +#: pkg/email/templates.go:70 +msgid "Hello {SpaceGrantee}," +msgstr "你好,{SpaceGrantee}," + +#. MembershipExpired email template, Subject field (resolves directly) +#: pkg/email/templates.go:68 +msgid "Membership of '{SpaceName}' expired at {ExpiredAt}" +msgstr "在“{SpaceName}”中的成员身份已经在{ExpiredAt}过期" + +#. ShareExpired email template, Subject field (resolves directly) +#: pkg/email/templates.go:26 +msgid "Share to '{ShareFolder}' expired at {ExpiredAt}" +msgstr "对于“{ShareFolder}”的共享已经在{ExpiredAt}过期" + +#. MembershipExpired email template, resolves via {{ .MessageBody }} +#: pkg/email/templates.go:72 +msgid "" +"Your membership of space {SpaceName} has expired at {ExpiredAt}\n" +"\n" +"Even though this membership has expired you still might have access through other shares and/or space memberships" +msgstr "" +"你在空间 {SpaceName} 中的成员身份已经在 {ExpiredAt}过期\n" +"\n" +"即使该成员身份过期了你也仍有可能通过其他共享或空间成员身份对其进行访问" + +#. ShareExpired email template, resolves via {{ .MessageBody }} +#: pkg/email/templates.go:30 +msgid "" +"Your share to {ShareFolder} has expired at {ExpiredAt}\n" +"\n" +"Even though this share has been revoked you still might have access through other shares and/or space memberships." +msgstr "" +"你对{ShareFolder}的共享已经在{ExpiredAt}过期。\n" +"\n" +"即使这个共享已经被撤销,你仍然可以通过其他共享或空间成员身份进行访问。" + +#. ShareCreated email template, resolves via {{ .MessageBody }} +#: pkg/email/templates.go:17 +msgid "{ShareSharer} has shared \"{ShareFolder}\" with you." +msgstr "{ShareSharer} 向你共享了 “{ShareFolder}” 。" + +#. ShareCreated email template, Subject field (resolves directly) +#: pkg/email/templates.go:13 +msgid "{ShareSharer} shared '{ShareFolder}' with you" +msgstr "{ShareSharer} 向你共享了“{ShareFolder}”" + +#. SharedSpace email template, resolves via {{ .MessageBody }} +#: pkg/email/templates.go:44 +msgid "{SpaceSharer} has invited you to join \"{SpaceName}\"." +msgstr "{SpaceSharer}邀请你加入“{SpaceName}”。" + +#. UnsharedSpace email template, resolves via {{ .MessageBody }} +#: pkg/email/templates.go:57 +msgid "" +"{SpaceSharer} has removed you from \"{SpaceName}\".\n" +"\n" +"You might still have access through your other groups or direct membership." +msgstr "" +"{SpaceSharer}已经把你从“{SpaceName}”中移除。\n" +"\n" +"你仍有可能通过你其他的分组或者直接的成员身份访问它。" + +#. SharedSpace email template, Subject field (resolves directly) +#: pkg/email/templates.go:40 +msgid "{SpaceSharer} invited you to join {SpaceName}" +msgstr "{SpaceSharer}邀请你加入{SpaceName}" + +#. UnsharedSpace email template, Subject field (resolves directly) +#: pkg/email/templates.go:53 +msgid "{SpaceSharer} removed you from {SpaceName}" +msgstr "{SpaceSharer}已经把你从{SpaceName}中移除。" diff --git a/services/notifications/pkg/email/templates/html/email.html.tmpl b/services/notifications/pkg/email/templates/html/email.html.tmpl index f7345fe96..f36f07666 100644 --- a/services/notifications/pkg/email/templates/html/email.html.tmpl +++ b/services/notifications/pkg/email/templates/html/email.html.tmpl @@ -11,9 +11,8 @@ {{ .Greeting }}

{{ .MessageBody }} - {{if ne .CallToAction "" }} -

{{ .CallToAction }} - {{end}} + {{if ne .CallToAction "" }}

+ {{ .CallToAction }}{{end}} diff --git a/services/notifications/pkg/service/notification_suite_test.go b/services/notifications/pkg/service/notification_suite_test.go index 5735c73ff..dc3c1f425 100644 --- a/services/notifications/pkg/service/notification_suite_test.go +++ b/services/notifications/pkg/service/notification_suite_test.go @@ -3,10 +3,24 @@ package service_test import ( "testing" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + mRegistry "go-micro.dev/v4/registry" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) +func init() { + registry.Configure("memory") + r := registry.GetRegistry() + service := registry.BuildGRPCService("com.owncloud.api.gateway", "", "", "") + service.Nodes = []*mRegistry.Node{{ + Address: "any", + }} + + _ = r.Register(service) +} + func TestNotifications(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Notification Suite") diff --git a/services/notifications/pkg/service/service.go b/services/notifications/pkg/service/service.go index e5f67a392..c55eef6af 100644 --- a/services/notifications/pkg/service/service.go +++ b/services/notifications/pkg/service/service.go @@ -16,6 +16,7 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/middleware" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" @@ -38,7 +39,7 @@ func NewEventsNotifier( events <-chan events.Event, channel channels.Channel, logger log.Logger, - gwClient gateway.GatewayAPIClient, + gatewaySelector pool.Selectable[gateway.GatewayAPIClient], valueService settingssvc.ValueService, machineAuthAPIKey, emailTemplatePath, ocisURL string) Service { @@ -47,7 +48,7 @@ func NewEventsNotifier( channel: channel, events: events, signals: make(chan os.Signal, 1), - gwClient: gwClient, + gatewaySelector: gatewaySelector, valueService: valueService, machineAuthAPIKey: machineAuthAPIKey, emailTemplatePath: emailTemplatePath, @@ -60,7 +61,7 @@ type eventsNotifier struct { channel channels.Channel events <-chan events.Event signals chan os.Signal - gwClient gateway.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] valueService settingssvc.ValueService machineAuthAPIKey string emailTemplatePath string @@ -98,7 +99,7 @@ func (s eventsNotifier) Run() error { } func (s eventsNotifier) render(ctx context.Context, template email.MessageTemplate, - granteeFieldName string, fields map[string]interface{}, granteeList []*user.User, sender string) ([]*channels.Message, error) { + granteeFieldName string, fields map[string]string, granteeList []*user.User, sender string) ([]*channels.Message, error) { // Render the Email Template for each user messageList := make([]*channels.Message, len(granteeList)) for i, usr := range granteeList { @@ -137,7 +138,12 @@ func (s eventsNotifier) getGranteeList(ctx context.Context, executant, u *user.U } return []*user.User{usr}, nil case g != nil: - res, err := s.gwClient.GetGroup(ctx, &group.GetGroupRequest{GroupId: g}) + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + + res, err := gatewayClient.GetGroup(ctx, &group.GetGroupRequest{GroupId: g}) if err != nil { return nil, err } @@ -171,7 +177,11 @@ func (s eventsNotifier) getUser(ctx context.Context, u *user.UserId) (*user.User if u == nil { return nil, errors.New("need at least one non-nil grantee") } - r, err := s.gwClient.GetUser(ctx, &user.GetUserRequest{UserId: u}) + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + r, err := gatewayClient.GetUser(ctx, &user.GetUserRequest{UserId: u}) if err != nil { return nil, err } @@ -213,7 +223,12 @@ func (s eventsNotifier) disableEmails(ctx context.Context, u *user.UserId) bool func (s eventsNotifier) getResourceInfo(ctx context.Context, resourceID *provider.ResourceId, fieldmask *fieldmaskpb.FieldMask) (*provider.ResourceInfo, error) { // TODO: maybe cache this stat to reduce storage iops - md, err := s.gwClient.Stat(ctx, &provider.StatRequest{ + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + + md, err := gatewayClient.Stat(ctx, &provider.StatRequest{ Ref: &provider.Reference{ ResourceId: resourceID, }, diff --git a/services/notifications/pkg/service/service_test.go b/services/notifications/pkg/service/service_test.go index fdc64410c..28b37068c 100644 --- a/services/notifications/pkg/service/service_test.go +++ b/services/notifications/pkg/service/service_test.go @@ -9,6 +9,7 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/utils" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" . "github.com/onsi/ginkgo/v2" @@ -20,15 +21,17 @@ import ( "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" "github.com/owncloud/ocis/v2/services/notifications/pkg/channels" "github.com/owncloud/ocis/v2/services/notifications/pkg/service" - "github.com/test-go/testify/mock" + "github.com/stretchr/testify/mock" "go-micro.dev/v4/client" + "google.golang.org/grpc" ) var _ = Describe("Notifications", func() { var ( - gwc *cs3mocks.GatewayAPIClient - vs *settingssvc.MockValueService - sharer = &user.User{ + gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + vs *settingssvc.MockValueService + sharer = &user.User{ Id: &user.UserId{ OpaqueId: "sharer", }, @@ -50,11 +53,20 @@ var _ = Describe("Notifications", func() { ) BeforeEach(func() { - gwc = &cs3mocks.GatewayAPIClient{} - gwc.On("GetUser", mock.Anything, mock.Anything).Return(&user.GetUserResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}, User: sharer}, nil).Once() - gwc.On("GetUser", mock.Anything, mock.Anything).Return(&user.GetUserResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}, User: sharee}, nil).Once() - gwc.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}, User: sharer}, nil) - gwc.On("Stat", mock.Anything, mock.Anything).Return(&provider.StatResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}, Info: &provider.ResourceInfo{Name: "secrets of the board", Space: &provider.StorageSpace{Name: "secret space"}}}, nil) + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") + gatewayClient = &cs3mocks.GatewayAPIClient{} + gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return gatewayClient + }, + ) + + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(&user.GetUserResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}, User: sharer}, nil).Once() + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(&user.GetUserResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}, User: sharee}, nil).Once() + gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}, User: sharer}, nil) + gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(&provider.StatResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}, Info: &provider.ResourceInfo{Name: "secrets of the board", Space: &provider.StorageSpace{Name: "secret space"}}}, nil) vs = &settingssvc.MockValueService{} vs.GetValueByUniqueIdentifiersFunc = func(ctx context.Context, req *settingssvc.GetValueByUniqueIdentifiersRequest, opts ...client.CallOption) (*settingssvc.GetValueResponse, error) { return nil, nil @@ -67,7 +79,7 @@ var _ = Describe("Notifications", func() { cfg.GRPCClientTLS = &shared.GRPCClientTLS{} _ = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...) ch := make(chan events.Event) - evts := service.NewEventsNotifier(ch, tc, log.NewLogger(), gwc, vs, "", "", "") + evts := service.NewEventsNotifier(ch, tc, log.NewLogger(), gatewaySelector, vs, "", "", "") go evts.Run() ch <- ev @@ -82,7 +94,7 @@ var _ = Describe("Notifications", func() { Entry("Share Created", testChannel{ expectedReceipients: []string{sharee.GetMail()}, expectedSubject: "Dr. S. Harer shared 'secrets of the board' with you", - expectedMessage: `Hello Eric Expireling + expectedTextBody: `Hello Eric Expireling Dr. S. Harer has shared "secrets of the board" with you. @@ -107,7 +119,7 @@ https://owncloud.com Entry("Share Expired", testChannel{ expectedReceipients: []string{sharee.GetMail()}, expectedSubject: "Share to 'secrets of the board' expired at 2023-04-17 16:42:00", - expectedMessage: `Hello Eric Expireling, + expectedTextBody: `Hello Eric Expireling, Your share to secrets of the board has expired at 2023-04-17 16:42:00 @@ -132,7 +144,7 @@ https://owncloud.com Entry("Added to Space", testChannel{ expectedReceipients: []string{sharee.GetMail()}, expectedSubject: "Dr. S. Harer invited you to join secret space", - expectedMessage: `Hello Eric Expireling, + expectedTextBody: `Hello Eric Expireling, Dr. S. Harer has invited you to join "secret space". @@ -157,7 +169,7 @@ https://owncloud.com Entry("Removed from Space", testChannel{ expectedReceipients: []string{sharee.GetMail()}, expectedSubject: "Dr. S. Harer removed you from secret space", - expectedMessage: `Hello Eric Expireling, + expectedTextBody: `Hello Eric Expireling, Dr. S. Harer has removed you from "secret space". @@ -183,7 +195,7 @@ https://owncloud.com Entry("Space Expired", testChannel{ expectedReceipients: []string{sharee.GetMail()}, expectedSubject: "Membership of 'secret space' expired at 2023-04-17 16:42:00", - expectedMessage: `Hello Eric Expireling, + expectedTextBody: `Hello Eric Expireling, Your membership of space secret space has expired at 2023-04-17 16:42:00 @@ -208,11 +220,219 @@ https://owncloud.com ) }) +var _ = Describe("Notifications X-Site Scripting", func() { + var ( + gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + vs *settingssvc.MockValueService + sharer = &user.User{ + Id: &user.UserId{ + OpaqueId: "sharer", + }, + Mail: "sharer@owncloud.com", + DisplayName: "Dr. O'reilly", + } + sharee = &user.User{ + Id: &user.UserId{ + OpaqueId: "sharee", + }, + Mail: "sharee@owncloud.com", + DisplayName: "", + } + resourceid = &provider.ResourceId{ + StorageId: "storageid", + SpaceId: "spaceid", + OpaqueId: "itemid", + } + ) + + BeforeEach(func() { + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") + gatewayClient = &cs3mocks.GatewayAPIClient{} + gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return gatewayClient + }, + ) + + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(&user.GetUserResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}, User: sharer}, nil).Once() + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(&user.GetUserResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}, User: sharee}, nil).Once() + gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}, User: sharer}, nil) + gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(&provider.StatResponse{ + Status: &rpc.Status{Code: rpc.Code_CODE_OK}, + Info: &provider.ResourceInfo{ + Name: "", + Space: &provider.StorageSpace{Name: ""}}, + }, nil) + vs = &settingssvc.MockValueService{} + vs.GetValueByUniqueIdentifiersFunc = func(ctx context.Context, req *settingssvc.GetValueByUniqueIdentifiersRequest, opts ...client.CallOption) (*settingssvc.GetValueResponse, error) { + return nil, nil + } + }) + + DescribeTable("Sending notifications", + func(tc testChannel, ev events.Event) { + cfg := defaults.FullDefaultConfig() + cfg.GRPCClientTLS = &shared.GRPCClientTLS{} + _ = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...) + ch := make(chan events.Event) + evts := service.NewEventsNotifier(ch, tc, log.NewLogger(), gatewaySelector, vs, "", "", "") + go evts.Run() + + ch <- ev + select { + case <-tc.done: + // finished + case <-time.Tick(3 * time.Second): + Fail("timeout waiting for notification") + } + }, + + Entry("Share Created", testChannel{ + expectedReceipients: []string{sharee.GetMail()}, + expectedSubject: "Dr. O'reilly shared '' with you", + expectedTextBody: `Hello + +Dr. O'reilly has shared "" with you. + +Click here to view it: files/shares/with-me + + +--- +ownCloud - Store. Share. Work. +https://owncloud.com +`, + expectedHTMLBody: ` + + + + + + +
+ + + + + + + + + + + + + + + +
  + Hello <script>alert('Eric Expireling');</script> +

+ Dr. O'reilly has shared "<script>alert('secrets of the board');</script>" with you. +

+ Click here to view it: files/shares/with-me +
 
  + +
 
+
+ + +`, + expectedSender: sharer.GetDisplayName(), + + done: make(chan struct{}), + }, events.Event{ + Event: events.ShareCreated{ + Sharer: sharer.GetId(), + GranteeUserID: sharee.GetId(), + CTime: utils.TimeToTS(time.Date(2023, 4, 17, 16, 42, 0, 0, time.UTC)), + ItemID: resourceid, + }, + }), + + Entry("Added to Space", testChannel{ + expectedReceipients: []string{sharee.GetMail()}, + expectedSubject: "Dr. O'reilly invited you to join ", + expectedTextBody: `Hello , + +Dr. O'reilly has invited you to join "". + +Click here to view it: f/spaceid + + +--- +ownCloud - Store. Share. Work. +https://owncloud.com +`, + expectedSender: sharer.GetDisplayName(), + expectedHTMLBody: ` + + + + + + +
+ + + + + + + + + + + + + + + +
  + Hello <script>alert('Eric Expireling');</script>, +

+ Dr. O'reilly has invited you to join "<script>alert('secret space');</script>". +

+ Click here to view it: f/spaceid +
 
  + +
 
+
+ + +`, + done: make(chan struct{}), + }, events.Event{ + Event: events.SpaceShared{ + Executant: sharer.GetId(), + Creator: sharer.GetId(), + GranteeUserID: sharee.GetId(), + ID: &provider.StorageSpaceId{OpaqueId: "spaceid"}, + }, + }), + ) +}) + // NOTE: This is explictitly not testing the message itself. Should we? type testChannel struct { expectedReceipients []string expectedSubject string - expectedMessage string + expectedTextBody string + expectedHTMLBody string expectedSender string done chan struct{} } @@ -220,10 +440,13 @@ type testChannel struct { func (tc testChannel) SendMessage(ctx context.Context, m *channels.Message) error { defer GinkgoRecover() - Expect(m.Recipient).To(Equal(tc.expectedReceipients)) - Expect(m.Subject).To(Equal(tc.expectedSubject)) - Expect(m.TextBody).To(Equal(tc.expectedMessage)) - Expect(m.Sender).To(Equal(tc.expectedSender)) + Expect(tc.expectedReceipients).To(Equal(m.Recipient)) + Expect(tc.expectedSubject).To(Equal(m.Subject)) + Expect(tc.expectedTextBody).To(Equal(m.TextBody)) + Expect(tc.expectedSender).To(Equal(m.Sender)) + if tc.expectedHTMLBody != "" { + Expect(tc.expectedHTMLBody).To(Equal(m.HTMLBody)) + } tc.done <- struct{}{} return nil } diff --git a/services/notifications/pkg/service/shares.go b/services/notifications/pkg/service/shares.go index 1182ea38e..9328c7f88 100644 --- a/services/notifications/pkg/service/shares.go +++ b/services/notifications/pkg/service/shares.go @@ -13,7 +13,13 @@ func (s eventsNotifier) handleShareCreated(e events.ShareCreated) { Str("itemid", e.ItemID.OpaqueId). Logger() - ownerCtx, owner, err := utils.Impersonate(e.Sharer, s.gwClient, s.machineAuthAPIKey) + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + logger.Error().Err(err).Msg("could not select next gateway client") + return + } + + ownerCtx, owner, err := utils.Impersonate(e.Sharer, gatewayClient, s.machineAuthAPIKey) if err != nil { logger.Error().Err(err).Msg("Could not impersonate sharer") return @@ -44,7 +50,7 @@ func (s eventsNotifier) handleShareCreated(e events.ShareCreated) { sharerDisplayName := owner.GetDisplayName() recipientList, err := s.render(ownerCtx, email.ShareCreated, "ShareGrantee", - map[string]interface{}{ + map[string]string{ "ShareSharer": sharerDisplayName, "ShareFolder": resourceInfo.Name, "ShareLink": shareLink, @@ -62,7 +68,13 @@ func (s eventsNotifier) handleShareExpired(e events.ShareExpired) { Str("itemid", e.ItemID.GetOpaqueId()). Logger() - ownerCtx, owner, err := utils.Impersonate(e.ShareOwner, s.gwClient, s.machineAuthAPIKey) + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + logger.Error().Err(err).Msg("could not select next gateway client") + return + } + + ownerCtx, owner, err := utils.Impersonate(e.ShareOwner, gatewayClient, s.machineAuthAPIKey) if err != nil { logger.Error().Err(err).Msg("Could not impersonate sharer") return @@ -84,7 +96,7 @@ func (s eventsNotifier) handleShareExpired(e events.ShareExpired) { recipientList, err := s.render(ownerCtx, email.ShareExpired, "ShareGrantee", - map[string]interface{}{ + map[string]string{ "ShareFolder": resourceInfo.GetName(), "ExpiredAt": e.ExpiredAt.Format("2006-01-02 15:04:05"), }, granteeList, owner.GetDisplayName()) diff --git a/services/notifications/pkg/service/spaces.go b/services/notifications/pkg/service/spaces.go index 37af81713..cd8f07278 100644 --- a/services/notifications/pkg/service/spaces.go +++ b/services/notifications/pkg/service/spaces.go @@ -13,7 +13,13 @@ func (s eventsNotifier) handleSpaceShared(e events.SpaceShared) { Str("itemid", e.ID.OpaqueId). Logger() - executantCtx, executant, err := utils.Impersonate(e.Executant, s.gwClient, s.machineAuthAPIKey) + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + logger.Error().Err(err).Msg("could not select next gateway client") + return + } + + executantCtx, executant, err := utils.Impersonate(e.Executant, gatewayClient, s.machineAuthAPIKey) if err != nil { logger.Error(). Err(err). @@ -57,7 +63,7 @@ func (s eventsNotifier) handleSpaceShared(e events.SpaceShared) { sharerDisplayName := executant.GetDisplayName() recipientList, err := s.render(executantCtx, email.SharedSpace, "SpaceGrantee", - map[string]interface{}{ + map[string]string{ "SpaceSharer": sharerDisplayName, "SpaceName": resourceInfo.GetSpace().GetName(), "ShareLink": shareLink, @@ -75,7 +81,13 @@ func (s eventsNotifier) handleSpaceUnshared(e events.SpaceUnshared) { Str("itemid", e.ID.OpaqueId). Logger() - executantCtx, executant, err := utils.Impersonate(e.Executant, s.gwClient, s.machineAuthAPIKey) + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + logger.Error().Err(err).Msg("could not select next gateway client") + return + } + + executantCtx, executant, err := utils.Impersonate(e.Executant, gatewayClient, s.machineAuthAPIKey) if err != nil { logger.Error().Err(err).Msg("could not handle space unshared event") return @@ -117,7 +129,7 @@ func (s eventsNotifier) handleSpaceUnshared(e events.SpaceUnshared) { sharerDisplayName := executant.GetDisplayName() recipientList, err := s.render(executantCtx, email.UnsharedSpace, "SpaceGrantee", - map[string]interface{}{ + map[string]string{ "SpaceSharer": sharerDisplayName, "SpaceName": resourceInfo.GetSpace().Name, "ShareLink": shareLink, @@ -135,7 +147,13 @@ func (s eventsNotifier) handleSpaceMembershipExpired(e events.SpaceMembershipExp Str("itemid", e.SpaceID.GetOpaqueId()). Logger() - ownerCtx, owner, err := utils.Impersonate(e.SpaceOwner, s.gwClient, s.machineAuthAPIKey) + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + logger.Error().Err(err).Msg("could not select next gateway client") + return + } + + ownerCtx, owner, err := utils.Impersonate(e.SpaceOwner, gatewayClient, s.machineAuthAPIKey) if err != nil { logger.Error().Err(err).Msg("Could not impersonate sharer") return @@ -149,7 +167,7 @@ func (s eventsNotifier) handleSpaceMembershipExpired(e events.SpaceMembershipExp recipientList, err := s.render(ownerCtx, email.MembershipExpired, "SpaceGrantee", - map[string]interface{}{ + map[string]string{ "SpaceName": e.SpaceName, "ExpiredAt": e.ExpiredAt.Format("2006-01-02 15:04:05"), }, granteeList, owner.GetDisplayName()) diff --git a/services/ocdav/README.md b/services/ocdav/README.md new file mode 100644 index 000000000..e718b35db --- /dev/null +++ b/services/ocdav/README.md @@ -0,0 +1,3 @@ +# ocDAV + +The ocdav service provides the WebDAV API which is required by ownCloud clients. Previews (thumbnails) are provided by the [WebDAV service](../webdav). diff --git a/services/ocdav/pkg/command/root.go b/services/ocdav/pkg/command/root.go index 95de57e24..5b1afc5b3 100644 --- a/services/ocdav/pkg/command/root.go +++ b/services/ocdav/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/ocdav/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the ocdav command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new ocdav.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.OCDav.Commons = cfg.Commons - return SutureService{ - cfg: cfg.OCDav, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/ocdav/pkg/config/config.go b/services/ocdav/pkg/config/config.go index 248606a18..03f92d2a2 100644 --- a/services/ocdav/pkg/config/config.go +++ b/services/ocdav/pkg/config/config.go @@ -73,7 +73,7 @@ type HTTPConfig struct { type CORS struct { AllowedOrigins []string `yaml:"allow_origins" env:"OCIS_CORS_ALLOW_ORIGINS;OCDAV_CORS_ALLOW_ORIGINS" desc:"A comma-separated list of allowed CORS origins. See following chapter for more details: *Access-Control-Allow-Origin* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin"` AllowedMethods []string `yaml:"allow_methods" env:"OCIS_CORS_ALLOW_METHODS;OCDAV_CORS_ALLOW_METHODS" desc:"A comma-separated list of allowed CORS methods. See following chapter for more details: *Access-Control-Request-Method* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method"` - AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;OCDAV_CORS_ALLOW_HEADERS" desc:"A comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` + AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;OCDAV_CORS_ALLOW_HEADERS" desc:"A blank or comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` AllowCredentials bool `yaml:"allow_credentials" env:"OCIS_CORS_ALLOW_CREDENTIALS;OCDAV_CORS_ALLOW_CREDENTIALS" desc:"Allow credentials for CORS.See following chapter for more details: *Access-Control-Allow-Credentials* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials."` } diff --git a/services/ocs/pkg/command/root.go b/services/ocs/pkg/command/root.go index 6d87fb6f2..569ff06ab 100644 --- a/services/ocs/pkg/command/root.go +++ b/services/ocs/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/ocs/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the ocs command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new ocs.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.OCS.Commons = cfg.Commons - return SutureService{ - cfg: cfg.OCS, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/ocs/pkg/config/http.go b/services/ocs/pkg/config/http.go index 4062e2195..2fc997ae6 100644 --- a/services/ocs/pkg/config/http.go +++ b/services/ocs/pkg/config/http.go @@ -15,6 +15,6 @@ type HTTP struct { type CORS struct { AllowedOrigins []string `yaml:"allow_origins" env:"OCIS_CORS_ALLOW_ORIGINS;OCS_CORS_ALLOW_ORIGINS" desc:"A comma-separated list of allowed CORS origins. See following chapter for more details: *Access-Control-Allow-Origin* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin"` AllowedMethods []string `yaml:"allow_methods" env:"OCIS_CORS_ALLOW_METHODS;OCS_CORS_ALLOW_METHODS" desc:"A comma-separated list of allowed CORS methods. See following chapter for more details: *Access-Control-Request-Method* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method"` - AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;OCS_CORS_ALLOW_HEADERS" desc:"A comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` + AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;OCS_CORS_ALLOW_HEADERS" desc:"A blank or comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` AllowCredentials bool `yaml:"allow_credentials" env:"OCIS_CORS_ALLOW_CREDENTIALS;OCS_CORS_ALLOW_CREDENTIALS" desc:"Allow credentials for CORS.See following chapter for more details: *Access-Control-Allow-Credentials* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials."` } diff --git a/services/ocs/pkg/middleware/logtrace.go b/services/ocs/pkg/middleware/logtrace.go index a9444902d..202de69e0 100644 --- a/services/ocs/pkg/middleware/logtrace.go +++ b/services/ocs/pkg/middleware/logtrace.go @@ -5,6 +5,7 @@ import ( ocstracing "github.com/owncloud/ocis/v2/services/ocs/pkg/tracing" "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" ) var propagator = propagation.NewCompositeTextMapPropagator( @@ -15,7 +16,10 @@ var propagator = propagation.NewCompositeTextMapPropagator( // LogTrace Sets the initial trace in the ocs service. func LogTrace(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx, span := ocstracing.TraceProvider.Tracer("ocs").Start(r.Context(), r.URL.Path) + spanOpts := []trace.SpanStartOption{ + trace.WithSpanKind(trace.SpanKindServer), + } + ctx, span := ocstracing.TraceProvider.Tracer("ocs").Start(r.Context(), r.URL.Path, spanOpts...) defer span.End() propagator.Inject(ctx, propagation.HeaderCarrier(r.Header)) diff --git a/services/policies/pkg/command/root.go b/services/policies/pkg/command/root.go index b57f8aa62..368ca7657 100644 --- a/services/policies/pkg/command/root.go +++ b/services/policies/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/policies/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -30,25 +27,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the web command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new web.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Policies.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Policies, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/postprocessing/README.md b/services/postprocessing/README.md index dd13d92d5..c8cd1182f 100644 --- a/services/postprocessing/README.md +++ b/services/postprocessing/README.md @@ -60,3 +60,19 @@ When setting a custom postprocessing step (eg. `"customstep"`) the postprocessin Once the custom service has finished its work, it should sent an event of type `PostprocessingFinished` via the configured events system. This event needs to contain a `FinishedStep` field set to `"customstep"`. It also must contain the outcome of the step, which can be one of "delete" (abort postprocessing, delete the file), "abort" (abort postprocessing, keep the file) and "continue" (continue postprocessing, this is the success case). See the [cs3 org](https://github.com/cs3org/reva/blob/edge/pkg/events/postprocessing.go) for up-to-date information of reserved step names and event definitions. + +## CLI Commands + +### Resume Postprocessing + +If postprocessing fails in one step due to an unforseen error, current uploads will not be retried automatically. A system admin can instead run a CLI command to retry the failed upload which is a two step process: + +- First find the upload ID of the failed upload. +```bash +ocis storage-users uploads list +``` + +- Then use the restart command to resume postprocessing of the ID selected. +```bash +ocis postprocessing restart -u +``` diff --git a/services/postprocessing/pkg/command/postprocessing.go b/services/postprocessing/pkg/command/postprocessing.go new file mode 100644 index 000000000..999727f8c --- /dev/null +++ b/services/postprocessing/pkg/command/postprocessing.go @@ -0,0 +1,56 @@ +package command + +import ( + "time" + + "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/utils" + "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" + "github.com/owncloud/ocis/v2/services/postprocessing/pkg/config" + "github.com/owncloud/ocis/v2/services/postprocessing/pkg/config/parser" + "github.com/urfave/cli/v2" +) + +// RestartPostprocessing cli command to restart postprocessing +func RestartPostprocessing(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "restart", + Usage: "restart postprocessing for an uploadID", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "upload-id", + Aliases: []string{"u"}, + Required: true, + Usage: "the uploadid to restart", + }, + }, + Before: func(c *cli.Context) error { + return configlog.ReturnFatal(parser.ParseConfig(cfg)) + }, + Action: func(c *cli.Context) error { + stream, err := getEventBus(cfg.Postprocessing.Events) + if err != nil { + return err + } + + ev := events.ResumePostprocessing{ + UploadID: c.String("upload-id"), + Timestamp: utils.TSNow(), + } + + if err := events.Publish(stream, ev); err != nil { + return err + } + + // go-micro nats implementation uses async publishing, + // therefore we need to manually wait. + // + // FIXME: upstream pr + // + // https://github.com/go-micro/plugins/blob/3e77393890683be4bacfb613bc5751867d584692/v4/events/natsjs/nats.go#L115 + time.Sleep(5 * time.Second) + + return nil + }, + } +} diff --git a/services/postprocessing/pkg/command/root.go b/services/postprocessing/pkg/command/root.go index 0b558d9b7..f4a208632 100644 --- a/services/postprocessing/pkg/command/root.go +++ b/services/postprocessing/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/postprocessing/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -18,6 +15,7 @@ func GetCommands(cfg *config.Config) cli.Commands { Server(cfg), // interaction with this service + RestartPostprocessing(cfg), // infos about this service Health(cfg), @@ -35,22 +33,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the postprocessing command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new postprocessing.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Postprocessing.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Postprocessing, - } -} - -// Serve to implement Server interface -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - return Execute(s.cfg) -} diff --git a/services/postprocessing/pkg/command/server.go b/services/postprocessing/pkg/command/server.go index 8824a630f..fb81fc714 100644 --- a/services/postprocessing/pkg/command/server.go +++ b/services/postprocessing/pkg/command/server.go @@ -7,6 +7,7 @@ import ( "fmt" "os" + "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/events/stream" "github.com/cs3org/reva/v2/pkg/store" "github.com/go-micro/plugins/v4/events/natsjs" @@ -42,9 +43,6 @@ func Server(cfg *config.Config) *cli.Command { gr = run.Group{} logger = logging.Configure(cfg.Service.Name, cfg.Log) - evtsCfg = cfg.Postprocessing.Events - tlsConf *tls.Config - ctx, cancel = func() (context.Context, context.CancelFunc) { if cfg.Context == nil { return context.WithCancel(context.Background()) @@ -55,31 +53,7 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() { - if evtsCfg.EnableTLS { - var rootCAPool *x509.CertPool - if evtsCfg.TLSRootCACertificate != "" { - rootCrtFile, err := os.Open(evtsCfg.TLSRootCACertificate) - if err != nil { - return err - } - - rootCAPool, err = ociscrypto.NewCertPoolFromPEM(rootCrtFile) - if err != nil { - return err - } - evtsCfg.TLSInsecure = false - } - - tlsConf = &tls.Config{ - RootCAs: rootCAPool, - } - } - - bus, err := stream.Nats( - natsjs.TLSConfig(tlsConf), - natsjs.Address(evtsCfg.Endpoint), - natsjs.ClusterID(evtsCfg.Cluster), - ) + bus, err := getEventBus(cfg.Postprocessing.Events) if err != nil { return err } @@ -136,3 +110,32 @@ func Server(cfg *config.Config) *cli.Command { }, } } + +func getEventBus(evtsCfg config.Events) (events.Stream, error) { + var tlsConf *tls.Config + if evtsCfg.EnableTLS { + var rootCAPool *x509.CertPool + if evtsCfg.TLSRootCACertificate != "" { + rootCrtFile, err := os.Open(evtsCfg.TLSRootCACertificate) + if err != nil { + return nil, err + } + + rootCAPool, err = ociscrypto.NewCertPoolFromPEM(rootCrtFile) + if err != nil { + return nil, err + } + evtsCfg.TLSInsecure = false + } + + tlsConf = &tls.Config{ + RootCAs: rootCAPool, + } + } + + return stream.Nats( + natsjs.TLSConfig(tlsConf), + natsjs.Address(evtsCfg.Endpoint), + natsjs.ClusterID(evtsCfg.Cluster), + ) +} diff --git a/services/postprocessing/pkg/config/defaults/defaultconfig.go b/services/postprocessing/pkg/config/defaults/defaultconfig.go index 18baf3acc..a1e76b555 100644 --- a/services/postprocessing/pkg/config/defaults/defaultconfig.go +++ b/services/postprocessing/pkg/config/defaults/defaultconfig.go @@ -30,6 +30,11 @@ func DefaultConfig() *config.Config { Cluster: "ocis-cluster", }, }, + Store: config.Store{ + Store: "memory", + Database: "postprocessing", + Table: "postprocessing", + }, } } diff --git a/services/postprocessing/pkg/postprocessing/postprocessing.go b/services/postprocessing/pkg/postprocessing/postprocessing.go index bb4eaf512..29188e7dc 100644 --- a/services/postprocessing/pkg/postprocessing/postprocessing.go +++ b/services/postprocessing/pkg/postprocessing/postprocessing.go @@ -17,9 +17,16 @@ type Postprocessing struct { Filesize uint64 ResourceID *provider.ResourceId Steps []events.Postprocessingstep + Status Status PPDelay time.Duration } +// Status is helper struct to show current postprocessing status +type Status struct { + CurrentStep events.Postprocessingstep + Outcome events.PostprocessingOutcome +} + // New returns a new postprocessing instance func New(uploadID string, uploadURL string, user *user.User, filename string, filesize uint64, resourceID *provider.ResourceId, steps []events.Postprocessingstep, delay time.Duration) *Postprocessing { return &Postprocessing{ @@ -40,7 +47,7 @@ func (pp *Postprocessing) Init(ev events.BytesReceived) interface{} { return pp.finished(events.PPOutcomeContinue) } - return pp.nextStep(pp.Steps[0]) + return pp.step(pp.Steps[0]) } // NextStep returns the next postprocessing step @@ -54,6 +61,14 @@ func (pp *Postprocessing) NextStep(ev events.PostprocessingStepFinished) interfa } } +// CurrentStep returns the current postprocessing step +func (pp *Postprocessing) CurrentStep() interface{} { + if pp.Status.Outcome != "" { + return pp.finished(pp.Status.Outcome) + } + return pp.step(pp.Status.CurrentStep) +} + // Delay will sleep the configured time then continue func (pp *Postprocessing) Delay(ev events.StartPostprocessingStep) interface{} { time.Sleep(pp.PPDelay) @@ -64,13 +79,14 @@ func (pp *Postprocessing) next(current events.Postprocessingstep) interface{} { l := len(pp.Steps) for i, s := range pp.Steps { if s == current && i+1 < l { - return pp.nextStep(pp.Steps[i+1]) + return pp.step(pp.Steps[i+1]) } } return pp.finished(events.PPOutcomeContinue) } -func (pp *Postprocessing) nextStep(next events.Postprocessingstep) events.StartPostprocessingStep { +func (pp *Postprocessing) step(next events.Postprocessingstep) events.StartPostprocessingStep { + pp.Status.CurrentStep = next return events.StartPostprocessingStep{ UploadID: pp.ID, URL: pp.URL, @@ -83,6 +99,7 @@ func (pp *Postprocessing) nextStep(next events.Postprocessingstep) events.StartP } func (pp *Postprocessing) finished(outcome events.PostprocessingOutcome) events.PostprocessingFinished { + pp.Status.Outcome = outcome return events.PostprocessingFinished{ UploadID: pp.ID, ExecutingUser: pp.User, diff --git a/services/postprocessing/pkg/service/service.go b/services/postprocessing/pkg/service/service.go index b6ce3fba5..3e2afc077 100644 --- a/services/postprocessing/pkg/service/service.go +++ b/services/postprocessing/pkg/service/service.go @@ -28,6 +28,7 @@ func NewPostprocessingService(stream events.Stream, logger log.Logger, sto store events.StartPostprocessingStep{}, events.UploadReady{}, events.PostprocessingStepFinished{}, + events.ResumePostprocessing{}, ) if err != nil { return nil, err @@ -46,22 +47,22 @@ func NewPostprocessingService(stream events.Stream, logger log.Logger, sto store // Run to fulfil Runner interface func (pps *PostprocessingService) Run() error { for e := range pps.events { - var next interface{} + var ( + next interface{} + pp *postprocessing.Postprocessing + err error + ) + switch ev := e.Event.(type) { case events.BytesReceived: - pp := postprocessing.New(ev.UploadID, ev.URL, ev.ExecutingUser, ev.Filename, ev.Filesize, ev.ResourceID, pps.steps, pps.c.Delayprocessing) - if err := storePP(pps.store, pp); err != nil { - pps.log.Error().Str("uploadID", ev.UploadID).Err(err).Msg("cannot store upload") - continue - } - + pp = postprocessing.New(ev.UploadID, ev.URL, ev.ExecutingUser, ev.Filename, ev.Filesize, ev.ResourceID, pps.steps, pps.c.Delayprocessing) next = pp.Init(ev) case events.PostprocessingStepFinished: if ev.UploadID == "" { // no current upload - this was an on demand scan continue } - pp, err := getPP(pps.store, ev.UploadID) + pp, err = getPP(pps.store, ev.UploadID) if err != nil { pps.log.Error().Str("uploadID", ev.UploadID).Err(err).Msg("cannot get upload") continue @@ -71,7 +72,7 @@ func (pps *PostprocessingService) Run() error { if ev.StepToStart != events.PPStepDelay { continue } - pp, err := getPP(pps.store, ev.UploadID) + pp, err = getPP(pps.store, ev.UploadID) if err != nil { pps.log.Error().Str("uploadID", ev.UploadID).Err(err).Msg("cannot get upload") continue @@ -83,8 +84,21 @@ func (pps *PostprocessingService) Run() error { pps.log.Error().Str("uploadID", ev.UploadID).Err(err).Msg("cannot delete upload") continue } + case events.ResumePostprocessing: + pp, err = getPP(pps.store, ev.UploadID) + if err != nil { + pps.log.Error().Str("uploadID", ev.UploadID).Err(err).Msg("cannot get upload") + continue + } + next = pp.CurrentStep() } + if pp != nil { + if err := storePP(pps.store, pp); err != nil { + pps.log.Error().Str("uploadID", pp.ID).Err(err).Msg("cannot store upload") + continue // TODO: should we really continue here? + } + } if next != nil { if err := events.Publish(pps.pub, next); err != nil { pps.log.Error().Err(err).Msg("unable to publish event") diff --git a/services/proxy/pkg/command/root.go b/services/proxy/pkg/command/root.go index 4c310fc67..65d32b575 100644 --- a/services/proxy/pkg/command/root.go +++ b/services/proxy/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/proxy/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the proxy command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new proxy.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Proxy.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Proxy, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/proxy/pkg/command/server.go b/services/proxy/pkg/command/server.go index e49317c17..c3d95ed66 100644 --- a/services/proxy/pkg/command/server.go +++ b/services/proxy/pkg/command/server.go @@ -20,7 +20,9 @@ import ( "github.com/owncloud/ocis/v2/ocis-pkg/log" pkgmiddleware "github.com/owncloud/ocis/v2/ocis-pkg/middleware" "github.com/owncloud/ocis/v2/ocis-pkg/oidc" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" "github.com/owncloud/ocis/v2/ocis-pkg/version" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" storesvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/store/v0" @@ -34,11 +36,12 @@ import ( "github.com/owncloud/ocis/v2/services/proxy/pkg/router" "github.com/owncloud/ocis/v2/services/proxy/pkg/server/debug" proxyHTTP "github.com/owncloud/ocis/v2/services/proxy/pkg/server/http" - "github.com/owncloud/ocis/v2/services/proxy/pkg/tracing" "github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend" "github.com/owncloud/ocis/v2/services/proxy/pkg/userroles" "github.com/urfave/cli/v2" microstore "go-micro.dev/v4/store" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel/trace" ) // Server is the entrypoint for the server command. @@ -61,7 +64,7 @@ func Server(cfg *config.Config) *cli.Command { ) logger := logging.Configure(cfg.Service.Name, cfg.Log) - err := tracing.Configure(cfg) + traceProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name) if err != nil { return err } @@ -70,7 +73,7 @@ func Server(cfg *config.Config) *cli.Command { return err } - var oidcHTTPClient = &http.Client{ + oidcHTTPClient := &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ MinVersion: tls.VersionTLS12, @@ -89,9 +92,7 @@ func Server(cfg *config.Config) *cli.Command { oidc.WithJWKSOptions(cfg.OIDC.JWKS), ) - var ( - m = metrics.New() - ) + m := metrics.New() gr := run.Group{} ctx, cancel := func() (context.Context, context.CancelFunc) { @@ -123,7 +124,7 @@ func Server(cfg *config.Config) *cli.Command { } { - middlewares := loadMiddlewares(ctx, logger, cfg, userInfoCache) + middlewares := loadMiddlewares(ctx, logger, cfg, userInfoCache, traceProvider) server, err := proxyHTTP.Server( proxyHTTP.Handler(lh.handler()), proxyHTTP.Logger(logger), @@ -132,7 +133,6 @@ func Server(cfg *config.Config) *cli.Command { proxyHTTP.Metrics(metrics.New()), proxyHTTP.Middlewares(middlewares), ) - if err != nil { logger.Error(). Err(err). @@ -160,7 +160,6 @@ func Server(cfg *config.Config) *cli.Command { debug.Context(ctx), debug.Config(cfg), ) - if err != nil { logger.Error().Err(err).Str("server", "debug").Msg("Failed to initialize server") return err @@ -195,7 +194,6 @@ func (h *StaticRouteHandler) handler() http.Handler { // TODO: migrate oidc well knowns here in a second wrapper r.HandleFunc("/*", h.proxy.ServeHTTP) - }) // This is commented out due to a race issue in chi //var methods = []string{"PROPFIND", "DELETE", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", "REPORT"} @@ -270,15 +268,18 @@ func (h *StaticRouteHandler) backchannelLogout(w http.ResponseWriter, r *http.Re render.JSON(w, r, nil) } -func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config, userInfoCache microstore.Store) alice.Chain { - grpcClient, err := grpc.NewClient(append(grpc.GetClientOptions(cfg.GRPCClientTLS), grpc.WithTraceProvider(tracing.TraceProvider))...) +func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config, userInfoCache microstore.Store, traceProvider trace.TracerProvider) alice.Chain { + grpcClient, err := grpc.NewClient( + append( + grpc.GetClientOptions(cfg.GRPCClientTLS), + grpc.WithTraceProvider(traceProvider))...) if err != nil { logger.Fatal().Err(err).Msg("Failed to get gateway client") } rolesClient := settingssvc.NewRoleService("com.owncloud.api.settings", grpcClient) - revaClient, err := pool.GetGatewayServiceClient(cfg.Reva.Address, cfg.Reva.GetRevaOptions()...) + gatewaySelector, err := pool.GatewaySelector(cfg.Reva.Address, append(cfg.Reva.GetRevaOptions(), pool.WithRegistry(registry.GetRegistry()))...) if err != nil { - logger.Fatal().Err(err).Msg("Failed to get gateway client") + logger.Fatal().Err(err).Msg("Failed to get gateway selector") } tokenManager, err := jwt.New(map[string]interface{}{ "secret": cfg.TokenManager.JWTSecret, @@ -291,10 +292,9 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config, var userProvider backend.UserBackend switch cfg.AccountBackend { case "cs3": - userProvider = backend.NewCS3UserBackend( backend.WithLogger(logger), - backend.WithRevaAuthenticator(revaClient), + backend.WithRevaGatewaySelector(gatewaySelector), backend.WithMachineAuthAPIKey(cfg.MachineAuthAPIKey), backend.WithOIDCissuer(cfg.OIDC.Issuer), backend.WithAutoProvisonCreator(autoProvsionCreator), @@ -329,7 +329,7 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config, Msg("Failed to create reva gateway service client") } - var oidcHTTPClient = &http.Client{ + oidcHTTPClient := &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ MinVersion: tls.VersionTLS12, @@ -344,10 +344,8 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config, if cfg.EnableBasicAuth { logger.Warn().Msg("basic auth enabled, use only for testing or development") authenticators = append(authenticators, middleware.BasicAuthenticator{ - Logger: logger, - UserProvider: userProvider, - UserCS3Claim: cfg.UserCS3Claim, - UserOIDCClaim: cfg.UserOIDCClaim, + Logger: logger, + UserProvider: userProvider, }) } @@ -366,8 +364,8 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config, )), )) authenticators = append(authenticators, middleware.PublicShareAuthenticator{ - Logger: logger, - RevaGatewayClient: revaClient, + Logger: logger, + RevaGatewaySelector: gatewaySelector, }) authenticators = append(authenticators, middleware.SignedURLAuthenticator{ Logger: logger, @@ -379,7 +377,13 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config, return alice.New( // first make sure we log all requests and redirect to https if necessary - middleware.Tracer(), + otelhttp.NewMiddleware("proxy", + otelhttp.WithTracerProvider(traceProvider), + otelhttp.WithSpanNameFormatter(func(name string, r *http.Request) string { + return fmt.Sprintf("%s %s", r.Method, r.URL.Path) + }), + ), + middleware.Tracer(traceProvider), pkgmiddleware.TraceContext, chimiddleware.RealIP, chimiddleware.RequestID, @@ -397,6 +401,7 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config, middleware.Logger(logger), middleware.OIDCIss(cfg.OIDC.Issuer), middleware.EnableBasicAuth(cfg.EnableBasicAuth), + middleware.TraceProvider(traceProvider), ), middleware.AccountResolver( middleware.Logger(logger), @@ -414,7 +419,7 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config, // finally, trigger home creation when a user logs in middleware.CreateHome( middleware.Logger(logger), - middleware.RevaGatewayClient(revaClient), + middleware.WithRevaGatewaySelector(gatewaySelector), middleware.RoleQuotas(cfg.RoleQuotas), ), ) diff --git a/services/proxy/pkg/config/defaults/defaultconfig.go b/services/proxy/pkg/config/defaults/defaultconfig.go index 9c21bbf2c..16ee92d71 100644 --- a/services/proxy/pkg/config/defaults/defaultconfig.go +++ b/services/proxy/pkg/config/defaults/defaultconfig.go @@ -74,8 +74,8 @@ func DefaultConfig() *config.Config { Enabled: true, }, AccountBackend: "cs3", - UserOIDCClaim: "lg.uuid", - UserCS3Claim: "userid", + UserOIDCClaim: "preferred_username", + UserCS3Claim: "username", AutoprovisionAccounts: false, EnableBasicAuth: false, InsecureBackends: false, diff --git a/services/proxy/pkg/config/tracing.go b/services/proxy/pkg/config/tracing.go index f8a1bc79d..388f0a16b 100644 --- a/services/proxy/pkg/config/tracing.go +++ b/services/proxy/pkg/config/tracing.go @@ -1,5 +1,7 @@ package config +import "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + // Tracing defines the available tracing configuration. type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;PROXY_TRACING_ENABLED" desc:"Activates tracing."` @@ -7,3 +9,13 @@ type Tracing struct { Endpoint string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;PROXY_TRACING_ENDPOINT" desc:"The endpoint of the tracing agent."` Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;PROXY_TRACING_COLLECTOR" desc:"The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset."` } + +// Convert Tracing to the tracing package's Config struct. +func (t Tracing) Convert() tracing.Config { + return tracing.Config{ + Enabled: t.Enabled, + Type: t.Type, + Endpoint: t.Endpoint, + Collector: t.Collector, + } +} diff --git a/services/proxy/pkg/middleware/account_resolver_test.go b/services/proxy/pkg/middleware/account_resolver_test.go index e1b3b227d..3d80dfc44 100644 --- a/services/proxy/pkg/middleware/account_resolver_test.go +++ b/services/proxy/pkg/middleware/account_resolver_test.go @@ -17,7 +17,7 @@ import ( "github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend/mocks" userRoleMocks "github.com/owncloud/ocis/v2/services/proxy/pkg/userroles/mocks" "github.com/stretchr/testify/assert" - "github.com/test-go/testify/mock" + "github.com/stretchr/testify/mock" ) func TestTokenIsAddedWithMailClaim(t *testing.T) { diff --git a/services/proxy/pkg/middleware/authentication.go b/services/proxy/pkg/middleware/authentication.go index 9958e8401..f81e4dec1 100644 --- a/services/proxy/pkg/middleware/authentication.go +++ b/services/proxy/pkg/middleware/authentication.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/owncloud/ocis/v2/services/proxy/pkg/router" - proxytracing "github.com/owncloud/ocis/v2/services/proxy/pkg/tracing" "github.com/owncloud/ocis/v2/services/proxy/pkg/webdav" "go.opentelemetry.io/otel/trace" "golang.org/x/text/cases" @@ -50,7 +49,8 @@ type Authenticator interface { func Authentication(auths []Authenticator, opts ...Option) func(next http.Handler) http.Handler { options := newOptions(opts...) configureSupportedChallenges(options) - tracer := proxytracing.TraceProvider.Tracer("proxy") + tracer := getTraceProvider(options).Tracer("proxy") + spanOpts := []trace.SpanStartOption{ trace.WithSpanKind(trace.SpanKindServer), } @@ -206,3 +206,10 @@ func evalRequestURI(l userAgentLocker, r regexp.Regexp) { } l.w.Header().Add(WwwAuthenticate, fmt.Sprintf("%v realm=\"%s\", charset=\"UTF-8\"", caser.String(l.fallback), l.r.Host)) } + +func getTraceProvider(o Options) trace.TracerProvider { + if o.TraceProvider != nil { + return o.TraceProvider + } + return trace.NewNoopTracerProvider() +} diff --git a/services/proxy/pkg/middleware/basic_auth_test.go b/services/proxy/pkg/middleware/basic_auth_test.go index 3e7660cf0..986884b4c 100644 --- a/services/proxy/pkg/middleware/basic_auth_test.go +++ b/services/proxy/pkg/middleware/basic_auth_test.go @@ -6,7 +6,7 @@ import ( userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" . "github.com/onsi/ginkgo/v2" - "github.com/test-go/testify/mock" + "github.com/stretchr/testify/mock" . "github.com/onsi/gomega" "github.com/owncloud/ocis/v2/ocis-pkg/log" diff --git a/services/proxy/pkg/middleware/create_home.go b/services/proxy/pkg/middleware/create_home.go index cc4abf778..f7da59a0e 100644 --- a/services/proxy/pkg/middleware/create_home.go +++ b/services/proxy/pkg/middleware/create_home.go @@ -10,6 +10,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/rgrpc/status" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/utils" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" @@ -23,19 +24,19 @@ func CreateHome(optionSetters ...Option) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return &createHome{ - next: next, - logger: logger, - revaGatewayClient: options.RevaGatewayClient, - roleQuotas: options.RoleQuotas, + next: next, + logger: logger, + revaGatewaySelector: options.RevaGatewaySelector, + roleQuotas: options.RoleQuotas, } } } type createHome struct { - next http.Handler - logger log.Logger - revaGatewayClient gateway.GatewayAPIClient - roleQuotas map[string]uint64 + next http.Handler + logger log.Logger + revaGatewaySelector pool.Selectable[gateway.GatewayAPIClient] + roleQuotas map[string]uint64 } func (m createHome) ServeHTTP(w http.ResponseWriter, req *http.Request) { @@ -64,14 +65,18 @@ func (m createHome) ServeHTTP(w http.ResponseWriter, req *http.Request) { } } - createHomeRes, err := m.revaGatewayClient.CreateHome(ctx, createHomeReq) - + client, err := m.revaGatewaySelector.Next() if err != nil { - m.logger.Err(err).Msg("error calling CreateHome") - } else if createHomeRes.Status.Code != rpc.Code_CODE_OK { - err := status.NewErrorFromCode(createHomeRes.Status.Code, "gateway") - if createHomeRes.Status.Code != rpc.Code_CODE_ALREADY_EXISTS { - m.logger.Err(err).Msg("error when calling Createhome") + m.logger.Err(err).Msg("error selecting next gateway client") + } else { + createHomeRes, err := client.CreateHome(ctx, createHomeReq) + if err != nil { + m.logger.Err(err).Msg("error calling CreateHome") + } else if createHomeRes.Status.Code != rpc.Code_CODE_OK { + err := status.NewErrorFromCode(createHomeRes.Status.Code, "gateway") + if createHomeRes.Status.Code != rpc.Code_CODE_ALREADY_EXISTS { + m.logger.Err(err).Msg("error when calling Createhome") + } } } diff --git a/services/proxy/pkg/middleware/options.go b/services/proxy/pkg/middleware/options.go index d4759079f..cd64b4472 100644 --- a/services/proxy/pkg/middleware/options.go +++ b/services/proxy/pkg/middleware/options.go @@ -5,6 +5,7 @@ import ( "time" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/oidc" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" @@ -13,6 +14,7 @@ import ( "github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend" "github.com/owncloud/ocis/v2/services/proxy/pkg/userroles" store "go-micro.dev/v4/store" + "go.opentelemetry.io/otel/trace" ) // Option defines a single option function. @@ -38,8 +40,8 @@ type Options struct { OIDCClient oidc.OIDCClient // OIDCIss is the oidcAuth-issuer OIDCIss string - // RevaGatewayClient to send requests to the reva gateway - RevaGatewayClient gateway.GatewayAPIClient + // RevaGatewaySelector to send requests to the reva gateway + RevaGatewaySelector pool.Selectable[gateway.GatewayAPIClient] // Store for persisting data Store storesvc.StoreService // PreSignedURLConfig to configure the middleware @@ -66,6 +68,8 @@ type Options struct { // RoleQuotas hold userid:quota mappings. These will be used when provisioning new users. // The users will get as much quota as is set for their role. RoleQuotas map[string]uint64 + // TraceProvider sets the tracing provider. + TraceProvider trace.TracerProvider } // newOptions initializes the available default options. @@ -135,10 +139,10 @@ func CredentialsByUserAgent(v map[string]string) Option { } } -// RevaGatewayClient provides a function to set the the reva gateway service client option. -func RevaGatewayClient(gc gateway.GatewayAPIClient) Option { +// WithRevaGatewaySelector provides a function to set the the reva gateway service selector option. +func WithRevaGatewaySelector(val pool.Selectable[gateway.GatewayAPIClient]) Option { return func(o *Options) { - o.RevaGatewayClient = gc + o.RevaGatewaySelector = val } } @@ -225,3 +229,10 @@ func RoleQuotas(roleQuotas map[string]uint64) Option { o.RoleQuotas = roleQuotas } } + +// TraceProvider sets the tracing provider. +func TraceProvider(tp trace.TracerProvider) Option { + return func(o *Options) { + o.TraceProvider = tp + } +} diff --git a/services/proxy/pkg/middleware/public_share_auth.go b/services/proxy/pkg/middleware/public_share_auth.go index 0f7d8004c..f66e030a0 100644 --- a/services/proxy/pkg/middleware/public_share_auth.go +++ b/services/proxy/pkg/middleware/public_share_auth.go @@ -5,6 +5,7 @@ import ( "strings" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/owncloud/ocis/v2/ocis-pkg/log" ) @@ -21,8 +22,8 @@ const ( // PublicShareAuthenticator is the authenticator which can authenticate public share requests. // It will add the share owner into the request context. type PublicShareAuthenticator struct { - Logger log.Logger - RevaGatewayClient gateway.GatewayAPIClient + Logger log.Logger + RevaGatewaySelector pool.Selectable[gateway.GatewayAPIClient] } // The archiver is able to create archives from public shares in which case it needs to use the @@ -83,7 +84,18 @@ func (a PublicShareAuthenticator) Authenticate(r *http.Request) (*http.Request, } } - authResp, err := a.RevaGatewayClient.Authenticate(r.Context(), &gateway.AuthenticateRequest{ + client, err := a.RevaGatewaySelector.Next() + if err != nil { + a.Logger.Error(). + Err(err). + Str("authenticator", "public_share"). + Str("public_share_token", shareToken). + Str("path", r.URL.Path). + Msg("could not select next gateway client") + return nil, false + } + + authResp, err := client.Authenticate(r.Context(), &gateway.AuthenticateRequest{ Type: authenticationType, ClientId: shareToken, ClientSecret: sharePassword, diff --git a/services/proxy/pkg/middleware/public_share_auth_test.go b/services/proxy/pkg/middleware/public_share_auth_test.go index 4df9a47ca..5c3aad445 100644 --- a/services/proxy/pkg/middleware/public_share_auth_test.go +++ b/services/proxy/pkg/middleware/public_share_auth_test.go @@ -5,8 +5,10 @@ import ( "net/http" "net/http/httptest" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/owncloud/ocis/v2/ocis-pkg/log" @@ -18,23 +20,29 @@ var _ = Describe("Authenticating requests", Label("PublicShareAuthenticator"), f BeforeEach(func() { authenticator = PublicShareAuthenticator{ Logger: log.NewLogger(), - RevaGatewayClient: mockGatewayClient{ - AuthenticateFunc: func(authType, clientID, clientSecret string) (string, rpcv1beta1.Code) { - if authType != "publicshares" { - return "", rpcv1beta1.Code_CODE_NOT_FOUND - } + RevaGatewaySelector: pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return mockGatewayClient{ + AuthenticateFunc: func(authType, clientID, clientSecret string) (string, rpcv1beta1.Code) { + if authType != "publicshares" { + return "", rpcv1beta1.Code_CODE_NOT_FOUND + } - if clientID == "sharetoken" && (clientSecret == "password|examples3cr3t" || clientSecret == "signature|examplesignature|exampleexpiration") { - return "exampletoken", rpcv1beta1.Code_CODE_OK - } + if clientID == "sharetoken" && (clientSecret == "password|examples3cr3t" || clientSecret == "signature|examplesignature|exampleexpiration") { + return "exampletoken", rpcv1beta1.Code_CODE_OK + } - if clientID == "sharetoken" && clientSecret == "password|" { - return "otherexampletoken", rpcv1beta1.Code_CODE_OK - } + if clientID == "sharetoken" && clientSecret == "password|" { + return "otherexampletoken", rpcv1beta1.Code_CODE_OK + } - return "", rpcv1beta1.Code_CODE_NOT_FOUND + return "", rpcv1beta1.Code_CODE_NOT_FOUND + }, + } }, - }, + ), } }) When("the request contains correct data", func() { diff --git a/services/proxy/pkg/middleware/tracing.go b/services/proxy/pkg/middleware/tracing.go index 84760cba2..9b897720d 100644 --- a/services/proxy/pkg/middleware/tracing.go +++ b/services/proxy/pkg/middleware/tracing.go @@ -6,23 +6,24 @@ import ( chimiddleware "github.com/go-chi/chi/v5/middleware" pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" - proxytracing "github.com/owncloud/ocis/v2/services/proxy/pkg/tracing" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) // Tracer provides a middleware to start traces -func Tracer() func(next http.Handler) http.Handler { +func Tracer(tp trace.TracerProvider) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return &tracer{ - next: next, + next: next, + traceProvider: tp, } } } type tracer struct { - next http.Handler + next http.Handler + traceProvider trace.TracerProvider } func (m tracer) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -31,7 +32,7 @@ func (m tracer) ServeHTTP(w http.ResponseWriter, r *http.Request) { span trace.Span ) - tracer := proxytracing.TraceProvider.Tracer("proxy") + tracer := m.traceProvider.Tracer("proxy") spanOpts := []trace.SpanStartOption{ trace.WithSpanKind(trace.SpanKindServer), } diff --git a/services/proxy/pkg/tracing/tracing.go b/services/proxy/pkg/tracing/tracing.go deleted file mode 100644 index b8a07b12c..000000000 --- a/services/proxy/pkg/tracing/tracing.go +++ /dev/null @@ -1,23 +0,0 @@ -package tracing - -import ( - pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" - "github.com/owncloud/ocis/v2/services/proxy/pkg/config" - "go.opentelemetry.io/otel/trace" -) - -var ( - // TraceProvider is the global trace provider for the proxy service. - TraceProvider = trace.NewNoopTracerProvider() -) - -func Configure(cfg *config.Config) error { - var err error - if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { - return err - } - } - - return nil -} diff --git a/services/proxy/pkg/user/backend/backend.go b/services/proxy/pkg/user/backend/backend.go index efce21d55..8ff2c254a 100644 --- a/services/proxy/pkg/user/backend/backend.go +++ b/services/proxy/pkg/user/backend/backend.go @@ -4,9 +4,7 @@ import ( "context" "errors" - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" cs3 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - "google.golang.org/grpc" ) var ( @@ -26,8 +24,3 @@ type UserBackend interface { Authenticate(ctx context.Context, username string, password string) (*cs3.User, string, error) CreateUserFromClaims(ctx context.Context, claims map[string]interface{}) (*cs3.User, error) } - -// RevaAuthenticator helper interface to mock auth-method from reva gateway-client. -type RevaAuthenticator interface { - Authenticate(ctx context.Context, in *gateway.AuthenticateRequest, opts ...grpc.CallOption) (*gateway.AuthenticateResponse, error) -} diff --git a/services/proxy/pkg/user/backend/cs3.go b/services/proxy/pkg/user/backend/cs3.go index 0dcbc8fd1..6a006fb9c 100644 --- a/services/proxy/pkg/user/backend/cs3.go +++ b/services/proxy/pkg/user/backend/cs3.go @@ -11,6 +11,7 @@ import ( cs3 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/oidc" @@ -31,7 +32,7 @@ type Option func(o *Options) // Options defines the available options for this package. type Options struct { logger log.Logger - authProvider RevaAuthenticator + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] machineAuthAPIKey string oidcISS string autoProvsionCreator autoprovision.Creator @@ -44,10 +45,10 @@ func WithLogger(l log.Logger) Option { } } -// WithRevaAuthenticator set the RevaAuthenticator option -func WithRevaAuthenticator(ra RevaAuthenticator) Option { +// WithRevaGatewaySelector set the gatewaySelector option +func WithRevaGatewaySelector(selectable pool.Selectable[gateway.GatewayAPIClient]) Option { return func(o *Options) { - o.authProvider = ra + o.gatewaySelector = selectable } } @@ -91,7 +92,12 @@ func NewCS3UserBackend(opts ...Option) UserBackend { } func (c *cs3backend) GetUserByClaims(ctx context.Context, claim, value string) (*cs3.User, string, error) { - res, err := c.authProvider.Authenticate(ctx, &gateway.AuthenticateRequest{ + gatewayClient, err := c.gatewaySelector.Next() + if err != nil { + return nil, "", fmt.Errorf("could not obtain gatewayClient: %s", err) + } + + res, err := gatewayClient.Authenticate(ctx, &gateway.AuthenticateRequest{ Type: "machine", ClientId: claim + ":" + value, ClientSecret: c.machineAuthAPIKey, @@ -113,7 +119,12 @@ func (c *cs3backend) GetUserByClaims(ctx context.Context, claim, value string) ( } func (c *cs3backend) Authenticate(ctx context.Context, username string, password string) (*cs3.User, string, error) { - res, err := c.authProvider.Authenticate(ctx, &gateway.AuthenticateRequest{ + gatewayClient, err := c.gatewaySelector.Next() + if err != nil { + return nil, "", fmt.Errorf("could not obtain gatewayClient: %s", err) + } + + res, err := gatewayClient.Authenticate(ctx, &gateway.AuthenticateRequest{ Type: "basic", ClientId: username, ClientSecret: password, diff --git a/services/proxy/pkg/userroles/defaultrole.go b/services/proxy/pkg/userroles/defaultrole.go index d1a59eb2d..29125177f 100644 --- a/services/proxy/pkg/userroles/defaultrole.go +++ b/services/proxy/pkg/userroles/defaultrole.go @@ -35,7 +35,7 @@ func (d defaultRoleAssigner) UpdateUserRoleAssignment(ctx context.Context, user var err error roleIDs, err = loadRolesIDs(ctx, user.Id.OpaqueId, d.roleService) if err != nil { - d.logger.Error().Err(err).Msgf("Could not load roles") + d.logger.Error().Err(err).Msg("Could not load roles") return nil, err } @@ -68,7 +68,7 @@ func (d defaultRoleAssigner) UpdateUserRoleAssignment(ctx context.Context, user func (d defaultRoleAssigner) ApplyUserRole(ctx context.Context, user *cs3.User) (*cs3.User, error) { roleIDs, err := loadRolesIDs(ctx, user.Id.OpaqueId, d.roleService) if err != nil { - d.logger.Error().Err(err).Msgf("Could not load roles") + d.logger.Error().Err(err).Msg("Could not load roles") return nil, err } diff --git a/services/proxy/pkg/userroles/oidcroles.go b/services/proxy/pkg/userroles/oidcroles.go index 0d36c922f..a5db4c10c 100644 --- a/services/proxy/pkg/userroles/oidcroles.go +++ b/services/proxy/pkg/userroles/oidcroles.go @@ -89,13 +89,11 @@ func (ra oidcRoleAssigner) UpdateUserRoleAssignment(ctx context.Context, user *c return nil, err } if len(assignedRoles) > 1 { - err := errors.New("too many roles assigned") - logger.Error().Err(err).Msg("The user has too many roles assigned") - return nil, err + logger.Error().Str("userID", user.GetId().GetOpaqueId()).Int("numRoles", len(assignedRoles)).Msg("The user has too many roles assigned") } logger.Debug().Interface("assignedRoleIds", assignedRoles).Msg("Currently assigned roles") - if len(assignedRoles) == 0 || (assignedRoles[0] != roleIDFromClaim) { + if len(assignedRoles) != 1 || (assignedRoles[0] != roleIDFromClaim) { logger.Debug().Interface("assignedRoleIds", assignedRoles).Interface("newRoleId", roleIDFromClaim).Msg("Updating role assignment for user") newctx, err := ra.prepareAdminContext() if err != nil { @@ -120,7 +118,7 @@ func (ra oidcRoleAssigner) UpdateUserRoleAssignment(ctx context.Context, user *c func (ra oidcRoleAssigner) ApplyUserRole(ctx context.Context, user *cs3.User) (*cs3.User, error) { roleIDs, err := loadRolesIDs(ctx, user.Id.OpaqueId, ra.roleService) if err != nil { - ra.logger.Error().Err(err).Msgf("Could not load roles") + ra.logger.Error().Err(err).Msg("Could not load roles") return nil, err } diff --git a/services/search/pkg/command/root.go b/services/search/pkg/command/root.go index 9bed2b315..e850b3612 100644 --- a/services/search/pkg/command/root.go +++ b/services/search/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - "github.com/thejerf/suture/v4" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/search/pkg/config" "github.com/urfave/cli/v2" ) @@ -36,25 +33,3 @@ func Execute(cfg *config.Config) error { }) return app.Run(os.Args) } - -// SutureService allows for the search command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new search.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Search.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Search, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/search/pkg/command/server.go b/services/search/pkg/command/server.go index 9dc3f1110..ef38771ce 100644 --- a/services/search/pkg/command/server.go +++ b/services/search/pkg/command/server.go @@ -33,7 +33,7 @@ func Server(cfg *config.Config) *cli.Command { if err != nil { return err } - err = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...) + err = ogrpc.Configure(append(ogrpc.GetClientOptions(cfg.GRPCClientTLS), ogrpc.WithTraceProvider(tracing.TraceProvider))...) if err != nil { return err } @@ -56,6 +56,7 @@ func Server(cfg *config.Config) *cli.Command { grpc.Name(cfg.Service.Name), grpc.Context(ctx), grpc.Metrics(mtrcs), + grpc.JWTSecret(cfg.Commons.TokenManager.JWTSecret), ) defer teardown() if err != nil { diff --git a/services/search/pkg/config/config.go b/services/search/pkg/config/config.go index 5e0363846..ab164fe0c 100644 --- a/services/search/pkg/config/config.go +++ b/services/search/pkg/config/config.go @@ -18,6 +18,8 @@ type Config struct { GRPC GRPCConfig `yaml:"grpc"` + TokenManager *TokenManager `yaml:"token_manager"` + Reva *shared.Reva `yaml:"reva"` GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"` Events Events `yaml:"events"` diff --git a/services/search/pkg/config/defaults/defaultconfig.go b/services/search/pkg/config/defaults/defaultconfig.go index 5519dbb31..50fe01b12 100644 --- a/services/search/pkg/config/defaults/defaultconfig.go +++ b/services/search/pkg/config/defaults/defaultconfig.go @@ -83,6 +83,14 @@ func EnsureDefaults(cfg *config.Config) { cfg.Tracing = &config.Tracing{} } + if cfg.TokenManager == nil && cfg.Commons != nil && cfg.Commons.TokenManager != nil { + cfg.TokenManager = &config.TokenManager{ + JWTSecret: cfg.Commons.TokenManager.JWTSecret, + } + } else if cfg.TokenManager == nil { + cfg.TokenManager = &config.TokenManager{} + } + if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey } diff --git a/services/search/pkg/config/reva.go b/services/search/pkg/config/reva.go index 773424a28..95025f477 100644 --- a/services/search/pkg/config/reva.go +++ b/services/search/pkg/config/reva.go @@ -4,3 +4,8 @@ package config type Reva struct { Address string `ocisConfig:"address" env:"OCIS_REVA_GATEWAY;REVA_GATEWAY" desc:"The CS3 gateway endpoint." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"REVA_GATEWAY changing name for consistency" deprecationReplacement:"OCIS_REVA_GATEWAY"` } + +// TokenManager is the config for using the reva token manager +type TokenManager struct { + JWTSecret string `yaml:"jwt_secret" env:"OCIS_JWT_SECRET;SEARCH_JWT_SECRET" desc:"The secret to mint and validate jwt tokens."` +} diff --git a/services/search/pkg/content/cs3.go b/services/search/pkg/content/cs3.go index 9d2332fdc..4a90e85a9 100644 --- a/services/search/pkg/content/cs3.go +++ b/services/search/pkg/content/cs3.go @@ -11,24 +11,25 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/owncloud/ocis/v2/ocis-pkg/log" ) type cs3 struct { - httpClient http.Client - gwClient gateway.GatewayAPIClient - logger log.Logger + httpClient http.Client + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + logger log.Logger } -func newCS3Retriever(client gateway.GatewayAPIClient, logger log.Logger, insecure bool) cs3 { +func newCS3Retriever(gatewaySelector pool.Selectable[gateway.GatewayAPIClient], logger log.Logger, insecure bool) cs3 { return cs3{ httpClient: http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}, //nolint:gosec }, }, - gwClient: client, - logger: logger, + gatewaySelector: gatewaySelector, + logger: logger, } } @@ -40,7 +41,13 @@ func (s cs3) Retrieve(ctx context.Context, rID *provider.ResourceId) (io.ReadClo return nil, fmt.Errorf("context without %s", revactx.TokenHeader) } - res, err := s.gwClient.InitiateFileDownload(ctx, &provider.InitiateFileDownloadRequest{Ref: &provider.Reference{ResourceId: rID, Path: "."}}) + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + s.logger.Error().Err(err).Msg("could not get reva gatewayClient") + return nil, err + } + + res, err := gatewayClient.InitiateFileDownload(ctx, &provider.InitiateFileDownloadRequest{Ref: &provider.Reference{ResourceId: rID, Path: "."}}) if err != nil { return nil, err } diff --git a/services/search/pkg/content/tika.go b/services/search/pkg/content/tika.go index 46283a4c1..1979cdbcd 100644 --- a/services/search/pkg/content/tika.go +++ b/services/search/pkg/content/tika.go @@ -8,6 +8,7 @@ import ( "github.com/bbalet/stopwords" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/google/go-tika/tika" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/services/search/pkg/config" @@ -23,7 +24,7 @@ type Tika struct { } // NewTikaExtractor creates a new Tika instance. -func NewTikaExtractor(gw gateway.GatewayAPIClient, logger log.Logger, cfg *config.Config) (*Tika, error) { +func NewTikaExtractor(gatewaySelector pool.Selectable[gateway.GatewayAPIClient], logger log.Logger, cfg *config.Config) (*Tika, error) { basic, err := NewBasicExtractor(logger) if err != nil { return nil, err @@ -38,7 +39,7 @@ func NewTikaExtractor(gw gateway.GatewayAPIClient, logger log.Logger, cfg *confi return &Tika{ Basic: basic, - Retriever: newCS3Retriever(gw, logger, cfg.Extractor.CS3AllowInsecure), + Retriever: newCS3Retriever(gatewaySelector, logger, cfg.Extractor.CS3AllowInsecure), tika: tika.NewClient(nil, cfg.Extractor.Tika.TikaURL), contentExtractionSizeLimit: cfg.ContentExtractionSizeLimit, }, nil diff --git a/services/search/pkg/engine/bleve.go b/services/search/pkg/engine/bleve.go index d6cd737dc..3831b42e7 100644 --- a/services/search/pkg/engine/bleve.go +++ b/services/search/pkg/engine/bleve.go @@ -10,23 +10,23 @@ import ( "strings" "time" - "github.com/blevesearch/bleve/v2/analysis/token/porter" - "github.com/blevesearch/bleve/v2/analysis/tokenizer/unicode" - "github.com/blevesearch/bleve/v2" "github.com/blevesearch/bleve/v2/analysis/analyzer/custom" "github.com/blevesearch/bleve/v2/analysis/analyzer/keyword" "github.com/blevesearch/bleve/v2/analysis/token/lowercase" + "github.com/blevesearch/bleve/v2/analysis/token/porter" "github.com/blevesearch/bleve/v2/analysis/tokenizer/single" + "github.com/blevesearch/bleve/v2/analysis/tokenizer/unicode" "github.com/blevesearch/bleve/v2/mapping" "github.com/blevesearch/bleve/v2/search/query" storageProvider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "google.golang.org/protobuf/types/known/timestamppb" + "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" searchMessage "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/search/v0" searchService "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" "github.com/owncloud/ocis/v2/services/search/pkg/content" - "google.golang.org/protobuf/types/known/timestamppb" ) // Bleve represents a search engine which utilizes bleve to search and store resources. @@ -163,40 +163,41 @@ func (b *Bleve) Search(_ context.Context, sir *searchService.SearchIndexRequest) matches := make([]*searchMessage.Match, 0, len(res.Hits)) for _, hit := range res.Hits { - if sir.Ref != nil && !strings.HasPrefix(getValue[string](hit.Fields, "Path"), utils.MakeRelativePath(path.Join(sir.Ref.Path, "/"))) { + if sir.Ref != nil && !strings.HasPrefix(getFieldValue[string](hit.Fields, "Path"), utils.MakeRelativePath(path.Join(sir.Ref.Path, "/"))) { continue } - rootID, err := storagespace.ParseID(getValue[string](hit.Fields, "RootID")) + rootID, err := storagespace.ParseID(getFieldValue[string](hit.Fields, "RootID")) if err != nil { return nil, err } - rID, err := storagespace.ParseID(getValue[string](hit.Fields, "ID")) + rID, err := storagespace.ParseID(getFieldValue[string](hit.Fields, "ID")) if err != nil { return nil, err } - pID, _ := storagespace.ParseID(getValue[string](hit.Fields, "ParentID")) + pID, _ := storagespace.ParseID(getFieldValue[string](hit.Fields, "ParentID")) match := &searchMessage.Match{ Score: float32(hit.Score), Entity: &searchMessage.Entity{ Ref: &searchMessage.Reference{ ResourceId: resourceIDtoSearchID(rootID), - Path: getValue[string](hit.Fields, "Path"), + Path: getFieldValue[string](hit.Fields, "Path"), }, - Id: resourceIDtoSearchID(rID), - Name: getValue[string](hit.Fields, "Name"), - ParentId: resourceIDtoSearchID(pID), - Size: uint64(getValue[float64](hit.Fields, "Size")), - Type: uint64(getValue[float64](hit.Fields, "Type")), - MimeType: getValue[string](hit.Fields, "MimeType"), - Deleted: getValue[bool](hit.Fields, "Deleted"), - Tags: getSliceValue[string](hit.Fields, "Tags"), + Id: resourceIDtoSearchID(rID), + Name: getFieldValue[string](hit.Fields, "Name"), + ParentId: resourceIDtoSearchID(pID), + Size: uint64(getFieldValue[float64](hit.Fields, "Size")), + Type: uint64(getFieldValue[float64](hit.Fields, "Type")), + MimeType: getFieldValue[string](hit.Fields, "MimeType"), + Deleted: getFieldValue[bool](hit.Fields, "Deleted"), + Tags: getFieldSliceValue[string](hit.Fields, "Tags"), + Highlights: getFragmentValue(hit.Fragments, "Content", 0), }, } - if mtime, err := time.Parse(time.RFC3339, getValue[string](hit.Fields, "Mtime")); err == nil { + if mtime, err := time.Parse(time.RFC3339, getFieldValue[string](hit.Fields, "Mtime")); err == nil { match.Entity.LastModifiedTime = ×tamppb.Timestamp{Seconds: mtime.Unix(), Nanos: int32(mtime.Nanosecond())} } @@ -296,20 +297,20 @@ func (b *Bleve) getResource(id string) (*Resource, error) { fields := res.Hits[0].Fields return &Resource{ - ID: getValue[string](fields, "ID"), - RootID: getValue[string](fields, "RootID"), - Path: getValue[string](fields, "Path"), - ParentID: getValue[string](fields, "ParentID"), - Type: uint64(getValue[float64](fields, "Type")), - Deleted: getValue[bool](fields, "Deleted"), + ID: getFieldValue[string](fields, "ID"), + RootID: getFieldValue[string](fields, "RootID"), + Path: getFieldValue[string](fields, "Path"), + ParentID: getFieldValue[string](fields, "ParentID"), + Type: uint64(getFieldValue[float64](fields, "Type")), + Deleted: getFieldValue[bool](fields, "Deleted"), Document: content.Document{ - Name: getValue[string](fields, "Name"), - Title: getValue[string](fields, "Title"), - Size: uint64(getValue[float64](fields, "Size")), - Mtime: getValue[string](fields, "Mtime"), - MimeType: getValue[string](fields, "MimeType"), - Content: getValue[string](fields, "Content"), - Tags: getSliceValue[string](fields, "Tags"), + Name: getFieldValue[string](fields, "Name"), + Title: getFieldValue[string](fields, "Title"), + Size: uint64(getFieldValue[float64](fields, "Size")), + Mtime: getFieldValue[string](fields, "Mtime"), + MimeType: getFieldValue[string](fields, "MimeType"), + Content: getFieldValue[string](fields, "Content"), + Tags: getFieldSliceValue[string](fields, "Tags"), }, }, nil } @@ -368,6 +369,7 @@ func formatQuery(q string) string { fieldRe := regexp.MustCompile(`\w+:[^ ]+`) if fieldRe.MatchString(cq) { + nameTagesRe := regexp.MustCompile(`\+?(Name|Tags)`) // detect "Name", "+Name, "Tags" and "+Tags" parts := strings.Split(cq, " ") cq = "" @@ -376,7 +378,7 @@ func formatQuery(q string) string { if len(fieldParts) > 1 { key := fieldParts[0] value := fieldParts[1] - if key == "Name" || key == "Tags" { + if nameTagesRe.MatchString(key) { value = strings.ToLower(value) // do a lowercase query on the lowercased fields } cq += key + ":" + value + " " diff --git a/services/search/pkg/engine/bleve_test.go b/services/search/pkg/engine/bleve_test.go index 63ca73b5e..219167fff 100644 --- a/services/search/pkg/engine/bleve_test.go +++ b/services/search/pkg/engine/bleve_test.go @@ -10,6 +10,7 @@ import ( sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + searchmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/search/v0" searchsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" "github.com/owncloud/ocis/v2/services/search/pkg/content" @@ -242,6 +243,34 @@ var _ = Describe("Bleve", func() { }) }) }) + + Context("Highlights", func() { + + It("highlights only for content searches", func() { + parentResource.Document.Name = "baz.pdf" + parentResource.Document.Content = "foo bar baz" + err := eng.Upsert(parentResource.ID, parentResource) + Expect(err).ToNot(HaveOccurred()) + + res, err := doSearch(rootResource.ID, "Name:baz*") + Expect(err).ToNot(HaveOccurred()) + Expect(res.TotalMatches).To(Equal(int32(1))) + Expect(res.Matches[0].Entity.Highlights).To(Equal("")) + }) + + It("highlights search terms", func() { + parentResource.Document.Name = "baz.pdf" + parentResource.Document.Content = "foo bar baz" + err := eng.Upsert(parentResource.ID, parentResource) + Expect(err).ToNot(HaveOccurred()) + + res, err := doSearch(rootResource.ID, "Content:bar") + Expect(err).ToNot(HaveOccurred()) + Expect(res.TotalMatches).To(Equal(int32(1))) + Expect(res.Matches[0].Entity.Highlights).To(Equal("foo bar baz")) + }) + + }) }) Describe("Upsert", func() { diff --git a/services/search/pkg/engine/engine.go b/services/search/pkg/engine/engine.go index d050583ff..931b04265 100644 --- a/services/search/pkg/engine/engine.go +++ b/services/search/pkg/engine/engine.go @@ -4,7 +4,9 @@ import ( "context" "regexp" + "github.com/blevesearch/bleve/v2/search" storageProvider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + searchMessage "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/search/v0" searchService "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" "github.com/owncloud/ocis/v2/services/search/pkg/content" @@ -49,7 +51,20 @@ func escapeQuery(s string) string { return queryEscape.ReplaceAllString(s, "\\$1") } -func getValue[T any](m map[string]interface{}, key string) (out T) { +func getFragmentValue(m search.FieldFragmentMap, key string, idx int) string { + val, ok := m[key] + if !ok { + return "" + } + + if len(val) <= idx { + return "" + } + + return val[idx] +} + +func getFieldValue[T any](m map[string]interface{}, key string) (out T) { val, ok := m[key] if !ok { return @@ -60,8 +75,8 @@ func getValue[T any](m map[string]interface{}, key string) (out T) { return } -func getSliceValue[T any](m map[string]interface{}, key string) (out []T) { - iv := getValue[interface{}](m, key) +func getFieldSliceValue[T any](m map[string]interface{}, key string) (out []T) { + iv := getFieldValue[interface{}](m, key) add := func(v interface{}) { cv, ok := v.(T) if !ok { diff --git a/services/search/pkg/search/events.go b/services/search/pkg/search/events.go index 65e5c22b3..27bee83b2 100644 --- a/services/search/pkg/search/events.go +++ b/services/search/pkg/search/events.go @@ -72,45 +72,48 @@ func HandleEvents(s Searcher, bus events.Consumer, logger log.Logger, cfg *confi for i := 0; i < cfg.Events.NumConsumers; i++ { go func(s Searcher, ch <-chan events.Event) { - for e := range ch { - logger.Debug().Interface("event", e).Msg("updating index") + for event := range ch { + e := event + go func() { + logger.Debug().Interface("event", e).Msg("updating index") - var err error + var err error - switch ev := e.Event.(type) { - case events.ItemTrashed: - u := getUser(ev.SpaceOwner, ev.Executant) - s.TrashItem(ev.ID) - indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), u) - case events.ItemMoved: - u := getUser(ev.SpaceOwner, ev.Executant) - s.MoveItem(ev.Ref, u) - indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), getUser(ev.SpaceOwner, ev.Executant)) - case events.ItemRestored: - u := getUser(ev.SpaceOwner, ev.Executant) - s.RestoreItem(ev.Ref, u) - indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), u) - case events.ContainerCreated: - indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), getUser(ev.SpaceOwner, ev.Executant)) - case events.FileTouched: - indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), getUser(ev.SpaceOwner, ev.Executant)) - case events.FileVersionRestored: - indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), getUser(ev.SpaceOwner, ev.Executant)) - case events.TagsAdded: - s.UpsertItem(ev.Ref, ev.Executant) - case events.TagsRemoved: - s.UpsertItem(ev.Ref, ev.Executant) - case events.FileUploaded: - indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), getUser(ev.SpaceOwner, ev.Executant)) - case events.UploadReady: - indexSpaceDebouncer.Debounce(getSpaceID(ev.FileRef), getUser(ev.SpaceOwner, ev.ExecutingUser.Id)) - case events.SpaceRenamed: - indexSpaceDebouncer.Debounce(ev.ID, getUser(ev.Executant)) - } + switch ev := e.Event.(type) { + case events.ItemTrashed: + u := getUser(ev.SpaceOwner, ev.Executant) + s.TrashItem(ev.ID) + indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), u) + case events.ItemMoved: + u := getUser(ev.SpaceOwner, ev.Executant) + s.MoveItem(ev.Ref, u) + indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), getUser(ev.SpaceOwner, ev.Executant)) + case events.ItemRestored: + u := getUser(ev.SpaceOwner, ev.Executant) + s.RestoreItem(ev.Ref, u) + indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), u) + case events.ContainerCreated: + indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), getUser(ev.SpaceOwner, ev.Executant)) + case events.FileTouched: + indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), getUser(ev.SpaceOwner, ev.Executant)) + case events.FileVersionRestored: + indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), getUser(ev.SpaceOwner, ev.Executant)) + case events.TagsAdded: + s.UpsertItem(ev.Ref, ev.Executant) + case events.TagsRemoved: + s.UpsertItem(ev.Ref, ev.Executant) + case events.FileUploaded: + indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), getUser(ev.SpaceOwner, ev.Executant)) + case events.UploadReady: + indexSpaceDebouncer.Debounce(getSpaceID(ev.FileRef), getUser(ev.SpaceOwner, ev.ExecutingUser.Id)) + case events.SpaceRenamed: + indexSpaceDebouncer.Debounce(ev.ID, getUser(ev.Executant)) + } - if err != nil { - logger.Error().Err(err).Interface("event", e) - } + if err != nil { + logger.Error().Err(err).Interface("event", e) + } + }() } }( s, diff --git a/services/search/pkg/search/search.go b/services/search/pkg/search/search.go index 954ea55f5..b4a1ad749 100644 --- a/services/search/pkg/search/search.go +++ b/services/search/pkg/search/search.go @@ -12,6 +12,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/errtypes" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/utils" "github.com/owncloud/ocis/v2/ocis-pkg/log" searchmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/search/v0" @@ -20,12 +21,17 @@ import ( ) // ResolveReference makes sure the path is relative to the space root -func ResolveReference(ctx context.Context, ref *provider.Reference, ri *provider.ResourceInfo, gw gateway.GatewayAPIClient) (*provider.Reference, error) { +func ResolveReference(ctx context.Context, ref *provider.Reference, ri *provider.ResourceInfo, gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) (*provider.Reference, error) { if ref.GetResourceId().GetOpaqueId() == ref.GetResourceId().GetSpaceId() { return ref, nil } - gpRes, err := gw.GetPath(ctx, &provider.GetPathRequest{ + gatewayClient, err := gatewaySelector.Next() + if err != nil { + return nil, err + } + + gpRes, err := gatewayClient.GetPath(ctx, &provider.GetPathRequest{ ResourceId: ri.Id, }) if err != nil || gpRes.Status.Code != rpc.Code_CODE_OK { @@ -61,9 +67,15 @@ func logDocCount(engine engine.Engine, logger log.Logger) { logger.Debug().Interface("count", c).Msg("new document count") } -func getAuthContext(owner *user.User, gw gateway.GatewayAPIClient, secret string, logger log.Logger) (context.Context, error) { +func getAuthContext(owner *user.User, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], secret string, logger log.Logger) (context.Context, error) { + gatewayClient, err := gatewaySelector.Next() + if err != nil { + logger.Error().Err(err).Msg("could not get reva gatewayClient") + return nil, err + } + ownerCtx := ctxpkg.ContextSetUser(context.Background(), owner) - authRes, err := gw.Authenticate(ownerCtx, &gateway.AuthenticateRequest{ + authRes, err := gatewayClient.Authenticate(ownerCtx, &gateway.AuthenticateRequest{ Type: "machine", ClientId: "userid:" + owner.GetId().GetOpaqueId(), ClientSecret: secret, @@ -81,8 +93,13 @@ func getAuthContext(owner *user.User, gw gateway.GatewayAPIClient, secret string return metadata.AppendToOutgoingContext(ownerCtx, ctxpkg.TokenHeader, authRes.Token), nil } -func statResource(ctx context.Context, ref *provider.Reference, gw gateway.GatewayAPIClient, logger log.Logger) (*provider.StatResponse, error) { - res, err := gw.Stat(ctx, &provider.StatRequest{Ref: ref}) +func statResource(ctx context.Context, ref *provider.Reference, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], logger log.Logger) (*provider.StatResponse, error) { + gatewayClient, err := gatewaySelector.Next() + if err != nil { + return nil, err + } + + res, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: ref}) if err != nil { logger.Error().Err(err).Msg("failed to stat the moved resource") return nil, err @@ -118,8 +135,7 @@ func convertToWebDAVPermissions(isShared, isMountpoint, isDir bool, p *provider. if isMountpoint { fmt.Fprintf(&b, "M") } - if p.Delete && - p.PurgeRecycle { + if p.Delete { fmt.Fprintf(&b, "D") } if p.InitiateFileUpload && diff --git a/services/search/pkg/search/search_suite_test.go b/services/search/pkg/search/search_suite_test.go index 9b2060ec0..75873d6da 100644 --- a/services/search/pkg/search/search_suite_test.go +++ b/services/search/pkg/search/search_suite_test.go @@ -3,10 +3,23 @@ package search_test import ( "testing" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + mRegistry "go-micro.dev/v4/registry" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) +func init() { + registry.Configure("memory") + r := registry.GetRegistry() + service := registry.BuildGRPCService("com.owncloud.api.gateway", "", "", "") + service.Nodes = []*mRegistry.Node{{ + Address: "any", + }} + + _ = r.Register(service) +} func TestSearch(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Search Suite") diff --git a/services/search/pkg/search/service.go b/services/search/pkg/search/service.go index 8407ae67f..fd39dea05 100644 --- a/services/search/pkg/search/service.go +++ b/services/search/pkg/search/service.go @@ -12,7 +12,9 @@ import ( user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/errtypes" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" sdk "github.com/cs3org/reva/v2/pkg/sdk/common" "github.com/cs3org/reva/v2/pkg/storage/utils/walker" "github.com/cs3org/reva/v2/pkg/storagespace" @@ -46,23 +48,23 @@ type Searcher interface { // Service is responsible for indexing spaces and pass on a search // to it's underlying engine. type Service struct { - logger log.Logger - gateway gateway.GatewayAPIClient - engine engine.Engine - extractor content.Extractor - secret string + logger log.Logger + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + engine engine.Engine + extractor content.Extractor + secret string } var errSkipSpace error // NewService creates a new Provider instance. -func NewService(gw gateway.GatewayAPIClient, eng engine.Engine, extractor content.Extractor, logger log.Logger, cfg *config.Config) *Service { +func NewService(gatewaySelector pool.Selectable[gateway.GatewayAPIClient], eng engine.Engine, extractor content.Extractor, logger log.Logger, cfg *config.Config) *Service { var s = &Service{ - gateway: gw, - engine: eng, - secret: cfg.MachineAuthAPIKey, - logger: logger, - extractor: extractor, + gatewaySelector: gatewaySelector, + engine: eng, + secret: cfg.MachineAuthAPIKey, + logger: logger, + extractor: extractor, } return s @@ -75,8 +77,19 @@ func (s *Service) Search(ctx context.Context, req *searchsvc.SearchRequest) (*se } s.logger.Debug().Str("query", req.Query).Msg("performing a search") - listSpacesRes, err := s.gateway.ListStorageSpaces(ctx, &provider.ListStorageSpacesRequest{ + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + + currentUser := revactx.ContextMustGetUser(ctx) + + listSpacesRes, err := gatewayClient.ListStorageSpaces(ctx, &provider.ListStorageSpacesRequest{ Filters: []*provider.ListStorageSpacesRequest_Filter{ + { + Type: provider.ListStorageSpacesRequest_Filter_TYPE_USER, + Term: &provider.ListStorageSpacesRequest_Filter_User{User: currentUser.GetId()}, + }, { Type: provider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE, Term: &provider.ListStorageSpacesRequest_Filter_SpaceType{SpaceType: "+grant"}, @@ -227,7 +240,13 @@ func (s *Service) searchIndex(ctx context.Context, req *searchsvc.SearchRequest, s.logger.Warn().Interface("space", space).Msg("could not find mountpoint space for grant space") return nil, errSkipSpace } - gpRes, err := s.gateway.GetPath(ctx, &provider.GetPathRequest{ + + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + + gpRes, err := gatewayClient.GetPath(ctx, &provider.GetPathRequest{ ResourceId: space.Root, }) if err != nil { @@ -252,7 +271,7 @@ func (s *Service) searchIndex(ctx context.Context, req *searchsvc.SearchRequest, rootName = space.GetRootInfo().GetPath() permissions = space.GetRootInfo().GetPermissionSet() s.logger.Debug().Interface("grantSpace", space).Interface("mountpointRootId", mountpointRootID).Msg("searching a grant") - case "personal": + case "personal", "project": permissions = space.GetRootInfo().GetPermissionSet() } @@ -296,7 +315,7 @@ func (s *Service) searchIndex(ctx context.Context, req *searchsvc.SearchRequest, // IndexSpace (re)indexes all resources of a given space. func (s *Service) IndexSpace(spaceID *provider.StorageSpaceId, uID *user.UserId) error { - ownerCtx, err := getAuthContext(&user.User{Id: uID}, s.gateway, s.secret, s.logger) + ownerCtx, err := getAuthContext(&user.User{Id: uID}, s.gatewaySelector, s.secret, s.logger) if err != nil { return err } @@ -312,7 +331,7 @@ func (s *Service) IndexSpace(spaceID *provider.StorageSpaceId, uID *user.UserId) } rootID.OpaqueId = rootID.SpaceId - w := walker.NewWalker(s.gateway) + w := walker.NewWalker(s.gatewaySelector) err = w.Walk(ownerCtx, &rootID, func(wd string, info *provider.ResourceInfo, err error) error { if err != nil { s.logger.Error().Err(err).Msg("error walking the tree") @@ -426,17 +445,17 @@ func (s *Service) MoveItem(ref *provider.Reference, uID *user.UserId) { } func (s *Service) resInfo(uID *user.UserId, ref *provider.Reference) (context.Context, *provider.StatResponse, string) { - ownerCtx, err := getAuthContext(&user.User{Id: uID}, s.gateway, s.secret, s.logger) + ownerCtx, err := getAuthContext(&user.User{Id: uID}, s.gatewaySelector, s.secret, s.logger) if err != nil { return nil, nil, "" } - statRes, err := statResource(ownerCtx, ref, s.gateway, s.logger) + statRes, err := statResource(ownerCtx, ref, s.gatewaySelector, s.logger) if err != nil { return nil, nil, "" } - r, err := ResolveReference(ownerCtx, ref, statRes.GetInfo(), s.gateway) + r, err := ResolveReference(ownerCtx, ref, statRes.GetInfo(), s.gatewaySelector) if err != nil { return nil, nil, "" } diff --git a/services/search/pkg/search/service_test.go b/services/search/pkg/search/service_test.go index fc374c7e1..50600ecff 100644 --- a/services/search/pkg/search/service_test.go +++ b/services/search/pkg/search/service_test.go @@ -7,7 +7,9 @@ import ( userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/rgrpc/status" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -20,17 +22,19 @@ import ( engineMocks "github.com/owncloud/ocis/v2/services/search/pkg/engine/mocks" "github.com/owncloud/ocis/v2/services/search/pkg/search" "github.com/stretchr/testify/mock" + "google.golang.org/grpc" ) var _ = Describe("Searchprovider", func() { var ( - s search.Searcher - extractor *contentMocks.Extractor - gw *cs3mocks.GatewayAPIClient - indexClient *engineMocks.Engine - ctx context.Context - logger = log.NewLogger() - user = &userv1beta1.User{ + s search.Searcher + extractor *contentMocks.Extractor + gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + indexClient *engineMocks.Engine + ctx context.Context + logger = log.NewLogger() + user = &userv1beta1.User{ Id: &userv1beta1.UserId{ OpaqueId: "user", }, @@ -70,22 +74,31 @@ var _ = Describe("Searchprovider", func() { ) BeforeEach(func() { - ctx = context.Background() - gw = &cs3mocks.GatewayAPIClient{} + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") + gatewayClient = &cs3mocks.GatewayAPIClient{} + gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return gatewayClient + }, + ) + + ctx = revactx.ContextSetUser(context.Background(), user) indexClient = &engineMocks.Engine{} extractor = &contentMocks.Extractor{} - s = search.NewService(gw, indexClient, extractor, logger, &config.Config{}) + s = search.NewService(gatewaySelector, indexClient, extractor, logger, &config.Config{}) - gw.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{ + gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{ Status: status.NewOK(ctx), Token: "authtoken", }, nil) - gw.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{ + gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{ Status: status.NewOK(context.Background()), Info: ri, }, nil) - gw.On("GetPath", mock.Anything, mock.MatchedBy(func(req *sprovider.GetPathRequest) bool { + gatewayClient.On("GetPath", mock.Anything, mock.MatchedBy(func(req *sprovider.GetPathRequest) bool { return req.ResourceId.OpaqueId == ri.Id.OpaqueId })).Return(&sprovider.GetPathResponse{ Status: status.NewOK(context.Background()), @@ -96,14 +109,14 @@ var _ = Describe("Searchprovider", func() { Describe("New", func() { It("returns a new instance", func() { - s := search.NewService(gw, indexClient, extractor, logger, &config.Config{}) + s := search.NewService(gatewaySelector, indexClient, extractor, logger, &config.Config{}) Expect(s).ToNot(BeNil()) }) }) Describe("IndexSpace", func() { It("walks the space and indexes all files", func() { - gw.On("GetUserByClaim", mock.Anything, mock.Anything).Return(&userv1beta1.GetUserByClaimResponse{ + gatewayClient.On("GetUserByClaim", mock.Anything, mock.Anything).Return(&userv1beta1.GetUserByClaimResponse{ Status: status.NewOK(context.Background()), User: user, }, nil) @@ -127,7 +140,7 @@ var _ = Describe("Searchprovider", func() { Context("with a personal space", func() { BeforeEach(func() { - gw.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&sprovider.ListStorageSpacesResponse{ + gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&sprovider.ListStorageSpacesResponse{ Status: status.NewOK(ctx), StorageSpaces: []*sprovider.StorageSpace{personalSpace}, }, nil) @@ -210,14 +223,14 @@ var _ = Describe("Searchprovider", func() { }, }, } - gw.On("GetPath", mock.Anything, mock.Anything).Return(&sprovider.GetPathResponse{ + gatewayClient.On("GetPath", mock.Anything, mock.Anything).Return(&sprovider.GetPathResponse{ Status: status.NewOK(ctx), Path: "/grant/path", }, nil) }) It("searches the received spaces", func() { - gw.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&sprovider.ListStorageSpacesResponse{ + gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&sprovider.ListStorageSpacesResponse{ Status: status.NewOK(ctx), StorageSpaces: []*sprovider.StorageSpace{grantSpace, mountpointSpace}, }, nil) @@ -259,7 +272,7 @@ var _ = Describe("Searchprovider", func() { Context("when searching both spaces", func() { BeforeEach(func() { - gw.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&sprovider.ListStorageSpacesResponse{ + gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&sprovider.ListStorageSpacesResponse{ Status: status.NewOK(ctx), StorageSpaces: []*sprovider.StorageSpace{personalSpace, grantSpace, mountpointSpace}, }, nil) diff --git a/services/search/pkg/server/grpc/option.go b/services/search/pkg/server/grpc/option.go index 577a862d6..a0fbe5d2f 100644 --- a/services/search/pkg/server/grpc/option.go +++ b/services/search/pkg/server/grpc/option.go @@ -15,13 +15,14 @@ type Option func(o *Options) // Options defines the available options for this package. type Options struct { - Name string - Logger log.Logger - Context context.Context - Config *config.Config - Metrics *metrics.Metrics - Flags []cli.Flag - Handler *svc.Service + Name string + Logger log.Logger + Context context.Context + Config *config.Config + Metrics *metrics.Metrics + Flags []cli.Flag + Handler *svc.Service + JWTSecret string } // newOptions initializes the available default options. @@ -83,3 +84,10 @@ func Handler(val *svc.Service) Option { o.Handler = val } } + +// JWTSecret provides a function to set the Config option. +func JWTSecret(val string) Option { + return func(o *Options) { + o.JWTSecret = val + } +} diff --git a/services/search/pkg/server/grpc/server.go b/services/search/pkg/server/grpc/server.go index da3c14f19..19d53ea6f 100644 --- a/services/search/pkg/server/grpc/server.go +++ b/services/search/pkg/server/grpc/server.go @@ -33,6 +33,7 @@ func Server(opts ...Option) (grpc.Service, func(), error) { handle, teardown, err := svc.NewHandler( svc.Config(options.Config), svc.Logger(options.Logger), + svc.JWTSecret(options.JWTSecret), ) if err != nil { options.Logger.Error(). diff --git a/services/search/pkg/service/grpc/v0/option.go b/services/search/pkg/service/grpc/v0/option.go index 9f1a7d19e..72014b8e4 100644 --- a/services/search/pkg/service/grpc/v0/option.go +++ b/services/search/pkg/service/grpc/v0/option.go @@ -10,8 +10,9 @@ type Option func(o *Options) // Options defines the available options for this package. type Options struct { - Logger log.Logger - Config *config.Config + Logger log.Logger + Config *config.Config + JWTSecret string } func newOptions(opts ...Option) Options { @@ -37,3 +38,10 @@ func Config(val *config.Config) Option { o.Config = val } } + +// JWTSecret provides a function to set the Config option. +func JWTSecret(val string) Option { + return func(o *Options) { + o.JWTSecret = val + } +} diff --git a/services/search/pkg/service/grpc/v0/service.go b/services/search/pkg/service/grpc/v0/service.go index 38e55d133..5d8d7ea11 100644 --- a/services/search/pkg/service/grpc/v0/service.go +++ b/services/search/pkg/service/grpc/v0/service.go @@ -15,10 +15,13 @@ import ( "github.com/cs3org/reva/v2/pkg/errtypes" "github.com/cs3org/reva/v2/pkg/events/stream" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/v2/pkg/token" + "github.com/cs3org/reva/v2/pkg/token/manager/jwt" "github.com/go-micro/plugins/v4/events/natsjs" "github.com/jellydator/ttlcache/v2" ociscrypto "github.com/owncloud/ocis/v2/ocis-pkg/crypto" "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" v0 "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/search/v0" searchsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" "github.com/owncloud/ocis/v2/services/search/pkg/content" @@ -55,9 +58,9 @@ func NewHandler(opts ...Option) (searchsvc.SearchProviderHandler, func(), error) } // initialize gateway - gw, err := pool.GetGatewayServiceClient(cfg.Reva.Address) + selector, err := pool.GatewaySelector(cfg.Reva.Address, pool.WithRegistry(registry.GetRegistry())) if err != nil { - logger.Fatal().Err(err).Str("addr", cfg.Reva.Address).Msg("could not get reva client") + logger.Fatal().Err(err).Msg("could not get reva gateway selector") return nil, teardown, err } // initialize search content extractor @@ -68,7 +71,7 @@ func NewHandler(opts ...Option) (searchsvc.SearchProviderHandler, func(), error) return nil, teardown, err } case "tika": - if extractor, err = content.NewTikaExtractor(gw, logger, cfg); err != nil { + if extractor, err = content.NewTikaExtractor(selector, logger, cfg); err != nil { return nil, teardown, err } default: @@ -106,7 +109,7 @@ func NewHandler(opts ...Option) (searchsvc.SearchProviderHandler, func(), error) return nil, teardown, err } - ss := search.NewService(gw, eng, extractor, logger, cfg) + ss := search.NewService(selector, eng, extractor, logger, cfg) // setup event handling if err := search.HandleEvents(ss, bus, logger, cfg); err != nil { @@ -118,20 +121,30 @@ func NewHandler(opts ...Option) (searchsvc.SearchProviderHandler, func(), error) return nil, teardown, err } + tokenManager, err := jwt.New(map[string]interface{}{ + "secret": options.JWTSecret, + "expires": int64(24 * 60 * 60), + }) + if err != nil { + return nil, teardown, err + } + return &Service{ - id: cfg.GRPC.Namespace + "." + cfg.Service.Name, - log: logger, - searcher: ss, - cache: cache, + id: cfg.GRPC.Namespace + "." + cfg.Service.Name, + log: logger, + searcher: ss, + cache: cache, + tokenManager: tokenManager, }, teardown, nil } // Service implements the searchServiceHandler interface type Service struct { - id string - log log.Logger - searcher search.Searcher - cache *ttlcache.Cache + id string + log log.Logger + searcher search.Searcher + cache *ttlcache.Cache + tokenManager token.Manager } // Search handles the search @@ -144,7 +157,13 @@ func (s Service) Search(ctx context.Context, in *searchsvc.SearchRequest, out *s } ctx = grpcmetadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, t) - u, _ := revactx.ContextGetUser(ctx) + // unpack user + u, _, err := s.tokenManager.DismantleToken(ctx, t) + if err != nil { + return err + } + ctx = revactx.ContextSetUser(ctx, u) + key := cacheKey(in.Query, in.PageSize, in.Ref, u) res, ok := s.FromCache(key) if !ok { diff --git a/services/settings/pkg/command/root.go b/services/settings/pkg/command/root.go index 29812fe05..641cf491c 100644 --- a/services/settings/pkg/command/root.go +++ b/services/settings/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/settings/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the settings command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new settings.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Settings.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Settings, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/settings/pkg/command/server.go b/services/settings/pkg/command/server.go index cd48ba639..1f60f59da 100644 --- a/services/settings/pkg/command/server.go +++ b/services/settings/pkg/command/server.go @@ -7,6 +7,7 @@ import ( "github.com/oklog/run" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" "github.com/owncloud/ocis/v2/ocis-pkg/version" "github.com/owncloud/ocis/v2/services/settings/pkg/config" "github.com/owncloud/ocis/v2/services/settings/pkg/config/parser" @@ -16,7 +17,6 @@ import ( "github.com/owncloud/ocis/v2/services/settings/pkg/server/grpc" "github.com/owncloud/ocis/v2/services/settings/pkg/server/http" svc "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" - "github.com/owncloud/ocis/v2/services/settings/pkg/tracing" "github.com/urfave/cli/v2" ) @@ -31,11 +31,13 @@ func Server(cfg *config.Config) *cli.Command { }, Action: func(c *cli.Context) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - err := tracing.Configure(cfg) + tracingProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name) if err != nil { return err } - err = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...) + err = ogrpc.Configure( + append(ogrpc.GetClientOptions(cfg.GRPCClientTLS), ogrpc.WithTraceProvider(tracingProvider))..., + ) if err != nil { return err } @@ -62,6 +64,7 @@ func Server(cfg *config.Config) *cli.Command { http.Config(cfg), http.Metrics(mtrcs), http.ServiceHandler(handle), + http.TraceProvider(tracingProvider), ) if err != nil { logger.Error(). @@ -82,6 +85,7 @@ func Server(cfg *config.Config) *cli.Command { grpc.Config(cfg), grpc.Metrics(mtrcs), grpc.ServiceHandler(handle), + grpc.TraceProvider(tracingProvider), ) servers.Add(grpcServer.Run, func(_ error) { logger.Info().Str("server", "grpc").Msg("Shutting down server") diff --git a/services/settings/pkg/config/defaults/defaultconfig.go b/services/settings/pkg/config/defaults/defaultconfig.go index d5fd3ab5e..64866ae3f 100644 --- a/services/settings/pkg/config/defaults/defaultconfig.go +++ b/services/settings/pkg/config/defaults/defaultconfig.go @@ -53,8 +53,8 @@ func DefaultConfig() *config.Config { DataPath: path.Join(defaults.BaseDataPath(), "settings"), SetupDefaultAssignments: false, Metadata: config.Metadata{ - GatewayAddress: "127.0.0.1:9215", // system storage - StorageAddress: "127.0.0.1:9215", + GatewayAddress: "com.owncloud.api.storage-system", + StorageAddress: "com.owncloud.api.storage-system", SystemUserIDP: "internal", Cache: &config.Cache{ Store: "memory", diff --git a/services/settings/pkg/config/http.go b/services/settings/pkg/config/http.go index 8705daf1b..b03f35fad 100644 --- a/services/settings/pkg/config/http.go +++ b/services/settings/pkg/config/http.go @@ -15,6 +15,6 @@ type HTTP struct { type CORS struct { AllowedOrigins []string `yaml:"allow_origins" env:"OCIS_CORS_ALLOW_ORIGINS;SETTINGS_CORS_ALLOW_ORIGINS" desc:"A comma-separated list of allowed CORS origins. See following chapter for more details: *Access-Control-Allow-Origin* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin"` AllowedMethods []string `yaml:"allow_methods" env:"OCIS_CORS_ALLOW_METHODS;SETTINGS_CORS_ALLOW_METHODS" desc:"A comma-separated list of allowed CORS methods. See following chapter for more details: *Access-Control-Request-Method* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method"` - AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;SETTINGS_CORS_ALLOW_HEADERS" desc:"A comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` + AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;SETTINGS_CORS_ALLOW_HEADERS" desc:"A blank or comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` AllowCredentials bool `yaml:"allow_credentials" env:"OCIS_CORS_ALLOW_CREDENTIALS;SETTINGS_CORS_ALLOW_CREDENTIALS" desc:"Allow credentials for CORS.See following chapter for more details: *Access-Control-Allow-Credentials* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials."` } diff --git a/services/settings/pkg/config/tracing.go b/services/settings/pkg/config/tracing.go index 60a992596..88a18d429 100644 --- a/services/settings/pkg/config/tracing.go +++ b/services/settings/pkg/config/tracing.go @@ -1,5 +1,7 @@ package config +import "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + // Tracing defines the available tracing configuration. type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;SETTINGS_TRACING_ENABLED" desc:"Activates tracing."` @@ -7,3 +9,13 @@ type Tracing struct { Endpoint string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;SETTINGS_TRACING_ENDPOINT" desc:"The endpoint of the tracing agent."` Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;SETTINGS_TRACING_COLLECTOR" desc:"The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset."` } + +// Convert Tracing to the tracing package's Config struct. +func (t Tracing) Convert() tracing.Config { + return tracing.Config{ + Enabled: t.Enabled, + Type: t.Type, + Endpoint: t.Endpoint, + Collector: t.Collector, + } +} diff --git a/services/settings/pkg/server/grpc/option.go b/services/settings/pkg/server/grpc/option.go index afcc6a39b..5359d883e 100644 --- a/services/settings/pkg/server/grpc/option.go +++ b/services/settings/pkg/server/grpc/option.go @@ -8,6 +8,7 @@ import ( "github.com/owncloud/ocis/v2/services/settings/pkg/metrics" svc "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" "github.com/urfave/cli/v2" + "go.opentelemetry.io/otel/trace" ) // Option defines a single option function. @@ -22,6 +23,7 @@ type Options struct { Metrics *metrics.Metrics ServiceHandler svc.Service Flags []cli.Flag + TraceProvider trace.TracerProvider } // newOptions initializes the available default options. @@ -83,3 +85,10 @@ func ServiceHandler(val svc.Service) Option { o.ServiceHandler = val } } + +// TraceProvider provides a function to set the TraceProvider option +func TraceProvider(val trace.TracerProvider) Option { + return func(o *Options) { + o.TraceProvider = val + } +} diff --git a/services/settings/pkg/server/grpc/server.go b/services/settings/pkg/server/grpc/server.go index 4cad0fdd1..dd4e5cdae 100644 --- a/services/settings/pkg/server/grpc/server.go +++ b/services/settings/pkg/server/grpc/server.go @@ -28,6 +28,7 @@ func Server(opts ...Option) grpc.Service { grpc.Namespace(options.Config.GRPC.Namespace), grpc.Context(options.Context), grpc.Flags(options.Flags...), + grpc.TraceProvider(options.TraceProvider), ) if err != nil { options.Logger.Fatal().Err(err).Msg("Error creating settings service") diff --git a/services/settings/pkg/server/http/option.go b/services/settings/pkg/server/http/option.go index f3e3fdcb7..793ab7f2c 100644 --- a/services/settings/pkg/server/http/option.go +++ b/services/settings/pkg/server/http/option.go @@ -8,6 +8,7 @@ import ( "github.com/owncloud/ocis/v2/services/settings/pkg/metrics" svc "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" "github.com/urfave/cli/v2" + "go.opentelemetry.io/otel/trace" ) // Option defines a single option function. @@ -22,6 +23,7 @@ type Options struct { Metrics *metrics.Metrics ServiceHandler svc.Service Flags []cli.Flag + TraceProvider trace.TracerProvider } // newOptions initializes the available default options. @@ -83,3 +85,10 @@ func ServiceHandler(val svc.Service) Option { o.ServiceHandler = val } } + +// TraceProvider provides a function to set the TraceProvider option +func TraceProvider(val trace.TracerProvider) Option { + return func(o *Options) { + o.TraceProvider = val + } +} diff --git a/services/settings/pkg/server/http/server.go b/services/settings/pkg/server/http/server.go index 3fcb4a469..d523fc94a 100644 --- a/services/settings/pkg/server/http/server.go +++ b/services/settings/pkg/server/http/server.go @@ -10,9 +10,10 @@ import ( "github.com/owncloud/ocis/v2/ocis-pkg/cors" "github.com/owncloud/ocis/v2/ocis-pkg/middleware" ohttp "github.com/owncloud/ocis/v2/ocis-pkg/service/http" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" "github.com/owncloud/ocis/v2/ocis-pkg/version" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" - svc "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" + "github.com/riandyrn/otelchi" "go-micro.dev/v4" ) @@ -39,12 +40,6 @@ func Server(opts ...Option) (ohttp.Service, error) { handle := options.ServiceHandler - { - handle = svc.NewInstrument(handle, options.Metrics) - handle = svc.NewLogging(handle, options.Logger) - handle = svc.NewTracing(handle) - } - mux := chi.NewMux() mux.Use(chimiddleware.RealIP) @@ -72,6 +67,15 @@ func Server(opts ...Option) (ohttp.Service, error) { options.Logger, )) + mux.Use( + otelchi.Middleware( + options.Name, + otelchi.WithChiRoutes(mux), + otelchi.WithTracerProvider(options.TraceProvider), + otelchi.WithPropagators(tracing.GetPropagator()), + ), + ) + mux.Route(options.Config.HTTP.Root, func(r chi.Router) { settingssvc.RegisterBundleServiceWeb(r, handle) settingssvc.RegisterValueServiceWeb(r, handle) diff --git a/services/settings/pkg/service/v0/instrument.go b/services/settings/pkg/service/v0/instrument.go deleted file mode 100644 index 882a1e184..000000000 --- a/services/settings/pkg/service/v0/instrument.go +++ /dev/null @@ -1,13 +0,0 @@ -package svc - -import ( - "github.com/owncloud/ocis/v2/services/settings/pkg/metrics" -) - -// NewInstrument returns a service that instruments metrics. -func NewInstrument(next Service, metrics *metrics.Metrics) Service { - return Service{ - manager: next.manager, - config: next.config, - } -} diff --git a/services/settings/pkg/service/v0/logging.go b/services/settings/pkg/service/v0/logging.go deleted file mode 100644 index a84dcabeb..000000000 --- a/services/settings/pkg/service/v0/logging.go +++ /dev/null @@ -1,13 +0,0 @@ -package svc - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/log" -) - -// NewLogging returns a service that logs messages. -func NewLogging(next Service, logger log.Logger) Service { - return Service{ - manager: next.manager, - config: next.config, - } -} diff --git a/services/settings/pkg/service/v0/service_test.go b/services/settings/pkg/service/v0/service_test.go index c927cd66a..d143b48a5 100644 --- a/services/settings/pkg/service/v0/service_test.go +++ b/services/settings/pkg/service/v0/service_test.go @@ -11,7 +11,7 @@ import ( "github.com/owncloud/ocis/v2/services/settings/pkg/settings/mocks" "github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults" "github.com/stretchr/testify/assert" - "github.com/test-go/testify/mock" + "github.com/stretchr/testify/mock" merrors "go-micro.dev/v4/errors" "go-micro.dev/v4/metadata" ) diff --git a/services/settings/pkg/service/v0/tracing.go b/services/settings/pkg/service/v0/tracing.go deleted file mode 100644 index e4a0e89da..000000000 --- a/services/settings/pkg/service/v0/tracing.go +++ /dev/null @@ -1,9 +0,0 @@ -package svc - -// NewTracing returns a service that instruments traces. -func NewTracing(next Service) Service { - return Service{ - manager: next.manager, - config: next.config, - } -} diff --git a/services/settings/pkg/store/defaults/defaults.go b/services/settings/pkg/store/defaults/defaults.go index 53388915d..f3de77394 100644 --- a/services/settings/pkg/store/defaults/defaults.go +++ b/services/settings/pkg/store/defaults/defaults.go @@ -15,23 +15,23 @@ const ( // BundleUUIDRoleUser represents the user role. BundleUUIDRoleUser = "d7beeea8-8ff4-406b-8fb6-ab2dd81e6b11" - // BundleUUIDRoleGuest represents the guest role. - BundleUUIDRoleGuest = "38071a68-456a-4553-846a-fa67bf5596cc" + // BundleUUIDRoleUserLight represents the user light role. + BundleUUIDRoleUserLight = "38071a68-456a-4553-846a-fa67bf5596cc" // RoleManagementPermissionID is the hardcoded setting UUID for the role management permission RoleManagementPermissionID string = "a53e601e-571f-4f86-8fec-d4576ef49c62" // RoleManagementPermissionName is the hardcoded setting name for the role management permission - RoleManagementPermissionName string = "role-management" + RoleManagementPermissionName string = "Roles.ReadWrite" // SettingsManagementPermissionID is the hardcoded setting UUID for the settings management permission SettingsManagementPermissionID string = "3d58f441-4a05-42f8-9411-ef5874528ae1" // SettingsManagementPermissionName is the hardcoded setting name for the settings management permission - SettingsManagementPermissionName string = "settings-management" + SettingsManagementPermissionName string = "Settings.ReadWrite" - // LanguageReadWriteID is the hardcoded setting UUID for the language read write permission + // LanguageReadWriteID is the hardcoded setting UUID for the language read write all permission LanguageReadWriteID string = "7d81f103-0488-4853-bce5-98dcce36d649" - // LanguageReadWriteName is the hardcoded setting name for the language read write permission - LanguageReadWriteName string = "language-readwrite" + // LanguageReadWriteName is the hardcoded setting name for the language read write all permission + LanguageReadWriteName string = "Language.ReadWrite" // DisableEmailNotificationsPermissionID is the hardcoded setting UUID for the disable email notifications permission DisableEmailNotificationsPermissionID string = "ad5bb5e5-dc13-4cd3-9304-09a424564ea8" @@ -43,42 +43,42 @@ const ( // SetPersonalSpaceQuotaPermissionID is the hardcoded setting UUID for the set personal space quota permission SetPersonalSpaceQuotaPermissionID string = "4e6f9709-f9e7-44f1-95d4-b762d27b7896" // SetPersonalSpaceQuotaPermissionName is the hardcoded setting name for the set personal space quota permission - SetPersonalSpaceQuotaPermissionName string = "set-space-quota" + SetPersonalSpaceQuotaPermissionName string = "Drives.ReadWritePersonalQuota" // SetProjectSpaceQuotaPermissionID is the hardcoded setting UUID for the set project space quota permission SetProjectSpaceQuotaPermissionID string = "977f0ae6-0da2-4856-93f3-22e0a8482489" // SetProjectSpaceQuotaPermissionName is the hardcoded setting name for the set project space quota permission - SetProjectSpaceQuotaPermissionName string = "Drive.ReadWriteQuota.Project" + SetProjectSpaceQuotaPermissionName string = "Drives.ReadWriteProjectQuota" // ListAllSpacesPermissionID is the hardcoded setting UUID for the list all spaces permission ListAllSpacesPermissionID string = "016f6ddd-9501-4a0a-8ebe-64a20ee8ec82" // ListAllSpacesPermissionName is the hardcoded setting name for the list all spaces permission - ListAllSpacesPermissionName string = "list-all-spaces" + ListAllSpacesPermissionName string = "Drives.List" // CreateSpacePermissionID is the hardcoded setting UUID for the create space permission CreateSpacePermissionID string = "79e13b30-3e22-11eb-bc51-0b9f0bad9a58" // CreateSpacePermissionName is the hardcoded setting name for the create space permission - CreateSpacePermissionName string = "create-space" + CreateSpacePermissionName string = "Drives.Create" // DeleteHomeSpacesPermissionID is the hardcoded setting UUID for the delete home space permission DeleteHomeSpacesPermissionID string = "5de9fe0a-4bc5-4a47-b758-28f370caf169" // DeleteHomeSpacesPermissionName is the hardcoded setting name for the delete home space permission - DeleteHomeSpacesPermissionName string = "delete-all-home-spaces" + DeleteHomeSpacesPermissionName string = "Drives.DeletePersonal" // DeleteAllSpacesPermissionID is the hardcoded setting UUID for the delete all spaces permission DeleteAllSpacesPermissionID string = "fb60b004-c1fa-4f09-bf87-55ce7d46ac61" // DeleteAllSpacesPermissionName is the hardcoded setting name for the delete all space permission - DeleteAllSpacesPermissionName string = "delete-all-spaces" + DeleteAllSpacesPermissionName string = "Drives.DeleteProject" // ManageSpacePropertiesPermissionID is the hardcoded setting UUID for the manage space properties permission ManageSpacePropertiesPermissionID string = "b44b4054-31a2-42b8-bb71-968b15cfbd4f" // ManageSpacePropertiesPermissionName is the hardcoded setting name for the manage space properties permission - ManageSpacePropertiesPermissionName string = "Drive.ReadWrite" + ManageSpacePropertiesPermissionName string = "Drives.ReadWrite" // SpaceAbilityPermissionID is the hardcoded setting UUID for the space ability permission SpaceAbilityPermissionID string = "cf3faa8c-50d9-4f84-9650-ff9faf21aa9d" // SpaceAbilityPermissionName is the hardcoded setting name for the space ability permission - SpaceAbilityPermissionName string = "Drive.ReadWriteEnabled" + SpaceAbilityPermissionName string = "Drives.ReadWriteEnabled" // SettingUUIDProfileLanguage is the hardcoded setting UUID for the user profile language SettingUUIDProfileLanguage = "aa8cfbe5-95d4-4f7e-a032-c3c01f5f062f" @@ -88,20 +88,20 @@ const ( // AccountManagementPermissionID is the hardcoded setting UUID for the account management permission AccountManagementPermissionID string = "8e587774-d929-4215-910b-a317b1e80f73" // AccountManagementPermissionName is the hardcoded setting name for the account management permission - AccountManagementPermissionName string = "account-management" + AccountManagementPermissionName string = "Accounts.ReadWrite" // GroupManagementPermissionID is the hardcoded setting UUID for the group management permission GroupManagementPermissionID string = "522adfbe-5908-45b4-b135-41979de73245" // GroupManagementPermissionName is the hardcoded setting name for the group management permission - GroupManagementPermissionName string = "group-management" + GroupManagementPermissionName string = "Groups.ReadWrite" // SelfManagementPermissionID is the hardcoded setting UUID for the self management permission SelfManagementPermissionID string = "e03070e9-4362-4cc6-a872-1c7cb2eb2b8e" // SelfManagementPermissionName is the hardcoded setting name for the self management permission - SelfManagementPermissionName string = "self-management" + SelfManagementPermissionName string = "Self.ReadWrite" // ChangeLogoPermissionID is the hardcoded setting UUID for the change-logo permission ChangeLogoPermissionID string = "ed83fc10-1f54-4a9e-b5a7-fb517f5f3e01" // ChangeLogoPermissionName is the hardcoded setting name for the change-logo permission - ChangeLogoPermissionName string = "change-logo" + ChangeLogoPermissionName string = "Logo.Write" // WritePublicLinkPermissionID is the hardcoded setting UUID for the PublicLink.Write permission WritePublicLinkPermissionID string = "11516bbd-7157-49e1-b6ac-d00c820f980b" @@ -114,7 +114,7 @@ func GenerateBundlesDefaultRoles() []*settingsmsg.Bundle { return []*settingsmsg.Bundle{ generateBundleAdminRole(), generateBundleUserRole(), - generateBundleGuestRole(), + generateBundleUserLightRole(), generateBundleProfileRequest(), generateBundleSpaceAdminRole(), } @@ -481,8 +481,8 @@ func generateBundleSpaceAdminRole() *settingsmsg.Bundle { }, }, { - Id: "640e00d2-4df8-41bd-b1c2-9f30a01e0e99", - Name: "language-readwrite", + Id: LanguageReadWriteID, + Name: LanguageReadWriteName, DisplayName: "Permission to read and set the language (self)", Resource: &settingsmsg.Resource{ Type: settingsmsg.Resource_TYPE_SETTING, @@ -572,8 +572,8 @@ func generateBundleUserRole() *settingsmsg.Bundle { }, Settings: []*settingsmsg.Setting{ { - Id: "640e00d2-4df8-41bd-b1c2-9f30a01e0e99", - Name: "language-readwrite", + Id: LanguageReadWriteID, + Name: LanguageReadWriteName, DisplayName: "Permission to read and set the language (self)", Resource: &settingsmsg.Resource{ Type: settingsmsg.Resource_TYPE_SETTING, @@ -651,20 +651,20 @@ func generateBundleUserRole() *settingsmsg.Bundle { } } -func generateBundleGuestRole() *settingsmsg.Bundle { +func generateBundleUserLightRole() *settingsmsg.Bundle { return &settingsmsg.Bundle{ - Id: BundleUUIDRoleGuest, - Name: "guest", + Id: BundleUUIDRoleUserLight, + Name: "user-light", Type: settingsmsg.Bundle_TYPE_ROLE, Extension: "ocis-roles", - DisplayName: "Guest", + DisplayName: "User Light", Resource: &settingsmsg.Resource{ Type: settingsmsg.Resource_TYPE_SYSTEM, }, Settings: []*settingsmsg.Setting{ { - Id: "ca878636-8b1a-4fae-8282-8617a4c13597", - Name: "language-readwrite", + Id: LanguageReadWriteID, + Name: LanguageReadWriteName, DisplayName: "Permission to read and set the language (self)", Resource: &settingsmsg.Resource{ Type: settingsmsg.Resource_TYPE_SETTING, diff --git a/services/settings/pkg/tracing/tracing.go b/services/settings/pkg/tracing/tracing.go deleted file mode 100644 index 46a5bbd81..000000000 --- a/services/settings/pkg/tracing/tracing.go +++ /dev/null @@ -1,23 +0,0 @@ -package tracing - -import ( - pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" - "github.com/owncloud/ocis/v2/services/settings/pkg/config" - "go.opentelemetry.io/otel/trace" -) - -var ( - // TraceProvider is the global trace provider for the settings service. - TraceProvider = trace.NewNoopTracerProvider() -) - -func Configure(cfg *config.Config) error { - var err error - if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { - return err - } - } - - return nil -} diff --git a/services/sharing/pkg/command/root.go b/services/sharing/pkg/command/root.go index 406e09b6f..fba13bf90 100644 --- a/services/sharing/pkg/command/root.go +++ b/services/sharing/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/sharing/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the sharing command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new sharing.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Sharing.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Sharing, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/sharing/pkg/command/server.go b/services/sharing/pkg/command/server.go index 6154ff20b..11a521b0c 100644 --- a/services/sharing/pkg/command/server.go +++ b/services/sharing/pkg/command/server.go @@ -11,7 +11,7 @@ import ( "github.com/gofrs/uuid" "github.com/oklog/run" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/sync" "github.com/owncloud/ocis/v2/ocis-pkg/version" "github.com/owncloud/ocis/v2/services/sharing/pkg/config" @@ -55,12 +55,16 @@ func Server(cfg *config.Config) *cli.Command { } } - pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") - - rcfg := revaconfig.SharingConfigFromStruct(cfg) - gr.Add(func() error { - runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + rCfg := revaconfig.SharingConfigFromStruct(cfg) + reg := registry.GetRegistry() + + runtime.RunWithOptions(rCfg, pidFile, + runtime.WithLogger(&logger.Logger), + runtime.WithRegistry(reg), + ) + return nil }, func(err error) { logger.Error(). @@ -90,15 +94,9 @@ func Server(cfg *config.Config) *cli.Command { sync.Trap(&gr, cancel) } - if err := external.RegisterGRPCEndpoint( - ctx, - cfg.GRPC.Namespace+"."+cfg.Service.Name, - uuid.Must(uuid.NewV4()).String(), - cfg.GRPC.Addr, - version.GetString(), - logger, - ); err != nil { - logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, version.GetString()) + if err := registry.RegisterService(ctx, grpcSvc, logger); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc service") } return gr.Run() diff --git a/services/sharing/pkg/config/defaults/defaultconfig.go b/services/sharing/pkg/config/defaults/defaultconfig.go index 044e4694a..3a2d450d2 100644 --- a/services/sharing/pkg/config/defaults/defaultconfig.go +++ b/services/sharing/pkg/config/defaults/defaultconfig.go @@ -41,11 +41,11 @@ func DefaultConfig() *config.Config { File: filepath.Join(defaults.BaseDataPath(), "storage", "shares.json"), }, CS3: config.UserSharingCS3Driver{ - ProviderAddr: "127.0.0.1:9215", // system storage + ProviderAddr: "com.owncloud.api.storage-system", SystemUserIDP: "internal", }, JSONCS3: config.UserSharingJSONCS3Driver{ - ProviderAddr: "127.0.0.1:9215", // system storage + ProviderAddr: "com.owncloud.api.storage-system", SystemUserIDP: "internal", }, OwnCloudSQL: config.UserSharingOwnCloudSQLDriver{ @@ -61,11 +61,11 @@ func DefaultConfig() *config.Config { File: filepath.Join(defaults.BaseDataPath(), "storage", "publicshares.json"), }, CS3: config.PublicSharingCS3Driver{ - ProviderAddr: "127.0.0.1:9215", // system storage + ProviderAddr: "com.owncloud.api.storage-system", // system storage SystemUserIDP: "internal", }, JSONCS3: config.PublicSharingJSONCS3Driver{ - ProviderAddr: "127.0.0.1:9215", // system storage + ProviderAddr: "com.owncloud.api.storage-system", // system storage SystemUserIDP: "internal", }, // TODO implement and add owncloudsql publicshare driver diff --git a/services/storage-publiclink/pkg/command/root.go b/services/storage-publiclink/pkg/command/root.go index 141d7ac6d..9ca05cc71 100644 --- a/services/storage-publiclink/pkg/command/root.go +++ b/services/storage-publiclink/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the accounts command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new accounts.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.StoragePublicLink.Commons = cfg.Commons - return SutureService{ - cfg: cfg.StoragePublicLink, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/storage-publiclink/pkg/command/server.go b/services/storage-publiclink/pkg/command/server.go index 1c731ee52..9eb75d21d 100644 --- a/services/storage-publiclink/pkg/command/server.go +++ b/services/storage-publiclink/pkg/command/server.go @@ -10,7 +10,7 @@ import ( "github.com/gofrs/uuid" "github.com/oklog/run" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/sync" "github.com/owncloud/ocis/v2/ocis-pkg/version" "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/config" @@ -42,12 +42,16 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") - - rcfg := revaconfig.StoragePublicLinkConfigFromStruct(cfg) - gr.Add(func() error { - runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + rCfg := revaconfig.StoragePublicLinkConfigFromStruct(cfg) + reg := registry.GetRegistry() + + runtime.RunWithOptions(rCfg, pidFile, + runtime.WithLogger(&logger.Logger), + runtime.WithRegistry(reg), + ) + return nil }, func(err error) { logger.Error(). @@ -77,15 +81,9 @@ func Server(cfg *config.Config) *cli.Command { sync.Trap(&gr, cancel) } - if err := external.RegisterGRPCEndpoint( - ctx, - cfg.GRPC.Namespace+"."+cfg.Service.Name, - uuid.Must(uuid.NewV4()).String(), - cfg.GRPC.Addr, - version.GetString(), - logger, - ); err != nil { - logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, version.GetString()) + if err := registry.RegisterService(ctx, grpcSvc, logger); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc service") } return gr.Run() diff --git a/services/storage-shares/pkg/command/root.go b/services/storage-shares/pkg/command/root.go index faf2066e6..fd36affa1 100644 --- a/services/storage-shares/pkg/command/root.go +++ b/services/storage-shares/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/storage-shares/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the accounts command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new storage-shares.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.StorageShares.Commons = cfg.Commons - return SutureService{ - cfg: cfg.StorageShares, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/storage-shares/pkg/command/server.go b/services/storage-shares/pkg/command/server.go index d3e507e2b..78e546f0e 100644 --- a/services/storage-shares/pkg/command/server.go +++ b/services/storage-shares/pkg/command/server.go @@ -10,7 +10,7 @@ import ( "github.com/gofrs/uuid" "github.com/oklog/run" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/sync" "github.com/owncloud/ocis/v2/ocis-pkg/version" "github.com/owncloud/ocis/v2/services/storage-shares/pkg/config" @@ -42,12 +42,16 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") - - rcfg := revaconfig.StorageSharesConfigFromStruct(cfg) - gr.Add(func() error { - runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + rCfg := revaconfig.StorageSharesConfigFromStruct(cfg) + reg := registry.GetRegistry() + + runtime.RunWithOptions(rCfg, pidFile, + runtime.WithLogger(&logger.Logger), + runtime.WithRegistry(reg), + ) + return nil }, func(err error) { logger.Error(). @@ -77,15 +81,9 @@ func Server(cfg *config.Config) *cli.Command { sync.Trap(&gr, cancel) } - if err := external.RegisterGRPCEndpoint( - ctx, - cfg.GRPC.Namespace+"."+cfg.Service.Name, - uuid.Must(uuid.NewV4()).String(), - cfg.GRPC.Addr, - version.GetString(), - logger, - ); err != nil { - logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, version.GetString()) + if err := registry.RegisterService(ctx, grpcSvc, logger); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc service") } return gr.Run() diff --git a/services/storage-shares/pkg/config/defaults/defaultconfig.go b/services/storage-shares/pkg/config/defaults/defaultconfig.go index 44680f424..202cc0241 100644 --- a/services/storage-shares/pkg/config/defaults/defaultconfig.go +++ b/services/storage-shares/pkg/config/defaults/defaultconfig.go @@ -34,7 +34,7 @@ func DefaultConfig() *config.Config { Reva: shared.DefaultRevaConfig(), MountID: "7639e57c-4433-4a12-8201-722fd0009154", ReadOnly: false, - SharesProviderEndpoint: "localhost:9150", + SharesProviderEndpoint: "com.owncloud.api.sharing", } } diff --git a/services/storage-system/pkg/command/root.go b/services/storage-system/pkg/command/root.go index 0f1cde0b6..92b6e757a 100644 --- a/services/storage-system/pkg/command/root.go +++ b/services/storage-system/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/storage-system/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the storage-system command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new storage-system.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.StorageSystem.Commons = cfg.Commons - return SutureService{ - cfg: cfg.StorageSystem, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/storage-system/pkg/command/server.go b/services/storage-system/pkg/command/server.go index de6095a66..03e5ab825 100644 --- a/services/storage-system/pkg/command/server.go +++ b/services/storage-system/pkg/command/server.go @@ -10,7 +10,7 @@ import ( "github.com/gofrs/uuid" "github.com/oklog/run" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/sync" "github.com/owncloud/ocis/v2/ocis-pkg/version" "github.com/owncloud/ocis/v2/services/storage-system/pkg/config" @@ -42,12 +42,16 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") - - rcfg := revaconfig.StorageSystemFromStruct(cfg) - gr.Add(func() error { - runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + rCfg := revaconfig.StorageSystemFromStruct(cfg) + reg := registry.GetRegistry() + + runtime.RunWithOptions(rCfg, pidFile, + runtime.WithLogger(&logger.Logger), + runtime.WithRegistry(reg), + ) + return nil }, func(err error) { logger.Error(). @@ -77,26 +81,14 @@ func Server(cfg *config.Config) *cli.Command { sync.Trap(&gr, cancel) } - if err := external.RegisterGRPCEndpoint( - ctx, - cfg.GRPC.Namespace+"."+cfg.Service.Name, - uuid.Must(uuid.NewV4()).String(), - cfg.GRPC.Addr, - version.GetString(), - logger, - ); err != nil { - logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, version.GetString()) + if err := registry.RegisterService(ctx, grpcSvc, logger); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc service") } - if err := external.RegisterHTTPEndpoint( - ctx, - cfg.HTTP.Namespace+"."+cfg.Service.Name, - uuid.Must(uuid.NewV4()).String(), - cfg.HTTP.Addr, - version.GetString(), - logger, - ); err != nil { - logger.Fatal().Err(err).Msg("failed to register the http endpoint") + httpScv := registry.BuildHTTPService(cfg.HTTP.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.HTTP.Addr, version.GetString()) + if err := registry.RegisterService(ctx, httpScv, logger); err != nil { + logger.Fatal().Err(err).Msg("failed to register the http service") } return gr.Run() diff --git a/services/storage-system/pkg/revaconfig/config.go b/services/storage-system/pkg/revaconfig/config.go index 3f9ebd1b2..7f079e768 100644 --- a/services/storage-system/pkg/revaconfig/config.go +++ b/services/storage-system/pkg/revaconfig/config.go @@ -34,12 +34,12 @@ func StorageSystemFromStruct(cfg *config.Config) map[string]interface{} { "services": map[string]interface{}{ "gateway": map[string]interface{}{ // registries are located on the gateway - "authregistrysvc": cfg.GRPC.Addr, - "storageregistrysvc": cfg.GRPC.Addr, + "authregistrysvc": "com.owncloud.api.storage-system", + "storageregistrysvc": "com.owncloud.api.storage-system", // user metadata is located on the users services - "userprovidersvc": cfg.GRPC.Addr, - "groupprovidersvc": cfg.GRPC.Addr, - "permissionssvc": cfg.GRPC.Addr, + "userprovidersvc": "com.owncloud.api.storage-system", + "groupprovidersvc": "com.owncloud.api.storage-system", + "permissionssvc": "com.owncloud.api.storage-system", // other "disable_home_creation_on_login": true, // metadata manually creates a space // metadata always uses the simple upload, so no transfer secret or datagateway needed @@ -69,7 +69,7 @@ func StorageSystemFromStruct(cfg *config.Config) map[string]interface{} { "drivers": map[string]interface{}{ "static": map[string]interface{}{ "rules": map[string]interface{}{ - "machine": cfg.GRPC.Addr, + "machine": "com.owncloud.api.storage-system", }, }, }, @@ -79,7 +79,7 @@ func StorageSystemFromStruct(cfg *config.Config) map[string]interface{} { "auth_managers": map[string]interface{}{ "machine": map[string]interface{}{ "api_key": cfg.SystemUserAPIKey, - "gateway_addr": cfg.GRPC.Addr, + "gateway_addr": "com.owncloud.api.storage-system", }, }, }, @@ -95,7 +95,7 @@ func StorageSystemFromStruct(cfg *config.Config) map[string]interface{} { "static": map[string]interface{}{ "rules": map[string]interface{}{ "/": map[string]interface{}{ - "address": cfg.GRPC.Addr, + "address": "com.owncloud.api.storage-system", }, }, }, @@ -161,7 +161,7 @@ func metadataDrivers(cfg *config.Config) map[string]interface{} { "user_layout": "{{.Id.OpaqueId}}", "treetime_accounting": false, "treesize_accounting": false, - "permissionssvc": cfg.GRPC.Addr, + "permissionssvc": "com.owncloud.api.storage-system", "max_acquire_lock_cycles": cfg.Drivers.OCIS.MaxAcquireLockCycles, "lock_cycle_duration_factor": cfg.Drivers.OCIS.LockCycleDurationFactor, "statcache": map[string]interface{}{ diff --git a/services/storage-users/pkg/command/root.go b/services/storage-users/pkg/command/root.go index 848e866c7..777ec6f2f 100644 --- a/services/storage-users/pkg/command/root.go +++ b/services/storage-users/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/storage-users/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -37,25 +34,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the accounts command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new storage-users.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.StorageUsers.Commons = cfg.Commons - return SutureService{ - cfg: cfg.StorageUsers, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/storage-users/pkg/command/server.go b/services/storage-users/pkg/command/server.go index ffff3afe2..d7fa6b986 100644 --- a/services/storage-users/pkg/command/server.go +++ b/services/storage-users/pkg/command/server.go @@ -11,7 +11,7 @@ import ( "github.com/gofrs/uuid" "github.com/oklog/run" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/sync" "github.com/owncloud/ocis/v2/ocis-pkg/version" "github.com/owncloud/ocis/v2/services/storage-users/pkg/config" @@ -44,12 +44,16 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") - - rcfg := revaconfig.StorageUsersConfigFromStruct(cfg) - gr.Add(func() error { - runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + rCfg := revaconfig.StorageUsersConfigFromStruct(cfg) + reg := registry.GetRegistry() + + runtime.RunWithOptions(rCfg, pidFile, + runtime.WithLogger(&logger.Logger), + runtime.WithRegistry(reg), + ) + return nil }, func(err error) { logger.Error(). @@ -79,15 +83,9 @@ func Server(cfg *config.Config) *cli.Command { sync.Trap(&gr, cancel) } - if err := external.RegisterGRPCEndpoint( - ctx, - cfg.GRPC.Namespace+"."+cfg.Service.Name, - uuid.Must(uuid.NewV4()).String(), - cfg.GRPC.Addr, - version.GetString(), - logger, - ); err != nil { - logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, version.GetString()) + if err := registry.RegisterService(ctx, grpcSvc, logger); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc service") } { @@ -96,12 +94,12 @@ func Server(cfg *config.Config) *cli.Command { logger.Fatal().Err(err).Msg("can't connect to nats") } - gw, err := pool.GetGatewayServiceClient(cfg.Reva.Address) + selector, err := pool.GatewaySelector(cfg.Reva.Address, pool.WithRegistry(registry.GetRegistry())) if err != nil { return err } - eventSVC, err := event.NewService(gw, stream, logger, *cfg) + eventSVC, err := event.NewService(selector, stream, logger, *cfg) if err != nil { logger.Fatal().Err(err).Msg("can't create event service") } diff --git a/services/storage-users/pkg/config/config.go b/services/storage-users/pkg/config/config.go index 152ff6310..1d22962e7 100644 --- a/services/storage-users/pkg/config/config.go +++ b/services/storage-users/pkg/config/config.go @@ -272,7 +272,7 @@ type Tasks struct { // PurgeTrashBin contains all necessary configurations to clean up the respective trash cans type PurgeTrashBin struct { - UserID string `yaml:"user_id" env:"OCIS_ADMIN_USER_ID;STORAGE_USERS_PURGE_TRASH_BIN_USER_ID" desc:"ID of the user who collects all necessary information for deletion."` + UserID string `yaml:"user_id" env:"OCIS_ADMIN_USER_ID;STORAGE_USERS_PURGE_TRASH_BIN_USER_ID" desc:"ID of the user who collects all necessary information for deletion. Consider that the UUID can be encoded in some LDAP deployment configurations like in .ldif files. These need to be decoded beforehand."` PersonalDeleteBefore time.Duration `yaml:"personal_delete_before" env:"STORAGE_USERS_PURGE_TRASH_BIN_PERSONAL_DELETE_BEFORE" desc:"Specifies the period of time in which items that have been in the personal trash-bin for longer than this value should be deleted. A value of 0 means no automatic deletion. The value is human-readable, valid values are '24h', '60m', '60s' etc."` ProjectDeleteBefore time.Duration `yaml:"project_delete_before" env:"STORAGE_USERS_PURGE_TRASH_BIN_PROJECT_DELETE_BEFORE" desc:"Specifies the period of time in which items that have been in the project trash-bin for longer than this value should be deleted. A value of 0 means no automatic deletion. The value is human-readable, valid values are '24h', '60m', '60s' etc."` } diff --git a/services/storage-users/pkg/config/defaults/defaultconfig.go b/services/storage-users/pkg/config/defaults/defaultconfig.go index 30a174f33..f8673c23e 100644 --- a/services/storage-users/pkg/config/defaults/defaultconfig.go +++ b/services/storage-users/pkg/config/defaults/defaultconfig.go @@ -58,7 +58,7 @@ func DefaultConfig() *config.Config { DBHost: "", DBPort: 3306, DBName: "owncloud", - UsersProviderEndpoint: "localhost:9144", + UsersProviderEndpoint: "com.owncloud.api.users", }, S3NG: config.S3NGDriver{ MetadataBackend: "messagepack", @@ -68,7 +68,7 @@ func DefaultConfig() *config.Config { Region: "default", PersonalSpaceAliasTemplate: "{{.SpaceType}}/{{.User.Username | lower}}", GeneralSpaceAliasTemplate: "{{.SpaceType}}/{{.SpaceName | replace \" \" \"-\" | lower}}", - PermissionsEndpoint: "127.0.0.1:9191", + PermissionsEndpoint: "com.owncloud.api.settings", MaxAcquireLockCycles: 20, LockCycleDurationFactor: 30, }, @@ -79,7 +79,7 @@ func DefaultConfig() *config.Config { UserLayout: "{{.Id.OpaqueId}}", PersonalSpaceAliasTemplate: "{{.SpaceType}}/{{.User.Username | lower}}", GeneralSpaceAliasTemplate: "{{.SpaceType}}/{{.SpaceName | replace \" \" \"-\" | lower}}", - PermissionsEndpoint: "127.0.0.1:9191", + PermissionsEndpoint: "com.owncloud.api.settings", MaxAcquireLockCycles: 20, LockCycleDurationFactor: 30, }, diff --git a/services/storage-users/pkg/event/service.go b/services/storage-users/pkg/event/service.go index b546a13b1..91384f75d 100644 --- a/services/storage-users/pkg/event/service.go +++ b/services/storage-users/pkg/event/service.go @@ -6,6 +6,7 @@ import ( apiGateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" apiUser "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/services/storage-users/pkg/config" "github.com/owncloud/ocis/v2/services/storage-users/pkg/task" @@ -17,19 +18,19 @@ const ( // Service wraps all common logic that is needed to react to incoming events. type Service struct { - gatewayClient apiGateway.GatewayAPIClient - eventStream events.Stream - logger log.Logger - config config.Config + gatewaySelector pool.Selectable[apiGateway.GatewayAPIClient] + eventStream events.Stream + logger log.Logger + config config.Config } // NewService prepares and returns a Service implementation. -func NewService(gatewayClient apiGateway.GatewayAPIClient, eventStream events.Stream, logger log.Logger, conf config.Config) (Service, error) { +func NewService(gatewaySelector pool.Selectable[apiGateway.GatewayAPIClient], eventStream events.Stream, logger log.Logger, conf config.Config) (Service, error) { svc := Service{ - gatewayClient: gatewayClient, - eventStream: eventStream, - logger: logger, - config: conf, + gatewaySelector: gatewaySelector, + eventStream: eventStream, + logger: logger, + config: conf, } return svc, nil @@ -69,7 +70,7 @@ func (s Service) Run() error { continue } - if err = task.PurgeTrashBin(executantID, deleteBefore, spaceType, s.gatewayClient, s.config.Commons.MachineAuthAPIKey); err != nil { + if err = task.PurgeTrashBin(executantID, deleteBefore, spaceType, s.gatewaySelector, s.config.Commons.MachineAuthAPIKey); err != nil { errs = append(errs, err) } } diff --git a/services/storage-users/pkg/task/task_suite_test.go b/services/storage-users/pkg/task/task_suite_test.go index dbcb7996a..5b904e7e3 100644 --- a/services/storage-users/pkg/task/task_suite_test.go +++ b/services/storage-users/pkg/task/task_suite_test.go @@ -3,10 +3,23 @@ package task_test import ( "testing" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + mRegistry "go-micro.dev/v4/registry" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) +func init() { + registry.Configure("memory") + r := registry.GetRegistry() + service := registry.BuildGRPCService("com.owncloud.api.gateway", "", "", "") + service.Nodes = []*mRegistry.Node{{ + Address: "any", + }} + + _ = r.Register(service) +} func TestTask(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Task Suite") diff --git a/services/storage-users/pkg/task/trash_bin.go b/services/storage-users/pkg/task/trash_bin.go index 354f9cf09..daf49fad1 100644 --- a/services/storage-users/pkg/task/trash_bin.go +++ b/services/storage-users/pkg/task/trash_bin.go @@ -9,6 +9,7 @@ import ( apiRpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" apiProvider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/errtypes" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/utils" ) @@ -16,13 +17,18 @@ import ( // the provided executantID must have space access. // removeBefore specifies how long an item must be in the trash-bin to be deleted, // items that stay there for a shorter time are ignored and kept in place. -func PurgeTrashBin(executantID *apiUser.UserId, deleteBefore time.Time, spaceType SpaceType, gwc apiGateway.GatewayAPIClient, machineAuthAPIKey string) error { - executantCtx, _, err := utils.Impersonate(executantID, gwc, machineAuthAPIKey) +func PurgeTrashBin(executantID *apiUser.UserId, deleteBefore time.Time, spaceType SpaceType, gatewaySelector pool.Selectable[apiGateway.GatewayAPIClient], machineAuthAPIKey string) error { + gatewayClient, err := gatewaySelector.Next() if err != nil { return err } - listStorageSpacesResponse, err := gwc.ListStorageSpaces(executantCtx, &apiProvider.ListStorageSpacesRequest{ + executantCtx, _, err := utils.Impersonate(executantID, gatewayClient, machineAuthAPIKey) + if err != nil { + return err + } + + listStorageSpacesResponse, err := gatewayClient.ListStorageSpaces(executantCtx, &apiProvider.ListStorageSpacesRequest{ Filters: []*apiProvider.ListStorageSpacesRequest_Filter{ { Type: apiProvider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE, @@ -77,12 +83,12 @@ func PurgeTrashBin(executantID *apiUser.UserId, deleteBefore time.Time, spaceTyp return fmt.Errorf("can't impersonate space user for space: %s", storageSpace.GetId().GetOpaqueId()) } - impersonatedCtx, _, err := utils.Impersonate(impersonationID, gwc, machineAuthAPIKey) + impersonatedCtx, _, err := utils.Impersonate(impersonationID, gatewayClient, machineAuthAPIKey) if err != nil { return err } - listRecycleResponse, err := gwc.ListRecycle(impersonatedCtx, &apiProvider.ListRecycleRequest{Ref: storageSpaceReference}) + listRecycleResponse, err := gatewayClient.ListRecycle(impersonatedCtx, &apiProvider.ListRecycleRequest{Ref: storageSpaceReference}) if err != nil { return err } @@ -93,7 +99,7 @@ func PurgeTrashBin(executantID *apiUser.UserId, deleteBefore time.Time, spaceTyp continue } - purgeRecycleResponse, err := gwc.PurgeRecycle(impersonatedCtx, &apiProvider.PurgeRecycleRequest{ + purgeRecycleResponse, err := gatewayClient.PurgeRecycle(impersonatedCtx, &apiProvider.PurgeRecycleRequest{ Ref: storageSpaceReference, Key: recycleItem.Key, }) diff --git a/services/storage-users/pkg/task/trash_bin_test.go b/services/storage-users/pkg/task/trash_bin_test.go index 1da8c231d..1066565c9 100644 --- a/services/storage-users/pkg/task/trash_bin_test.go +++ b/services/storage-users/pkg/task/trash_bin_test.go @@ -4,21 +4,23 @@ import ( "context" "encoding/json" "errors" - "google.golang.org/grpc" "time" apiGateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" apiUser "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" apiRpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" apiProvider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" apiTypes "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/v2/pkg/rgrpc/status" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/utils" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/owncloud/ocis/v2/services/storage-users/pkg/task" "github.com/stretchr/testify/mock" + "google.golang.org/grpc" ) func MustMarshal(v any) []byte { @@ -31,7 +33,8 @@ func MustMarshal(v any) []byte { var _ = Describe("trash", func() { var ( - gwc *cs3mocks.GatewayAPIClient + gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] ctx context.Context now time.Time genericError error @@ -45,7 +48,16 @@ var _ = Describe("trash", func() { ) BeforeEach(func() { - gwc = &cs3mocks.GatewayAPIClient{} + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") + gatewayClient = &cs3mocks.GatewayAPIClient{} + gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return gatewayClient + }, + ) + ctx = context.Background() now = time.Now() genericError = errors.New("any") @@ -100,36 +112,36 @@ var _ = Describe("trash", func() { Describe("PurgeTrashBin", func() { It("throws an error if the user cannot authenticate", func() { - gwc.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) - gwc.On("Authenticate", mock.Anything, mock.Anything).Return(nil, genericError) + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) + gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(nil, genericError) - err := task.PurgeTrashBin(user.Id, now, task.Project, gwc, "") + err := task.PurgeTrashBin(user.Id, now, task.Project, gatewaySelector, "") Expect(err).To(HaveOccurred()) }) It("throws an error if space listing fails", func() { - gwc.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) - gwc.On("Authenticate", mock.Anything, mock.Anything).Return(authenticateResponse, nil) - gwc.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(nil, genericError) + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) + gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(authenticateResponse, nil) + gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(nil, genericError) - err := task.PurgeTrashBin(user.Id, now, task.Project, gwc, "") + err := task.PurgeTrashBin(user.Id, now, task.Project, gatewaySelector, "") Expect(err).To(HaveOccurred()) }) It("throws an error if a personal space user can't be impersonated", func() { listStorageSpacesResponse.StorageSpaces = []*apiProvider.StorageSpace{personalSpace} - gwc.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) - gwc.On("Authenticate", mock.Anything, mock.Anything).Return(authenticateResponse, nil) - gwc.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listStorageSpacesResponse, nil) + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) + gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(authenticateResponse, nil) + gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listStorageSpacesResponse, nil) - err := task.PurgeTrashBin(user.Id, now, task.Project, gwc, "") + err := task.PurgeTrashBin(user.Id, now, task.Project, gatewaySelector, "") Expect(err).To(MatchError(errors.New("can't impersonate space user for space: personal"))) }) It("throws an error if a project space user can't be impersonated", func() { listStorageSpacesResponse.StorageSpaces = []*apiProvider.StorageSpace{projectSpace} - gwc.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) - gwc.On("Authenticate", mock.Anything, mock.Anything).Return(authenticateResponse, nil) - gwc.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listStorageSpacesResponse, nil) + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) + gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(authenticateResponse, nil) + gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listStorageSpacesResponse, nil) - err := task.PurgeTrashBin(user.Id, now, task.Project, gwc, "") + err := task.PurgeTrashBin(user.Id, now, task.Project, gatewaySelector, "") Expect(err).To(MatchError(errors.New("can't impersonate space user for space: project"))) }) It("throws an error if a project space has no user with delete permissions", func() { @@ -143,11 +155,11 @@ var _ = Describe("trash", func() { }), }, } - gwc.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) - gwc.On("Authenticate", mock.Anything, mock.Anything).Return(authenticateResponse, nil) - gwc.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listStorageSpacesResponse, nil) + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) + gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(authenticateResponse, nil) + gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listStorageSpacesResponse, nil) - err := task.PurgeTrashBin(user.Id, now, task.Project, gwc, "") + err := task.PurgeTrashBin(user.Id, now, task.Project, gatewaySelector, "") Expect(err).To(MatchError(errors.New("can't impersonate space user for space: project"))) }) It("only deletes items older than the specified period", func() { @@ -188,17 +200,17 @@ var _ = Describe("trash", func() { }, } - gwc.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) - gwc.On("Authenticate", mock.Anything, mock.Anything).Return(authenticateResponse, nil) - gwc.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listStorageSpacesResponse, nil) - gwc.On("ListRecycle", mock.Anything, mock.Anything).Return( + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) + gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(authenticateResponse, nil) + gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listStorageSpacesResponse, nil) + gatewayClient.On("ListRecycle", mock.Anything, mock.Anything).Return( func(_ context.Context, req *apiProvider.ListRecycleRequest, _ ...grpc.CallOption) *apiProvider.ListRecycleResponse { return &apiProvider.ListRecycleResponse{ RecycleItems: recycleItems[req.Ref.ResourceId.OpaqueId], } }, nil, ) - gwc.On("PurgeRecycle", mock.Anything, mock.Anything).Return( + gatewayClient.On("PurgeRecycle", mock.Anything, mock.Anything).Return( func(_ context.Context, req *apiProvider.PurgeRecycleRequest, _ ...grpc.CallOption) *apiProvider.PurgeRecycleResponse { var items []*apiProvider.RecycleItem for _, item := range recycleItems[req.Ref.ResourceId.OpaqueId] { @@ -219,7 +231,7 @@ var _ = Describe("trash", func() { }, nil, ) - err := task.PurgeTrashBin(user.Id, now, task.Project, gwc, "") + err := task.PurgeTrashBin(user.Id, now, task.Project, gatewaySelector, "") Expect(err).To(BeNil()) Expect(recycleItems["personal"]).To(HaveLen(2)) Expect(recycleItems["project"]).To(HaveLen(2)) diff --git a/services/store/pkg/command/root.go b/services/store/pkg/command/root.go index 795a66a74..8e208dba5 100644 --- a/services/store/pkg/command/root.go +++ b/services/store/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/store/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the store command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new store.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Store.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Store, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/thumbnails/pkg/command/root.go b/services/thumbnails/pkg/command/root.go index 28588a261..8ddf1c23d 100644 --- a/services/thumbnails/pkg/command/root.go +++ b/services/thumbnails/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the thumbnails command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new thumbnails.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Thumbnails.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Thumbnails, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/thumbnails/pkg/server/grpc/server.go b/services/thumbnails/pkg/server/grpc/server.go index a783d18fa..26d10bc71 100644 --- a/services/thumbnails/pkg/server/grpc/server.go +++ b/services/thumbnails/pkg/server/grpc/server.go @@ -2,6 +2,7 @@ package grpc import ( "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" "github.com/owncloud/ocis/v2/ocis-pkg/version" thumbnailssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/thumbnails/v0" @@ -41,12 +42,14 @@ func NewService(opts ...Option) grpc.Service { options.Logger.Error().Err(err).Msg("could not get gateway client tls mode") return grpc.Service{} } - gc, err := pool.GetGatewayServiceClient(tconf.RevaGateway, + + gatewaySelector, err := pool.GatewaySelector(tconf.RevaGateway, pool.WithTLSCACert(options.Config.GRPCClientTLS.CACert), pool.WithTLSMode(tm), + pool.WithRegistry(registry.GetRegistry()), ) if err != nil { - options.Logger.Error().Err(err).Msg("could not get gateway client") + options.Logger.Error().Err(err).Msg("could not get gateway selector") return grpc.Service{} } var thumbnail decorators.DecoratedService @@ -61,8 +64,8 @@ func NewService(opts ...Option) grpc.Service { options.Logger, ), ), - svc.CS3Source(imgsource.NewCS3Source(tconf, gc)), - svc.CS3Client(gc), + svc.CS3Source(imgsource.NewCS3Source(tconf, gatewaySelector)), + svc.GatewaySelector(gatewaySelector), ) thumbnail = decorators.NewInstrument(thumbnail, options.Metrics) thumbnail = decorators.NewLogging(thumbnail, options.Logger) diff --git a/services/thumbnails/pkg/service/grpc/v0/decorators/tracing.go b/services/thumbnails/pkg/service/grpc/v0/decorators/tracing.go index d19828a70..636143d96 100644 --- a/services/thumbnails/pkg/service/grpc/v0/decorators/tracing.go +++ b/services/thumbnails/pkg/service/grpc/v0/decorators/tracing.go @@ -27,7 +27,10 @@ func (t tracing) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumbna if thumbnailsTracing.TraceProvider != nil { tracer := thumbnailsTracing.TraceProvider.Tracer("thumbnails") - ctx, span = tracer.Start(ctx, "Thumbnails.GetThumbnail") + spanOpts := []trace.SpanStartOption{ + trace.WithSpanKind(trace.SpanKindServer), + } + ctx, span = tracer.Start(ctx, "Thumbnails.GetThumbnail", spanOpts...) defer span.End() span.SetAttributes( diff --git a/services/thumbnails/pkg/service/grpc/v0/option.go b/services/thumbnails/pkg/service/grpc/v0/option.go index 19b341c58..00cf0eeab 100644 --- a/services/thumbnails/pkg/service/grpc/v0/option.go +++ b/services/thumbnails/pkg/service/grpc/v0/option.go @@ -4,7 +4,7 @@ import ( "net/http" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" "github.com/owncloud/ocis/v2/services/thumbnails/pkg/thumbnail/imgsource" @@ -22,7 +22,7 @@ type Options struct { ThumbnailStorage storage.Storage ImageSource imgsource.Source CS3Source imgsource.Source - CS3Client gateway.GatewayAPIClient + GatewaySelector pool.Selectable[gateway.GatewayAPIClient] } // newOptions initializes the available default options. @@ -71,14 +71,16 @@ func ThumbnailSource(val imgsource.Source) Option { } } +// CS3Source provides a function to set the CS3Source option func CS3Source(val imgsource.Source) Option { return func(o *Options) { o.CS3Source = val } } -func CS3Client(c gateway.GatewayAPIClient) Option { +// GatewaySelector adds a grpc client selector for the gateway service +func GatewaySelector(val pool.Selectable[gateway.GatewayAPIClient]) Option { return func(o *Options) { - o.CS3Client = c + o.GatewaySelector = val } } diff --git a/services/thumbnails/pkg/service/grpc/v0/service.go b/services/thumbnails/pkg/service/grpc/v0/service.go index 541e56bca..070613b0c 100644 --- a/services/thumbnails/pkg/service/grpc/v0/service.go +++ b/services/thumbnails/pkg/service/grpc/v0/service.go @@ -12,6 +12,7 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" "github.com/golang-jwt/jwt/v4" @@ -45,7 +46,7 @@ func NewService(opts ...Option) decorators.DecoratedService { webdavSource: options.ImageSource, cs3Source: options.CS3Source, logger: logger, - cs3Client: options.CS3Client, + selector: options.GatewaySelector, preprocessorOpts: PreprocessorOpts{ TxtFontFileMap: options.Config.Thumbnail.FontMapFile, }, @@ -65,10 +66,11 @@ type Thumbnail struct { webdavSource imgsource.Source cs3Source imgsource.Source logger log.Logger - cs3Client gateway.GatewayAPIClient + selector pool.Selectable[gateway.GatewayAPIClient] preprocessorOpts PreprocessorOpts } +// PreprocessorOpts holds the options for the preprocessor type PreprocessorOpts struct { TxtFontFileMap string } @@ -161,21 +163,24 @@ func (g Thumbnail) handleWebdavSource(ctx context.Context, req *thumbnailssvc.Ge } var auth, statPath string - if src.IsPublicLink { q := imgURL.Query() var rsp *gateway.AuthenticateResponse + client, err := g.selector.Next() + if err != nil { + return "", merrors.InternalServerError(g.serviceID, "could not select next gateway client: %s", err.Error()) + } if q.Get("signature") != "" && q.Get("expiration") != "" { // Handle pre-signed public links sig := q.Get("signature") exp := q.Get("expiration") - rsp, err = g.cs3Client.Authenticate(ctx, &gateway.AuthenticateRequest{ + rsp, err = client.Authenticate(ctx, &gateway.AuthenticateRequest{ Type: "publicshares", ClientId: src.PublicLinkToken, ClientSecret: strings.Join([]string{"signature", sig, exp}, "|"), }) } else { - rsp, err = g.cs3Client.Authenticate(ctx, &gateway.AuthenticateRequest{ + rsp, err = client.Authenticate(ctx, &gateway.AuthenticateRequest{ Type: "publicshares", ClientId: src.PublicLinkToken, // We pass an empty password because we expect non pre-signed public links @@ -248,8 +253,12 @@ func (g Thumbnail) stat(path, auth string) (*provider.StatResponse, error) { } } + client, err := g.selector.Next() + if err != nil { + return nil, merrors.InternalServerError(g.serviceID, "could not select next gateway client: %s", err.Error()) + } req := &provider.StatRequest{Ref: &ref} - rsp, err := g.cs3Client.Stat(ctx, req) + rsp, err := client.Stat(ctx, req) if err != nil { g.logger.Error().Err(err).Str("path", path).Msg("could not stat file") return nil, merrors.InternalServerError(g.serviceID, "could not stat file: %s", err.Error()) diff --git a/services/thumbnails/pkg/thumbnail/imgsource/cs3.go b/services/thumbnails/pkg/thumbnail/imgsource/cs3.go index be17365c0..d17648e5f 100644 --- a/services/thumbnails/pkg/thumbnail/imgsource/cs3.go +++ b/services/thumbnails/pkg/thumbnail/imgsource/cs3.go @@ -11,6 +11,7 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/rhttp" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" @@ -24,15 +25,17 @@ const ( TokenTransportHeader = "X-Reva-Transfer" ) +// CS3 implements a CS3 image source type CS3 struct { - client gateway.GatewayAPIClient - insecure bool + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + insecure bool } -func NewCS3Source(cfg config.Thumbnail, c gateway.GatewayAPIClient) CS3 { +// NewCS3Source configures a new CS3 image source +func NewCS3Source(cfg config.Thumbnail, gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) CS3 { return CS3{ - client: c, - insecure: cfg.CS3AllowInsecure, + gatewaySelector: gatewaySelector, + insecure: cfg.CS3AllowInsecure, } } @@ -51,8 +54,13 @@ func (s CS3) Get(ctx context.Context, path string) (io.ReadCloser, error) { Path: path, } } + ctx = metadata.AppendToOutgoingContext(context.Background(), revactx.TokenHeader, auth) - rsp, err := s.client.InitiateFileDownload(ctx, &provider.InitiateFileDownloadRequest{Ref: &ref}) + gwc, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + rsp, err := gwc.InitiateFileDownload(ctx, &provider.InitiateFileDownloadRequest{Ref: &ref}) if err != nil { return nil, err diff --git a/services/userlog/README.md b/services/userlog/README.md index 009c561bf..701fbfa29 100644 --- a/services/userlog/README.md +++ b/services/userlog/README.md @@ -30,6 +30,10 @@ For the time being, the configuration which user related events are of interest The `userlog` service provides an API to retrieve configured events. For now, this API is mostly following the [oc10 notification GET API](https://doc.owncloud.com/server/next/developer_manual/core/apis/ocs-notification-endpoint-v1.html#get-user-notifications). +## Subscribing + +Additionally to the oc10 API, the `userlog` service also provides an `/sse` (Server-Sent Events) endpoint to be informed by the server when an event happens. See [What is Server-Sent Events](https://medium.com/yemeksepeti-teknoloji/what-is-server-sent-events-sse-and-how-to-implement-it-904938bffd73) for a simple introduction and examples of server sent events. The `sse` endpoint will respect language changes of the user without needing to reconnect. Note that SSE has a limitation of six open connections per browser which can be reached if one has opened various tabs of the Web UI pointing to the same Infinite Scale instance. + ## Deleting To delete events for an user, use a `DELETE` request to `ocs/v2.php/apps/notifications/api/v1/notifications` containing the IDs to delete. diff --git a/services/userlog/pkg/command/root.go b/services/userlog/pkg/command/root.go index 4a7e79839..dd80e5acb 100644 --- a/services/userlog/pkg/command/root.go +++ b/services/userlog/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/userlog/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the userlog command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new userlog.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Userlog.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Userlog, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/userlog/pkg/command/server.go b/services/userlog/pkg/command/server.go index c814b322c..7417c1709 100644 --- a/services/userlog/pkg/command/server.go +++ b/services/userlog/pkg/command/server.go @@ -11,10 +11,12 @@ import ( "github.com/oklog/run" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" "github.com/owncloud/ocis/v2/ocis-pkg/handlers" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" "github.com/owncloud/ocis/v2/ocis-pkg/version" ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" "github.com/owncloud/ocis/v2/services/userlog/pkg/config" "github.com/owncloud/ocis/v2/services/userlog/pkg/config/parser" "github.com/owncloud/ocis/v2/services/userlog/pkg/logging" @@ -90,16 +92,18 @@ func Server(cfg *config.Config) *cli.Command { if err != nil { return err } - gwclient, err := pool.GetGatewayServiceClient( + gatewaySelector, err := pool.GatewaySelector( cfg.RevaGateway, pool.WithTLSCACert(cfg.GRPCClientTLS.CACert), pool.WithTLSMode(tm), + pool.WithRegistry(registry.GetRegistry()), ) if err != nil { - return fmt.Errorf("could not get reva client: %s", err) + return fmt.Errorf("could not get reva client selector: %s", err) } hClient := ehsvc.NewEventHistoryService("com.owncloud.api.eventhistory", ogrpc.DefaultClient()) + vClient := settingssvc.NewValueService("com.owncloud.api.settings", ogrpc.DefaultClient()) { server, err := http.Server( @@ -109,8 +113,9 @@ func Server(cfg *config.Config) *cli.Command { http.Metrics(mtrcs), http.Store(st), http.Consumer(consumer), - http.Gateway(gwclient), + http.GatewaySelector(gatewaySelector), http.History(hClient), + http.Value(vClient), http.RegisteredEvents(_registeredEvents), ) diff --git a/services/userlog/pkg/config/config.go b/services/userlog/pkg/config/config.go index 7f0b99675..b369ac22f 100644 --- a/services/userlog/pkg/config/config.go +++ b/services/userlog/pkg/config/config.go @@ -28,6 +28,8 @@ type Config struct { Events Events `yaml:"events"` Persistence Persistence `yaml:"persistence"` + DisableSSE bool `yaml:"disable_sse" env:"USERLOG_DISABLE_SSE" desc:"Disables server-sent events (sse). When disabled, clients will no longer be able to connect to the sse endpoint."` + Context context.Context `yaml:"-"` } @@ -54,7 +56,7 @@ type Events struct { type CORS struct { AllowedOrigins []string `yaml:"allow_origins" env:"OCIS_CORS_ALLOW_ORIGINS;USERLOG_CORS_ALLOW_ORIGINS" desc:"A comma-separated list of allowed CORS origins. See following chapter for more details: *Access-Control-Allow-Origin* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin"` AllowedMethods []string `yaml:"allow_methods" env:"OCIS_CORS_ALLOW_METHODS;USERLOG_CORS_ALLOW_METHODS" desc:"A comma-separated list of allowed CORS methods. See following chapter for more details: *Access-Control-Request-Method* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method"` - AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;USERLOG_CORS_ALLOW_HEADERS" desc:"A comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` + AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;USERLOG_CORS_ALLOW_HEADERS" desc:"A blank or comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` AllowCredentials bool `yaml:"allow_credentials" env:"OCIS_CORS_ALLOW_CREDENTIALS;USERLOG_CORS_ALLOW_CREDENTIALS" desc:"Allow credentials for CORS.See following chapter for more details: *Access-Control-Allow-Credentials* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials."` } diff --git a/services/userlog/pkg/server/http/option.go b/services/userlog/pkg/server/http/option.go index 11700b5c1..efe691101 100644 --- a/services/userlog/pkg/server/http/option.go +++ b/services/userlog/pkg/server/http/option.go @@ -5,8 +5,10 @@ import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/owncloud/ocis/v2/ocis-pkg/log" ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" "github.com/owncloud/ocis/v2/services/userlog/pkg/config" "github.com/owncloud/ocis/v2/services/userlog/pkg/metrics" "github.com/urfave/cli/v2" @@ -26,8 +28,9 @@ type Options struct { Namespace string Store store.Store Consumer events.Consumer - GatewayClient gateway.GatewayAPIClient + GatewaySelector pool.Selectable[gateway.GatewayAPIClient] HistoryClient ehsvc.EventHistoryService + ValueClient settingssvc.ValueService RegisteredEvents []events.Unmarshaller } @@ -98,10 +101,10 @@ func Consumer(consumer events.Consumer) Option { } } -// Gateway provides a function to configure the gateway client -func Gateway(gw gateway.GatewayAPIClient) Option { +// GatewaySelector provides a function to configure the gateway client selector +func GatewaySelector(gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) Option { return func(o *Options) { - o.GatewayClient = gw + o.GatewaySelector = gatewaySelector } } @@ -118,3 +121,10 @@ func RegisteredEvents(evs []events.Unmarshaller) Option { o.RegisteredEvents = evs } } + +// Value provides a function to configure the value service client +func Value(vs settingssvc.ValueService) Option { + return func(o *Options) { + o.ValueClient = vs + } +} diff --git a/services/userlog/pkg/server/http/server.go b/services/userlog/pkg/server/http/server.go index 280c5b775..705529ed2 100644 --- a/services/userlog/pkg/server/http/server.go +++ b/services/userlog/pkg/server/http/server.go @@ -75,7 +75,8 @@ func Server(opts ...Option) (http.Service, error) { svc.Store(options.Store), svc.Config(options.Config), svc.HistoryClient(options.HistoryClient), - svc.GatewayClient(options.GatewayClient), + svc.GatewaySelector(options.GatewaySelector), + svc.ValueClient(options.ValueClient), svc.RegisteredEvents(options.RegisteredEvents), ) if err != nil { diff --git a/services/userlog/pkg/service/conversion.go b/services/userlog/pkg/service/conversion.go index e1bce3525..e10191f90 100644 --- a/services/userlog/pkg/service/conversion.go +++ b/services/userlog/pkg/service/conversion.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "embed" - "errors" "fmt" "io/fs" "strings" @@ -16,10 +15,10 @@ import ( collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" "github.com/leonelquinteros/gotext" - ehmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/eventhistory/v0" ) //go:embed l10n/locale @@ -52,10 +51,9 @@ type OC10Notification struct { // Converter is responsible for converting eventhistory events to OC10Notifications type Converter struct { locale string - gwClient gateway.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] machineAuthAPIKey string serviceName string - registeredEvents map[string]events.Unmarshaller translationPath string // cached within one request not to query other service too much @@ -66,13 +64,12 @@ type Converter struct { } // NewConverter returns a new Converter -func NewConverter(loc string, gwc gateway.GatewayAPIClient, machineAuthAPIKey string, name string, translationPath string, registeredEvents map[string]events.Unmarshaller) *Converter { +func NewConverter(loc string, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], machineAuthAPIKey string, name string, translationPath string) *Converter { return &Converter{ locale: loc, - gwClient: gwc, + gatewaySelector: gatewaySelector, machineAuthAPIKey: machineAuthAPIKey, serviceName: name, - registeredEvents: registeredEvents, translationPath: translationPath, spaces: make(map[string]*storageprovider.StorageSpace), users: make(map[string]*user.User), @@ -82,20 +79,8 @@ func NewConverter(loc string, gwc gateway.GatewayAPIClient, machineAuthAPIKey st } // ConvertEvent converts an eventhistory event to an OC10Notification -func (c *Converter) ConvertEvent(event *ehmsg.Event) (OC10Notification, error) { - etype, ok := c.registeredEvents[event.Type] - if !ok { - // this should not happen - return OC10Notification{}, errors.New("eventtype not registered") - } - - einterface, err := etype.Unmarshal(event.Event) - if err != nil { - // this shouldn't happen either - return OC10Notification{}, errors.New("cant unmarshal event") - } - - switch ev := einterface.(type) { +func (c *Converter) ConvertEvent(eventid string, event interface{}) (OC10Notification, error) { + switch ev := event.(type) { default: return OC10Notification{}, fmt.Errorf("unknown event type: %T", ev) // file related @@ -103,31 +88,31 @@ func (c *Converter) ConvertEvent(event *ehmsg.Event) (OC10Notification, error) { switch ev.FinishedStep { case events.PPStepAntivirus: res := ev.Result.(events.VirusscanResult) - return c.virusMessage(event.Id, VirusFound, ev.ExecutingUser, res.ResourceID, ev.Filename, res.Description, res.Scandate) + return c.virusMessage(eventid, VirusFound, ev.ExecutingUser, res.ResourceID, ev.Filename, res.Description, res.Scandate) case events.PPStepPolicies: - return c.policiesMessage(event.Id, PoliciesEnforced, ev.ExecutingUser, ev.Filename, time.Now()) + return c.policiesMessage(eventid, PoliciesEnforced, ev.ExecutingUser, ev.Filename, time.Now()) default: return OC10Notification{}, fmt.Errorf("unknown postprocessing step: %s", ev.FinishedStep) } // space related case events.SpaceDisabled: - return c.spaceMessage(event.Id, SpaceDisabled, ev.Executant, ev.ID.GetOpaqueId(), ev.Timestamp) + return c.spaceMessage(eventid, SpaceDisabled, ev.Executant, ev.ID.GetOpaqueId(), ev.Timestamp) case events.SpaceDeleted: - return c.spaceDeletedMessage(event.Id, ev.Executant, ev.ID.GetOpaqueId(), ev.SpaceName, ev.Timestamp) + return c.spaceDeletedMessage(eventid, ev.Executant, ev.ID.GetOpaqueId(), ev.SpaceName, ev.Timestamp) case events.SpaceShared: - return c.spaceMessage(event.Id, SpaceShared, ev.Executant, ev.ID.GetOpaqueId(), ev.Timestamp) + return c.spaceMessage(eventid, SpaceShared, ev.Executant, ev.ID.GetOpaqueId(), ev.Timestamp) case events.SpaceUnshared: - return c.spaceMessage(event.Id, SpaceUnshared, ev.Executant, ev.ID.GetOpaqueId(), ev.Timestamp) + return c.spaceMessage(eventid, SpaceUnshared, ev.Executant, ev.ID.GetOpaqueId(), ev.Timestamp) case events.SpaceMembershipExpired: - return c.spaceMessage(event.Id, SpaceMembershipExpired, ev.SpaceOwner, ev.SpaceID.GetOpaqueId(), ev.ExpiredAt) + return c.spaceMessage(eventid, SpaceMembershipExpired, ev.SpaceOwner, ev.SpaceID.GetOpaqueId(), ev.ExpiredAt) // share related case events.ShareCreated: - return c.shareMessage(event.Id, ShareCreated, ev.Executant, ev.ItemID, ev.ShareID, utils.TSToTime(ev.CTime)) + return c.shareMessage(eventid, ShareCreated, ev.Executant, ev.ItemID, ev.ShareID, utils.TSToTime(ev.CTime)) case events.ShareExpired: - return c.shareMessage(event.Id, ShareExpired, ev.ShareOwner, ev.ItemID, ev.ShareID, ev.ExpiredAt) + return c.shareMessage(eventid, ShareExpired, ev.ShareOwner, ev.ItemID, ev.ShareID, ev.ExpiredAt) case events.ShareRemoved: - return c.shareMessage(event.Id, ShareRemoved, ev.Executant, ev.ItemID, ev.ShareID, ev.Timestamp) + return c.shareMessage(eventid, ShareRemoved, ev.Executant, ev.ItemID, ev.ShareID, ev.Timestamp) } } @@ -306,7 +291,7 @@ func (c *Converter) authenticate(usr *user.User) (context.Context, error) { if ctx, ok := c.contexts[usr.GetId().GetOpaqueId()]; ok { return ctx, nil } - ctx, err := authenticate(usr, c.gwClient, c.machineAuthAPIKey) + ctx, err := authenticate(usr, c.gatewaySelector, c.machineAuthAPIKey) if err == nil { c.contexts[usr.GetId().GetOpaqueId()] = ctx } @@ -317,7 +302,7 @@ func (c *Converter) getSpace(ctx context.Context, spaceID string) (*storageprovi if space, ok := c.spaces[spaceID]; ok { return space, nil } - space, err := getSpace(ctx, spaceID, c.gwClient) + space, err := getSpace(ctx, spaceID, c.gatewaySelector) if err == nil { c.spaces[spaceID] = space } @@ -328,7 +313,7 @@ func (c *Converter) getResource(ctx context.Context, resourceID *storageprovider if r, ok := c.resources[resourceID.GetOpaqueId()]; ok { return r, nil } - resource, err := getResource(ctx, resourceID, c.gwClient) + resource, err := getResource(ctx, resourceID, c.gatewaySelector) if err == nil { c.resources[resourceID.GetOpaqueId()] = resource } @@ -339,7 +324,7 @@ func (c *Converter) getUser(ctx context.Context, userID *user.UserId) (*user.Use if u, ok := c.users[userID.GetOpaqueId()]; ok { return u, nil } - usr, err := getUser(ctx, userID, c.gwClient) + usr, err := getUser(ctx, userID, c.gatewaySelector) if err == nil { c.users[userID.GetOpaqueId()] = usr } diff --git a/services/userlog/pkg/service/http.go b/services/userlog/pkg/service/http.go index 31d7c9b64..b1a9e8258 100644 --- a/services/userlog/pkg/service/http.go +++ b/services/userlog/pkg/service/http.go @@ -4,6 +4,7 @@ import ( "encoding/json" "net/http" + "github.com/cs3org/reva/v2/pkg/ctx" revactx "github.com/cs3org/reva/v2/pkg/ctx" ) @@ -31,11 +32,23 @@ func (ul *UserlogService) HandleGetEvents(w http.ResponseWriter, r *http.Request return } - conv := NewConverter(r.Header.Get(HeaderAcceptLanguage), ul.gwClient, ul.cfg.MachineAuthAPIKey, ul.cfg.Service.Name, ul.cfg.TranslationPath, ul.registeredEvents) + conv := ul.getConverter(r.Header.Get(HeaderAcceptLanguage)) resp := GetEventResponseOC10{} for _, e := range evs { - noti, err := conv.ConvertEvent(e) + etype, ok := ul.registeredEvents[e.Type] + if !ok { + ul.log.Error().Str("eventid", e.Id).Str("eventtype", e.Type).Msg("event not registered") + continue + } + + einterface, err := etype.Unmarshal(e.Event) + if err != nil { + ul.log.Error().Str("eventid", e.Id).Str("eventtype", e.Type).Msg("failed to umarshal event") + continue + } + + noti, err := conv.ConvertEvent(e.Id, einterface) if err != nil { ul.log.Error().Err(err).Str("eventid", e.Id).Str("eventtype", e.Type).Msg("failed to convert event") continue @@ -49,6 +62,33 @@ func (ul *UserlogService) HandleGetEvents(w http.ResponseWriter, r *http.Request w.Write(b) } +// HandleSSE is the GET handler for events +func (ul *UserlogService) HandleSSE(w http.ResponseWriter, r *http.Request) { + u, ok := ctx.ContextGetUser(r.Context()) + if !ok { + ul.log.Error().Msg("sse: no user in context") + w.WriteHeader(http.StatusInternalServerError) + return + } + + uid := u.GetId().GetOpaqueId() + if uid == "" { + ul.log.Error().Msg("sse: user in context is broken") + w.WriteHeader(http.StatusInternalServerError) + return + } + + stream := ul.sse.CreateStream(uid) + stream.AutoReplay = false + + // add stream to URL + q := r.URL.Query() + q.Set("stream", uid) + r.URL.RawQuery = q.Encode() + + ul.sse.ServeHTTP(w, r) +} + // HandleDeleteEvents is the DELETE handler for events func (ul *UserlogService) HandleDeleteEvents(w http.ResponseWriter, r *http.Request) { u, ok := revactx.ContextGetUser(r.Context()) diff --git a/services/userlog/pkg/service/l10n/locale/bg_BG/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/bg_BG/LC_MESSAGES/userlog.po index 5d0a59bf5..495e29f32 100644 --- a/services/userlog/pkg/service/l10n/locale/bg_BG/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/bg_BG/LC_MESSAGES/userlog.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-30 00:02+0000\n" +"POT-Creation-Date: 2023-06-19 00:15+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Martin Zhekov , 2023\n" "Language-Team: Bulgarian (Bulgaria) (https://app.transifex.com/owncloud-org/teams/6149/bg_BG/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/de/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/de/LC_MESSAGES/userlog.po index 8dd097f92..3a41d901c 100644 --- a/services/userlog/pkg/service/l10n/locale/de/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/de/LC_MESSAGES/userlog.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-30 00:02+0000\n" +"POT-Creation-Date: 2023-06-19 00:15+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: hurradieweltgehtunter , 2023\n" "Language-Team: German (https://app.transifex.com/owncloud-org/teams/6149/de/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/de_DE/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/de_DE/LC_MESSAGES/userlog.po index d0ff73c9d..3807435dc 100644 --- a/services/userlog/pkg/service/l10n/locale/de_DE/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/de_DE/LC_MESSAGES/userlog.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-30 00:02+0000\n" +"POT-Creation-Date: 2023-06-19 00:15+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Edith Parzefall, 2023\n" "Language-Team: German (Germany) (https://app.transifex.com/owncloud-org/teams/6149/de_DE/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/en_GB/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/en_GB/LC_MESSAGES/userlog.po index 969a5e327..562271d78 100644 --- a/services/userlog/pkg/service/l10n/locale/en_GB/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/en_GB/LC_MESSAGES/userlog.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-20 00:06+0000\n" +"POT-Creation-Date: 2023-06-29 00:49+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Andi Chandler , 2023\n" "Language-Team: English (United Kingdom) (https://app.transifex.com/owncloud-org/teams/6149/en_GB/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/es/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/es/LC_MESSAGES/userlog.po index b4ab76948..fec31014d 100644 --- a/services/userlog/pkg/service/l10n/locale/es/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/es/LC_MESSAGES/userlog.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-30 00:02+0000\n" +"POT-Creation-Date: 2023-06-19 00:15+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Juan Carlos Garrote, 2023\n" "Language-Team: Spanish (https://app.transifex.com/owncloud-org/teams/6149/es/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/es_AR/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/es_AR/LC_MESSAGES/userlog.po index 37be2c77d..2525ce5c9 100644 --- a/services/userlog/pkg/service/l10n/locale/es_AR/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/es_AR/LC_MESSAGES/userlog.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-30 00:02+0000\n" +"POT-Creation-Date: 2023-06-19 00:15+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Toli Bal , 2023\n" "Language-Team: Spanish (Argentina) (https://app.transifex.com/owncloud-org/teams/6149/es_AR/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/ko/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/ko/LC_MESSAGES/userlog.po index 4f6e81c8d..6f9dd5ab6 100644 --- a/services/userlog/pkg/service/l10n/locale/ko/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/ko/LC_MESSAGES/userlog.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-30 00:02+0000\n" +"POT-Creation-Date: 2023-06-19 00:15+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Hugo Blackwood, 2023\n" "Language-Team: Korean (https://app.transifex.com/owncloud-org/teams/6149/ko/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/pt_BR/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/pt_BR/LC_MESSAGES/userlog.po index b0c81b452..93589361f 100644 --- a/services/userlog/pkg/service/l10n/locale/pt_BR/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/pt_BR/LC_MESSAGES/userlog.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-30 00:02+0000\n" +"POT-Creation-Date: 2023-06-19 00:15+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Flávio Veras , 2023\n" "Language-Team: Portuguese (Brazil) (https://app.transifex.com/owncloud-org/teams/6149/pt_BR/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/ru/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/ru/LC_MESSAGES/userlog.po index 40337998f..1d9b8caf9 100644 --- a/services/userlog/pkg/service/l10n/locale/ru/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/ru/LC_MESSAGES/userlog.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-30 00:02+0000\n" +"POT-Creation-Date: 2023-06-19 00:15+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Viktor Scharf, 2023\n" "Language-Team: Russian (https://app.transifex.com/owncloud-org/teams/6149/ru/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/sq/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/sq/LC_MESSAGES/userlog.po index 89d106835..6f228770f 100644 --- a/services/userlog/pkg/service/l10n/locale/sq/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/sq/LC_MESSAGES/userlog.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-30 00:02+0000\n" +"POT-Creation-Date: 2023-06-19 00:15+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Besnik Bleta , 2023\n" "Language-Team: Albanian (https://app.transifex.com/owncloud-org/teams/6149/sq/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/tr/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/tr/LC_MESSAGES/userlog.po index 5ec636baf..64f244c8e 100644 --- a/services/userlog/pkg/service/l10n/locale/tr/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/tr/LC_MESSAGES/userlog.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2023-05-17 00:41+0000\n" +"POT-Creation-Date: 2023-06-26 00:52+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Begüm Topyıldız , 2023\n" "Language-Team: Turkish (https://app.transifex.com/owncloud-org/teams/6149/tr/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/zh_CN/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/zh_CN/LC_MESSAGES/userlog.po new file mode 100644 index 000000000..cfb46d352 --- /dev/null +++ b/services/userlog/pkg/service/l10n/locale/zh_CN/LC_MESSAGES/userlog.po @@ -0,0 +1,102 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +# Translators: +# Shouyuan Shi, 2023 +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: EMAIL\n" +"POT-Creation-Date: 2023-06-28 04:01+0000\n" +"PO-Revision-Date: 2023-03-15 08:28+0000\n" +"Last-Translator: Shouyuan Shi, 2023\n" +"Language-Team: Chinese (China) (https://app.transifex.com/owncloud-org/teams/6149/zh_CN/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_CN\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: pkg/service/templates.go:40 +msgid "Access to Space {space} lost" +msgstr "对于空间 {space} 的访问权限丢失" + +#: pkg/service/templates.go:55 +msgid "Access to {resource} expired" +msgstr "对于 {resource} 的访问权限过期" + +#: pkg/service/templates.go:15 +msgid "File {resource} was deleted because it violates the policies" +msgstr "文件 {resource} 因违反了策略已被删除" + +#: pkg/service/templates.go:39 +msgid "Membership expired" +msgstr "成员身份过期" + +#: pkg/service/templates.go:14 +msgid "Policies enforced" +msgstr "策略已被执行" + +#: pkg/service/templates.go:24 +msgid "Removed from Space" +msgstr "已从空间中删除" + +#: pkg/service/templates.go:44 +msgid "Resource shared" +msgstr "资源已共享" + +#: pkg/service/templates.go:49 +msgid "Resource unshared" +msgstr "资源未共享" + +#: pkg/service/templates.go:54 +msgid "Share expired" +msgstr "共享已过期" + +#: pkg/service/templates.go:34 +msgid "Space deleted" +msgstr "空间已删除" + +#: pkg/service/templates.go:29 +msgid "Space disabled" +msgstr "空间已禁用" + +#: pkg/service/templates.go:19 +msgid "Space shared" +msgstr "空间已共享" + +#: pkg/service/templates.go:9 +msgid "Virus found" +msgstr "发现病毒" + +#: pkg/service/templates.go:10 +msgid "Virus found in {resource}. Upload not possible. Virus: {virus}" +msgstr "在 {resource} 中发现了病毒。 上传无法完成。病毒: {virus}" + +#: pkg/service/templates.go:20 +msgid "{user} added you to Space {space}" +msgstr "{user} 已将你加入空间 {space}" + +#: pkg/service/templates.go:35 +msgid "{user} deleted Space {space}" +msgstr "{user} 删除了空间 {space}" + +#: pkg/service/templates.go:30 +msgid "{user} disabled Space {space}" +msgstr "{user} 禁用了空间 {space}" + +#: pkg/service/templates.go:25 +msgid "{user} removed you from Space {space}" +msgstr "{user} 已将你从空间 {space} 中移除" + +#: pkg/service/templates.go:45 +msgid "{user} shared {resource} with you" +msgstr "{user} 和你共享了 {resource}" + +#: pkg/service/templates.go:50 +msgid "{user} unshared {resource} with you" +msgstr "{user} 已取消和你共享 {resource}" diff --git a/services/userlog/pkg/service/options.go b/services/userlog/pkg/service/options.go index b84c67725..98628cfd6 100644 --- a/services/userlog/pkg/service/options.go +++ b/services/userlog/pkg/service/options.go @@ -3,9 +3,11 @@ package service import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/go-chi/chi/v5" "github.com/owncloud/ocis/v2/ocis-pkg/log" ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" "github.com/owncloud/ocis/v2/services/userlog/pkg/config" "go-micro.dev/v4/store" ) @@ -21,7 +23,8 @@ type Options struct { Store store.Store Config *config.Config HistoryClient ehsvc.EventHistoryService - GatewayClient gateway.GatewayAPIClient + GatewaySelector pool.Selectable[gateway.GatewayAPIClient] + ValueClient settingssvc.ValueService RegisteredEvents []events.Unmarshaller } @@ -67,10 +70,10 @@ func HistoryClient(hc ehsvc.EventHistoryService) Option { } } -// GatewayClient adds a grpc client for the gateway service -func GatewayClient(gwc gateway.GatewayAPIClient) Option { +// GatewaySelector adds a grpc client selector for the gateway service +func GatewaySelector(gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) Option { return func(o *Options) { - o.GatewayClient = gwc + o.GatewaySelector = gatewaySelector } } @@ -80,3 +83,9 @@ func RegisteredEvents(e []events.Unmarshaller) Option { o.RegisteredEvents = e } } + +func ValueClient(vs settingssvc.ValueService) Option { + return func(o *Options) { + o.ValueClient = vs + } +} diff --git a/services/userlog/pkg/service/service.go b/services/userlog/pkg/service/service.go index d9a663ffb..1c105bccd 100644 --- a/services/userlog/pkg/service/service.go +++ b/services/userlog/pkg/service/service.go @@ -14,12 +14,18 @@ import ( storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/utils" "github.com/go-chi/chi/v5" "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/middleware" ehmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/eventhistory/v0" ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" + "github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults" "github.com/owncloud/ocis/v2/services/userlog/pkg/config" + "github.com/r3labs/sse/v2" + micrometadata "go-micro.dev/v4/metadata" "go-micro.dev/v4/store" "google.golang.org/grpc/metadata" ) @@ -31,7 +37,9 @@ type UserlogService struct { store store.Store cfg *config.Config historyClient ehsvc.EventHistoryService - gwClient gateway.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + valueClient settingssvc.ValueService + sse *sse.Server registeredEvents map[string]events.Unmarshaller translationPath string } @@ -58,18 +66,27 @@ func NewUserlogService(opts ...Option) (*UserlogService, error) { store: o.Store, cfg: o.Config, historyClient: o.HistoryClient, - gwClient: o.GatewayClient, + gatewaySelector: o.GatewaySelector, + valueClient: o.ValueClient, registeredEvents: make(map[string]events.Unmarshaller), } + if !ul.cfg.DisableSSE { + ul.sse = sse.New() + } + for _, e := range o.RegisteredEvents { typ := reflect.TypeOf(e) ul.registeredEvents[typ.String()] = e } - ul.m.Route("/", func(r chi.Router) { - r.Get("/*", ul.HandleGetEvents) - r.Delete("/*", ul.HandleDeleteEvents) + ul.m.Route("/ocs/v2.php/apps/notifications/api/v1/notifications", func(r chi.Router) { + r.Get("/", ul.HandleGetEvents) + r.Delete("/", ul.HandleDeleteEvents) + + if !ul.cfg.DisableSSE { + r.Get("/sse", ul.HandleSSE) + } }) go ul.MemorizeEvents(ch) @@ -154,7 +171,7 @@ func (ul *UserlogService) MemorizeEvents(ch <-chan events.Event) { // III) store the eventID for each user for _, id := range users { - if err := ul.addEventsToUser(id, event.ID); err != nil { + if err := ul.addEventToUser(id, event); err != nil { ul.log.Error().Err(err).Str("userID", id).Str("eventid", event.ID).Msg("failed to store event for user") continue } @@ -218,12 +235,32 @@ func (ul *UserlogService) DeleteEvents(userid string, evids []string) error { }) } -func (ul *UserlogService) addEventsToUser(userid string, eventids ...string) error { +func (ul *UserlogService) addEventToUser(userid string, event events.Event) error { + if !ul.cfg.DisableSSE { + if err := ul.sendSSE(userid, event); err != nil { + ul.log.Error().Err(err).Str("userid", userid).Str("eventid", event.ID).Msg("cannot create sse event") + } + } return ul.alterUserEventList(userid, func(ids []string) []string { - return append(ids, eventids...) + return append(ids, event.ID) }) } +func (ul *UserlogService) sendSSE(userid string, event events.Event) error { + ev, err := ul.getConverter(ul.getUserLocale(userid)).ConvertEvent(event.ID, event.Event) + if err != nil { + return err + } + + b, err := json.Marshal(ev) + if err != nil { + return err + } + + ul.sse.Publish(userid, &sse.Event{Data: b}) + return nil +} + func (ul *UserlogService) removeExpiredEvents(userid string, all []string, received []*ehmsg.Event) error { exists := make(map[string]struct{}, len(received)) for _, e := range received { @@ -283,7 +320,7 @@ func (ul *UserlogService) findSpaceMembers(ctx context.Context, spaceID string, return nil, errors.New("need authenticated context to find space members") } - space, err := getSpace(ctx, spaceID, ul.gwClient) + space, err := getSpace(ctx, spaceID, ul.gatewaySelector) if err != nil { return nil, err } @@ -361,7 +398,7 @@ func (ul *UserlogService) resolveID(ctx context.Context, userid *user.UserId, gr // resolves the users of a group func (ul *UserlogService) resolveGroup(ctx context.Context, groupID string) ([]string, error) { - grp, err := getGroup(ctx, groupID, ul.gwClient) + grp, err := getGroup(ctx, groupID, ul.gatewaySelector) if err != nil { return nil, err } @@ -380,13 +417,13 @@ func (ul *UserlogService) impersonate(uid *user.UserId) context.Context { return nil } - u, err := getUser(context.Background(), uid, ul.gwClient) + u, err := getUser(context.Background(), uid, ul.gatewaySelector) if err != nil { ul.log.Error().Err(err).Msg("cannot get user") return nil } - ctx, err := authenticate(u, ul.gwClient, ul.cfg.MachineAuthAPIKey) + ctx, err := authenticate(u, ul.gatewaySelector, ul.cfg.MachineAuthAPIKey) if err != nil { ul.log.Error().Err(err).Str("userid", u.GetId().GetOpaqueId()).Msg("failed to impersonate user") return nil @@ -394,9 +431,37 @@ func (ul *UserlogService) impersonate(uid *user.UserId) context.Context { return ctx } -func authenticate(usr *user.User, gwc gateway.GatewayAPIClient, machineAuthAPIKey string) (context.Context, error) { +func (ul *UserlogService) getUserLocale(userid string) string { + resp, err := ul.valueClient.GetValueByUniqueIdentifiers( + micrometadata.Set(context.Background(), middleware.AccountID, userid), + &settingssvc.GetValueByUniqueIdentifiersRequest{ + AccountUuid: userid, + SettingId: defaults.SettingUUIDProfileLanguage, + }, + ) + if err != nil { + ul.log.Error().Err(err).Str("userid", userid).Msg("cannot get users locale") + return "" + } + val := resp.GetValue().GetValue().GetListValue().GetValues() + if len(val) == 0 { + return "" + } + return val[0].GetStringValue() +} + +func (ul *UserlogService) getConverter(locale string) *Converter { + return NewConverter(locale, ul.gatewaySelector, ul.cfg.MachineAuthAPIKey, ul.cfg.Service.Name, ul.cfg.TranslationPath) +} + +func authenticate(usr *user.User, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], machineAuthAPIKey string) (context.Context, error) { + gatewayClient, err := gatewaySelector.Next() + if err != nil { + return nil, err + } + ctx := revactx.ContextSetUser(context.Background(), usr) - authRes, err := gwc.Authenticate(ctx, &gateway.AuthenticateRequest{ + authRes, err := gatewayClient.Authenticate(ctx, &gateway.AuthenticateRequest{ Type: "machine", ClientId: "userid:" + usr.GetId().GetOpaqueId(), ClientSecret: machineAuthAPIKey, @@ -411,8 +476,13 @@ func authenticate(usr *user.User, gwc gateway.GatewayAPIClient, machineAuthAPIKe return metadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, authRes.Token), nil } -func getSpace(ctx context.Context, spaceID string, gwc gateway.GatewayAPIClient) (*storageprovider.StorageSpace, error) { - res, err := gwc.ListStorageSpaces(ctx, listStorageSpaceRequest(spaceID)) +func getSpace(ctx context.Context, spaceID string, gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) (*storageprovider.StorageSpace, error) { + gatewayClient, err := gatewaySelector.Next() + if err != nil { + return nil, err + } + + res, err := gatewayClient.ListStorageSpaces(ctx, listStorageSpaceRequest(spaceID)) if err != nil { return nil, err } @@ -428,8 +498,13 @@ func getSpace(ctx context.Context, spaceID string, gwc gateway.GatewayAPIClient) return res.StorageSpaces[0], nil } -func getUser(ctx context.Context, userid *user.UserId, gwc gateway.GatewayAPIClient) (*user.User, error) { - getUserResponse, err := gwc.GetUser(context.Background(), &user.GetUserRequest{ +func getUser(ctx context.Context, userid *user.UserId, gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) (*user.User, error) { + gatewayClient, err := gatewaySelector.Next() + if err != nil { + return nil, err + } + + getUserResponse, err := gatewayClient.GetUser(context.Background(), &user.GetUserRequest{ UserId: userid, }) if err != nil { @@ -443,8 +518,13 @@ func getUser(ctx context.Context, userid *user.UserId, gwc gateway.GatewayAPICli return getUserResponse.GetUser(), nil } -func getGroup(ctx context.Context, groupid string, gwc gateway.GatewayAPIClient) (*group.Group, error) { - r, err := gwc.GetGroup(ctx, &group.GetGroupRequest{GroupId: &group.GroupId{OpaqueId: groupid}}) +func getGroup(ctx context.Context, groupid string, gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) (*group.Group, error) { + gatewayClient, err := gatewaySelector.Next() + if err != nil { + return nil, err + } + + r, err := gatewayClient.GetGroup(ctx, &group.GetGroupRequest{GroupId: &group.GroupId{OpaqueId: groupid}}) if err != nil { return nil, err } @@ -456,8 +536,13 @@ func getGroup(ctx context.Context, groupid string, gwc gateway.GatewayAPIClient) return r.GetGroup(), nil } -func getResource(ctx context.Context, resourceid *storageprovider.ResourceId, gwc gateway.GatewayAPIClient) (*storageprovider.ResourceInfo, error) { - res, err := gwc.Stat(ctx, &storageprovider.StatRequest{Ref: &storageprovider.Reference{ResourceId: resourceid}}) +func getResource(ctx context.Context, resourceid *storageprovider.ResourceId, gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) (*storageprovider.ResourceInfo, error) { + gatewayClient, err := gatewaySelector.Next() + if err != nil { + return nil, err + } + + res, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &storageprovider.Reference{ResourceId: resourceid}}) if err != nil { return nil, err } diff --git a/services/userlog/pkg/service/service_suit_test.go b/services/userlog/pkg/service/service_suit_test.go index 77d1081a8..ab7fb7b19 100644 --- a/services/userlog/pkg/service/service_suit_test.go +++ b/services/userlog/pkg/service/service_suit_test.go @@ -3,10 +3,23 @@ package service_test import ( "testing" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + mRegistry "go-micro.dev/v4/registry" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) +func init() { + registry.Configure("memory") + r := registry.GetRegistry() + service := registry.BuildGRPCService("com.owncloud.api.gateway", "", "", "") + service.Nodes = []*mRegistry.Node{{ + Address: "any", + }} + + _ = r.Register(service) +} func TestSearch(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Userlog service Suite") diff --git a/services/userlog/pkg/service/service_test.go b/services/userlog/pkg/service/service_test.go index acecb66a8..284b8742d 100644 --- a/services/userlog/pkg/service/service_test.go +++ b/services/userlog/pkg/service/service_test.go @@ -11,6 +11,7 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/store" "github.com/cs3org/reva/v2/pkg/utils" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" @@ -21,12 +22,15 @@ import ( "github.com/owncloud/ocis/v2/ocis-pkg/log" ehmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/eventhistory/v0" ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" "github.com/owncloud/ocis/v2/services/userlog/mocks" "github.com/owncloud/ocis/v2/services/userlog/pkg/config" "github.com/owncloud/ocis/v2/services/userlog/pkg/service" - "github.com/test-go/testify/mock" + "github.com/stretchr/testify/mock" + "go-micro.dev/v4/client" microevents "go-micro.dev/v4/events" microstore "go-micro.dev/v4/store" + "google.golang.org/grpc" ) var _ = Describe("UserlogService", func() { @@ -37,23 +41,40 @@ var _ = Describe("UserlogService", func() { bus testBus sto microstore.Store - gwc cs3mocks.GatewayAPIClient + gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + ehc mocks.EventHistoryService + vc settingssvc.MockValueService ) BeforeEach(func() { var err error sto = store.Create() bus = testBus(make(chan events.Event)) + + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") + gatewayClient = &cs3mocks.GatewayAPIClient{} + gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return gatewayClient + }, + ) + o := utils.AppendJSONToOpaque(nil, "grants", map[string]*provider.ResourcePermissions{"userid": {Stat: true}}) - gwc.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{StorageSpaces: []*provider.StorageSpace{ + gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{StorageSpaces: []*provider.StorageSpace{ { Opaque: o, SpaceType: "project", }, }, Status: &rpc.Status{Code: rpc.Code_CODE_OK}}, nil) - gwc.On("GetUser", mock.Anything, mock.Anything).Return(&user.GetUserResponse{User: &user.User{Id: &user.UserId{OpaqueId: "userid"}}, Status: &rpc.Status{Code: rpc.Code_CODE_OK}}, nil) - gwc.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}}, nil) + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(&user.GetUserResponse{User: &user.User{Id: &user.UserId{OpaqueId: "userid"}}, Status: &rpc.Status{Code: rpc.Code_CODE_OK}}, nil) + gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}}, nil) + vc.GetValueByUniqueIdentifiersFunc = func(ctx context.Context, req *settingssvc.GetValueByUniqueIdentifiersRequest, opts ...client.CallOption) (*settingssvc.GetValueResponse, error) { + return nil, nil + } ul, err = service.NewUserlogService( service.Config(cfg), @@ -61,8 +82,9 @@ var _ = Describe("UserlogService", func() { service.Store(sto), service.Logger(log.NewLogger()), service.Mux(chi.NewMux()), - service.GatewayClient(&gwc), + service.GatewaySelector(gatewaySelector), service.HistoryClient(&ehc), + service.ValueClient(&vc), service.RegisteredEvents([]events.Unmarshaller{ events.SpaceDisabled{}, }), diff --git a/services/users/pkg/command/root.go b/services/users/pkg/command/root.go index c84c93603..e0039b1c3 100644 --- a/services/users/pkg/command/root.go +++ b/services/users/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/users/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the user command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new user.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Users.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Users, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/users/pkg/command/server.go b/services/users/pkg/command/server.go index b0419b305..e661eee74 100644 --- a/services/users/pkg/command/server.go +++ b/services/users/pkg/command/server.go @@ -11,7 +11,7 @@ import ( "github.com/oklog/run" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" "github.com/owncloud/ocis/v2/ocis-pkg/ldap" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/sync" "github.com/owncloud/ocis/v2/ocis-pkg/version" "github.com/owncloud/ocis/v2/services/users/pkg/config" @@ -43,10 +43,6 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") - - rcfg := revaconfig.UsersConfigFromStruct(cfg) - // the reva runtime calls os.Exit in the case of a failure and there is no way for the oCIS // runtime to catch it and restart a reva service. Therefore we need to ensure the service has // everything it needs, before starting the service. @@ -60,7 +56,15 @@ func Server(cfg *config.Config) *cli.Command { } gr.Add(func() error { - runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + rCfg := revaconfig.UsersConfigFromStruct(cfg) + reg := registry.GetRegistry() + + runtime.RunWithOptions(rCfg, pidFile, + runtime.WithLogger(&logger.Logger), + runtime.WithRegistry(reg), + ) + return nil }, func(err error) { logger.Error(). @@ -90,15 +94,9 @@ func Server(cfg *config.Config) *cli.Command { sync.Trap(&gr, cancel) } - if err := external.RegisterGRPCEndpoint( - ctx, - cfg.GRPC.Namespace+"."+cfg.Service.Name, - uuid.Must(uuid.NewV4()).String(), - cfg.GRPC.Addr, - version.GetString(), - logger, - ); err != nil { - logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, version.GetString()) + if err := registry.RegisterService(ctx, grpcSvc, logger); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc service") } return gr.Run() diff --git a/services/users/pkg/config/config.go b/services/users/pkg/config/config.go index a467b4a74..a5107f1a5 100644 --- a/services/users/pkg/config/config.go +++ b/services/users/pkg/config/config.go @@ -93,8 +93,8 @@ type LDAPDriver struct { } type LDAPUserSchema struct { - ID string `yaml:"id" env:"OCIS_LDAP_USER_SCHEMA_ID;LDAP_USER_SCHEMA_ID;USERS_LDAP_USER_SCHEMA_ID" desc:"LDAP Attribute to use as the unique id for users. This should be a stable globally unique id like a UUID." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_ID changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_ID"` - IDIsOctetString bool `yaml:"id_is_octet_string" env:"OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;USERS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING" desc:"Set this to true if the defined 'id' attribute for users is of the 'OCTETSTRING' syntax. This is e.g. required when using the 'objectGUID' attribute of Active Directory for the user ID's." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_ID_IS_OCTETSTRING changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING"` + ID string `yaml:"id" env:"OCIS_LDAP_USER_SCHEMA_ID;LDAP_USER_SCHEMA_ID;USERS_LDAP_USER_SCHEMA_ID" desc:"LDAP Attribute to use as the unique ID for users. This should be a stable globally unique ID like a UUID." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_ID changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_ID"` + IDIsOctetString bool `yaml:"id_is_octet_string" env:"OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;USERS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING" desc:"Set this to true if the defined 'ID' attribute for users is of the 'OCTETSTRING' syntax. This is e.g. required when using the 'objectGUID' attribute of Active Directory for the user ID's." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_ID_IS_OCTETSTRING changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING"` Mail string `yaml:"mail" env:"OCIS_LDAP_USER_SCHEMA_MAIL;LDAP_USER_SCHEMA_MAIL;USERS_LDAP_USER_SCHEMA_MAIL" desc:"LDAP Attribute to use for the email address of users." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_MAIL changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_MAIL"` DisplayName string `yaml:"display_name" env:"OCIS_LDAP_USER_SCHEMA_DISPLAYNAME;LDAP_USER_SCHEMA_DISPLAYNAME;USERS_LDAP_USER_SCHEMA_DISPLAYNAME" desc:"LDAP Attribute to use for the displayname of users." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_DISPLAYNAME changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_DISPLAYNAME"` Username string `yaml:"user_name" env:"OCIS_LDAP_USER_SCHEMA_USERNAME;LDAP_USER_SCHEMA_USERNAME;USERS_LDAP_USER_SCHEMA_USERNAME" desc:"LDAP Attribute to use for username of users." deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"LDAP_USER_SCHEMA_USERNAME changing name for consistency" deprecationReplacement:"OCIS_LDAP_USER_SCHEMA_USERNAME"` diff --git a/services/users/pkg/config/defaults/defaultconfig.go b/services/users/pkg/config/defaults/defaultconfig.go index 0523158e6..533d63621 100644 --- a/services/users/pkg/config/defaults/defaultconfig.go +++ b/services/users/pkg/config/defaults/defaultconfig.go @@ -51,7 +51,7 @@ func DefaultConfig() *config.Config { UserObjectClass: "inetOrgPerson", GroupObjectClass: "groupOfNames", BindDN: "uid=reva,ou=sysusers,o=libregraph-idm", - DisableUserMechanism: "none", + DisableUserMechanism: "attribute", LdapDisabledUsersGroupDN: "cn=DisabledUsersGroup,ou=groups,o=libregraph-idm", UserTypeAttribute: "ownCloudUserType", IDP: "https://localhost:9200", diff --git a/services/web/Makefile b/services/web/Makefile index c7e86be11..c46ae345b 100644 --- a/services/web/Makefile +++ b/services/web/Makefile @@ -1,6 +1,6 @@ SHELL := bash NAME := web -WEB_ASSETS_VERSION = v7.0.0-rc.38 +WEB_ASSETS_VERSION = v7.0.1 include ../../.make/recursion.mk diff --git a/services/web/pkg/command/root.go b/services/web/pkg/command/root.go index 7dfe694e2..91a648c1a 100644 --- a/services/web/pkg/command/root.go +++ b/services/web/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/web/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the web command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new web.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Web.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Web, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/web/pkg/config/config.go b/services/web/pkg/config/config.go index 99f9de93c..d1f07cef3 100644 --- a/services/web/pkg/config/config.go +++ b/services/web/pkg/config/config.go @@ -65,11 +65,12 @@ type WebConfig struct { // OIDC defines the available oidc configuration type OIDC struct { - MetadataURL string `json:"metadata_url,omitempty" yaml:"metadata_url" env:"WEB_OIDC_METADATA_URL" desc:"URL for the OIDC well-known configuration endpoint. Defaults to the oCIS API URL + \"/.well-known/openid-configuration\"."` - Authority string `json:"authority,omitempty" yaml:"authority" env:"OCIS_URL;OCIS_OIDC_ISSUER;WEB_OIDC_AUTHORITY" desc:"URL of the OIDC issuer. It defaults to URL of the builtin IDP."` - ClientID string `json:"client_id,omitempty" yaml:"client_id" env:"OCIS_OIDC_CLIENT_ID;WEB_OIDC_CLIENT_ID" desc:"OIDC client ID, which ownCloud Web uses. This client needs to be set up in your IDP (has no effect when using the builtin IDP)."` - ResponseType string `json:"response_type,omitempty" yaml:"response_type" env:"WEB_OIDC_RESPONSE_TYPE" desc:"OIDC response type to use for authentication."` - Scope string `json:"scope,omitempty" yaml:"scope" env:"WEB_OIDC_SCOPE" desc:"OIDC scopes to request during authentication to authorize access to user details. Defaults to 'openid profile email'. Values are separated by blank. More example values but not limited to are 'address' or 'phone' etc."` + MetadataURL string `json:"metadata_url,omitempty" yaml:"metadata_url" env:"WEB_OIDC_METADATA_URL" desc:"URL for the OIDC well-known configuration endpoint. Defaults to the oCIS API URL + '/.well-known/openid-configuration'."` + Authority string `json:"authority,omitempty" yaml:"authority" env:"OCIS_URL;OCIS_OIDC_ISSUER;WEB_OIDC_AUTHORITY" desc:"URL of the OIDC issuer. It defaults to URL of the builtin IDP."` + ClientID string `json:"client_id,omitempty" yaml:"client_id" env:"OCIS_OIDC_CLIENT_ID;WEB_OIDC_CLIENT_ID" desc:"The OIDC client ID which ownCloud Web uses. This client needs to be set up in your IDP. Note that this setting has no effect when using the builtin IDP."` + ResponseType string `json:"response_type,omitempty" yaml:"response_type" env:"WEB_OIDC_RESPONSE_TYPE" desc:"The OIDC response type to use for authentication."` + Scope string `json:"scope,omitempty" yaml:"scope" env:"WEB_OIDC_SCOPE" desc:"OIDC scopes to request during authentication to authorize access to user details. Defaults to 'openid profile email'. Values are separated by blank. More example values but not limited to are 'address' or 'phone' etc."` + PostLogoutRedirectURI string `json:"post_logout_redirect_uri,omitempty" yaml:"post_logout_redirect_uri" env:"WEB_OIDC_POST_LOGOUT_REDIRECT_URI" desc:"This value needs to point to a valid and reachable web page. The web client will trigger a redirect to that page directly after the logout action. The default value is empty and redirects to the login page."` } // Application defines an application for the Web app switcher. diff --git a/services/web/pkg/config/defaults/defaultconfig.go b/services/web/pkg/config/defaults/defaultconfig.go index c2acd8f03..110a12d8d 100644 --- a/services/web/pkg/config/defaults/defaultconfig.go +++ b/services/web/pkg/config/defaults/defaultconfig.go @@ -82,7 +82,7 @@ func DefaultConfig() *config.Config { Asset: config.Asset{ Path: filepath.Join(defaults.BaseDataPath(), "web/assets"), }, - GatewayAddress: "127.0.0.1:9142", + GatewayAddress: "com.owncloud.api.gateway", Web: config.Web{ Path: "", ThemeServer: "https://localhost:9200", @@ -194,7 +194,7 @@ func Sanitize(cfg *config.Config) { cfg.Web.Config.Options.FeedbackLink = nil } // remove Upload parent if no value is set - if cfg.Web.Config.Options.Upload.XHR.Timeout == 0 { + if cfg.Web.Config.Options.Upload.XHR.Timeout == 0 && cfg.Web.Config.Options.Upload.CompanionURL == "" { cfg.Web.Config.Options.Upload = nil } } diff --git a/services/web/pkg/config/http.go b/services/web/pkg/config/http.go index 1992c8d39..03676deed 100644 --- a/services/web/pkg/config/http.go +++ b/services/web/pkg/config/http.go @@ -16,6 +16,6 @@ type HTTP struct { type CORS struct { AllowedOrigins []string `yaml:"allow_origins" env:"OCIS_CORS_ALLOW_ORIGINS;WEB_CORS_ALLOW_ORIGINS" desc:"A comma-separated list of allowed CORS origins. See following chapter for more details: *Access-Control-Allow-Origin* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin"` AllowedMethods []string `yaml:"allow_methods" env:"OCIS_CORS_ALLOW_METHODS;WEB_CORS_ALLOW_METHODS" desc:"A comma-separated list of allowed CORS methods. See following chapter for more details: *Access-Control-Request-Method* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method"` - AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;WEB_CORS_ALLOW_HEADERS" desc:"A comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` + AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;WEB_CORS_ALLOW_HEADERS" desc:"A blank or comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` AllowCredentials bool `yaml:"allow_credentials" env:"OCIS_CORS_ALLOW_CREDENTIALS;WEB_CORS_ALLOW_CREDENTIALS" desc:"Allow credentials for CORS. See following chapter for more details: *Access-Control-Allow-Credentials* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials."` } diff --git a/services/web/pkg/config/options.go b/services/web/pkg/config/options.go index 2ea969639..5c7d8bef2 100644 --- a/services/web/pkg/config/options.go +++ b/services/web/pkg/config/options.go @@ -4,7 +4,7 @@ package config type Options struct { HomeFolder string `json:"homeFolder,omitempty" yaml:"homeFolder" env:"WEB_OPTION_HOME_FOLDER" desc:"Specifies a folder that is used when the user navigates 'home'. Navigating home gets triggered by clicking on the 'All files' menu item. The user will not be jailed in that directory, it simply serves as a default location. A static location can be provided, or variables of the user object to come up with a user specific home path can be used. This uses the twig template variable style and allows picking a value or a substring of a value of the authenticated user. Examples are '/Shares', '/{{.Id}}' and '/{{substr 0 3 .Id}}/{{.Id}'."` OpenAppsInTab bool `json:"openAppsInTab,omitempty" yaml:"openAppsInTab" env:"WEB_OPTION_OPEN_APPS_IN_TAB" desc:"Configures whether apps and extensions should generally open in a new tab. Defaults to false."` - DisablePreviews bool `json:"disablePreviews,omitempty" yaml:"disablePreviews" env:"WEB_OPTION_DISABLE_PREVIEWS" desc:"Set this option to 'true' to disable previews in all the different file listing views. The only list view that is not affected by this setting is the trash bin, as it does not allow previewing at all."` + DisablePreviews bool `json:"disablePreviews,omitempty" yaml:"disablePreviews" env:"OCIS_DISABLE_PREVIEWS;WEB_OPTION_DISABLE_PREVIEWS" desc:"Set this option to 'true' to disable previews in all the different web file listing views. This can speed up file listings in folders with many files. The only list view that is not affected by this setting is the trash bin, as it does not allow previewing at all."` PreviewFileMimeTypes []string `json:"previewFileMimeTypes,omitempty" yaml:"previewFileMimeTypes" env:"WEB_OPTION_PREVIEW_FILE_MIMETYPES" desc:"Specifies which mimeTypes will be previewed in the UI. For example to only preview jpg and text files, set this option to ['image/jpeg', 'text/plain']."` AccountEditLink *AccountEditLink `json:"accountEditLink,omitempty" yaml:"accountEditLink"` DisableFeedbackLink bool `json:"disableFeedbackLink,omitempty" yaml:"disableFeedbackLink" env:"WEB_OPTION_DISABLE_FEEDBACK_LINK" desc:"Set this option to 'true' to disable the feedback link in the topbar. Keeping it enabled by setting the value to 'false' or with the absence of the option, allows ownCloud to get feedback from your user base through a dedicated survey website."` @@ -20,6 +20,9 @@ type Options struct { ContextHelpersReadMore bool `json:"contextHelpersReadMore,omitempty" yaml:"contextHelpersReadMore" env:"WEB_OPTION_CONTEXTHELPERS_READ_MORE" desc:"Specifies whether the 'Read more' link should be displayed or not."` LogoutURL string `json:"logoutUrl,omitempty" yaml:"logoutUrl" env:"WEB_OPTION_LOGOUT_URL" desc:"Adds a link to the user's profile page to point him to an external page, where he can manage his session and devices. This is helpful when an external IdP is used. This option is disabled by default."` OpenLinksWithDefaultApp bool `json:"openLinksWithDefaultApp,omitempty" yaml:"openLinksWithDefaultApp" env:"WEB_OPTION_OPEN_LINKS_WITH_DEFAULT_APP" desc:"Specifies whether single file link shares should be opened with the default app or not. If not opened by the default app, the Web UI just displays the file details. Defaults to 'true'."` + ImprintURL string `json:"imprintUrl,omitempty" yaml:"imprintUrl" env:"WEB_OPTION_IMPRINT_URL" desc:"Specifies the target URL for the imprint link valid for the ocis instance in the account menu."` + PrivacyURL string `json:"privacyUrl,omitempty" yaml:"privacyUrl" env:"WEB_OPTION_PRIVACY_URL" desc:"Specifies the target URL for the privacy link valid for the ocis instance in the account menu."` + AccessDeniedHelpURL string `json:"accessDeniedHelpURL,omitempty" yaml:"accessDeniedHelpURL" env:"WEB_OPTION_ACCESS_DENIED_HELP_URL" desc:"Specifies the target URL valid for the ocis instance for the generic logged out / access denied page."` } // AccountEditLink are the AccountEditLink options @@ -51,7 +54,8 @@ type Routing struct { // Upload are the upload options type Upload struct { - XHR XHR `json:"xhr,omitempty" yaml:"xhr"` + XHR XHR `json:"xhr,omitempty" yaml:"xhr"` + CompanionURL string `json:"companionUrl,omitempty" yaml:"companionUrl" env:"WEB_OPTION_UPLOAD_COMPANION_URL" desc:"Sets the URL of Companion which is a service provided by Uppy to import files from external cloud providers. See https://uppy.io/docs/companion/ for instructions on how to set up Companion. This feature is disabled as long as no URL is given."` } // XHR are the XHR options diff --git a/services/web/pkg/server/http/server.go b/services/web/pkg/server/http/server.go index 564afabbd..8312f54b2 100644 --- a/services/web/pkg/server/http/server.go +++ b/services/web/pkg/server/http/server.go @@ -7,6 +7,7 @@ import ( chimiddleware "github.com/go-chi/chi/v5/middleware" "github.com/owncloud/ocis/v2/ocis-pkg/cors" "github.com/owncloud/ocis/v2/ocis-pkg/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/service/http" "github.com/owncloud/ocis/v2/ocis-pkg/version" webmid "github.com/owncloud/ocis/v2/services/web/pkg/middleware" @@ -35,7 +36,7 @@ func Server(opts ...Option) (http.Service, error) { return http.Service{}, fmt.Errorf("could not initialize http service: %w", err) } - client, err := pool.GetGatewayServiceClient(options.Config.GatewayAddress) + gatewaySelector, err := pool.GatewaySelector(options.Config.GatewayAddress, pool.WithRegistry(registry.GetRegistry())) if err != nil { return http.Service{}, err } @@ -43,7 +44,7 @@ func Server(opts ...Option) (http.Service, error) { handle := svc.NewService( svc.Logger(options.Logger), svc.Config(options.Config), - svc.GatewayClient(client), + svc.GatewaySelector(gatewaySelector), svc.Middleware( chimiddleware.RealIP, chimiddleware.RequestID, diff --git a/services/web/pkg/service/v0/branding.go b/services/web/pkg/service/v0/branding.go index 25a2746a4..4eae62732 100644 --- a/services/web/pkg/service/v0/branding.go +++ b/services/web/pkg/service/v0/branding.go @@ -26,9 +26,15 @@ var ( // UploadLogo implements the endpoint to upload a custom logo for the oCIS instance. func (p Web) UploadLogo(w http.ResponseWriter, r *http.Request) { + gatewayClient, err := p.gatewaySelector.Next() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + user := revactx.ContextMustGetUser(r.Context()) - rsp, err := p.gatewayClient.CheckPermission(r.Context(), &permissionsapi.CheckPermissionRequest{ - Permission: "change-logo", + rsp, err := gatewayClient.CheckPermission(r.Context(), &permissionsapi.CheckPermissionRequest{ + Permission: "Logo.Write", SubjectRef: &permissionsapi.SubjectReference{ Spec: &permissionsapi.SubjectReference_UserId{ UserId: user.Id, @@ -79,9 +85,15 @@ func (p Web) UploadLogo(w http.ResponseWriter, r *http.Request) { // ResetLogo implements the endpoint to reset the instance logo. // The config will be changed back to use the embedded logo asset. func (p Web) ResetLogo(w http.ResponseWriter, r *http.Request) { + gatewayClient, err := p.gatewaySelector.Next() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + user := revactx.ContextMustGetUser(r.Context()) - rsp, err := p.gatewayClient.CheckPermission(r.Context(), &permissionsapi.CheckPermissionRequest{ - Permission: "change-logo", + rsp, err := gatewayClient.CheckPermission(r.Context(), &permissionsapi.CheckPermissionRequest{ + Permission: "Logo.Write", SubjectRef: &permissionsapi.SubjectReference{ Spec: &permissionsapi.SubjectReference_UserId{ UserId: user.Id, diff --git a/services/web/pkg/service/v0/option.go b/services/web/pkg/service/v0/option.go index 8a0ed024b..8f6f261a4 100644 --- a/services/web/pkg/service/v0/option.go +++ b/services/web/pkg/service/v0/option.go @@ -4,6 +4,7 @@ import ( "net/http" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/services/web/pkg/config" ) @@ -13,10 +14,10 @@ type Option func(o *Options) // Options defines the available options for this package. type Options struct { - Logger log.Logger - Config *config.Config - Middleware []func(http.Handler) http.Handler - GatewayClient gateway.GatewayAPIClient + Logger log.Logger + Config *config.Config + Middleware []func(http.Handler) http.Handler + GatewaySelector pool.Selectable[gateway.GatewayAPIClient] } // newOptions initializes the available default options. @@ -51,9 +52,9 @@ func Middleware(val ...func(http.Handler) http.Handler) Option { } } -// GatewayClient provides a function to set the GatewayClient option. -func GatewayClient(client gateway.GatewayAPIClient) Option { +// GatewaySelector provides a function to set the gatewaySelector option. +func GatewaySelector(gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) Option { return func(o *Options) { - o.GatewayClient = client + o.GatewaySelector = gatewaySelector } } diff --git a/services/web/pkg/service/v0/service.go b/services/web/pkg/service/v0/service.go index 2699f975f..7e557b357 100644 --- a/services/web/pkg/service/v0/service.go +++ b/services/web/pkg/service/v0/service.go @@ -11,6 +11,7 @@ import ( "time" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/go-chi/chi/v5" "github.com/owncloud/ocis/v2/ocis-pkg/account" "github.com/owncloud/ocis/v2/ocis-pkg/assetsfs" @@ -42,11 +43,11 @@ func NewService(opts ...Option) Service { m.Use(options.Middleware...) svc := Web{ - logger: options.Logger, - config: options.Config, - mux: m, - fs: assetsfs.New(web.Assets, options.Config.Asset.Path, options.Logger), - gatewayClient: options.GatewayClient, + logger: options.Logger, + config: options.Config, + mux: m, + fs: assetsfs.New(web.Assets, options.Config.Asset.Path, options.Logger), + gatewaySelector: options.GatewaySelector, } m.Route(options.Config.HTTP.Root, func(r chi.Router) { @@ -72,11 +73,11 @@ func NewService(opts ...Option) Service { // Web defines implements the business logic for Service. type Web struct { - logger log.Logger - config *config.Config - mux *chi.Mux - fs *assetsfs.FileSystem - gatewayClient gateway.GatewayAPIClient + logger log.Logger + config *config.Config + mux *chi.Mux + fs *assetsfs.FileSystem + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] } // ServeHTTP implements the Service interface. diff --git a/services/webdav/README.md b/services/webdav/README.md index d13656c4d..0430401b0 100644 --- a/services/webdav/README.md +++ b/services/webdav/README.md @@ -1,6 +1,6 @@ # Webdav -The webdav service, like the [ocdav](https://github.com/owncloud/ocis/tree/master/services/ocdav) service, provides a HTTP API following the webdav protocol. It receives HTTP calls from requestors like clients and issues gRPC calls to other services executing these requests. After the called service has finished the request, the webdav service will render their responses in `xml` and sends them back to the requestor. +The webdav service, like the [ocdav](../ocdav) service, provides a HTTP API following the webdav protocol. It receives HTTP calls from requestors like clients and issues gRPC calls to other services executing these requests. After the called service has finished the request, the webdav service will render their responses in `xml` and sends them back to the requestor. ## Endpoints Overview diff --git a/services/webdav/pkg/command/root.go b/services/webdav/pkg/command/root.go index fd1255911..8a1d46b23 100644 --- a/services/webdav/pkg/command/root.go +++ b/services/webdav/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/webdav/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the webdav command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new webdav.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.WebDAV.Commons = cfg.Commons - return SutureService{ - cfg: cfg.WebDAV, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/webdav/pkg/config/config.go b/services/webdav/pkg/config/config.go index c4462833a..3edbb56fe 100644 --- a/services/webdav/pkg/config/config.go +++ b/services/webdav/pkg/config/config.go @@ -20,6 +20,7 @@ type Config struct { HTTP HTTP `yaml:"http"` + DisablePreviews bool `yaml:"disablePreviews" env:"OCIS_DISABLE_PREVIEWS;WEBDAV_DISABLE_PREVIEWS" desc:"Set this option to 'true' to disable rendering of thumbnails triggered via webdav access. Note that when disabled, all access to preview related webdav paths will return a 404."` OcisPublicURL string `yaml:"ocis_public_url" env:"OCIS_URL;OCIS_PUBLIC_URL" desc:"URL, where oCIS is reachable for users."` WebdavNamespace string `yaml:"webdav_namespace" env:"WEBDAV_WEBDAV_NAMESPACE" desc:"CS3 path layout to use when forwarding /webdav requests"` RevaGateway string `yaml:"reva_gateway" env:"OCIS_REVA_GATEWAY;REVA_GATEWAY" desc:"CS3 gateway used to look up user metadata" deprecationVersion:"3.0" removalVersion:"4.0.0" deprecationInfo:"REVA_GATEWAY changing name for consistency" deprecationReplacement:"OCIS_REVA_GATEWAY"` diff --git a/services/webdav/pkg/config/http.go b/services/webdav/pkg/config/http.go index 7b44da9e0..095b125aa 100644 --- a/services/webdav/pkg/config/http.go +++ b/services/webdav/pkg/config/http.go @@ -6,7 +6,7 @@ import "github.com/owncloud/ocis/v2/ocis-pkg/shared" type CORS struct { AllowedOrigins []string `yaml:"allow_origins" env:"OCIS_CORS_ALLOW_ORIGINS;WEBDAV_CORS_ALLOW_ORIGINS" desc:"A comma-separated list of allowed CORS origins. See following chapter for more details: *Access-Control-Allow-Origin* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin"` AllowedMethods []string `yaml:"allow_methods" env:"OCIS_CORS_ALLOW_METHODS;WEBDAV_CORS_ALLOW_METHODS" desc:"A comma-separated list of allowed CORS methods. See following chapter for more details: *Access-Control-Request-Method* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method"` - AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;WEBDAV_CORS_ALLOW_HEADERS" desc:"A comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` + AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;WEBDAV_CORS_ALLOW_HEADERS" desc:"A blank or comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` AllowCredentials bool `yaml:"allow_credentials" env:"OCIS_CORS_ALLOW_CREDENTIALS;WEBDAV_CORS_ALLOW_CREDENTIALS" desc:"Allow credentials for CORS.See following chapter for more details: *Access-Control-Allow-Credentials* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials."` } diff --git a/services/webdav/pkg/service/v0/search.go b/services/webdav/pkg/service/v0/search.go index 72c08f7fc..848b77f22 100644 --- a/services/webdav/pkg/service/v0/search.go +++ b/services/webdav/pkg/service/v0/search.go @@ -12,6 +12,9 @@ import ( "time" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + merrors "go-micro.dev/v4/errors" + "go-micro.dev/v4/metadata" + revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/tags" @@ -21,8 +24,6 @@ import ( "github.com/owncloud/ocis/v2/services/webdav/pkg/net" "github.com/owncloud/ocis/v2/services/webdav/pkg/prop" "github.com/owncloud/ocis/v2/services/webdav/pkg/propfind" - merrors "go-micro.dev/v4/errors" - "go-micro.dev/v4/metadata" ) const ( @@ -189,6 +190,7 @@ func matchToPropResponse(ctx context.Context, match *searchmsg.Match) (*propfind propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("d:getlastmodified", match.Entity.LastModifiedTime.AsTime().Format(time.RFC3339))) propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("d:getcontenttype", match.Entity.MimeType)) propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:permissions", match.Entity.Permissions)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:highlights", match.Entity.Highlights)) t := tags.New(match.Entity.Tags...) propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:tags", t.AsList())) diff --git a/services/webdav/pkg/service/v0/service.go b/services/webdav/pkg/service/v0/service.go index fe63df7cb..f60b75a62 100644 --- a/services/webdav/pkg/service/v0/service.go +++ b/services/webdav/pkg/service/v0/service.go @@ -17,10 +17,8 @@ import ( "github.com/cs3org/reva/v2/pkg/storage/utils/templates" "github.com/go-chi/chi/v5" "github.com/go-chi/render" - merrors "go-micro.dev/v4/errors" - "google.golang.org/grpc/metadata" - "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" thumbnailsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/thumbnails/v0" searchsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" @@ -28,6 +26,8 @@ import ( "github.com/owncloud/ocis/v2/services/webdav/pkg/config" "github.com/owncloud/ocis/v2/services/webdav/pkg/constants" "github.com/owncloud/ocis/v2/services/webdav/pkg/dav/requests" + merrors "go-micro.dev/v4/errors" + "google.golang.org/grpc/metadata" ) const ( @@ -64,9 +64,10 @@ func NewService(opts ...Option) (Service, error) { if err != nil { return nil, err } - gwc, err := pool.GetGatewayServiceClient(conf.RevaGateway, + gatewaySelector, err := pool.GatewaySelector(conf.RevaGateway, pool.WithTLSCACert(conf.GRPCClientTLS.CACert), pool.WithTLSMode(tm), + pool.WithRegistry(registry.GetRegistry()), ) if err != nil { return nil, err @@ -78,40 +79,45 @@ func NewService(opts ...Option) (Service, error) { mux: m, searchClient: searchsvc.NewSearchProviderService("com.owncloud.api.search", grpc.DefaultClient()), thumbnailsClient: thumbnailssvc.NewThumbnailService("com.owncloud.api.thumbnails", grpc.DefaultClient()), - revaClient: gwc, + gatewaySelector: gatewaySelector, } + if svc.config.DisablePreviews { + svc.thumbnailsClient = nil + } m.Route(options.Config.HTTP.Root, func(r chi.Router) { - r.Group(func(r chi.Router) { - r.Use(svc.DavUserContext()) + if !svc.config.DisablePreviews { + r.Group(func(r chi.Router) { + r.Use(svc.DavUserContext()) - r.Get("/remote.php/dav/spaces/{id}", svc.SpacesThumbnail) - r.Get("/remote.php/dav/spaces/{id}/*", svc.SpacesThumbnail) - r.Get("/dav/spaces/{id}", svc.SpacesThumbnail) - r.Get("/dav/spaces/{id}/*", svc.SpacesThumbnail) + r.Get("/remote.php/dav/spaces/{id}", svc.SpacesThumbnail) + r.Get("/remote.php/dav/spaces/{id}/*", svc.SpacesThumbnail) + r.Get("/dav/spaces/{id}", svc.SpacesThumbnail) + r.Get("/dav/spaces/{id}/*", svc.SpacesThumbnail) - r.Get("/remote.php/dav/files/{id}", svc.Thumbnail) - r.Get("/remote.php/dav/files/{id}/*", svc.Thumbnail) - r.Get("/dav/files/{id}", svc.Thumbnail) - r.Get("/dav/files/{id}/*", svc.Thumbnail) - }) + r.Get("/remote.php/dav/files/{id}", svc.Thumbnail) + r.Get("/remote.php/dav/files/{id}/*", svc.Thumbnail) + r.Get("/dav/files/{id}", svc.Thumbnail) + r.Get("/dav/files/{id}/*", svc.Thumbnail) + }) - r.Group(func(r chi.Router) { - r.Use(svc.DavPublicContext()) + r.Group(func(r chi.Router) { + r.Use(svc.DavPublicContext()) - r.Head("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnailHead) - r.Head("/dav/public-files/{token}/*", svc.PublicThumbnailHead) + r.Head("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnailHead) + r.Head("/dav/public-files/{token}/*", svc.PublicThumbnailHead) - r.Get("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnail) - r.Get("/dav/public-files/{token}/*", svc.PublicThumbnail) - }) + r.Get("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnail) + r.Get("/dav/public-files/{token}/*", svc.PublicThumbnail) + }) - r.Group(func(r chi.Router) { - r.Use(svc.WebDAVContext()) - r.Get("/remote.php/webdav/*", svc.Thumbnail) - r.Get("/webdav/*", svc.Thumbnail) - }) + r.Group(func(r chi.Router) { + r.Use(svc.WebDAVContext()) + r.Get("/remote.php/webdav/*", svc.Thumbnail) + r.Get("/webdav/*", svc.Thumbnail) + }) + } // r.MethodFunc("REPORT", "/remote.php/dav/files/{id}/*", svc.Search) @@ -149,7 +155,7 @@ type Webdav struct { mux *chi.Mux searchClient searchsvc.SearchProviderService thumbnailsClient thumbnailssvc.ThumbnailService - revaClient gatewayv1beta1.GatewayAPIClient + gatewaySelector pool.Selectable[gatewayv1beta1.GatewayAPIClient] } // ServeHTTP implements the Service interface. @@ -278,11 +284,17 @@ func (g Webdav) Thumbnail(w http.ResponseWriter, r *http.Request) { t := r.Header.Get(TokenHeader) - var user *userv1beta1.User + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + logger.Error().Err(err).Msg("could not get reva gatewayClient") + renderError(w, r, errInternalError("could not get reva gatewayClient")) + return + } + var user *userv1beta1.User if tr.Identifier == "" { // look up user from token via WhoAmI - userRes, err := g.revaClient.WhoAmI(r.Context(), &gatewayv1beta1.WhoAmIRequest{ + userRes, err := gatewayClient.WhoAmI(r.Context(), &gatewayv1beta1.WhoAmIRequest{ Token: t, }) if err != nil { @@ -299,7 +311,7 @@ func (g Webdav) Thumbnail(w http.ResponseWriter, r *http.Request) { } else { // look up user from URL via GetUserByClaim ctx := metadata.AppendToOutgoingContext(r.Context(), TokenHeader, t) - userRes, err := g.revaClient.GetUserByClaim(ctx, &userv1beta1.GetUserByClaimRequest{ + userRes, err := gatewayClient.GetUserByClaim(ctx, &userv1beta1.GetUserByClaimRequest{ Claim: "username", Value: tr.Identifier, }) diff --git a/services/webfinger/pkg/command/root.go b/services/webfinger/pkg/command/root.go index 306f33b9b..4d553a265 100644 --- a/services/webfinger/pkg/command/root.go +++ b/services/webfinger/pkg/command/root.go @@ -1,13 +1,10 @@ package command import ( - "context" "os" "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/services/webfinger/pkg/config" - "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" ) @@ -35,25 +32,3 @@ func Execute(cfg *config.Config) error { return app.Run(os.Args) } - -// SutureService allows for the webdav command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new webdav.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Webfinger.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Webfinger, - } -} - -func (s SutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - if err := Execute(s.cfg); err != nil { - return err - } - - return nil -} diff --git a/services/webfinger/pkg/config/http.go b/services/webfinger/pkg/config/http.go index 0353427b6..d53a0aad2 100644 --- a/services/webfinger/pkg/config/http.go +++ b/services/webfinger/pkg/config/http.go @@ -6,7 +6,7 @@ import "github.com/owncloud/ocis/v2/ocis-pkg/shared" type CORS struct { AllowedOrigins []string `yaml:"allow_origins" env:"OCIS_CORS_ALLOW_ORIGINS;WEBFINGER_CORS_ALLOW_ORIGINS" desc:"A comma-separated list of allowed CORS origins. See following chapter for more details: *Access-Control-Allow-Origin* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin"` AllowedMethods []string `yaml:"allow_methods" env:"OCIS_CORS_ALLOW_METHODS;WEBFINGER_CORS_ALLOW_METHODS" desc:"A comma-separated list of allowed CORS methods. See following chapter for more details: *Access-Control-Request-Method* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method"` - AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;WEBFINGER_CORS_ALLOW_HEADERS" desc:"A comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` + AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;WEBFINGER_CORS_ALLOW_HEADERS" desc:"A blank or comma-separated list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers."` AllowCredentials bool `yaml:"allow_credentials" env:"OCIS_CORS_ALLOW_CREDENTIALS;WEBFINGER_CORS_ALLOW_CREDENTIALS" desc:"Allow credentials for CORS.See following chapter for more details: *Access-Control-Allow-Credentials* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials."` } diff --git a/services/webfinger/pkg/service/v0/tracing.go b/services/webfinger/pkg/service/v0/tracing.go index 43dc863ab..23edef36e 100644 --- a/services/webfinger/pkg/service/v0/tracing.go +++ b/services/webfinger/pkg/service/v0/tracing.go @@ -23,10 +23,14 @@ type tracing struct { // Webfinger implements the Service interface. func (t tracing) Webfinger(ctx context.Context, queryTarget *url.URL, rels []string) (webfinger.JSONResourceDescriptor, error) { - ctx, span := webfingertracing.TraceProvider.Tracer("webfinger").Start(ctx, "Webfinger", trace.WithAttributes( - attribute.KeyValue{Key: "query_target", Value: attribute.StringValue(queryTarget.String())}, - attribute.KeyValue{Key: "rels", Value: attribute.StringSliceValue(rels)}, - )) + spanOpts := []trace.SpanStartOption{ + trace.WithSpanKind(trace.SpanKindServer), + trace.WithAttributes( + attribute.KeyValue{Key: "query_target", Value: attribute.StringValue(queryTarget.String())}, + attribute.KeyValue{Key: "rels", Value: attribute.StringSliceValue(rels)}, + ), + } + ctx, span := webfingertracing.TraceProvider.Tracer("webfinger").Start(ctx, "Webfinger", spanOpts...) defer span.End() return t.next.Webfinger(ctx, queryTarget, rels) diff --git a/sonar-project.properties b/sonar-project.properties index c9aaa0ee7..98a2a1f69 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,7 +2,7 @@ sonar.projectKey=owncloud_ocis sonar.organization=owncloud-1 sonar.projectName=ocis -sonar.projectVersion=3.0.0-rc.4 +sonar.projectVersion=3.1.0-next.2 sonar.host.url=https://sonarcloud.io # ===================================================== diff --git a/tests/TestHelpers/HttpRequestHelper.php b/tests/TestHelpers/HttpRequestHelper.php index 9d327e12c..6ea218786 100644 --- a/tests/TestHelpers/HttpRequestHelper.php +++ b/tests/TestHelpers/HttpRequestHelper.php @@ -200,7 +200,6 @@ class HttpRequestHelper { $sendRetryLimit = self::numRetriesOnHttpTooEarly(); $sendCount = 0; $sendExceptionHappened = false; - do { $response = self::sendRequestOnce( $url, diff --git a/tests/TestHelpers/OcisConfigHelper.php b/tests/TestHelpers/OcisConfigHelper.php index 245934f58..c1ba42ad0 100644 --- a/tests/TestHelpers/OcisConfigHelper.php +++ b/tests/TestHelpers/OcisConfigHelper.php @@ -22,6 +22,7 @@ namespace TestHelpers; +use GuzzleHttp\Exception\ConnectException; use TestHelpers\HttpRequestHelper; use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Psr7\Request; @@ -51,7 +52,12 @@ class OcisConfigHelper { [], $body ); - return $client->send($request); + + try { + return $client->send($request); + } catch (ConnectException $e) { + throw new Error("Cannot connect to the ociswrapper at the moment, make sure that ociswrapper is running before proceeding with the test run.\n" . $e->getMessage()); + } } /** diff --git a/tests/TestHelpers/SharingHelper.php b/tests/TestHelpers/SharingHelper.php index da3458a68..a58c154fb 100644 --- a/tests/TestHelpers/SharingHelper.php +++ b/tests/TestHelpers/SharingHelper.php @@ -158,7 +158,6 @@ class SharingHelper { $fd['expireDate'] = $expireDate; } $headers = ['OCS-APIREQUEST' => 'true']; - return HttpRequestHelper::post( $fullUrl, $xRequestId, diff --git a/tests/acceptance/config/behat.yml b/tests/acceptance/config/behat.yml index 74d2e8fda..abc8e247e 100644 --- a/tests/acceptance/config/behat.yml +++ b/tests/acceptance/config/behat.yml @@ -23,14 +23,10 @@ default: regularUserPassword: 123456 ocPath: apps/testing/api/v1/occ - CapabilitiesContext: - - ChecksumContext: - - FavoritesContext: - FilesVersionsContext: - NotificationContext: - OCSContext: - PublicWebDavContext: - - TrashbinContext: - - WebDavPropertiesContext: apiSpaces: paths: @@ -41,19 +37,16 @@ default: - SpacesContext: - FeatureContext: *common_feature_context_params - CapabilitiesContext: - - ChecksumContext: - - FavoritesContext: - FilesVersionsContext: - NotificationContext: - OCSContext: - PublicWebDavContext: - SearchContext: - TagContext: - - TrashbinContext: - - WebDavPropertiesContext: - TUSContext: - SpacesTUSContext: - GraphContext: + - OcisConfigContext: apiSpacesShares: paths: @@ -86,14 +79,10 @@ default: - SpacesContext: - FeatureContext: *common_feature_context_params - CapabilitiesContext: - - ChecksumContext: - - FavoritesContext: - FilesVersionsContext: - OCSContext: - PublicWebDavContext: - SearchContext: - - TrashbinContext: - - WebDavPropertiesContext: - TUSContext: - SpacesTUSContext: - GraphContext: @@ -108,13 +97,9 @@ default: - SpacesContext: - FeatureContext: *common_feature_context_params - CapabilitiesContext: - - ChecksumContext: - FilesVersionsContext: - OCSContext: - PublicWebDavContext: - - FavoritesContext: - - TrashbinContext: - - WebDavPropertiesContext: - GraphContext: apiGraph: @@ -127,16 +112,14 @@ default: - SpacesContext: - FeatureContext: *common_feature_context_params - CapabilitiesContext: - - ChecksumContext: - - FavoritesContext: - FilesVersionsContext: - OCSContext: - PublicWebDavContext: - SearchContext: - - TrashbinContext: - - WebDavPropertiesContext: - TUSContext: - SpacesTUSContext: + - OcisConfigContext: + - TagContext: apiCors: paths: @@ -145,12 +128,8 @@ default: contexts: - SpacesContext: - FeatureContext: *common_feature_context_params - - WebDavPropertiesContext: - - FavoritesContext: - - ChecksumContext: - FilesVersionsContext: - OCSContext: - - TrashbinContext: - GraphContext: - OcisConfigContext: @@ -162,11 +141,8 @@ default: - SpacesContext: - FeatureContext: *common_feature_context_params - WebDavPropertiesContext: - - FavoritesContext: - - ChecksumContext: - FilesVersionsContext: - OCSContext: - - TrashbinContext: - GraphContext: - OcisConfigContext: @@ -178,15 +154,26 @@ default: - NotificationContext: - SpacesContext: - FeatureContext: *common_feature_context_params - - WebDavPropertiesContext: - OCSContext: - GraphContext: - - TrashbinContext: - - FavoritesContext: - - ChecksumContext: - FilesVersionsContext: - SettingsContext: + apiAntivirus: + paths: + - '%paths.base%/../features/apiAntivirus' + context: *common_ldap_suite_context + contexts: + - NotificationContext: + - SpacesContext: + - FeatureContext: *common_feature_context_params + - OCSContext: + - GraphContext: + - FilesVersionsContext: + - SettingsContext: + - OcisConfigContext: + - PublicWebDavContext: + extensions: rdx\behatvars\BehatVariablesExtension: ~ diff --git a/tests/acceptance/docker/Makefile b/tests/acceptance/docker/Makefile index eddf0f231..2303b5d4e 100644 --- a/tests/acceptance/docker/Makefile +++ b/tests/acceptance/docker/Makefile @@ -186,11 +186,19 @@ testSuite: $(OCIS_WRAPPER) build-dev-image clean-docker-container COMPOSE_FILE=src/email.yml \ docker-compose run start_email; \ fi; \ + if [ "${START_ANTIVIRUS}" == "true" ]; then \ + COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME) \ + COMPOSE_FILE=src/antivirus.yml \ + docker-compose run start_antivirus; \ + fi; \ COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME) \ COMPOSE_FILE=$(COMPOSE_FILE) \ STORAGE_DRIVER=$(STORAGE_DRIVER) \ TEST_SOURCE=$(TEST_SOURCE) \ WITH_WRAPPER=$(WITH_WRAPPER) \ + OCIS_ASYNC_UPLOADS=$(OCIS_ASYNC_UPLOADS) \ + OCIS_ADD_RUN_SERVICES=$(OCIS_ADD_RUN_SERVICES) \ + POSTPROCESSING_STEPS=$(POSTPROCESSING_STEPS) \ OCIS_IMAGE_TAG=$(OCIS_IMAGE_TAG) \ BEHAT_SUITE=$(BEHAT_SUITE) \ BEHAT_FEATURE=$(BEHAT_FEATURE) \ diff --git a/tests/acceptance/docker/src/antivirus.yml b/tests/acceptance/docker/src/antivirus.yml new file mode 100644 index 000000000..2c1234e7d --- /dev/null +++ b/tests/acceptance/docker/src/antivirus.yml @@ -0,0 +1,9 @@ +services: + start_antivirus: + image: dadarek/wait-for-dependencies + depends_on: + - clamav + command: clamav:3310 + clamav: + image: mkodockx/docker-clamav:alpine + diff --git a/tests/acceptance/docker/src/ocis-base.yml b/tests/acceptance/docker/src/ocis-base.yml index d6716720d..8924ca5c4 100644 --- a/tests/acceptance/docker/src/ocis-base.yml +++ b/tests/acceptance/docker/src/ocis-base.yml @@ -22,6 +22,9 @@ services: IDM_CREATE_DEMO_USERS: "true" IDM_ADMIN_PASSWORD: "admin" FRONTEND_SEARCH_MIN_LENGTH: "2" + OCIS_ASYNC_UPLOADS: $OCIS_ASYNC_UPLOADS + OCIS_ADD_RUN_SERVICES: $OCIS_ADD_RUN_SERVICES + # s3ng specific settings STORAGE_USERS_S3NG_ENDPOINT: http://ceph:8080 STORAGE_USERS_S3NG_REGION: default @@ -32,6 +35,13 @@ services: NOTIFICATIONS_SMTP_HOST: email NOTIFICATIONS_SMTP_PORT: 2500 NOTIFICATIONS_SMTP_INSECURE: "true" + + # antivirus + ANTIVIRUS_SCANNER_TYPE: "clamav" + ANTIVIRUS_CLAMAV_SOCKET: tcp://clamav:3310 + + # postprocessing step + POSTPROCESSING_STEPS: $POSTPROCESSING_STEPS volumes: - ../../../config:/drone/src/tests/config - ../../../ociswrapper/bin/ociswrapper:/usr/bin/ociswrapper diff --git a/tests/acceptance/expected-failures-API-on-OCIS-storage.md b/tests/acceptance/expected-failures-API-on-OCIS-storage.md index 63f75decc..bc9edd3c2 100644 --- a/tests/acceptance/expected-failures-API-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-API-on-OCIS-storage.md @@ -35,17 +35,17 @@ _ocdav: double-check the webdav property parsing when custom namespaces are used #### [file versions do not report the version author](https://github.com/owncloud/ocis/issues/2914) -- [coreApiVersions/fileVersionAuthor.feature:12](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L12) -- [coreApiVersions/fileVersionAuthor.feature:43](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L43) -- [coreApiVersions/fileVersionAuthor.feature:70](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L70) -- [coreApiVersions/fileVersionAuthor.feature:96](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L96) -- [coreApiVersions/fileVersionAuthor.feature:129](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L129) -- [coreApiVersions/fileVersionAuthor.feature:156](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L156) -- [coreApiVersions/fileVersionAuthor.feature:187](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L187) -- [coreApiVersions/fileVersionAuthor.feature:222](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L222) -- [coreApiVersions/fileVersionAuthor.feature:274](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L274) -- [coreApiVersions/fileVersionAuthor.feature:323](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L323) -- [coreApiVersions/fileVersionAuthor.feature:344](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L344) +- [coreApiVersions/fileVersionAuthor.feature:15](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L15) +- [coreApiVersions/fileVersionAuthor.feature:46](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L46) +- [coreApiVersions/fileVersionAuthor.feature:73](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L73) +- [coreApiVersions/fileVersionAuthor.feature:99](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L99) +- [coreApiVersions/fileVersionAuthor.feature:132](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L132) +- [coreApiVersions/fileVersionAuthor.feature:159](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L159) +- [coreApiVersions/fileVersionAuthor.feature:190](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L190) +- [coreApiVersions/fileVersionAuthor.feature:225](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L225) +- [coreApiVersions/fileVersionAuthor.feature:277](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L277) +- [coreApiVersions/fileVersionAuthor.feature:326](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L326) +- [coreApiVersions/fileVersionAuthor.feature:347](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L347) ### Sync @@ -58,116 +58,103 @@ Synchronization features like etag propagation, setting mtime and locking files #### [Webdav LOCK operations](https://github.com/owncloud/ocis/issues/1284) -- [coreApiWebdavLocks/exclusiveLocks.feature:46](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L46) -- [coreApiWebdavLocks/exclusiveLocks.feature:47](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L47) -- [coreApiWebdavLocks/exclusiveLocks.feature:48](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L48) - [coreApiWebdavLocks/exclusiveLocks.feature:49](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L49) -- [coreApiWebdavLocks/exclusiveLocks.feature:54](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L54) -- [coreApiWebdavLocks/exclusiveLocks.feature:55](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L55) -- [coreApiWebdavLocks/exclusiveLocks.feature:73](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L73) -- [coreApiWebdavLocks/exclusiveLocks.feature:74](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L74) -- [coreApiWebdavLocks/exclusiveLocks.feature:75](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L75) +- [coreApiWebdavLocks/exclusiveLocks.feature:50](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L50) +- [coreApiWebdavLocks/exclusiveLocks.feature:51](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L51) +- [coreApiWebdavLocks/exclusiveLocks.feature:52](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L52) +- [coreApiWebdavLocks/exclusiveLocks.feature:57](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L57) +- [coreApiWebdavLocks/exclusiveLocks.feature:58](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L58) - [coreApiWebdavLocks/exclusiveLocks.feature:76](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L76) -- [coreApiWebdavLocks/exclusiveLocks.feature:81](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L81) -- [coreApiWebdavLocks/exclusiveLocks.feature:82](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L82) -- [coreApiWebdavLocks/exclusiveLocks.feature:100](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L100) -- [coreApiWebdavLocks/exclusiveLocks.feature:101](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L101) -- [coreApiWebdavLocks/exclusiveLocks.feature:102](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L102) +- [coreApiWebdavLocks/exclusiveLocks.feature:77](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L77) +- [coreApiWebdavLocks/exclusiveLocks.feature:78](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L78) +- [coreApiWebdavLocks/exclusiveLocks.feature:79](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L79) +- [coreApiWebdavLocks/exclusiveLocks.feature:84](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L84) +- [coreApiWebdavLocks/exclusiveLocks.feature:85](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L85) - [coreApiWebdavLocks/exclusiveLocks.feature:103](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L103) -- [coreApiWebdavLocks/exclusiveLocks.feature:108](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L108) -- [coreApiWebdavLocks/exclusiveLocks.feature:109](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L109) -- [coreApiWebdavLocks/requestsWithToken.feature:29](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/requestsWithToken.feature#L29) -- [coreApiWebdavLocks/requestsWithToken.feature:30](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/requestsWithToken.feature#L30) -- [coreApiWebdavLocks/requestsWithToken.feature:35](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/requestsWithToken.feature#L35) -- [coreApiWebdavLocks2/independentLocks.feature:23](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L23) -- [coreApiWebdavLocks2/independentLocks.feature:24](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L24) +- [coreApiWebdavLocks/exclusiveLocks.feature:104](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L104) +- [coreApiWebdavLocks/exclusiveLocks.feature:105](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L105) +- [coreApiWebdavLocks/exclusiveLocks.feature:106](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L106) +- [coreApiWebdavLocks/exclusiveLocks.feature:111](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L111) +- [coreApiWebdavLocks/exclusiveLocks.feature:112](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L112) +- [coreApiWebdavLocks/requestsWithToken.feature:32](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/requestsWithToken.feature#L32) +- [coreApiWebdavLocks/requestsWithToken.feature:33](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/requestsWithToken.feature#L33) +- [coreApiWebdavLocks/requestsWithToken.feature:38](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/requestsWithToken.feature#L38) - [coreApiWebdavLocks2/independentLocks.feature:25](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L25) - [coreApiWebdavLocks2/independentLocks.feature:26](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L26) -- [coreApiWebdavLocks2/independentLocks.feature:31](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L31) -- [coreApiWebdavLocks2/independentLocks.feature:32](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L32) -- [coreApiWebdavLocks2/independentLocks.feature:51](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L51) -- [coreApiWebdavLocks2/independentLocks.feature:52](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L52) +- [coreApiWebdavLocks2/independentLocks.feature:27](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L27) +- [coreApiWebdavLocks2/independentLocks.feature:28](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L28) +- [coreApiWebdavLocks2/independentLocks.feature:33](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L33) +- [coreApiWebdavLocks2/independentLocks.feature:34](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L34) - [coreApiWebdavLocks2/independentLocks.feature:53](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L53) - [coreApiWebdavLocks2/independentLocks.feature:54](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L54) - [coreApiWebdavLocks2/independentLocks.feature:55](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L55) - [coreApiWebdavLocks2/independentLocks.feature:56](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L56) - [coreApiWebdavLocks2/independentLocks.feature:57](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L57) - [coreApiWebdavLocks2/independentLocks.feature:58](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L58) -- [coreApiWebdavLocks2/independentLocks.feature:63](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L63) -- [coreApiWebdavLocks2/independentLocks.feature:64](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L64) +- [coreApiWebdavLocks2/independentLocks.feature:59](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L59) +- [coreApiWebdavLocks2/independentLocks.feature:60](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L60) - [coreApiWebdavLocks2/independentLocks.feature:65](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L65) - [coreApiWebdavLocks2/independentLocks.feature:66](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L66) -- [coreApiWebdavLocks2/independentLocksShareToShares.feature:28](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L28) -- [coreApiWebdavLocks2/independentLocksShareToShares.feature:29](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L29) +- [coreApiWebdavLocks2/independentLocks.feature:67](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L67) +- [coreApiWebdavLocks2/independentLocks.feature:68](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature#L68) - [coreApiWebdavLocks2/independentLocksShareToShares.feature:30](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L30) - [coreApiWebdavLocks2/independentLocksShareToShares.feature:31](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L31) -- [coreApiWebdavLocks2/independentLocksShareToShares.feature:36](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L36) -- [coreApiWebdavLocks2/independentLocksShareToShares.feature:37](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L37) -- [coreApiWebdavLocks2/independentLocksShareToShares.feature:57](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L57) -- [coreApiWebdavLocks2/independentLocksShareToShares.feature:58](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L58) +- [coreApiWebdavLocks2/independentLocksShareToShares.feature:32](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L32) +- [coreApiWebdavLocks2/independentLocksShareToShares.feature:33](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L33) +- [coreApiWebdavLocks2/independentLocksShareToShares.feature:38](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L38) +- [coreApiWebdavLocks2/independentLocksShareToShares.feature:39](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L39) - [coreApiWebdavLocks2/independentLocksShareToShares.feature:59](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L59) - [coreApiWebdavLocks2/independentLocksShareToShares.feature:60](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L60) -- [coreApiWebdavLocks2/independentLocksShareToShares.feature:65](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L65) -- [coreApiWebdavLocks2/independentLocksShareToShares.feature:66](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L66) -- [coreApiWebdavLocksUnlock/unlock.feature:20](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L20) -- [coreApiWebdavLocksUnlock/unlock.feature:21](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L21) -- [coreApiWebdavLocksUnlock/unlock.feature:26](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L26) -- [coreApiWebdavLocksUnlock/unlock.feature:40](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L40) -- [coreApiWebdavLocksUnlock/unlock.feature:41](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L41) -- [coreApiWebdavLocksUnlock/unlock.feature:63](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L63) -- [coreApiWebdavLocksUnlock/unlock.feature:64](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L64) -- [coreApiWebdavLocksUnlock/unlock.feature:65](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L65) +- [coreApiWebdavLocks2/independentLocksShareToShares.feature:61](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L61) +- [coreApiWebdavLocks2/independentLocksShareToShares.feature:62](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L62) +- [coreApiWebdavLocks2/independentLocksShareToShares.feature:67](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L67) +- [coreApiWebdavLocks2/independentLocksShareToShares.feature:68](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature#L68) +- [coreApiWebdavLocksUnlock/unlock.feature:23](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L23) +- [coreApiWebdavLocksUnlock/unlock.feature:24](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L24) +- [coreApiWebdavLocksUnlock/unlock.feature:29](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L29) +- [coreApiWebdavLocksUnlock/unlock.feature:43](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L43) +- [coreApiWebdavLocksUnlock/unlock.feature:44](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L44) - [coreApiWebdavLocksUnlock/unlock.feature:66](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L66) -- [coreApiWebdavLocksUnlock/unlock.feature:71](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L71) -- [coreApiWebdavLocksUnlock/unlock.feature:72](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L72) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:25](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L25) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:26](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L26) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:27](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L27) +- [coreApiWebdavLocksUnlock/unlock.feature:67](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L67) +- [coreApiWebdavLocksUnlock/unlock.feature:68](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L68) +- [coreApiWebdavLocksUnlock/unlock.feature:69](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L69) +- [coreApiWebdavLocksUnlock/unlock.feature:74](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L74) +- [coreApiWebdavLocksUnlock/unlock.feature:75](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L75) - [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:28](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L28) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:33](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L33) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:34](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L34) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:49](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L49) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:50](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L50) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:51](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L51) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:29](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L29) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:30](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L30) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:31](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L31) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:36](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L36) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:37](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L37) - [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:52](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L52) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:57](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L57) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:58](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L58) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:73](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L73) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:74](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L74) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:75](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L75) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:53](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L53) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:54](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L54) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:55](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L55) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:60](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L60) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:61](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L61) - [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:76](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L76) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:81](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L81) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:82](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L82) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:97](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L97) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:98](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L98) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:99](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L99) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:77](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L77) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:78](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L78) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:79](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L79) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:84](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L84) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:85](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L85) - [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:100](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L100) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:105](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L105) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:106](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L106) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:121](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L121) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:122](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L122) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:123](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L123) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:101](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L101) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:102](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L102) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:103](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L103) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:108](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L108) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:109](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L109) - [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:124](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L124) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:129](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L129) -- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:130](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L130) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:125](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L125) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:126](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L126) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:127](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L127) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:132](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L132) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:133](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L133) ### Share File and sync features in a shared scenario -#### [ocs sharing api always returns an empty exact list while searching for a sharee](https://github.com/owncloud/ocis/issues/4265) - -- [coreApiSharees/sharees.feature:100](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L100) -- [coreApiSharees/sharees.feature:101](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L101) -- [coreApiSharees/sharees.feature:120](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L120) -- [coreApiSharees/sharees.feature:121](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L121) -- [coreApiSharees/sharees.feature:140](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L140) -- [coreApiSharees/sharees.feature:141](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L141) -- [coreApiSharees/sharees.feature:160](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L160) -- [coreApiSharees/sharees.feature:161](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L161) -- [coreApiSharees/sharees.feature:180](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L180) -- [coreApiSharees/sharees.feature:181](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L181) - #### [accepting matching name shared resources from different users/groups sets no serial identifiers on the resource name for the receiver](https://github.com/owncloud/ocis/issues/4289) - [coreApiShareManagementToShares/acceptShares.feature:238](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/acceptShares.feature#L238) @@ -223,11 +210,11 @@ cannot share a folder with create permission #### [download previews of other users file](https://github.com/owncloud/ocis/issues/2071) -- [coreApiWebdavPreviews/previews.feature:91](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L91) +- [coreApiWebdavPreviews/previews.feature:94](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L94) #### [different error message detail for previews of folder](https://github.com/owncloud/ocis/issues/2064) -- [coreApiWebdavPreviews/previews.feature:100](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L100) +- [coreApiWebdavPreviews/previews.feature:103](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L103) #### [copying a folder within a public link folder to folder with same name as an already existing file overwrites the parent file](https://github.com/owncloud/ocis/issues/1232) @@ -251,21 +238,21 @@ cannot share a folder with create permission #### [deleting a file inside a received shared folder is moved to the trash-bin of the sharer not the receiver](https://github.com/owncloud/ocis/issues/1124) -- [coreApiTrashbin/trashbinSharingToShares.feature:27](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L27) -- [coreApiTrashbin/trashbinSharingToShares.feature:44](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L44) -- [coreApiTrashbin/trashbinSharingToShares.feature:49](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L49) -- [coreApiTrashbin/trashbinSharingToShares.feature:71](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L71) -- [coreApiTrashbin/trashbinSharingToShares.feature:76](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L76) -- [coreApiTrashbin/trashbinSharingToShares.feature:98](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L98) -- [coreApiTrashbin/trashbinSharingToShares.feature:103](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L103) -- [coreApiTrashbin/trashbinSharingToShares.feature:126](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L126) -- [coreApiTrashbin/trashbinSharingToShares.feature:131](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L131) -- [coreApiTrashbin/trashbinSharingToShares.feature:154](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L154) -- [coreApiTrashbin/trashbinSharingToShares.feature:159](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L159) -- [coreApiTrashbin/trashbinSharingToShares.feature:182](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L182) -- [coreApiTrashbin/trashbinSharingToShares.feature:187](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L187) -- [coreApiTrashbin/trashbinSharingToShares.feature:210](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L210) -- [coreApiTrashbin/trashbinSharingToShares.feature:234](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L234) +- [coreApiTrashbin/trashbinSharingToShares.feature:30](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L30) +- [coreApiTrashbin/trashbinSharingToShares.feature:47](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L47) +- [coreApiTrashbin/trashbinSharingToShares.feature:52](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L52) +- [coreApiTrashbin/trashbinSharingToShares.feature:74](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L74) +- [coreApiTrashbin/trashbinSharingToShares.feature:79](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L79) +- [coreApiTrashbin/trashbinSharingToShares.feature:101](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L101) +- [coreApiTrashbin/trashbinSharingToShares.feature:106](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L106) +- [coreApiTrashbin/trashbinSharingToShares.feature:129](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L129) +- [coreApiTrashbin/trashbinSharingToShares.feature:134](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L134) +- [coreApiTrashbin/trashbinSharingToShares.feature:157](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L157) +- [coreApiTrashbin/trashbinSharingToShares.feature:162](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L162) +- [coreApiTrashbin/trashbinSharingToShares.feature:185](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L185) +- [coreApiTrashbin/trashbinSharingToShares.feature:190](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L190) +- [coreApiTrashbin/trashbinSharingToShares.feature:213](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L213) +- [coreApiTrashbin/trashbinSharingToShares.feature:237](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L237) #### [changing user quota gives ocs status 103 / Cannot set quota](https://github.com/owncloud/product/issues/247) @@ -469,44 +456,44 @@ And other missing implementation of favorites #### [PATCH request for TUS upload with wrong checksum gives incorrect response](https://github.com/owncloud/ocis/issues/1755) -- [coreApiWebdavUploadTUS/checksums.feature:84](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L84) -- [coreApiWebdavUploadTUS/checksums.feature:85](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L85) -- [coreApiWebdavUploadTUS/checksums.feature:86](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L86) - [coreApiWebdavUploadTUS/checksums.feature:87](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L87) -- [coreApiWebdavUploadTUS/checksums.feature:92](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L92) -- [coreApiWebdavUploadTUS/checksums.feature:93](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L93) -- [coreApiWebdavUploadTUS/checksums.feature:173](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L173) -- [coreApiWebdavUploadTUS/checksums.feature:174](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L174) -- [coreApiWebdavUploadTUS/checksums.feature:179](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L179) -- [coreApiWebdavUploadTUS/checksums.feature:226](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L226) -- [coreApiWebdavUploadTUS/checksums.feature:227](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L227) -- [coreApiWebdavUploadTUS/checksums.feature:228](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L228) +- [coreApiWebdavUploadTUS/checksums.feature:88](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L88) +- [coreApiWebdavUploadTUS/checksums.feature:89](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L89) +- [coreApiWebdavUploadTUS/checksums.feature:90](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L90) +- [coreApiWebdavUploadTUS/checksums.feature:95](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L95) +- [coreApiWebdavUploadTUS/checksums.feature:96](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L96) +- [coreApiWebdavUploadTUS/checksums.feature:176](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L176) +- [coreApiWebdavUploadTUS/checksums.feature:177](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L177) +- [coreApiWebdavUploadTUS/checksums.feature:182](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L182) - [coreApiWebdavUploadTUS/checksums.feature:229](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L229) -- [coreApiWebdavUploadTUS/checksums.feature:234](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L234) -- [coreApiWebdavUploadTUS/checksums.feature:235](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L235) -- [coreApiWebdavUploadTUS/checksums.feature:282](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L282) -- [coreApiWebdavUploadTUS/checksums.feature:283](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L283) -- [coreApiWebdavUploadTUS/checksums.feature:284](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L284) +- [coreApiWebdavUploadTUS/checksums.feature:230](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L230) +- [coreApiWebdavUploadTUS/checksums.feature:231](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L231) +- [coreApiWebdavUploadTUS/checksums.feature:232](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L232) +- [coreApiWebdavUploadTUS/checksums.feature:237](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L237) +- [coreApiWebdavUploadTUS/checksums.feature:238](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L238) - [coreApiWebdavUploadTUS/checksums.feature:285](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L285) -- [coreApiWebdavUploadTUS/checksums.feature:290](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L290) -- [coreApiWebdavUploadTUS/checksums.feature:291](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L291) -- [coreApiWebdavUploadTUS/optionsRequest.feature:8](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature#L8) -- [coreApiWebdavUploadTUS/optionsRequest.feature:23](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature#L23) -- [coreApiWebdavUploadTUS/uploadToShare.feature:174](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L174) -- [coreApiWebdavUploadTUS/uploadToShare.feature:175](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L175) -- [coreApiWebdavUploadTUS/uploadToShare.feature:193](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L193) -- [coreApiWebdavUploadTUS/uploadToShare.feature:194](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L194) -- [coreApiWebdavUploadTUS/uploadToShare.feature:212](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L212) -- [coreApiWebdavUploadTUS/uploadToShare.feature:213](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L213) -- [coreApiWebdavUploadTUS/uploadToShare.feature:251](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L251) -- [coreApiWebdavUploadTUS/uploadToShare.feature:252](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L252) -- [coreApiWebdavUploadTUS/uploadToShare.feature:293](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L293) -- [coreApiWebdavUploadTUS/uploadToShare.feature:294](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L294) +- [coreApiWebdavUploadTUS/checksums.feature:286](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L286) +- [coreApiWebdavUploadTUS/checksums.feature:287](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L287) +- [coreApiWebdavUploadTUS/checksums.feature:288](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L288) +- [coreApiWebdavUploadTUS/checksums.feature:293](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L293) +- [coreApiWebdavUploadTUS/checksums.feature:294](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L294) +- [coreApiWebdavUploadTUS/optionsRequest.feature:11](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature#L11) +- [coreApiWebdavUploadTUS/optionsRequest.feature:26](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature#L26) +- [coreApiWebdavUploadTUS/uploadToShare.feature:176](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L176) +- [coreApiWebdavUploadTUS/uploadToShare.feature:177](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L177) +- [coreApiWebdavUploadTUS/uploadToShare.feature:195](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L195) +- [coreApiWebdavUploadTUS/uploadToShare.feature:196](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L196) +- [coreApiWebdavUploadTUS/uploadToShare.feature:214](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L214) +- [coreApiWebdavUploadTUS/uploadToShare.feature:215](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L215) +- [coreApiWebdavUploadTUS/uploadToShare.feature:253](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L253) +- [coreApiWebdavUploadTUS/uploadToShare.feature:254](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L254) +- [coreApiWebdavUploadTUS/uploadToShare.feature:295](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L295) +- [coreApiWebdavUploadTUS/uploadToShare.feature:296](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L296) #### [TUS OPTIONS requests do not reply with TUS headers when invalid password](https://github.com/owncloud/ocis/issues/1012) -- [coreApiWebdavUploadTUS/optionsRequest.feature:38](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature#L38) -- [coreApiWebdavUploadTUS/optionsRequest.feature:53](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature#L53) +- [coreApiWebdavUploadTUS/optionsRequest.feature:41](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature#L41) +- [coreApiWebdavUploadTUS/optionsRequest.feature:56](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature#L56) #### [Trying to accept a share with invalid ID gives incorrect OCS and HTTP status](https://github.com/owncloud/ocis/issues/2111) @@ -577,7 +564,7 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers #### [Preview of text file with UTF content does not render correctly](https://github.com/owncloud/ocis/issues/2570) -- [coreApiWebdavPreviews/previews.feature:134](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L134) +- [coreApiWebdavPreviews/previews.feature:137](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L137) #### [Share path in the response is different between share states](https://github.com/owncloud/ocis/issues/2540) @@ -655,7 +642,7 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers - [coreApiCapabilities/capabilities.feature:136](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiCapabilities/capabilities.feature#L136) - [coreApiCapabilities/capabilities.feature:175](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiCapabilities/capabilities.feature#L175) -#### [Sharing of project space root via public link does no longer work](https://github.com/owncloud/ocis/issues/6278) +#### [sharing the shares folder to users exits with different status code than in oc10 backend](https://github.com/owncloud/ocis/issues/2215) - [coreApiShareCreateSpecialToShares2/createShareDefaultFolderForReceivedShares.feature:23](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareDefaultFolderForReceivedShares.feature#L23) - [coreApiShareCreateSpecialToShares2/createShareDefaultFolderForReceivedShares.feature:24](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareDefaultFolderForReceivedShares.feature#L24) diff --git a/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md b/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md index c930cf98d..1f4d0e803 100644 --- a/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md @@ -18,18 +18,6 @@ The expected failures in this file are from features in the owncloud/ocis repo. - [apiArchiver/downloadById.feature:134](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiArchiver/downloadById.feature#L134) - [apiArchiver/downloadById.feature:135](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiArchiver/downloadById.feature#L135) -### [create request for already existing user exits with status code 500 ](https://github.com/owncloud/ocis/issues/3516) - -- [apiGraph/createGroupCaseSensitive.feature:20](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/createGroupCaseSensitive.feature#L20) -- [apiGraph/createGroupCaseSensitive.feature:21](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/createGroupCaseSensitive.feature#L21) -- [apiGraph/createGroupCaseSensitive.feature:22](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/createGroupCaseSensitive.feature#L22) -- [apiGraph/createGroupCaseSensitive.feature:23](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/createGroupCaseSensitive.feature#L23) -- [apiGraph/createGroupCaseSensitive.feature:24](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/createGroupCaseSensitive.feature#L24) -- [apiGraph/createGroupCaseSensitive.feature:25](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/createGroupCaseSensitive.feature#L25) -- [apiGraph/createGroup.feature:28](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/createGroup.feature#L28) -- [apiGraph/createUser.feature:41](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/createUser.feature#L41) -- [apiGraph/createUser.feature:72](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/createUser.feature#L72) - ### [PROPFIND on accepted shares with identical names containing brackets exit with 404](https://github.com/owncloud/ocis/issues/4421) - [apiSpacesShares/changingFilesShare.feature:15](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSpacesShares/changingFilesShare.feature#L15) @@ -45,15 +33,6 @@ The expected failures in this file are from features in the owncloud/ocis repo. - [apiSpacesShares/shareUploadTUS.feature:219](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSpacesShares/shareUploadTUS.feature#L219) - [apiSpacesShares/shareUploadTUS.feature:284](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSpacesShares/shareUploadTUS.feature#L284) -### [Copy or move on an existing resource doesn't create a new version but deletes instead](https://github.com/owncloud/ocis/issues/4797) - -- [apiSpacesShares/moveSpaces.feature:322](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSpacesShares/moveSpaces.feature#L322) -- [apiSpacesShares/copySpaces.feature:793](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSpacesShares/copySpaces.feature#L793) - -### [Creating group with empty name returns status code 200](https://github.com/owncloud/ocis/issues/5050) - -- [apiGraph/createGroup.feature:48](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/createGroup.feature#L48) - ### [Settings service user can list other peoples assignments](https://github.com/owncloud/ocis/issues/5032) - [apiAccountsHashDifficulty/assignRole.feature:28](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiAccountsHashDifficulty/assignRole.feature#L28) @@ -78,16 +57,6 @@ The expected failures in this file are from features in the owncloud/ocis repo. - [apiCors/cors.feature:70](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiCors/cors.feature#L70) - [apiCors/cors.feature:71](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiCors/cors.feature#L71) -#### [POST response does not return correct path when creating public link](https://github.com/owncloud/ocis/issues/5139) - -- [apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature:63](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature#L63) -- [apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature:64](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature#L64) -- [apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature:65](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature#L65) -- [apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature:93](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature#L93) -- [apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature:167](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature#L167) -- [apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature:168](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature#L168) -- [apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature:169](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature#L169) - #### [A User can get information of another user with Graph API](https://github.com/owncloud/ocis/issues/5125) - [apiGraph/getUser.feature:83](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/getUser.feature#L83) diff --git a/tests/acceptance/features/apiAntivirus/antivirus.feature b/tests/acceptance/features/apiAntivirus/antivirus.feature new file mode 100644 index 000000000..d6d4bc359 --- /dev/null +++ b/tests/acceptance/features/apiAntivirus/antivirus.feature @@ -0,0 +1,518 @@ +@api @antivirus @skipOnReva +Feature: antivirus + As a system administrator and user + I want to protect myself and others from known viruses + So that I can prevent files with viruses from being uploaded + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: upload a normal file without virus + Given using DAV path + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/normalfile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/normalfile.txt" should exist + And the content of file "/normalfile.txt" for user "Alice" should be: + """ + This is a testfile. + + Cheers. + """ + Examples: + | dav-path-version | + | old | + | new | + | spaces | + + + Scenario Outline: upload a file with virus + Given using DAV path + When user "Alice" uploads file "filesForUpload/filesWithVirus/eicar.com" to "/aFileWithVirus.txt" using the WebDAV API + # antivirus service can scan files during post-processing. on demand scanning is currently not available + Then the HTTP status code should be "201" + And user "Alice" should get a notification with subject "Virus found" and message: + | message | + | Virus found in aFileWithVirus.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And as "Alice" file "/aFileWithVirus.txt" should not exist + Examples: + | dav-path-version | + | old | + | new | + | spaces | + + + Scenario Outline: upload a file with virus and a file without virus + Given using DAV path + When user "Alice" uploads file "filesForUpload/filesWithVirus/eicar.com" to "/aFileWithVirus.txt" using the WebDAV API + # antivirus service can scan files during post-processing. on demand scanning is currently not available + Then the HTTP status code should be "201" + And user "Alice" uploads file "filesForUpload/textfile.txt" to "/normalfile.txt" using the WebDAV API + And the HTTP status code should be "201" + And user "Alice" should get a notification with subject "Virus found" and message: + | message | + | Virus found in aFileWithVirus.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And as "Alice" file "/aFileWithVirus.txt" should not exist + But as "Alice" file "/normalfile.txt" should exist + And the content of file "/normalfile.txt" for user "Alice" should be: + """ + This is a testfile. + + Cheers. + """ + Examples: + | dav-path-version | + | old | + | new | + | spaces | + + + Scenario Outline: upload a file with virus in chunks + Given using DAV path + When user "Alice" uploads the following chunks to "/myChunkedFile.txt" with old chunking and using the WebDAV API + | number | content | + | 1 | X5O!P%@AP[4\PZX54(P^)7C | + | 2 | C)7}$EICAR-STANDARD-ANT | + | 3 | IVIRUS-TEST-FILE!$H+H* | + # antivirus service can scan files during post-processing. on demand scanning is currently not available + Then the HTTP status code should be "201" + And user "Alice" should get a notification with subject "Virus found" and message: + | message | + | Virus found in myChunkedFile.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And as "Alice" file "/myChunkedFile.txt" should not exist + Examples: + | dav-path-version | + | old | + | spaces | + + + Scenario Outline: upload a file with the virus to a public share + Given using DAV path + And user "Alice" has created folder "/uploadFolder" + And user "Alice" has created a public link share with settings + | path | /uploadFolder | + | name | sharedlink | + | permissions | change | + | expireDate | 2040-01-01T23:59:59+0100 | + When user "Alice" uploads file "filesForUpload/filesWithVirus/" to "/" using the WebDAV API + Then the HTTP status code should be "201" + And user "Alice" should get a notification with subject "Virus found" and message: + | message | + | Virus found in . Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And as "Alice" file "/uploadFolder/" should not exist + Examples: + | dav-path-version | filename | newfilename | + | old | eicar.com | virusFile1.txt | + | old | eicar_com.zip | virusFile2.zip | + | new | eicar.com | virusFile1.txt | + | new | eicar_com.zip | virusFile2.zip | + | spaces | eicar.com | virusFile1.txt | + | spaces | eicar_com.zip | virusFile2.zip | + + + Scenario Outline: upload a file with the virus to a password-protected public share + Given using DAV path + And user "Alice" has created folder "/uploadFolder" + And user "Alice" has created a public link share with settings + | path | /uploadFolder | + | name | sharedlink | + | permissions | change | + | password | newpasswd | + | expireDate | 2040-01-01T23:59:59+0100 | + When user "Alice" uploads file "filesForUpload/filesWithVirus/" to "/" using the WebDAV API + Then the HTTP status code should be "201" + And user "Alice" should get a notification with subject "Virus found" and message: + | message | + | Virus found in . Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And as "Alice" file "/uploadFolder/" should not exist + Examples: + | dav-path-version | filename | newfilename | + | old | eicar.com | virusFile1.txt | + | old | eicar_com.zip | virusFile2.zip | + | new | eicar.com | virusFile1.txt | + | new | eicar_com.zip | virusFile2.zip | + | spaces | eicar.com | virusFile1.txt | + | spaces | eicar_com.zip | virusFile2.zip | + + + Scenario Outline: upload a file with virus to a user share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "uploadFolder" + And user "Alice" has shared folder "uploadFolder" with user "Brian" with permissions "all" + And user "Brian" has accepted share "/uploadFolder" offered by user "Alice" + When user "Brian" uploads file "filesForUpload/filesWithVirus/" to "/Shares/uploadFolder/" using the WebDAV API + Then the HTTP status code should be "201" + And user "Brian" should get a notification with subject "Virus found" and message: + | message | + | Virus found in . Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And as "Brian" file "/Shares/uploadFolder/" should not exist + And as "Alice" file "/uploadFolder/" should not exist + Examples: + | dav-path-version | filename | newfilename | + | old | eicar.com | virusFile1.txt | + | old | eicar_com.zip | virusFile2.zip | + | new | eicar.com | virusFile1.txt | + | new | eicar_com.zip | virusFile2.zip | + + + Scenario Outline: upload a file with virus to a user share using spaces dav endpoint + Given using spaces DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "uploadFolder" + And user "Alice" has shared folder "uploadFolder" with user "Brian" with permissions "all" + And user "Brian" has accepted share "/uploadFolder" offered by user "Alice" + When user "Brian" uploads a file "filesForUpload/filesWithVirus/" to "/uploadFolder/" in space "Shares" using the WebDAV API + Then the HTTP status code should be "201" + And user "Brian" should get a notification with subject "Virus found" and message: + | message | + | Virus found in . Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And as "Brian" file "/Shares/uploadFolder/" should not exist + And as "Alice" file "/uploadFolder/" should not exist + Examples: + | filename | newfilename | + | eicar.com | virusFile1.txt | + | eicar_com.zip | virusFile2.zip | + + + Scenario Outline: upload a file with virus to a group share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And group "group1" has been created + And user "Brian" has been added to group "group1" + And user "Alice" has created folder "uploadFolder" + And user "Alice" has shared folder "uploadFolder" with group "group1" + And user "Brian" has accepted share "/uploadFolder" offered by user "Alice" + When user "Brian" uploads file "filesForUpload/filesWithVirus/" to "/Shares/uploadFolder/" using the WebDAV API + Then the HTTP status code should be "201" + And user "Brian" should get a notification with subject "Virus found" and message: + | message | + | Virus found in . Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And as "Brian" file "/Shares/uploadFolder/" should not exist + And as "Alice" file "/uploadFolder/" should not exist + Examples: + | dav-path-version | filename | newfilename | + | old | eicar.com | virusFile1.txt | + | old | eicar_com.zip | virusFile2.zip | + | new | eicar.com | virusFile1.txt | + | new | eicar_com.zip | virusFile2.zip | + + + Scenario Outline: upload a file with virus to a group share using spaces dav endpoint + Given using spaces DAV path + And user "Brian" has been created with default attributes and without skeleton files + And group "group1" has been created + And user "Brian" has been added to group "group1" + And user "Alice" has created folder "uploadFolder" + And user "Alice" has shared folder "uploadFolder" with group "group1" + And user "Brian" has accepted share "/uploadFolder" offered by user "Alice" + When user "Brian" uploads a file "filesForUpload/filesWithVirus/" to "/uploadFolder/" in space "Shares" using the WebDAV API + Then the HTTP status code should be "201" + And user "Brian" should get a notification with subject "Virus found" and message: + | message | + | Virus found in . Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And as "Brian" file "/Shares/uploadFolder/" should not exist + And as "Alice" file "/uploadFolder/" should not exist + Examples: + | filename | newfilename | + | eicar.com | virusFile1.txt | + | eicar_com.zip | virusFile2.zip | + + + Scenario Outline: upload a file with virus to a project space + Given using spaces DAV path + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the GraphApi + And user "Alice" has created a folder "uploadFolder" in space "new-space" + When user "Alice" uploads a file "filesForUpload/filesWithVirus/" to "/uploadFolder/" in space "new-space" using the WebDAV API + Then the HTTP status code should be "201" + And user "Alice" should get a notification with subject "Virus found" and message: + | message | + | Virus found in . Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And for user "Alice" the space "new-space" should not contain these entries: + | /uploadFolder/ | + When user "Alice" uploads a file "filesForUpload/filesWithVirus/" to "/" in space "new-space" using the WebDAV API + Then the HTTP status code should be "201" + And user "Alice" should get a notification with subject "Virus found" and message: + | message | + | Virus found in . Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And for user "Alice" the space "new-space" should not contain these entries: + | / | + Examples: + | filename | newfilename | + | eicar.com | virusFile1.txt | + | eicar_com.zip | virusFile2.zip | + + + Scenario Outline: upload a file with virus to a shared project space + Given using spaces DAV path + And user "Brian" has been created with default attributes and without skeleton files + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the GraphApi + And user "Alice" has shared a space "new-space" with settings: + | shareWith | Brian | + | role | editor | + When user "Brian" uploads a file "/filesForUpload/filesWithVirus/" to "/" in space "new-space" using the WebDAV API + Then the HTTP status code should be "201" + And user "Brian" should get a notification with subject "Virus found" and message: + | message | + | Virus found in . Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And for user "Brian" the space "new-space" should not contain these entries: + | / | + And for user "Alice" the space "new-space" should not contain these entries: + | / | + Examples: + | filename | newfilename | + | eicar.com | virusFile1.txt | + | eicar_com.zip | virusFile2.zip | + + @env-config @issue-6494 + Scenario Outline: upload a file with virus by setting antivirus infected file handling config to continue + Given the config "ANTIVIRUS_INFECTED_FILE_HANDLING" has been set to "continue" + And using DAV path + When user "Alice" uploads file "filesForUpload/filesWithVirus/eicar.com" to "/aFileWithVirus.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/aFileWithVirus.txt" should exist + Examples: + | dav-path-version | + | old | + | new | + | spaces | + + @env-config + Scenario Outline: upload a file with virus smaller than the upload threshold + Given the config "ANTIVIRUS_MAX_SCAN_SIZE" has been set to "100" + And using DAV path + When user "Alice" uploads file "filesForUpload/filesWithVirus/eicar.com" to "/aFileWithVirus.txt" using the WebDAV API + Then the HTTP status code should be "201" + And user "Alice" should get a notification with subject "Virus found" and message: + | message | + | Virus found in aFileWithVirus.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And as "Alice" file "/aFileWithVirus.txt" should not exist + Examples: + | dav-path-version | + | old | + | new | + | spaces | + + @env-config + Scenario Outline: upload a file with virus larger than the upload threshold + Given the config "ANTIVIRUS_MAX_SCAN_SIZE" has been set to "100" + And using DAV path + When user "Alice" uploads file "filesForUpload/filesWithVirus/eicar_com.zip" to "/aFileWithVirus.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/aFileWithVirus.txt" should exist + Examples: + | dav-path-version | + | old | + | new | + | spaces | + + @issue-enterprise-5706 + Scenario Outline: upload a file with virus and get notification in different languages + Given user "Alice" has switched the system language to "" + And using DAV path + When user "Alice" uploads file "filesForUpload/filesWithVirus/eicar.com" to "/aFileWithVirus.txt" using the WebDAV API + Then the HTTP status code should be "201" + And user "Alice" should get a notification with subject "" and message: + | message | + | | + And as "Alice" file "/aFileWithVirus.txt" should not exist + Examples: + | dav-path-version | language | subject | message | + | old | es | Virus encontrado | Virus encontrado en aFileWithVirus.txt. La subida no ha sido posible. Virus: Win.Test.EICAR_HDB-1 | + | new | es | Virus encontrado | Virus encontrado en aFileWithVirus.txt. La subida no ha sido posible. Virus: Win.Test.EICAR_HDB-1 | + | spaces | es | Virus encontrado | Virus encontrado en aFileWithVirus.txt. La subida no ha sido posible. Virus: Win.Test.EICAR_HDB-1 | + | old | de | Virus gefunden | In aFileWithVirus.txt wurde potenziell schädlicher Code gefunden. Das Hochladen wurde abgebrochen. Grund: Win.Test.EICAR_HDB-1 | + | new | de | Virus gefunden | In aFileWithVirus.txt wurde potenziell schädlicher Code gefunden. Das Hochladen wurde abgebrochen. Grund: Win.Test.EICAR_HDB-1 | + | spaces | de | Virus gefunden | In aFileWithVirus.txt wurde potenziell schädlicher Code gefunden. Das Hochladen wurde abgebrochen. Grund: Win.Test.EICAR_HDB-1 | + + @issue-enterprise-5709 + Scenario Outline: try to create a version of file by uploading virus content + Given using DAV path + And user "Alice" has uploaded file with content "hello world" to "test.txt" + And user "Alice" has uploaded file with content "hello nepal" to "test.txt" + When user "Alice" uploads file with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" to "test.txt" using the WebDAV API + Then the HTTP status code should be "204" + And user "Alice" should get a notification with subject "Virus found" and message: + | message | + | Virus found in test.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And as "Alice" file "/test.txt" should exist + And the version folder of file "/test.txt" for user "Alice" should contain "1" element + And the content of file "/test.txt" for user "Alice" should be "hello nepal" + When user "Alice" restores version index "1" of file "/test.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/test.txt" for user "Alice" should be "hello world" + Examples: + | dav-path-version | + | old | + | new | + | spaces | + + + Scenario Outline: try to overwrite a file with the virus content in a public link share + Given using DAV path + And user "Alice" has uploaded file with content "hello" to "test.txt" + And user "Alice" has created a public link share with settings + | path | /test.txt | + | name | sharedlink | + | permissions | change | + When the public overwrites file "test.txt" with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" using the new WebDAV API + Then the HTTP status code should be "204" + And user "Alice" should get a notification with subject "Virus found" and message: + | message | + | Virus found in test.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And the content of file "/test.txt" for user "Alice" should be "hello" + Examples: + | dav-path-version | + | old | + | new | + | spaces | + + + Scenario Outline: try to overwrite a file with the virus content in group share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And group "group1" has been created + And user "Brian" has been added to group "group1" + And user "Alice" has been added to group "group1" + And user "Alice" has uploaded file with content "hello" to "/test.txt" + And user "Alice" has shared file "test.txt" with group "group1" + And user "Brian" has accepted share "/test.txt" offered by user "Alice" + When user "Brian" uploads file with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" to "test.txt" using the WebDAV API + Then the HTTP status code should be "201" + And user "Brian" should get a notification with subject "Virus found" and message: + | message | + | Virus found in test.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And the content of file "/test.txt" for user "Alice" should be "hello" + And the content of file "Shares/test.txt" for user "Brian" should be "hello" + Examples: + | dav-path-version | + | old | + | new | + + + Scenario: try to overwrite a file with the virus content in group share using spaces dav endpoint + Given using spaces DAV path + And user "Brian" has been created with default attributes and without skeleton files + And group "group1" has been created + And user "Brian" has been added to group "group1" + And user "Alice" has been added to group "group1" + And user "Alice" has uploaded file with content "hello" to "/test.txt" + And user "Alice" has shared file "test.txt" with group "group1" + And user "Brian" has accepted share "/test.txt" offered by user "Alice" + When user "Brian" uploads a file "filesForUpload/filesWithVirus/eicar.com" to "/test.txt" in space "Shares" using the WebDAV API + Then the HTTP status code should be "204" + And user "Brian" should get a notification with subject "Virus found" and message: + | message | + | Virus found in test.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And for user "Brian" the content of the file "/test.txt" of the space "Shares" should be "hello" + And the content of file "/test.txt" for user "Alice" should be "hello" + + + Scenario Outline: try to overwrite a file with the virus content in user share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "uploadFolder" + And user "Alice" has uploaded file with content "this is a test file." to "uploadFolder/test.txt" + And user "Alice" has shared folder "uploadFolder" with user "Brian" with permissions "all" + And user "Alice" has uploaded file with content "this is a test file." to "/test.txt" + And user "Alice" has shared file "/test.txt" with user "Brian" + And user "Brian" has accepted share "/uploadFolder" offered by user "Alice" + And user "Brian" has accepted share "/test.txt" offered by user "Alice" + When user "Brian" uploads file with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" to "Shares/uploadFolder/test.txt" using the WebDAV API + Then the HTTP status code should be "204" + And user "Brian" should get a notification with subject "Virus found" and message: + | message | + | Virus found in test.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And the content of file "Shares/uploadFolder/test.txt" for user "Brian" should be "this is a test file." + And the content of file "uploadFolder/test.txt" for user "Alice" should be "this is a test file." + When user "Brian" uploads file with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" to "Shares/test.txt" using the WebDAV API + Then the HTTP status code should be "204" + And user "Brian" should get a notification with subject "Virus found" and message: + | message | + | Virus found in test.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And the content of file "Shares/test.txt" for user "Brian" should be "this is a test file." + And the content of file "/test.txt" for user "Alice" should be "this is a test file." + Examples: + | dav-path-version | + | old | + | new | + + + Scenario: try to overwrite a file with the virus content in user share using spaces dav endpoint + Given using spaces DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "uploadFolder" + And user "Alice" has uploaded file with content "this is a test file." to "uploadFolder/test.txt" + And user "Alice" has shared folder "uploadFolder" with user "Brian" with permissions "all" + And user "Alice" has uploaded file with content "this is a test file." to "/test.txt" + And user "Alice" has shared file "/test.txt" with user "Brian" + And user "Brian" has accepted share "/uploadFolder" offered by user "Alice" + And user "Brian" has accepted share "/test.txt" offered by user "Alice" + When user "Brian" uploads a file "filesForUpload/filesWithVirus/eicar.com" to "/uploadFolder/test.txt" in space "Shares" using the WebDAV API + Then the HTTP status code should be "204" + And user "Brian" should get a notification with subject "Virus found" and message: + | message | + | Virus found in test.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And for user "Brian" the content of the file "/uploadFolder/test.txt" of the space "Shares" should be "this is a test file." + And the content of file "uploadFolder/test.txt" for user "Alice" should be "this is a test file." + When user "Brian" uploads a file "filesForUpload/filesWithVirus/eicar.com" to "/test.txt" in space "Shares" using the WebDAV API + Then the HTTP status code should be "204" + And user "Brian" should get a notification with subject "Virus found" and message: + | message | + | Virus found in test.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And for user "Brian" the content of the file "/test.txt" of the space "Shares" should be "this is a test file." + And the content of file "/test.txt" for user "Alice" should be "this is a test file." + + + Scenario: try to overwrite the .space/readme.md file + Given using spaces DAV path + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the GraphApi + And user "Alice" has created a folder ".space" in space "new-space" + And user "Alice" has uploaded a file inside space "new-space" with content "Here you can add a description for this Space." to ".space/readme.md" + And user "Alice" has uploaded a file inside space "new-space" with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" to ".space/readme.md" + Then the HTTP status code should be "204" + And user "Alice" should get a notification with subject "Virus found" and message: + | message | + | Virus found in readme.md. Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And for user "Alice" the content of the file ".space/readme.md" of the space "new-space" should be "Here you can add a description for this Space." + + + Scenario: try to overwrite the .space/readme.md file in space share + Given using spaces DAV path + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created a space "new-space" with the default quota using the GraphApi + And user "Alice" has shared a space "new-space" with settings: + | shareWith | Brian | + | role | editor | + And user "Alice" has created a folder ".space" in space "new-space" + And user "Alice" has uploaded a file inside space "new-space" with content "Here you can add a description for this Space." to ".space/readme.md" + And user "Alice" has set the file ".space/readme.md" as a description in a special section of the "new-space" space + When user "Brian" uploads a file inside space "new-space" with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" to ".space/readme.md" using the WebDAV API + Then the HTTP status code should be "204" + And user "Brian" should get a notification with subject "Virus found" and message: + | message | + | Virus found in readme.md. Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And for user "Brian" the content of the file ".space/readme.md" of the space "new-space" should be "Here you can add a description for this Space." + And for user "Alice" the content of the file ".space/readme.md" of the space "new-space" should be "Here you can add a description for this Space." + + + Scenario: member of a project space tries to overwrite a file with virus content + Given using spaces DAV path + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created a space "new-space" with the default quota using the GraphApi + And user "Alice" has shared a space "new-space" with settings: + | shareWith | Brian | + | role | editor | + And user "Alice" has uploaded a file inside space "new-space" with content "hello world" to "text.txt" + When user "Brian" uploads a file "filesForUpload/filesWithVirus/eicar.com" to "text.txt" in space "new-space" using the WebDAV API + Then the HTTP status code should be "204" + And user "Brian" should get a notification with subject "Virus found" and message: + | message | + | Virus found in text.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 | + And for user "Brian" the content of the file "/text.txt" of the space "new-space" should be "hello world" + And for user "Alice" the content of the file "/text.txt" of the space "new-space" should be "hello world" diff --git a/tests/acceptance/features/apiAsyncUpload/delayPostprocessing.feature b/tests/acceptance/features/apiAsyncUpload/delayPostprocessing.feature index b09a2590c..fb4942336 100644 --- a/tests/acceptance/features/apiAsyncUpload/delayPostprocessing.feature +++ b/tests/acceptance/features/apiAsyncUpload/delayPostprocessing.feature @@ -43,4 +43,4 @@ Feature: delay post-processing of uploaded files | dav_path | | /remote.php/webdav/my_data | | /remote.php/dav/files/%username%/my_data | - | /dav/spaces/%spaceid%/my_data | \ No newline at end of file + | /dav/spaces/%spaceid%/my_data | diff --git a/tests/acceptance/features/apiContract/copy.feature b/tests/acceptance/features/apiContract/copy.feature index 54cf95f1b..c3cdd9f38 100644 --- a/tests/acceptance/features/apiContract/copy.feature +++ b/tests/acceptance/features/apiContract/copy.feature @@ -10,7 +10,7 @@ Feature: Copy test | username | | Alice | And using spaces DAV path - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "new-space" with the default quota using the GraphApi diff --git a/tests/acceptance/features/apiContract/propfind.feature b/tests/acceptance/features/apiContract/propfind.feature index 05690fa60..1ac112ab8 100644 --- a/tests/acceptance/features/apiContract/propfind.feature +++ b/tests/acceptance/features/apiContract/propfind.feature @@ -10,7 +10,7 @@ Feature: Propfind test | Alice | | Brian | And using spaces DAV path - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "new-space" with the default quota using the GraphApi @@ -22,7 +22,7 @@ Feature: Propfind test | key | value | | oc:fileid | UUIDof:new-space | | oc:name | new-space | - | oc:permissions | RDNVCKZ | + | oc:permissions | RDNVCKZP | | oc:privatelink | | | oc:size | 12 | @@ -43,6 +43,6 @@ Feature: Propfind test | oc:size | 12 | Examples: | role | oc_permission | - | manager | RDNVCKZ | + | manager | RDNVCKZP | | editor | DNVCK | | viewer | | diff --git a/tests/acceptance/features/apiContract/spacesReport.feature b/tests/acceptance/features/apiContract/spacesReport.feature new file mode 100644 index 000000000..803654ec4 --- /dev/null +++ b/tests/acceptance/features/apiContract/spacesReport.feature @@ -0,0 +1,75 @@ +@api +Feature: REPORT request to project space + As a user + I want to check the REPORT response of project spaces + So that I can make sure that the response contains all the relevant details + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And using spaces DAV path + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "findData" with the default quota using the GraphApi + + + Scenario: check the response of the searched file + Given user "Alice" has uploaded a file inside space "findData" with content "some content" to "testFile.txt" + And using new DAV path + When user "Alice" searches for "testFile.txt" using the WebDAV API + Then the HTTP status code should be "207" + And the "REPORT" response to user "Alice" should contain a mountpoint "findData" with these key and value pairs: + | key | value | + | oc:fileid | UUIDof:testFile.txt | + | oc:file-parent | UUIDof:findData | + | oc:name | testFile.txt | + | d:getcontenttype | text/plain | + | oc:permissions | RDNVW | + | d:getcontentlength | 12 | + + + Scenario: check the response of the searched sub-file + Given user "Alice" has created a folder "folderMain/SubFolder1/subFOLDER2" in space "findData" + And user "Alice" has uploaded a file inside space "findData" with content "some content" to "folderMain/SubFolder1/subFOLDER2/insideTheFolder.txt" + And using new DAV path + When user "Alice" searches for "insideTheFolder.txt" using the WebDAV API + Then the HTTP status code should be "207" + And the "REPORT" response to user "Alice" should contain a mountpoint "findData" with these key and value pairs: + | key | value | + | oc:fileid | UUIDof:folderMain/SubFolder1/subFOLDER2/insideTheFolder.txt | + | oc:file-parent | UUIDof:folderMain/SubFolder1/subFOLDER2 | + | oc:name | insideTheFolder.txt | + | d:getcontenttype | text/plain | + | oc:permissions | RDNVW | + | d:getcontentlength | 12 | + + + Scenario: check the response of the searched folder + Given user "Alice" has created a folder "folderMain" in space "findData" + And using new DAV path + When user "Alice" searches for "folderMain" using the WebDAV API + Then the HTTP status code should be "207" + And the "REPORT" response to user "Alice" should contain a mountpoint "findData" with these key and value pairs: + | key | value | + | oc:fileid | UUIDof:folderMain | + | oc:file-parent | UUIDof:findData | + | oc:name | folderMain | + | d:getcontenttype | httpd/unix-directory | + | oc:permissions | RDNVCK | + | oc:size | 0 | + + + Scenario: check the response of the searched sub-folder + Given user "Alice" has created a folder "folderMain/sub-folder" in space "findData" + And using new DAV path + When user "Alice" searches for "sub" using the WebDAV API + Then the HTTP status code should be "207" + And the "REPORT" response to user "Alice" should contain a mountpoint "findData" with these key and value pairs: + | key | value | + | oc:fileid | UUIDof:folderMain/sub-folder | + | oc:file-parent | UUIDof:folderMain | + | oc:name | sub-folder | + | d:getcontenttype | httpd/unix-directory | + | oc:permissions | RDNVCK | + | oc:size | 0 | diff --git a/tests/acceptance/features/apiContract/spacesSharesReport.feature b/tests/acceptance/features/apiContract/spacesSharesReport.feature index 044d6711e..29077310b 100644 --- a/tests/acceptance/features/apiContract/spacesSharesReport.feature +++ b/tests/acceptance/features/apiContract/spacesSharesReport.feature @@ -13,7 +13,7 @@ Feature: Report test | Alice | | Brian | And using spaces DAV path - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "find data" with the default quota using the GraphApi And user "Alice" has created a folder "folderMain/SubFolder1/subFOLDER2" in space "find data" And user "Alice" has uploaded a file inside space "find data" with content "some content" to "folderMain/SubFolder1/subFOLDER2/insideTheFolder.txt" diff --git a/tests/acceptance/features/apiCors/cors.feature b/tests/acceptance/features/apiCors/cors.feature index 726f83498..1c74fd10b 100644 --- a/tests/acceptance/features/apiCors/cors.feature +++ b/tests/acceptance/features/apiCors/cors.feature @@ -7,7 +7,7 @@ Feature: CORS headers Background: Given user "Alice" has been created with default attributes and without skeleton files - And cors allowed origins has been set to "https://aphno.badal" + And the config "OCIS_CORS_ALLOW_ORIGINS" has been set to "https://aphno.badal" @issue-5195 Scenario Outline: CORS headers should be returned when setting CORS domain sending origin header diff --git a/tests/acceptance/features/apiGraph/addUserToGroup.feature b/tests/acceptance/features/apiGraph/addUserToGroup.feature index e36599618..bcb57ef0e 100644 --- a/tests/acceptance/features/apiGraph/addUserToGroup.feature +++ b/tests/acceptance/features/apiGraph/addUserToGroup.feature @@ -1,4 +1,4 @@ -@api +@api Feature: add users to group As a admin I want to be able to add users to a group @@ -121,7 +121,7 @@ Feature: add users to group @issue-5938 Scenario Outline: user other than the admin tries to add herself to a group - Given the administrator has given "Alice" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API And group "groupA" has been created When user "Alice" tries to add herself to group "groupA" using the Graph API Then the HTTP status code should be "403" @@ -150,12 +150,12 @@ Feature: add users to group | role | | Space Admin | | User | - | Guest | + | User Light | @issue-5938 Scenario Outline: user other than the admin tries to add other user to a group Given user "Brian" has been created with default attributes and without skeleton files - And the administrator has given "Brian" the role "" using the settings api + And the administrator has assigned the role "" to user "Brian" using the Graph API And group "groupA" has been created When user "Alice" tries to add user "Brian" to group "groupA" using the Graph API Then the HTTP status code should be "403" @@ -184,7 +184,7 @@ Feature: add users to group | role | | Space Admin | | User | - | Guest | + | User Light | Scenario: admin tries to add user to a nonexistent group @@ -194,14 +194,14 @@ Feature: add users to group @issue-5939 Scenario Outline: user other than the admin tries to add user to a nonexistent group Given user "Brian" has been created with default attributes and without skeleton files - And the administrator has given "Alice" the role "" using the settings api + And the administrator has assigned the role "" to user "Alice" using the Graph API When the user "Alice" tries to add user "Brian" to a nonexistent group using the Graph API Then the HTTP status code should be "404" Examples: | role | | Space Admin | | User | - | Guest | + | User Light | Scenario: admin tries to add a nonexistent user to a group @@ -216,7 +216,7 @@ Feature: add users to group Scenario: add multiple users to a group at once - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And these users have been created with default attributes and without skeleton files: | username | | Brian | @@ -234,7 +234,7 @@ Feature: add users to group Scenario: admin tries to add users to a nonexistent group at once - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And these users have been created with default attributes and without skeleton files: | username | | Brian | @@ -247,7 +247,7 @@ Feature: add users to group Scenario: admin tries to add multiple nonexistent users to a group at once - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And user "Alice" has created a group "grp1" using the Graph API When the administrator "Alice" tries to add the following nonexistent users to a group "grp1" at once using the Graph API | username | @@ -257,7 +257,7 @@ Feature: add users to group Scenario: admin tries to add nonexistent and existing users to a group at once - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And these users have been created with default attributes and without skeleton files: | username | | Brian | @@ -284,7 +284,7 @@ Feature: add users to group @issue-5702 Scenario: try to add users to a group twice - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And these users have been created with default attributes and without skeleton files: | username | | Brian | @@ -306,7 +306,7 @@ Feature: add users to group @issue-5793 Scenario: try to add a group to another group with PATCH request - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And these users have been created with default attributes and without skeleton files: | username | | Brian | @@ -320,7 +320,7 @@ Feature: add users to group @issue-5793 Scenario: try to add a group to another group with POST request - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And these users have been created with default attributes and without skeleton files: | username | | Brian | @@ -334,7 +334,7 @@ Feature: add users to group Scenario Outline: admin tries to add a user to a group with invalid JSON - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And these users have been created with default attributes and without skeleton files: | username | | Brian | @@ -349,7 +349,7 @@ Feature: add users to group Scenario Outline: admin tries to add multiple users to a group at once with invalid JSON - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And these users have been created with default attributes and without skeleton files: | username | | Brian | @@ -368,7 +368,7 @@ Feature: add users to group @issue-5871 Scenario: admin tries to add multiple users with wrong host - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And these users have been created with default attributes and without skeleton files: | username | | Brian | @@ -382,7 +382,7 @@ Feature: add users to group @issue-5871 Scenario: admin tries to add single user with wrong host - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And these users have been created with default attributes and without skeleton files: | username | | Brian | @@ -392,7 +392,7 @@ Feature: add users to group Scenario Outline: try to add invalid user id to a group - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And user "Alice" has created a group "grp1" using the Graph API When the administrator "Alice" tries to add an invalid user id "" to a group "grp1" using the Graph API Then the HTTP status code should be "404" @@ -404,7 +404,7 @@ Feature: add users to group Scenario Outline: try to add invalid user ids to a group at once - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And user "Alice" has created a group "grp1" using the Graph API When the administrator "Alice" tries to add the following invalid user ids to a group "grp1" at once using the Graph API | user-id | @@ -419,7 +419,7 @@ Feature: add users to group @issue-5855 Scenario: add same user twice to a group at once - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And these users have been created with default attributes and without skeleton files: | username | | Brian | diff --git a/tests/acceptance/features/apiGraph/assignRole.feature b/tests/acceptance/features/apiGraph/assignRole.feature index 1cfdcac99..b14533c93 100644 --- a/tests/acceptance/features/apiGraph/assignRole.feature +++ b/tests/acceptance/features/apiGraph/assignRole.feature @@ -16,7 +16,7 @@ Feature: assign role | Admin | | Space Admin | | User | - | Guest | + | User Light | @issue-5032 Scenario Outline: assign role to the user with graph api and list role with setting api @@ -30,7 +30,7 @@ Feature: assign role | Admin | 201 | | Space Admin | 401 | | User | 401 | - | Guest | 401 | + | User Light | 401 | Scenario Outline: assign role to the user with setting api and list role with graph api @@ -44,4 +44,4 @@ Feature: assign role | Admin | | Space Admin | | User | - | Guest | + | User Light | diff --git a/tests/acceptance/features/apiGraph/changeRole.feature b/tests/acceptance/features/apiGraph/changeRole.feature index bdc03d4ad..ce1d5f091 100644 --- a/tests/acceptance/features/apiGraph/changeRole.feature +++ b/tests/acceptance/features/apiGraph/changeRole.feature @@ -20,19 +20,19 @@ Feature: change role | Admin | Admin | | Admin | Space Admin | | Admin | User | - | Admin | Guest | + | Admin | User Light | | Space Admin | Admin | | Space Admin | Space Admin | | Space Admin | User | - | Space Admin | Guest | + | Space Admin | User Light | | User | Admin | | User | Space Admin | | User | User | - | User | Guest | - | Guest | Admin | - | Guest | Space Admin | - | Guest | User | - | Guest | Guest | + | User | User Light | + | User Light | Admin | + | User Light | Space Admin | + | User Light | User | + | User Light | User Light | Scenario Outline: admin user tries to change his/her own role @@ -44,5 +44,18 @@ Feature: change role | newRole | | Space Admin | | User | - | Guest | + | User Light | | Admin | + + + Scenario Outline: non-admin cannot change the user role + Given the administrator has assigned the role "" to user "Alice" using the Graph API + And user "Brian" has been created with default attributes and without skeleton files + When user "Alice" tries to change the role of user "Alice" to role "Admin" using the Graph API + Then the HTTP status code should be "401" + And user "Brian" should have the role "User" + Examples: + | role | + | Space Admin | + | User | + | User Light | diff --git a/tests/acceptance/features/apiGraph/createGroup.feature b/tests/acceptance/features/apiGraph/createGroup.feature index b8263a100..57a0687c4 100644 --- a/tests/acceptance/features/apiGraph/createGroup.feature +++ b/tests/acceptance/features/apiGraph/createGroup.feature @@ -1,4 +1,4 @@ -@api +@api Feature: create group As an admin I want to create a group @@ -6,7 +6,7 @@ Feature: create group Background: Given user "Alice" has been created with default attributes and without skeleton files - And the administrator has given "Alice" the role "Admin" using the settings api + And the administrator has assigned the role "Admin" to user "Alice" using the Graph API Scenario Outline: admin user creates a group @@ -28,13 +28,13 @@ Feature: create group Scenario: admin user tries to create a group that already exists Given group "mygroup" has been created When user "Alice" tries to create a group "mygroup" using the Graph API - Then the HTTP status code should be "400" + Then the HTTP status code should be "409" And group "mygroup" should exist @issue-5938 Scenario Outline: user other than the admin can't create a group Given user "Brian" has been created with default attributes and without skeleton files - And the administrator has given "Brian" the role "" using the settings api + And the administrator has assigned the role "" to user "Brian" using the Graph API When user "Brian" tries to create a group "mygroup" using the Graph API Then the HTTP status code should be "403" And group "mygroup" should not exist @@ -42,7 +42,7 @@ Feature: create group | userRole | | Space Admin | | User | - | Guest | + | User Light | @issue-5050 Scenario: admin user tries to create a group that is the empty string diff --git a/tests/acceptance/features/apiGraph/createGroupCaseSensitive.feature b/tests/acceptance/features/apiGraph/createGroupCaseSensitive.feature index ad1969f9f..263baf5b2 100644 --- a/tests/acceptance/features/apiGraph/createGroupCaseSensitive.feature +++ b/tests/acceptance/features/apiGraph/createGroupCaseSensitive.feature @@ -10,7 +10,7 @@ Feature: create groups, group names are case insensitive And group "" has been created When the administrator creates a group "" using the Graph API And the administrator creates a group "" using the Graph API - Then the HTTP status code of responses on all endpoints should be "400" + Then the HTTP status code of responses on all endpoints should be "409" And these groups should not exist: | groupname | | | diff --git a/tests/acceptance/features/apiGraph/createUser.feature b/tests/acceptance/features/apiGraph/createUser.feature index 6b718059b..9d9b5b555 100644 --- a/tests/acceptance/features/apiGraph/createUser.feature +++ b/tests/acceptance/features/apiGraph/createUser.feature @@ -12,7 +12,7 @@ Feature: create user @issue-3516 Scenario Outline: admin creates a user - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API When the user "Alice" creates a new user using GraphAPI with the following settings: | userName | | | displayName | | @@ -22,27 +22,28 @@ Feature: create user Then the HTTP status code should be "" And user "" exist Examples: - | userName | displayName | email | password | code | enable | shouldOrNot | - | SameDisplayName | Alice Hansen | new@example.org | containsCharacters(*:!;_+-&) | 200 | true | should | - | withoutPassSameEmail | without pass | alice@example.org | | 200 | true | should | - | name | pass with space | example@example.org | my pass | 200 | true | should | - | nameWithCharacters(*:!;_+-&) | user | new@example.org | 123 | 400 | true | should not | - | name with space | name with space | example@example.org | 123 | 400 | true | should not | - | createDisabledUser | disabled user | example@example.org | 123 | 200 | false | should | - | nameWithNumbers0123456 | user | name0123456@example.org | 123 | 200 | true | should | - | name.with.dots | user | name.w.dots@example.org | 123 | 200 | true | should | - | 123456789 | user | 123456789@example.org | 123 | 400 | true | should not | - | 0.0 | user | float@example.org | 123 | 400 | true | should not | + | userName | displayName | email | password | code | enable | shouldOrNot | + | SameDisplayName | Alice Hansen | new@example.org | containsCharacters(*:!;_+-&) | 200 | true | should | + | withoutPassSameEmail | without pass | alice@example.org | | 200 | true | should | + | name | pass with space | example@example.org | my pass | 200 | true | should | + | user1 | user names must not start with a number | example@example.org | my pass | 200 | true | should | + | nameWithCharacters(*:!;_+-&) | user | new@example.org | 123 | 400 | true | should not | + | name with space | name with space | example@example.org | 123 | 400 | true | should not | + | createDisabledUser | disabled user | example@example.org | 123 | 200 | false | should | + | nameWithNumbers0123456 | user | name0123456@example.org | 123 | 200 | true | should | + | name.with.dots | user | name.w.dots@example.org | 123 | 200 | true | should | + | 123456789 | user | 123456789@example.org | 123 | 400 | true | should not | + | 0.0 | user | float@example.org | 123 | 400 | true | should not | @skipOnStable2.0 Examples: - | userName | displayName | email | password | code | enable | shouldOrNot | - | withoutEmail | without email | | 123 | 200 | true | should | - | Alice | same userName | new@example.org | 123 | 400 | true | should | + | userName | displayName | email | password | code | enable | shouldOrNot | + | withoutEmail | without email | | 123 | 200 | true | should | + | Alice | same userName | new@example.org | 123 | 409 | true | should | Scenario: user cannot be created with empty name - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API When the user "Alice" creates a new user using GraphAPI with the following settings: | userName | | | displayName | emptyName | @@ -53,7 +54,7 @@ Feature: create user Scenario Outline: user without admin right cannot create a user - Given the administrator has given "Alice" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API When the user "Alice" creates a new user using GraphAPI with the following settings: | userName | user | | displayName | user | @@ -66,12 +67,12 @@ Feature: create user | role | | Space Admin | | User | - | Guest | + | User Light | + - @issue-3516 @skipOnStable2.0 Scenario: user cannot be created with the name of the disabled user Given user "Brian" has been created with default attributes and without skeleton files - And the administrator has given "Alice" the role "Admin" using the settings api + And the administrator has assigned the role "Admin" to user "Alice" using the Graph API And the user "Alice" has disabled user "Brian" using the Graph API When the user "Alice" creates a new user using GraphAPI with the following settings: | userName | Brian | @@ -79,12 +80,12 @@ Feature: create user | email | brian@example.com | | password | 123 | | accountEnabled | true | - Then the HTTP status code should be "400" + Then the HTTP status code should be "409" @skipOnStable2.0 Scenario: user can be created with the name of the deleted user Given user "Brian" has been created with default attributes and without skeleton files - And the administrator has given "Alice" the role "Admin" using the settings api + And the administrator has assigned the role "Admin" to user "Alice" using the Graph API And the user "Alice" has deleted a user "Brian" using the Graph API When the user "Alice" creates a new user using GraphAPI with the following settings: | userName | Brian | @@ -94,3 +95,63 @@ Feature: create user | accountEnabled | true | Then the HTTP status code should be "200" And user "Brian" should exist + + + @env-config + Scenario Outline: create user with setting OCIS no restriction on the user name + Given the config "GRAPH_USERNAME_MATCH" has been set to "none" + And the administrator has assigned the role "Admin" to user "Alice" using the Graph API + When the user "Alice" creates a new user using GraphAPI with the following settings: + | userName | | + | displayName | test user | + | email | new@example.org | + | password | 123 | + | accountEnabled | true | + Then the HTTP status code should be "200" + And user "" should exist + Examples: + | userName | description | + | 1248Bob | user names starts with the number | + | (*:!;+-&$%)_alice | user names starts with the ASCII characters | + + + @env-config + Scenario: create user with setting OCIS not to assign the default user role + Given the config "GRAPH_ASSIGN_DEFAULT_USER_ROLE" has been set to "false" + When the user "admin" creates a new user using GraphAPI with the following settings: + | userName | sam | + | displayName | test user | + | email | new@example.org | + | password | 123 | + | accountEnabled | true | + Then the HTTP status code should be "200" + And user "sam" should exist + When the administrator retrieves the assigned role of user "sam" using the Graph API + Then the HTTP status code should be "200" + And the Graph API response should have no role + + + @env-config + Scenario: create user with setting OCIS assign the default user role + Given the config "GRAPH_ASSIGN_DEFAULT_USER_ROLE" has been set to "true" + When the user "admin" creates a new user using GraphAPI with the following settings: + | userName | sam | + | displayName | test user | + | email | new@example.org | + | password | 123 | + | accountEnabled | true | + Then the HTTP status code should be "200" + And user "sam" should exist + And user "sam" should have the role "User" assigned + + + Scenario: user is created with the default User role + When the user "admin" creates a new user using GraphAPI with the following settings: + | userName | sam | + | displayName | test user | + | email | new@example.org | + | password | 123 | + | accountEnabled | true | + Then the HTTP status code should be "200" + And user "sam" should exist + And user "sam" should have the role "User" assigned diff --git a/tests/acceptance/features/apiGraph/deleteGroup.feature b/tests/acceptance/features/apiGraph/deleteGroup.feature index 48ca52ccf..c6c70789a 100644 --- a/tests/acceptance/features/apiGraph/deleteGroup.feature +++ b/tests/acceptance/features/apiGraph/deleteGroup.feature @@ -6,7 +6,7 @@ Feature: delete groups Background: Given user "Alice" has been created with default attributes and without skeleton files - And the administrator has given "Alice" the role "Admin" using the settings api + And the administrator has assigned the role "Admin" to user "Alice" using the Graph API Scenario Outline: admin user deletes a group @@ -53,7 +53,7 @@ Feature: delete groups @issue-5938 Scenario Outline: user other than the admin can't delete a group Given user "Brian" has been created with default attributes and without skeleton files - And the administrator has given "Brian" the role "" using the settings api + And the administrator has assigned the role "" to user "Brian" using the Graph API And group "new-group" has been created When user "Brian" tries to delete group "new-group" using the Graph API Then the HTTP status code should be "403" @@ -62,7 +62,7 @@ Feature: delete groups | role | | Space Admin | | User | - | Guest | + | User Light | @issue-903 Scenario: deleted group should not be listed in the sharees list diff --git a/tests/acceptance/features/apiGraph/deleteUser.feature b/tests/acceptance/features/apiGraph/deleteUser.feature index def4fd50f..2920ed3fb 100644 --- a/tests/acceptance/features/apiGraph/deleteUser.feature +++ b/tests/acceptance/features/apiGraph/deleteUser.feature @@ -1,4 +1,4 @@ -@api +@api Feature: delete user As an admin I want to be able to delete users @@ -12,7 +12,7 @@ Feature: delete user Scenario Outline: admin user deletes a user - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And the user "Alice" has created a new user using the Graph API with the following settings: | userName | | | displayName | | @@ -30,7 +30,7 @@ Feature: delete user Scenario: delete a user and specify the user name in different case Given user "brand-new-user" has been created with default attributes and without skeleton files - And the administrator has given "Alice" the role "Admin" using the settings api + And the administrator has assigned the role "Admin" to user "Alice" using the Graph API When the user "Alice" deletes a user "Brand-New-User" using the Graph API Then the HTTP status code should be "204" And user "brand-new-user" should not exist @@ -38,8 +38,8 @@ Feature: delete user Scenario Outline: admin user deletes another user with different role Given user "Brian" has been created with default attributes and without skeleton files - And the administrator has given "Alice" the role "Admin" using the settings api - And the administrator has given "Brian" the role "" using the settings api + And the administrator has assigned the role "Admin" to user "Alice" using the Graph API + And the administrator has assigned the role "" to user "Brian" using the Graph API When the user "Alice" deletes a user "Brian" using the Graph API Then the HTTP status code should be "204" And user "Brian" should not exist @@ -48,18 +48,18 @@ Feature: delete user | Admin | | Space Admin | | User | - | Guest | + | User Light | Scenario: admin user tries to delete his/her own account - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API When the user "Alice" deletes a user "Alice" using the Graph API Then the HTTP status code should be "403" And user "Alice" should exist Scenario Outline: non-admin user tries to delete his/her own account - Given the administrator has given "Alice" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API When the user "Alice" deletes a user "Alice" using the Graph API Then the HTTP status code should be "401" And user "Alice" should exist @@ -67,30 +67,30 @@ Feature: delete user | role | | Space Admin | | User | - | Guest | + | User Light | Scenario: admin user tries to delete a nonexistent user - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API When the user "Alice" tries to delete a nonexistent user using the Graph API Then the HTTP status code should be "404" - + Scenario Outline: non-admin user tries to delete a nonexistent user - Given the administrator has given "Alice" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API When the user "Alice" tries to delete a nonexistent user using the Graph API Then the HTTP status code should be "401" Examples: | role | | Space Admin | | User | - | Guest | + | User Light | Scenario Outline: non-admin user tries to delete another user with different role Given user "Brian" has been created with default attributes and without skeleton files - And the administrator has given "Brian" the role "" using the settings api - And the administrator has given "Alice" the role "" using the settings api + And the administrator has assigned the role "" to user "Brian" using the Graph API + And the administrator has assigned the role "" to user "Alice" using the Graph API When the user "Alice" deletes a user "Brian" using the Graph API Then the HTTP status code should be "401" And user "Brian" should exist @@ -98,20 +98,20 @@ Feature: delete user | userRole | role | | Space Admin | Space Admin | | Space Admin | User | - | Space Admin | Guest | + | Space Admin | User Light | | Space Admin | Admin | | User | Space Admin | | User | User | - | User | Guest | + | User | User Light | | User | Admin | - | Guest | Space Admin | - | Guest | User | - | Guest | Guest | - | Guest | Admin | + | User Light | Space Admin | + | User Light | User | + | User Light | User Light | + | User Light | Admin | Scenario: admin user deletes a disabled user - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And user "Brian" has been created with default attributes and without skeleton files And the user "Alice" has disabled user "Brian" using the Graph API When the user "Alice" deletes a user "Brian" using the Graph API @@ -120,11 +120,11 @@ Feature: delete user Scenario Outline: normal user tries to delete a disabled user - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And user "Brian" has been created with default attributes and without skeleton files And user "Carol" has been created with default attributes and without skeleton files - And the administrator has given "Brian" the role "" using the settings api - And the administrator has given "Carol" the role "" using the settings api + And the administrator has assigned the role "" to user "Brian" using the Graph API + And the administrator has assigned the role "" to user "Carol" using the Graph API And the user "Alice" has disabled user "Brian" using the Graph API When the user "Carol" deletes a user "Brian" using the Graph API Then the HTTP status code should be "401" @@ -133,13 +133,36 @@ Feature: delete user | userRole | role | | Space Admin | Space Admin | | Space Admin | User | - | Space Admin | Guest | + | Space Admin | User Light | | Space Admin | Admin | | User | Space Admin | | User | User | - | User | Guest | + | User | User Light | | User | Admin | - | Guest | Space Admin | - | Guest | User | - | Guest | Guest | - | Guest | Admin | + | User Light | Space Admin | + | User Light | User | + | User Light | User Light | + | User Light | Admin | + + + Scenario: personal space is deleted automatically when the user is deleted + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API + And user "Brian" has been created with default attributes and without skeleton files + When the user "Alice" deletes a user "Brian" using the Graph API + Then the HTTP status code should be "204" + When user "Alice" lists all spaces via the GraphApi with query "$filter=driveType eq 'personal'" + Then the json responded should not contain a space with name "Brian Murphy" + + + Scenario: accepted share is deleted automatically when the user is deleted + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created a folder "new" in space "Brian Murphy" + And user "Brian" has created a share inside of space "Brian Murphy" with settings: + | path | new | + | shareWith | Alice | + | role | viewer | + And user "Alice" has accepted share "/new" offered by user "Brian" + When the user "Alice" deletes a user "Brian" using the Graph API + Then the HTTP status code should be "204" + And as "Alice" folder "Shares/new" should not exist diff --git a/tests/acceptance/features/apiGraph/editGroup.feature b/tests/acceptance/features/apiGraph/editGroup.feature index 39e729416..ff7c831cc 100644 --- a/tests/acceptance/features/apiGraph/editGroup.feature +++ b/tests/acceptance/features/apiGraph/editGroup.feature @@ -1,4 +1,4 @@ -@api +@api Feature: edit group name As an admin I want to be able to edit group name @@ -6,7 +6,7 @@ Feature: edit group name Background: Given user "Alice" has been created with default attributes and without skeleton files - And the administrator has given "Alice" the role "Admin" using the settings api + And the administrator has assigned the role "Admin" to user "Alice" using the Graph API @issue-5977 Scenario Outline: admin user renames a group @@ -26,7 +26,7 @@ Feature: edit group name @issue-5938 Scenario Outline: user other than the admin can't rename a group - Given the administrator has given "Alice" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API And group "grp1" has been created When user "Alice" tries to rename group "grp1" to "grp101" using the Graph API Then the HTTP status code should be "403" @@ -34,7 +34,7 @@ Feature: edit group name | role | | Space Admin | | User | - | Guest | + | User Light | Scenario: admin user tries to rename nonexistent group diff --git a/tests/acceptance/features/apiGraph/editUser.feature b/tests/acceptance/features/apiGraph/editUser.feature index e8803ecd4..91fc634ce 100644 --- a/tests/acceptance/features/apiGraph/editUser.feature +++ b/tests/acceptance/features/apiGraph/editUser.feature @@ -1,4 +1,4 @@ -@api +@api Feature: edit user As an admin I want to be able to edit user information @@ -9,7 +9,7 @@ Feature: edit user Background: Given user "Alice" has been created with default attributes and without skeleton files - And the administrator has given "Alice" the role "Admin" using the settings api + And the administrator has assigned the role "Admin" to user "Alice" using the Graph API And the user "Alice" has created a new user using the Graph API with the following settings: | userName | Brian | | displayName | Brian Murphy | @@ -66,9 +66,9 @@ Feature: edit user Examples: | action description | userName | code | newUserName | | change to a valid user name | Lionel | 200 | Lionel | - | user name characters | *:!;_+-& | 200 | *:!;_+-& | + | user name characters | a*!_+-& | 200 | a*!_+-& | | change to existing user name | Brian | 409 | Brian | - | empty user name | | 200 | Brian | + | empty user name | | 400 | Brian | @skipOnStable2.0 Scenario: admin user changes the name of a user to the name of an existing disabled user @@ -124,7 +124,7 @@ Feature: edit user Scenario Outline: normal user should not be able to change their email address - Given the administrator has given "Brian" the role "" using the settings api + Given the administrator has assigned the role "" to user "Brian" using the Graph API When the user "Brian" tries to change the email of user "Brian" to "newemail@example.com" using the Graph API Then the HTTP status code should be "401" And the user information of "Brian" should match this JSON schema @@ -146,17 +146,17 @@ Feature: edit user | role | | Space Admin | | User | - | Guest | + | User Light | Scenario Outline: normal user should not be able to edit another user's email - Given the administrator has given "Brian" the role "" using the settings api + Given the administrator has assigned the role "" to user "Brian" using the Graph API And the user "Alice" has created a new user using the Graph API with the following settings: | userName | Carol | | displayName | Carol King | | email | carol@example.com | | password | 1234 | - And the administrator has given "Carol" the role "" using the settings api + And the administrator has assigned the role "" to user "Carol" using the Graph API When the user "Brian" tries to change the email of user "Carol" to "newemail@example.com" using the Graph API Then the HTTP status code should be "401" And the user information of "Carol" should match this JSON schema @@ -178,16 +178,16 @@ Feature: edit user | userRole | role | | Space Admin | Space Admin | | Space Admin | User | - | Space Admin | Guest | + | Space Admin | User Light | | Space Admin | Admin | | User | Space Admin | | User | User | - | User | Guest | + | User | User Light | | User | Admin | - | Guest | Space Admin | - | Guest | User | - | Guest | Guest | - | Guest | Admin | + | User Light | Space Admin | + | User Light | User | + | User Light | User Light | + | User Light | Admin | Scenario Outline: admin user can edit another user display name @@ -217,7 +217,7 @@ Feature: edit user Scenario Outline: normal user should not be able to change his/her own display name - Given the administrator has given "Brian" the role "" using the settings api + Given the administrator has assigned the role "" to user "Brian" using the Graph API When the user "Brian" tries to change the display name of user "Brian" to "Brian Murphy" using the Graph API Then the HTTP status code should be "401" And the user information of "Alice" should match this JSON schema @@ -239,17 +239,17 @@ Feature: edit user | role | | Space Admin | | User | - | Guest | + | User Light | Scenario Outline: normal user should not be able to edit another user's display name - Given the administrator has given "Brian" the role "" using the settings api + Given the administrator has assigned the role "" to user "Brian" using the Graph API And the user "Alice" has created a new user using the Graph API with the following settings: | userName | Carol | | displayName | Carol King | | email | carol@example.com | | password | 1234 | - And the administrator has given "Carol" the role "" using the settings api + And the administrator has assigned the role "" to user "Carol" using the Graph API When the user "Brian" tries to change the display name of user "Carol" to "Alice Hansen" using the Graph API Then the HTTP status code should be "401" And the user information of "Carol" should match this JSON schema @@ -271,16 +271,16 @@ Feature: edit user | userRole | role | | Space Admin | Space Admin | | Space Admin | User | - | Space Admin | Guest | + | Space Admin | User Light | | Space Admin | Admin | | User | Space Admin | | User | User | - | User | Guest | + | User | User Light | | User | Admin | - | Guest | Space Admin | - | Guest | User | - | Guest | Guest | - | Guest | Admin | + | User Light | Space Admin | + | User Light | User | + | User Light | User Light | + | User Light | Admin | Scenario: admin user resets password of another user @@ -291,13 +291,13 @@ Feature: edit user Scenario Outline: normal user should not be able to reset the password of another user - Given the administrator has given "Brian" the role "" using the settings api + Given the administrator has assigned the role "" to user "Brian" using the Graph API And the user "Alice" has created a new user using the Graph API with the following settings: | userName | Carol | | displayName | Carol King | | email | carol@example.com | | password | 1234 | - And the administrator has given "Carol" the role "" using the settings api + And the administrator has assigned the role "" to user "Carol" using the Graph API And user "Carol" has uploaded file with content "test file for reset password" to "/resetpassword.txt" When the user "Brian" resets the password of user "Carol" to "newpassword" using the Graph API Then the HTTP status code should be "401" @@ -307,16 +307,16 @@ Feature: edit user | userRole | role | | Space Admin | Space Admin | | Space Admin | User | - | Space Admin | Guest | + | Space Admin | User Light | | Space Admin | Admin | | User | Space Admin | | User | User | - | User | Guest | + | User | User Light | | User | Admin | - | Guest | Space Admin | - | Guest | User | - | Guest | Guest | - | Guest | Admin | + | User Light | Space Admin | + | User Light | User | + | User Light | User Light | + | User Light | Admin | @skipOnStable2.0 Scenario: admin user disables another user @@ -363,7 +363,7 @@ Feature: edit user @skipOnStable2.0 Scenario Outline: normal user should not be able to disable another user Given user "Carol" has been created with default attributes and without skeleton files - And the administrator has given "Brian" the role "" using the settings api + And the administrator has assigned the role "" to user "Brian" using the Graph API When the user "Brian" tries to disable user "Carol" using the Graph API Then the HTTP status code should be "401" When user "Alice" gets information of user "Carol" using Graph API @@ -407,7 +407,7 @@ Feature: edit user | role | | Space Admin | | User | - | Guest | + | User Light | @skipOnStable2.0 Scenario: admin user enables disabled user @@ -456,7 +456,7 @@ Feature: edit user Scenario Outline: normal user should not be able to enable another user Given user "Carol" has been created with default attributes and without skeleton files And the user "Alice" has disabled user "Carol" using the Graph API - And the administrator has given "Brian" the role "" using the settings api + And the administrator has assigned the role "" to user "Brian" using the Graph API When the user "Brian" tries to enable user "Carol" using the Graph API Then the HTTP status code should be "401" When user "Alice" gets information of user "Carol" using Graph API @@ -500,4 +500,4 @@ Feature: edit user | role | | Space Admin | | User | - | Guest | + | User Light | diff --git a/tests/acceptance/features/apiGraph/enforcePasswordPublicLink.feature b/tests/acceptance/features/apiGraph/enforcePasswordPublicLink.feature new file mode 100644 index 000000000..c0feb39e6 --- /dev/null +++ b/tests/acceptance/features/apiGraph/enforcePasswordPublicLink.feature @@ -0,0 +1,62 @@ +@api @env-config +Feature: enforce password on public link + As a user + I want to enforce passwords on public links shared with upload, edit, or contribute permission + So that the password is required to access the contents of the link + + Background: + Given the config "OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD" has been set to "true" + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + And user "Alice" has uploaded file with content "test file" to "/testfile.txt" + + + Scenario Outline: create a public link with edit permission without a password when enforce-password is enabled + Given using OCS API version "" + When user "Alice" creates a public link share using the sharing API with settings + | path | /testfile.txt | + | permissions | 3 | + Then the HTTP status code should be "" + Then the OCS status code should be "400" + And the OCS status message should be "missing required password" + Examples: + | ocs-api-version | http-code | + | 1 | 200 | + | 2 | 400 | + + + Scenario Outline: update a public link to edit permission without a password + Given using OCS API version "" + And user "Alice" has created a public link share with settings + | path | /testfile.txt | + | permissions | 1 | + When user "Alice" updates the last public link share using the sharing API with + | permissions | 3 | + Then the HTTP status code should be "" + Then the OCS status code should be "400" + And the OCS status message should be "missing required password" + Examples: + | ocs-api-version | http-code | + | 1 | 200 | + | 2 | 400 | + + + Scenario Outline: updates a public link to edit permission with a password + Given using OCS API version "" + And user "Alice" has created a public link share with settings + | path | /testfile.txt | + | permissions | 1 | + When user "Alice" updates the last public link share using the sharing API with + | permissions | 3 | + | password | testpassword | + Then the HTTP status code should be "200" + And the OCS status code should be "" + And the OCS status message should be "OK" + And the public should not be able to download file "/textfile.txt" from inside the last public link shared folder using the new public WebDAV API without a password + And the public should not be able to download file "/textfile.txt" from inside the last public link shared folder using the new public WebDAV API with password "wrong pass" + But the public should be able to download file "/textfile.txt" from inside the last public link shared folder using the new public WebDAV API with password "testpassword" + Examples: + | ocs-api-version | ocs-code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/apiGraph/fullSearch.feature b/tests/acceptance/features/apiGraph/fullSearch.feature new file mode 100644 index 000000000..759ec0176 --- /dev/null +++ b/tests/acceptance/features/apiGraph/fullSearch.feature @@ -0,0 +1,53 @@ +@api +Feature: full text search + As a user + I want to do full text search + So that I can find the files with the content I am looking for + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: search files using a tag + Given using DAV path + And user "Alice" has uploaded file with content "hello world" to "file1.txt" + And user "Alice" has uploaded file with content "Namaste nepal" to "file2.txt" + And user "Alice" has uploaded file with content "hello nepal" to "file3.txt" + And user "Alice" has created the following tags for file "file1.txt" of the space "Personal": + | tag1 | + And user "Alice" has created the following tags for file "file2.txt" of the space "Personal": + | tag1 | + When user "Alice" searches for "Tags:tag1" using the WebDAV API + Then the HTTP status code should be "207" + And the search result of user "Alice" should contain only these files: + | file1.txt | + | file2.txt | + Examples: + | dav-path-version | + | old | + | new | + | spaces | + + + Scenario Outline: sharee searches a file tagged by sharer using tag + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "uploadFolder" + And user "Alice" has uploaded file with content "hello world" to "uploadFolder/file1.txt" + And user "Alice" has uploaded file with content "Namaste nepal" to "uploadFolder/file2.txt" + And user "Alice" has uploaded file with content "hello nepal" to "uploadFolder/file3.txt" + And user "Alice" has created the following tags for file "uploadFolder/file1.txt" of the space "Personal": + | tag1 | + And user "Alice" has created the following tags for file "uploadFolder/file2.txt" of the space "Personal": + | tag1 | + And user "Alice" has shared folder "/uploadFolder" with user "Brian" + And user "Brian" has accepted share "/uploadFolder" offered by user "Alice" + When user "Brian" searches for "Tags:tag1" using the WebDAV API + Then the HTTP status code should be "207" + And the search result of user "Brian" should contain only these files: + | file1.txt | + | file2.txt | + Examples: + | dav-path-version | + | old | + | new | diff --git a/tests/acceptance/features/apiGraph/getApplications.feature b/tests/acceptance/features/apiGraph/getApplications.feature index 01f6beee2..8ab7fa138 100644 --- a/tests/acceptance/features/apiGraph/getApplications.feature +++ b/tests/acceptance/features/apiGraph/getApplications.feature @@ -9,7 +9,7 @@ Feature: get applications Scenario Outline: admin user lists all the groups - Given the administrator has given "Alice" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API When user "Alice" gets all applications using the Graph API Then the HTTP status code should be "200" And the user API response should contain the following application information: @@ -20,10 +20,10 @@ Feature: get applications | Admin | | Space Admin | | User | - | Guest | + | User Light | Examples: | role | | Admin | | Space Admin | | User | - | Guest | + | User Light | diff --git a/tests/acceptance/features/apiGraph/getGroup.feature b/tests/acceptance/features/apiGraph/getGroup.feature index bc0ea867e..50548f72e 100644 --- a/tests/acceptance/features/apiGraph/getGroup.feature +++ b/tests/acceptance/features/apiGraph/getGroup.feature @@ -1,4 +1,4 @@ -@api +@api Feature: get groups and their members As an admin I want to be able to get groups @@ -6,9 +6,9 @@ Feature: get groups and their members Background: Given user "Alice" has been created with default attributes and without skeleton files - And the administrator has given "Alice" the role "Admin" using the settings api - + And the administrator has assigned the role "Admin" to user "Alice" using the Graph API + Scenario: admin user lists all the groups Given group "tea-lover" has been created And group "coffee-lover" has been created @@ -23,7 +23,7 @@ Feature: get groups and their members @issue-5938 Scenario Outline: user other than the admin shouldn't get the groups list Given user "Brian" has been created with default attributes and without skeleton files - And the administrator has given "Brian" the role "" using the settings api + And the administrator has assigned the role "" to user "Brian" using the Graph API And group "tea-lover" has been created And group "coffee-lover" has been created And group "h2o-lover" has been created @@ -54,7 +54,7 @@ Feature: get groups and their members | role | | Space Admin | | User | - | Guest | + | User Light | Scenario: admin user gets users of a group @@ -74,7 +74,7 @@ Feature: get groups and their members @issue-5938 Scenario Outline: user other than the admin shouldn't get users of a group Given user "Brian" has been created with default attributes and without skeleton files - And the administrator has given "Brian" the role "" using the settings api + And the administrator has assigned the role "" to user "Brian" using the Graph API And group "tea-lover" has been created When user "Brian" gets all the members of group "tea-lover" using the Graph API Then the HTTP status code should be "403" @@ -103,7 +103,7 @@ Feature: get groups and their members | role | | Space Admin | | User | - | Guest | + | User Light | Scenario: admin user gets all groups along with its member's information @@ -235,7 +235,7 @@ Feature: get groups and their members @issue-5938 Scenario Outline: user other than the admin shouldn't get all groups along with its member's information Given user "Brian" has been created with default attributes and without skeleton files - And the administrator has given "Brian" the role "" using the settings api + And the administrator has assigned the role "" to user "Brian" using the Graph API And group "tea-lover" has been created And group "coffee-lover" has been created And user "Alice" has been added to group "tea-lover" @@ -267,7 +267,7 @@ Feature: get groups and their members | role | | Space Admin | | User | - | Guest | + | User Light | Scenario: admin user gets a group along with its member's information @@ -350,7 +350,7 @@ Feature: get groups and their members @issue-5604 Scenario Outline: user other than the admin gets a group along with its member's information Given user "Brian" has been created with default attributes and without skeleton files - And the administrator has given "Brian" the role "" using the settings api + And the administrator has assigned the role "" to user "Brian" using the Graph API And group "tea-lover" has been created And user "Alice" has been added to group "tea-lover" And user "Brian" has been added to group "tea-lover" @@ -381,7 +381,7 @@ Feature: get groups and their members | role | | Space Admin | | User | - | Guest | + | User Light | Scenario: get details of a group diff --git a/tests/acceptance/features/apiGraph/getUser.feature b/tests/acceptance/features/apiGraph/getUser.feature index fb6031beb..97e80fd09 100644 --- a/tests/acceptance/features/apiGraph/getUser.feature +++ b/tests/acceptance/features/apiGraph/getUser.feature @@ -1,4 +1,4 @@ -@api +@api Feature: get users As an admin I want to be able to retrieve user information @@ -12,7 +12,7 @@ Feature: get users @skipOnStable2.0 Scenario: admin user gets the information of a user - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API When user "Alice" gets information of user "Brian" using Graph API Then the HTTP status code should be "200" And the JSON data of the response should match @@ -53,8 +53,8 @@ Feature: get users @issue-5125 Scenario Outline: non-admin user tries to get the information of a user - Given the administrator has given "Alice" the role "" using the settings api - And the administrator has given "Brian" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API + And the administrator has assigned the role "" to user "Brian" using the Graph API When user "Brian" tries to get information of user "Alice" using Graph API Then the HTTP status code should be "401" And the JSON data of the response should match @@ -82,20 +82,20 @@ Feature: get users | userRole | role | | Space Admin | Space Admin | | Space Admin | User | - | Space Admin | Guest | + | Space Admin | User Light | | Space Admin | Admin | | User | Space Admin | | User | User | - | User | Guest | + | User | User Light | | User | Admin | - | Guest | Space Admin | - | Guest | User | - | Guest | Guest | - | Guest | Admin | + | User Light | Space Admin | + | User Light | User | + | User Light | User Light | + | User Light | Admin | @skipOnStable2.0 Scenario: admin user gets all users - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API When user "Alice" gets all users using the Graph API Then the HTTP status code should be "200" And the JSON data of the response should contain the user "Alice Hansen" in the item 'value', the user-details should match @@ -161,7 +161,7 @@ Feature: get users @skipOnStable2.0 Scenario: admin user gets all users include disabled users - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And the user "Alice" has disabled user "Brian" using the Graph API When user "Alice" gets all users using the Graph API Then the HTTP status code should be "200" @@ -228,7 +228,7 @@ Feature: get users Scenario Outline: non-admin user tries to get all users - Given the administrator has given "Alice" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API When user "Brian" tries to get all users using the Graph API Then the HTTP status code should be "401" And the JSON data of the response should match @@ -256,11 +256,11 @@ Feature: get users | userRole | | Space Admin | | User | - | Guest | + | User Light | @skipOnStable2.0 Scenario: admin user gets the drive information of a user - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API When the user "Alice" gets user "Brian" along with his drive information using Graph API Then the HTTP status code should be "200" And the JSON data of the response should match @@ -383,7 +383,7 @@ Feature: get users @skipOnStable2.0 Scenario Outline: non-admin user gets his/her own drive information - Given the administrator has given "Brian" the role "" using the settings api + Given the administrator has assigned the role "" to user "Brian" using the Graph API When the user "Brian" gets his drive information using Graph API Then the HTTP status code should be "200" And the JSON data of the response should match @@ -507,11 +507,11 @@ Feature: get users | userRole | | Space Admin | | User | - | Guest | + | User Light | @skipOnStable2.0 Scenario: admin user gets the group information of a user - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And group "tea-lover" has been created And group "coffee-lover" has been created And user "Brian" has been added to group "tea-lover" @@ -575,8 +575,8 @@ Feature: get users @issue-5125 Scenario Outline: non-admin user tries to get the group information of a user - Given the administrator has given "Alice" the role "" using the settings api - And the administrator has given "Brian" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API + And the administrator has assigned the role "" to user "Brian" using the Graph API And group "coffee-lover" has been created And user "Brian" has been added to group "coffee-lover" When the user "Alice" gets user "Brian" along with his group information using Graph API @@ -606,20 +606,20 @@ Feature: get users | userRole | role | | Space Admin | Space Admin | | Space Admin | User | - | Space Admin | Guest | + | Space Admin | User Light | | Space Admin | Admin | | User | Space Admin | | User | User | - | User | Guest | + | User | User Light | | User | Admin | - | Guest | Space Admin | - | Guest | User | - | Guest | Guest | - | Guest | Admin | + | User Light | Space Admin | + | User Light | User | + | User Light | User Light | + | User Light | Admin | @skipOnStable2.0 Scenario: admin user gets all users of certain groups - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And user "Carol" has been created with default attributes and without skeleton files And the user "Alice" has disabled user "Carol" using the Graph API And group "tea-lover" has been created @@ -753,7 +753,7 @@ Feature: get users @skipOnStable2.0 Scenario: admin user gets all users of two groups - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And user "Carol" has been created with default attributes and without skeleton files And group "tea-lover" has been created And group "coffee-lover" has been created @@ -827,8 +827,8 @@ Feature: get users @skipOnStable2.0 Scenario Outline: non admin user tries to get users of certain groups - Given the administrator has given "Alice" the role "Admin" using the settings api - And the administrator has given "Brian" the role "" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API + And the administrator has assigned the role "" to user "Brian" using the Graph API And group "tea-lover" has been created And user "Alice" has been added to group "tea-lover" When the user "Brian" gets all users of the group "tea-lover" using the Graph API @@ -858,14 +858,14 @@ Feature: get users | role | | Space Admin | | User | - | Guest | + | User Light | @skipOnStable2.0 Scenario: admin user gets all users with certain roles and members of a certain group - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And user "Carol" has been created with default attributes and without skeleton files - And the administrator has given "Brian" the role "Space Admin" using the settings api - And the administrator has given "Carol" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API + And the administrator has assigned the role "Space Admin" to user "Carol" using the Graph API And group "tea-lover" has been created And user "Brian" has been added to group "tea-lover" When the user "Alice" gets all users with role "Space Admin" using the Graph API @@ -967,7 +967,7 @@ Feature: get users @skipOnStable2.0 Scenario Outline: non-admin user tries to get users with a certain role - Given the administrator has given "Alice" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API When the user "Alice" gets all users with role "" using the Graph API Then the HTTP status code should be "401" And the JSON data of the response should match @@ -995,16 +995,16 @@ Feature: get users | userRole | role | | Space Admin | Space Admin | | Space Admin | User | - | Space Admin | Guest | + | Space Admin | User Light | | Space Admin | Admin | | User | Space Admin | | User | User | - | User | Guest | + | User | User Light | | User | Admin | - | Guest | Space Admin | - | Guest | User | - | Guest | Guest | - | Guest | Admin | + | User Light | Space Admin | + | User Light | User | + | User Light | User Light | + | User Light | Admin | @issue-6017 Scenario Outline: admin user gets the drive information of a user with different user role @@ -1105,11 +1105,11 @@ Feature: get users | Admin | Admin | | Admin | Space Admin | | Admin | User | - | Admin | Guest | + | Admin | User Light | | Space Admin | Admin | | Space Admin | Space Admin | | Space Admin | User | - | Space Admin | Guest | + | Space Admin | User Light | Scenario Outline: non-admin user tries to get drive information of other user with different user role @@ -1150,11 +1150,11 @@ Feature: get users | User | Admin | | User | Space Admin | | User | User | - | User | Guest | - | Guest | Admin | - | Guest | Space Admin | - | Guest | User | - | Guest | Guest | + | User | User Light | + | User Light | Admin | + | User Light | Space Admin | + | User Light | User | + | User Light | User Light | Scenario Outline: user with different user role gets his/her own drive information @@ -1253,4 +1253,4 @@ Feature: get users | Admin | | Space Admin | | User | - | Guest | + | User Light | diff --git a/tests/acceptance/features/apiGraph/removeUserFromGroup.feature b/tests/acceptance/features/apiGraph/removeUserFromGroup.feature index 45d51bac5..111c588ee 100644 --- a/tests/acceptance/features/apiGraph/removeUserFromGroup.feature +++ b/tests/acceptance/features/apiGraph/removeUserFromGroup.feature @@ -1,4 +1,4 @@ -@api +@api Feature: remove a user from a group As an admin I want to be able to remove a user from a group @@ -159,7 +159,7 @@ Feature: remove a user from a group @issue-5938 Scenario Outline: user other than the admin can't remove a user from their group Given user "Brian" has been created with default attributes and without skeleton files - And the administrator has given "Brian" the role "" using the settings api + And the administrator has assigned the role "" to user "Brian" using the Graph API And group "grp1" has been created And user "Alice" has been added to group "grp1" And user "Brian" has been added to group "grp1" @@ -191,7 +191,7 @@ Feature: remove a user from a group | role | | Space Admin | | User | - | Guest | + | User Light | Scenario: admin removes a disabled user from a group diff --git a/tests/acceptance/features/apiGraph/unassignRole.feature b/tests/acceptance/features/apiGraph/unassignRole.feature index efe4fa2cd..43088e793 100644 --- a/tests/acceptance/features/apiGraph/unassignRole.feature +++ b/tests/acceptance/features/apiGraph/unassignRole.feature @@ -22,7 +22,7 @@ Feature: unassign user role | Admin | | Space Admin | | User | - | Guest | + | User Light | @issue-6035 Scenario: admin user tries to unassign his/her own role diff --git a/tests/acceptance/features/apiGraph/userGDPRExport.feature b/tests/acceptance/features/apiGraph/userGDPRExport.feature index cd9bcbabb..6afe2a20d 100644 --- a/tests/acceptance/features/apiGraph/userGDPRExport.feature +++ b/tests/acceptance/features/apiGraph/userGDPRExport.feature @@ -496,27 +496,27 @@ Feature: user GDPR (General Data Protection Regulation) report Scenario Outline: user tries to generate GDPR report of other users Given user "Brian" has been created with default attributes and without skeleton files - And the administrator has given "Alice" the role "" using the settings api - And the administrator has given "Brian" the role "" using the settings api + And the administrator has assigned the role "" to user "Alice" using the Graph API + And the administrator has assigned the role "" to user "Brian" using the Graph API When user "Alice" tries to export GDPR report of user "Brian" to "/.personal_data_export.json" using Graph API Then the HTTP status code should be "400" Examples: | userRole | role | | Space Admin | Space Admin | | Space Admin | User | - | Space Admin | Guest | + | Space Admin | User Light | | Space Admin | Admin | | User | Space Admin | | User | User | - | User | Guest | + | User | User Light | | User | Admin | - | Guest | Space Admin | - | Guest | User | - | Guest | Guest | - | Guest | Admin | + | User Light | Space Admin | + | User Light | User | + | User Light | User Light | + | User Light | Admin | | Admin | Space Admin | | Admin | User | - | Admin | Guest | + | Admin | User Light | | Admin | Admin | diff --git a/tests/acceptance/features/apiNotification/emailNotification.feature b/tests/acceptance/features/apiNotification/emailNotification.feature index d7bf0b9c0..98e097107 100644 --- a/tests/acceptance/features/apiNotification/emailNotification.feature +++ b/tests/acceptance/features/apiNotification/emailNotification.feature @@ -12,7 +12,7 @@ Feature: Email notification Scenario: user gets an email notification when someone shares a project space - Given the administrator has given "Alice" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "new-space" with the default quota using the GraphApi When user "Alice" shares a space "new-space" with settings: | shareWith | Brian | @@ -40,3 +40,33 @@ Feature: Email notification Click here to view it: %base_url%/files/shares/with-me """ + + + Scenario: group members get an email notification when someone shares a project space with the group + Given the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Carol" has been created with default attributes and without skeleton files + And group "group1" has been created + And user "Brian" has been added to group "group1" + And user "Carol" has been added to group "group1" + And user "Alice" has created a space "new-space" with the default quota using the GraphApi + When user "Alice" shares a space "new-space" with settings: + | shareWith | group1 | + | shareType | 8 | + | role | viewer | + Then the HTTP status code should be "200" + And user "Brian" should have received the following email from user "Alice" about the share of project space "new-space" + """ + Hello Brian Murphy, + + %displayname% has invited you to join "new-space". + + Click here to view it: %base_url%/f/%space_id% + """ + And user "Carol" should have received the following email from user "Alice" about the share of project space "new-space" + """ + Hello Carol King, + + %displayname% has invited you to join "new-space". + + Click here to view it: %base_url%/f/%space_id% + """ diff --git a/tests/acceptance/features/apiNotification/notification.feature b/tests/acceptance/features/apiNotification/notification.feature index 4734f2da0..4d522b33f 100644 --- a/tests/acceptance/features/apiNotification/notification.feature +++ b/tests/acceptance/features/apiNotification/notification.feature @@ -267,7 +267,6 @@ Feature: Notification Examples: | language | subject | message | | de | Neue Freigabe | Alice Hansen hat textfile1.txt mit Ihnen geteilt | - | en | Resource shared | Alice Hansen shared textfile1.txt with you | | es | Recurso compartido | Alice Hansen compartió textfile1.txt contigo | diff --git a/tests/acceptance/features/apiNotification/spaceNotification.feature b/tests/acceptance/features/apiNotification/spaceNotification.feature new file mode 100644 index 000000000..41b967d5d --- /dev/null +++ b/tests/acceptance/features/apiNotification/spaceNotification.feature @@ -0,0 +1,472 @@ +@api +Feature: Notification + As a user + I want to be notified of actions related to space + So that I can stay updated about the spaces + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + | Carol | + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "notification checking" with the default quota using the GraphApi + + + Scenario: get a notification of space shared + Given user "Alice" has shared a space "notification checking" with settings: + | shareWith | Brian | + | role | editor | + When user "Brian" lists all notifications + Then the HTTP status code should be "200" + And the JSON response should contain a notification message with the subject "Space shared" and the message-details should match + """ + { + "type": "object", + "required": [ + "app", + "datetime", + "message", + "messageRich", + "messageRichParameters", + "notification_id", + "object_id", + "object_type", + "subject", + "subjectRich", + "user" + ], + "properties": { + "app": { + "type": "string", + "enum": [ + "userlog" + ] + }, + "message": { + "type": "string", + "enum": [ + "Alice Hansen added you to Space notification checking" + ] + }, + "messageRich": { + "type": "string", + "enum": [ + "{user} added you to Space {space}" + ] + }, + "messageRichParameters": { + "type": "object", + "required": [ + "space", + "user" + ], + "properties": { + "space": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\\$[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}![a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" + }, + "name": { + "type": "string", + "enum": [ + "notification checking" + ] + } + } + }, + "user": { + "type": "object", + "required": [ + "displayname", + "id", + "name" + ], + "properties": { + "displayname": { + "type": "string", + "enum": [ + "Alice Hansen" + ] + }, + "id": { + "type": "string", + "enim": [ + "%user_id%" + ] + }, + "name": { + "type": "string", + "enum": [ + "Alice" + ] + } + } + } + } + }, + "notification_id": { + "type": "string" + }, + "object_id": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "object_type": { + "type": "string", + "enum": [ + "storagespace" + ] + }, + "subject": { + "type": "string", + "enum": [ + "Space shared" + ] + }, + "subjectRich": { + "type": "string", + "enum": [ + "Space shared" + ] + }, + "user": { + "type": "string", + "enum": [ + "Alice" + ] + } + } + } + """ + + + Scenario: get a notification of space unshared + Given user "Alice" has shared a space "notification checking" with settings: + | shareWith | Brian | + | role | editor | + And user "Alice" has unshared a space "notification checking" shared with "Brian" + When user "Brian" lists all notifications + Then the HTTP status code should be "200" + And the JSON response should contain a notification message with the subject "Removed from Space" and the message-details should match + """ + { + "type": "object", + "required": [ + "app", + "datetime", + "message", + "messageRich", + "messageRichParameters", + "notification_id", + "object_id", + "object_type", + "subject", + "subjectRich", + "user" + ], + "properties": { + "app": { + "type": "string", + "enum": [ + "userlog" + ] + }, + "message": { + "type": "string", + "enum": [ + "Alice Hansen removed you from Space notification checking" + ] + }, + "messageRich": { + "type": "string", + "enum": [ + "{user} removed you from Space {space}" + ] + }, + "messageRichParameters": { + "type": "object", + "required": [ + "space", + "user" + ], + "properties": { + "space": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\\$[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}![a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" + }, + "name": { + "type": "string", + "enum": [ + "notification checking" + ] + } + } + }, + "user": { + "type": "object", + "required": [ + "displayname", + "id", + "name" + ], + "properties": { + "displayname": { + "type": "string", + "enum": [ + "Alice Hansen" + ] + }, + "id": { + "type": "string", + "enim": [ + "%user_id%" + ] + }, + "name": { + "type": "string", + "enum": [ + "Alice" + ] + } + } + } + } + }, + "notification_id": { + "type": "string" + }, + "object_id": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "object_type": { + "type": "string", + "enum": [ + "storagespace" + ] + }, + "subject": { + "type": "string", + "enum": [ + "Removed from Space" + ] + }, + "subjectRich": { + "type": "string", + "enum": [ + "Removed from Space" + ] + }, + "user": { + "type": "string", + "enum": [ + "Alice" + ] + } + } + } + """ + + + Scenario: get a notification of space disabled + Given user "Alice" has shared a space "notification checking" with settings: + | shareWith | Brian | + | role | editor | + And user "Alice" has disabled a space "notification checking" + When user "Brian" lists all notifications + Then the HTTP status code should be "200" + And user "Brian" should have "2" notifications + And the JSON response should contain a notification message with the subject "Space disabled" and the message-details should match + """ + { + "type": "object", + "required": [ + "app", + "datetime", + "message", + "messageRich", + "messageRichParameters", + "notification_id", + "object_id", + "object_type", + "subject", + "subjectRich", + "user" + ], + "properties": { + "app": { + "type": "string", + "enum": [ + "userlog" + ] + }, + "message": { + "type": "string", + "enum": [ + "Alice Hansen disabled Space notification checking" + ] + }, + "messageRich": { + "type": "string", + "enum": [ + "{user} disabled Space {space}" + ] + }, + "messageRichParameters": { + "type": "object", + "required": [ + "space", + "user" + ], + "properties": { + "space": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\\$[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}![a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" + }, + "name": { + "type": "string", + "enum": [ + "notification checking" + ] + } + } + }, + "user": { + "type": "object", + "required": [ + "displayname", + "id", + "name" + ], + "properties": { + "displayname": { + "type": "string", + "enum": [ + "Alice Hansen" + ] + }, + "id": { + "type": "string", + "enim": [ + "%user_id%" + ] + }, + "name": { + "type": "string", + "enum": [ + "Alice" + ] + } + } + } + } + }, + "notification_id": { + "type": "string" + }, + "object_id": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "object_type": { + "type": "string", + "enum": [ + "storagespace" + ] + }, + "subject": { + "type": "string", + "enum": [ + "Space disabled" + ] + }, + "subjectRich": { + "type": "string", + "enum": [ + "Space disabled" + ] + }, + "user": { + "type": "string", + "enum": [ + "Alice" + ] + } + } + } + """ + + + Scenario Outline: get a notification about a space share in various languages + Given user "Brian" has switched the system language to "" + And user "Alice" has shared a space "notification checking" with settings: + | shareWith | Brian | + | role | editor | + When user "Brian" lists all notifications + Then the HTTP status code should be "200" + And the JSON response should contain a notification message with the subject "" and the message-details should match + """ + { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string", + "enum": [ + "" + ] + } + } + } + """ + Examples: + | language | subject | message | + | de | Space freigegeben | Alice Hansen hat Sie zu Space notification checking hinzugefügt | + | es | Space compartido | Alice Hansen te añadió al Space notification checking | + + + Scenario: all notification related to space get deleted when the sharer deletes that resource + Given user "Alice" has shared a space "notification checking" with settings: + | shareWith | Brian | + | role | editor | + And user "Alice" has unshared a space "notification checking" shared with "Brian" + And user "Alice" has disabled a space "notification checking" + And user "Alice" has deleted a space "notification checking" + When user "Brian" lists all notifications + Then the HTTP status code should be "200" + And the notifications should be empty + + + Scenario: user doesn't get any notification after being removed from space + Given user "Alice" has shared a space "notification checking" with settings: + | shareWith | Brian | + | role | editor | + And user "Alice" has unshared a space "notification checking" shared with "Brian" + And user "Alice" has disabled a space "notification checking" + When user "Brian" lists all notifications + Then the HTTP status code should be "200" + And user "Brian" should have "2" notifications diff --git a/tests/acceptance/features/apiSpaces/changeSpaces.feature b/tests/acceptance/features/apiSpaces/changeSpaces.feature index 4ad095a0a..7eb82e8b4 100644 --- a/tests/acceptance/features/apiSpaces/changeSpaces.feature +++ b/tests/acceptance/features/apiSpaces/changeSpaces.feature @@ -13,7 +13,7 @@ Feature: Change data of space | Alice | | Brian | | Bob | - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "Project Jupiter" of type "project" with quota "20" And user "Alice" has shared a space "Project Jupiter" with settings: | shareWith | Brian | @@ -509,7 +509,7 @@ Feature: Change data of space Scenario Outline: user can't upload resource greater than set quota - Given the administrator has given "Alice" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API And user "Admin" has changed the quota of the personal space of "Alice Hansen" space to "15" When user "Alice" uploads a file inside space "Alice Hansen" with content "file is more than 15 bytes" to "file.txt" using the WebDAV API Then the HTTP status code should be "507" @@ -520,7 +520,7 @@ Feature: Change data of space | Admin | | Space Admin | | User | - | Guest | + | User Light | Scenario Outline: admin user set own quota of a personal space via the Graph API and upload resource diff --git a/tests/acceptance/features/apiSpaces/createSpace.feature b/tests/acceptance/features/apiSpaces/createSpace.feature index 2500e7c51..266584730 100644 --- a/tests/acceptance/features/apiSpaces/createSpace.feature +++ b/tests/acceptance/features/apiSpaces/createSpace.feature @@ -8,19 +8,19 @@ Feature: create space Given user "Alice" has been created with default attributes and without skeleton files @issue-5938 - Scenario Outline: user with role user and guest can't create space via Graph API - Given the administrator has given "Alice" the role "" using the settings api + Scenario Outline: user with role user and user light can't create space via Graph API + Given the administrator has assigned the role "" to user "Alice" using the Graph API When user "Alice" tries to create a space "Project Mars" of type "project" with the default quota using the Graph API Then the HTTP status code should be "403" And the user "Alice" should not have a space called "share space" Examples: - | role | - | User | - | Guest | + | role | + | User | + | User Light | Scenario Outline: admin or space admin user can create a space via the Graph API with a default quota - Given the administrator has given "Alice" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API When user "Alice" creates a space "Project Mars" of type "project" with the default quota using the Graph API Then the HTTP status code should be "201" And the JSON response should contain space called "Project Mars" and match @@ -91,7 +91,7 @@ Feature: create space Scenario Outline: admin or space admin user can create a space via the Graph API with certain quota - Given the administrator has given "Alice" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API When user "Alice" creates a space "Project Venus" of type "project" with quota "2000" using the Graph API Then the HTTP status code should be "201" And the JSON response should contain space called "Project Venus" and match diff --git a/tests/acceptance/features/apiSpaces/disableAndDeleteSpaces.feature b/tests/acceptance/features/apiSpaces/disableAndDeleteSpaces.feature index d6fa1c900..7056cad24 100644 --- a/tests/acceptance/features/apiSpaces/disableAndDeleteSpaces.feature +++ b/tests/acceptance/features/apiSpaces/disableAndDeleteSpaces.feature @@ -14,7 +14,7 @@ Feature: Disabling and deleting space | Brian | | Bob | | Carol | - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "Project Moon" with the default quota using the GraphApi And user "Alice" has shared a space "Project Moon" with settings: | shareWith | Brian | @@ -25,7 +25,7 @@ Feature: Disabling and deleting space Scenario Outline: user can disable their own space via the Graph API - Given the administrator has given "Alice" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API When user "Alice" disables a space "Project Moon" Then the HTTP status code should be "204" And the user "Brian" should not have a space called "Project Moon" @@ -35,19 +35,19 @@ Feature: Disabling and deleting space | Admin | | Space Admin | | User | - | Guest | + | User Light | - Scenario Outline: user with role user and guest cannot disable other space via the Graph API - Given the administrator has given "Carol" the role "" using the settings api + Scenario Outline: user with role user and user light cannot disable other space via the Graph API + Given the administrator has assigned the role "" to user "Carol" using the Graph API When user "Carol" tries to disable a space "Project Moon" owned by user "Alice" Then the HTTP status code should be "404" And the user "Brian" should have a space called "Project Moon" And the user "Bob" should have a space called "Project Moon" Examples: - | role | - | User | - | Guest | + | role | + | User | + | User Light | Scenario: a space manager can disable and delete space in which files and folders exist via the webDav API @@ -61,7 +61,7 @@ Feature: Disabling and deleting space Scenario Outline: user cannot delete their own space without first disabling it - Given the administrator has given "Alice" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API When user "Alice" deletes a space "Project Moon" Then the HTTP status code should be "400" And the user "Alice" should have a space called "Project Moon" @@ -70,11 +70,11 @@ Feature: Disabling and deleting space | Admin | | Space Admin | | User | - | Guest | + | User Light | Scenario Outline: user can delete their own disabled space via the Graph API - Given the administrator has given "Alice" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API And user "Alice" has disabled a space "Project Moon" When user "Alice" deletes a space "Project Moon" Then the HTTP status code should be "204" @@ -84,11 +84,11 @@ Feature: Disabling and deleting space | Admin | | Space Admin | | User | - | Guest | + | User Light | Scenario Outline: an admin and space manager can disable other space via the Graph API - Given the administrator has given "Carol" the role "" using the settings api + Given the administrator has assigned the role "" to user "Carol" using the Graph API When user "Carol" disables a space "Project Moon" owned by user "Alice" Then the HTTP status code should be "204" And the user "Carol" should not have a space called "Project Moon" @@ -99,7 +99,7 @@ Feature: Disabling and deleting space Scenario Outline: an admin and space manager can delete other disabled Space - Given the administrator has given "Carol" the role "" using the settings api + Given the administrator has assigned the role "" to user "Carol" using the Graph API And user "Alice" has disabled a space "Project Moon" When user "Carol" deletes a space "Project Moon" owned by user "Alice" Then the HTTP status code should be "204" @@ -111,12 +111,12 @@ Feature: Disabling and deleting space | Space Admin | - Scenario Outline: user with role user and guest cannot delete others disabled space via the Graph API - Given the administrator has given "Carol" the role "" using the settings api + Scenario Outline: user with role user and user light cannot delete others disabled space via the Graph API + Given the administrator has assigned the role "" to user "Carol" using the Graph API And user "Alice" has disabled a space "Project Moon" When user "Carol" tries to delete a space "Project Moon" owned by user "Alice" Then the HTTP status code should be "404" Examples: - | role | - | User | - | Guest | + | role | + | User | + | User Light | diff --git a/tests/acceptance/features/apiSpaces/download.feature b/tests/acceptance/features/apiSpaces/download.feature index 56b3675ab..40dd9dfd5 100644 --- a/tests/acceptance/features/apiSpaces/download.feature +++ b/tests/acceptance/features/apiSpaces/download.feature @@ -14,7 +14,7 @@ Feature: Download file in project space | Brian | | Bob | And using spaces DAV path - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "download file" with the default quota using the GraphApi And user "Alice" has uploaded a file inside space "download file" with content "some content" to "file.txt" And user "Alice" has shared a space "download file" with settings: diff --git a/tests/acceptance/features/apiSpaces/editPublicLinkOfSpace.feature b/tests/acceptance/features/apiSpaces/editPublicLinkOfSpace.feature index d46316b5e..fb7be66a2 100644 --- a/tests/acceptance/features/apiSpaces/editPublicLinkOfSpace.feature +++ b/tests/acceptance/features/apiSpaces/editPublicLinkOfSpace.feature @@ -15,7 +15,7 @@ Feature: A manager of the space can edit public link | Alice | | Brian | And using spaces DAV path - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "edit space" with the default quota using the GraphApi And user "Alice" has created a public link share of the space "edit space" with settings: | permissions | 1 | diff --git a/tests/acceptance/features/apiSpaces/filePreviews.feature b/tests/acceptance/features/apiSpaces/filePreviews.feature index 82f58fa49..b0f7a2f56 100644 --- a/tests/acceptance/features/apiSpaces/filePreviews.feature +++ b/tests/acceptance/features/apiSpaces/filePreviews.feature @@ -11,7 +11,7 @@ Feature: Preview file in project space Given these users have been created with default attributes and without skeleton files: | username | | Alice | - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "previews of the files" with the default quota using the GraphApi And using spaces DAV path diff --git a/tests/acceptance/features/apiSpaces/listSpaces.feature b/tests/acceptance/features/apiSpaces/listSpaces.feature index 06d525df6..606e76d3a 100644 --- a/tests/acceptance/features/apiSpaces/listSpaces.feature +++ b/tests/acceptance/features/apiSpaces/listSpaces.feature @@ -1,4 +1,4 @@ -@api +@api Feature: List and create spaces As a user I want to be able to list project spaces @@ -150,7 +150,7 @@ Feature: List and create spaces Scenario: ordinary user will not see any space when using a filter for project - Given the administrator has given "Alice" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "my project" of type "project" with quota "20" When user "Alice" lists all available spaces via the GraphApi with query "$filter=driveType eq 'project'" Then the HTTP status code should be "200" @@ -289,7 +289,7 @@ Feature: List and create spaces Scenario Outline: user can list his created spaces via multiple endpoints - Given the administrator has given "Alice" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API When user "Alice" creates a space "Project Venus" of type "project" with quota "2000" using the Graph API Then the HTTP status code should be "201" And the JSON response should contain space called "Project Venus" and match @@ -419,7 +419,7 @@ Feature: List and create spaces Scenario Outline: user cannot list space by id if he is not member of the space - Given the administrator has given "Alice" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API And user "Admin" has created a space "Project Venus" with the default quota using the GraphApi When user "Alice" tries to look up the single space "Project Venus" owned by the user "Admin" by using its id Then the HTTP status code should be "404" @@ -429,4 +429,4 @@ Feature: List and create spaces | Admin | | Space Admin | | User | - | Guest | + | User Light | diff --git a/tests/acceptance/features/apiSpaces/notification.feature b/tests/acceptance/features/apiSpaces/notification.feature deleted file mode 100644 index 763f8fe26..000000000 --- a/tests/acceptance/features/apiSpaces/notification.feature +++ /dev/null @@ -1,180 +0,0 @@ -@api -Feature: Notification - As a user - I want to be notified of actions related to me - So that I can stay updated about the information - - Background: - Given these users have been created with default attributes and without skeleton files: - | username | - | Alice | - | Brian | - | Carol | - And the administrator has given "Alice" the role "Space Admin" using the settings api - - - Scenario: user gets a notification of space sharing - Given user "Alice" has created a space "notification checking" with the default quota using the GraphApi - And user "Alice" has shared a space "notification checking" with settings: - | shareWith | Brian | - | role | editor | - When user "Brian" lists all notifications - Then the HTTP status code should be "200" - And the JSON response should contain a notification message with the subject "Space shared" and the message-details should match - """ - { - "type": "object", - "required": [ - "app", - "datetime", - "message", - "messageRich", - "messageRichParameters", - "notification_id", - "object_id", - "object_type", - "subject", - "subjectRich", - "user" - ], - "properties": { - "app": { - "type": "string", - "enum": [ - "userlog" - ] - }, - "message": { - "type": "string", - "enum": [ - "Alice Hansen added you to Space notification checking" - ] - }, - "messageRich": { - "type": "string", - "enum": [ - "{user} added you to Space {space}" - ] - }, - "messageRichParameters": { - "type": "object", - "required": [ - "space", - "user" - ], - "properties": { - "space": { - "type": "object", - "required": [ - "id", - "name" - ], - "properties": { - "id": { - "type": "string", - "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\\$[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}![a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" - }, - "name": { - "type": "string", - "enum": [ - "notification checking" - ] - } - } - }, - "user": { - "type": "object", - "required": [ - "displayname", - "id", - "name" - ], - "properties": { - "displayname": { - "type": "string", - "enum": [ - "Alice Hansen" - ] - }, - "id": { - "type": "string", - "enim": [ - "%user_id%" - ] - }, - "name": { - "type": "string", - "enum": [ - "Alice" - ] - } - } - } - } - }, - "notification_id": { - "type": "string" - }, - "object_id": { - "type": "string", - "pattern": "^%space_id_pattern%$" - }, - "object_type": { - "type": "string", - "enum": [ - "storagespace" - ] - }, - "subject": { - "type": "string", - "enum": [ - "Space shared" - ] - }, - "subjectRich": { - "type": "string", - "enum": [ - "Space shared" - ] - }, - "user": { - "type": "string", - "enum": [ - "Alice" - ] - } - } - } - """ - - - Scenario Outline: user gets a notification of space sharing in different languages - Given user "Alice" has created a space "notification checking" with the default quota using the GraphApi - And user "Brian" has switched the system language to "" - And user "Alice" has shared a space "notification checking" with settings: - | shareWith | Brian | - | role | editor | - When user "Brian" lists all notifications - Then the HTTP status code should be "200" - And the JSON response should contain a notification message with the subject "" and the message-details should match - """ - { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string", - "enum": [ - "" - ] - } - } - } - """ - Examples: - | language | subject | message | - | de | Space freigegeben | Alice Hansen hat Sie zu Space notification checking hinzugefügt | - | en | Space shared | Alice Hansen added you to Space notification checking | - | es | Space compartido | Alice Hansen te añadió al Space notification checking | diff --git a/tests/acceptance/features/apiSpaces/publicLink.feature b/tests/acceptance/features/apiSpaces/publicLink.feature index 2d50dcafb..13486f35d 100644 --- a/tests/acceptance/features/apiSpaces/publicLink.feature +++ b/tests/acceptance/features/apiSpaces/publicLink.feature @@ -6,7 +6,7 @@ Feature: public link for a space | username | | Alice | And using spaces DAV path - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "public space" with the default quota using the GraphApi And user "Alice" has created a public link share of the space "public space" with settings: | permissions | 1 | diff --git a/tests/acceptance/features/apiSpaces/quota.feature b/tests/acceptance/features/apiSpaces/quota.feature index 006b3327b..fe80a9f21 100644 --- a/tests/acceptance/features/apiSpaces/quota.feature +++ b/tests/acceptance/features/apiSpaces/quota.feature @@ -1,4 +1,4 @@ -@api +@api Feature: State of the quota As a user I want to be able to see the state of the quota @@ -16,7 +16,7 @@ Feature: State of the quota Background: Given user "Alice" has been created with default attributes and without skeleton files - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And using spaces DAV path @@ -122,3 +122,20 @@ Feature: State of the quota | /filesForUpload/lorem-big.txt | /ocs/v1.php/cloud/users/%username% | 100 | 91.17 | | /filesForUpload/lorem.txt | /ocs/v2.php/cloud/users/%username% | 200 | 6.99 | | /filesForUpload/lorem-big.txt | /ocs/v2.php/cloud/users/%username% | 200 | 91.17 | + + + @env-config + Scenario: upload a file by setting OCIS spaces max quota + Given the config "OCIS_SPACES_MAX_QUOTA" has been set to "10" + And user "Brian" has been created with default attributes and without skeleton files + When user "Brian" uploads file with content "more than 10 bytes content" to "lorem.txt" using the WebDAV API + Then the HTTP status code should be "507" + + @env-config + Scenario: try to create a space with quota greater than OCIS spaces max quota + Given the config "OCIS_SPACES_MAX_QUOTA" has been set to "50" + And user "Brian" has been created with default attributes and without skeleton files + And the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API + When user "Brian" tries to create a space "new space" of type "project" with quota "51" using the Graph API + Then the HTTP status code should be "400" + And the user "Brian" should not have a space called "new space" diff --git a/tests/acceptance/features/apiSpaces/removeSpaceObjects.feature b/tests/acceptance/features/apiSpaces/removeSpaceObjects.feature index 76d0a3379..8eafce197 100644 --- a/tests/acceptance/features/apiSpaces/removeSpaceObjects.feature +++ b/tests/acceptance/features/apiSpaces/removeSpaceObjects.feature @@ -13,7 +13,7 @@ Feature: Remove files, folder | Alice | | Brian | And using spaces DAV path - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "delete objects" with the default quota using the GraphApi And user "Alice" has created a folder "folderForDeleting/sub1/sub2" in space "delete objects" And user "Alice" has uploaded a file inside space "delete objects" with content "some content" to "text.txt" diff --git a/tests/acceptance/features/apiSpaces/restoreSpaceObjects.feature b/tests/acceptance/features/apiSpaces/restoreSpaceObjects.feature index 37425d151..eaf30dbe6 100644 --- a/tests/acceptance/features/apiSpaces/restoreSpaceObjects.feature +++ b/tests/acceptance/features/apiSpaces/restoreSpaceObjects.feature @@ -15,7 +15,7 @@ Feature: Restore files, folder | Bob | | Carol | And using spaces DAV path - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "restore objects" with the default quota using the GraphApi And user "Alice" has created a folder "newFolder" in space "restore objects" And user "Alice" has uploaded a file inside space "restore objects" with content "test" to "newFolder/file.txt" diff --git a/tests/acceptance/features/apiSpaces/restoreSpaces.feature b/tests/acceptance/features/apiSpaces/restoreSpaces.feature index 58dbe7592..82afcc741 100644 --- a/tests/acceptance/features/apiSpaces/restoreSpaces.feature +++ b/tests/acceptance/features/apiSpaces/restoreSpaces.feature @@ -1,4 +1,4 @@ -@api +@api Feature: Restoring space As a manager of space I want to be able to restore a disabled space @@ -13,7 +13,7 @@ Feature: Restoring space | Alice | | Brian | | Bob | - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "restore a space" of type "project" with quota "10" And using spaces DAV path @@ -72,19 +72,19 @@ Feature: Restoring space | editor | - Scenario Outline: user with role user and guest cannot restore space - Given the administrator has given "Brian" the role "" using the settings api + Scenario Outline: user with role user and user light cannot restore space + Given the administrator has assigned the role "" to user "Brian" using the Graph API And user "Alice" has disabled a space "restore a space" When user "Brian" tries to restore a disabled space "restore a space" owned by user "Alice" Then the HTTP status code should be "404" Examples: - | role | - | User | - | Guest | + | role | + | User | + | User Light | @issue-5872 Scenario Outline: admin and space admin can restore other space - Given the administrator has given "Brian" the role "" using the settings api + Given the administrator has assigned the role "" to user "Brian" using the Graph API And user "Alice" has disabled a space "restore a space" When user "Brian" restores a disabled space "restore a space" owned by user "Alice" Then the HTTP status code should be "200" diff --git a/tests/acceptance/features/apiSpaces/search.feature b/tests/acceptance/features/apiSpaces/search.feature index d57ba8195..47cbe77ff 100644 --- a/tests/acceptance/features/apiSpaces/search.feature +++ b/tests/acceptance/features/apiSpaces/search.feature @@ -13,7 +13,7 @@ Feature: Search | Alice | | Brian | And using spaces DAV path - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "find data" with the default quota using the GraphApi And user "Alice" has created a folder "folderMain/SubFolder1/subFOLDER2" in space "find data" And user "Alice" has uploaded a file inside space "find data" with content "some content" to "folderMain/SubFolder1/subFOLDER2/insideTheFolder.txt" diff --git a/tests/acceptance/features/apiSpaces/setQuota.feature b/tests/acceptance/features/apiSpaces/setQuota.feature index feff65c69..3f977cdf4 100644 --- a/tests/acceptance/features/apiSpaces/setQuota.feature +++ b/tests/acceptance/features/apiSpaces/setQuota.feature @@ -12,8 +12,8 @@ Feature: Set quota Scenario Outline: admin sets personal space quota of user with different role - Given the administrator has given "Alice" the role "Admin" using the settings api - And the administrator has given "Brian" the role "" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API + And the administrator has assigned the role "" to user "Brian" using the Graph API When user "Alice" changes the quota of the "Brian Murphy" space to "100" owned by user "Brian" Then the HTTP status code should be "200" And the JSON data of the response should match @@ -44,12 +44,12 @@ Feature: Set quota | Admin | | Space Admin | | User | - | Guest | + | User Light | Scenario Outline: non-admin user tries to set the personal space quota of other users - Given the administrator has given "Alice" the role "" using the settings api - And the administrator has given "Brian" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API + And the administrator has assigned the role "" to user "Brian" using the Graph API When user "Alice" changes the quota of the "Brian Murphy" space to "100" owned by user "Brian" Then the HTTP status code should be "403" Examples: @@ -57,20 +57,20 @@ Feature: Set quota | Space Admin | Admin | | Space Admin | Space Admin | | Space Admin | User | - | Space Admin | Guest | + | Space Admin | User Light | | User | Admin | | User | Space Admin | | User | User | - | User | Guest | - | Guest | Admin | - | Guest | Space Admin | - | Guest | User | - | Guest | Guest | + | User | User Light | + | User Light | Admin | + | User Light | Space Admin | + | User Light | User | + | User Light | User Light | Scenario Outline: admin or space admin user sets a quota of a project space - Given the administrator has given "Alice" the role "Space Admin" using the settings api - And the administrator has given "Brian" the role "" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And the administrator has assigned the role "" to user "Brian" using the Graph API And user "Alice" has created a space "Project Jupiter" of type "project" with quota "20" When user "Brian" changes the quota of the "Project Jupiter" space to "100" owned by user "Alice" Then the HTTP status code should be "200" @@ -108,9 +108,9 @@ Feature: Set quota | Space Admin | - Scenario Outline: normal or guest user tries to set quota of a space - Given the administrator has given "Alice" the role "Space Admin" using the settings api - And the administrator has given "Brian" the role "" using the settings api + Scenario Outline: normal or user light user tries to set quota of a space + Given the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And the administrator has assigned the role "" to user "Brian" using the Graph API And user "Alice" has created a space "Project Jupiter" of type "project" with quota "20" And user "Alice" has shared a space "Project Jupiter" with settings: | shareWith | Brian | @@ -118,17 +118,17 @@ Feature: Set quota When user "Brian" changes the quota of the "Project Jupiter" space to "100" Then the HTTP status code should be "403" Examples: - | userRole | spaceRole | - | User | viewer | - | User | editor | - | User | manager | - | Guest | viewer | - | Guest | editor | - | Guest | manager | + | userRole | spaceRole | + | User | viewer | + | User | editor | + | User | manager | + | User Light | viewer | + | User Light | editor | + | User Light | manager | Scenario: admin user can set their own personal space quota - Given the administrator has given "Alice" the role "Admin" using the settings api + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API When user "Alice" changes the quota of the "Alice Hansen" space to "100" owned by user "Alice" Then the HTTP status code should be "200" And the JSON data of the response should match @@ -157,11 +157,11 @@ Feature: Set quota Scenario Outline: non-admin user tries to set their own personal space quota - Given the administrator has given "Alice" the role "" using the settings api + Given the administrator has assigned the role "" to user "Alice" using the Graph API When user "Alice" changes the quota of the "Alice Hansen" space to "100" owned by user "Alice" Then the HTTP status code should be "403" Examples: | userRole | | Space Admin | | User | - | Guest | + | User Light | diff --git a/tests/acceptance/features/apiSpaces/spaceManagement.feature b/tests/acceptance/features/apiSpaces/spaceManagement.feature index 9f81eb449..975c0414b 100644 --- a/tests/acceptance/features/apiSpaces/spaceManagement.feature +++ b/tests/acceptance/features/apiSpaces/spaceManagement.feature @@ -17,8 +17,8 @@ Feature: Space management | Brian | | Carol | And using spaces DAV path - And the administrator has given "Alice" the role "Space Admin" using the settings api - And the administrator has given "Brian" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Alice" has created a space "Project" of type "project" with quota "10" diff --git a/tests/acceptance/features/apiSpaces/tag.feature b/tests/acceptance/features/apiSpaces/tag.feature index 30df75643..e14cfb5bc 100644 --- a/tests/acceptance/features/apiSpaces/tag.feature +++ b/tests/acceptance/features/apiSpaces/tag.feature @@ -1,7 +1,7 @@ @api @skipOnStable2.0 Feature: Tag - As a user - I want to tag resources + As a user + I want to tag resources So that I can sort and search them quickly Note - this feature is run in CI with ACCOUNTS_HASH_DIFFICULTY set to the default for production @@ -13,7 +13,7 @@ Feature: Tag | Alice | | Brian | And using spaces DAV path - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "use-tag" with the default quota using the GraphApi And user "Alice" has created a folder "folderMain" in space "use-tag" And user "Alice" has uploaded a file inside space "use-tag" with content "some content" to "folderMain/insideTheFolder.txt" @@ -255,3 +255,52 @@ Feature: Tag And the response should contain following tags: | folderTag | | marketing | + + + Scenario: user creates a comma-separated list of tags for resources in the project space + Given user "Alice" has shared a space "use-tag" with settings: + | shareWith | Brian | + | role | viewer | + When user "Alice" creates the following tags for folder "folderMain" of space "use-tag": + | finance,नेपाल | + Then the HTTP status code should be "200" + When user "Alice" sends PROPFIND request from the space "use-tag" to the resource "folderMain" using the WebDAV API + Then the HTTP status code should be "207" + And the "PROPFIND" response should contain a space "use-tag" with these key and value pairs: + | key | value | + | oc:tags | finance,नेपाल | + When user "Alice" creates the following tags for file "folderMain/insideTheFolder.txt" of space "use-tag": + | file,नेपाल,Tag | + Then the HTTP status code should be "200" + When user "Brian" sends PROPFIND request from the space "use-tag" to the resource "folderMain/insideTheFolder.txt" using the WebDAV API + Then the HTTP status code should be "207" + And the "PROPFIND" response should contain a space "use-tag" with these key and value pairs: + | key | value | + | oc:tags | file,नेपाल,Tag | + When user "Alice" lists all available tags via the GraphApi + Then the HTTP status code should be "200" + And the response should contain following tags: + | finance | + | नेपाल | + | file | + | Tag | + + + Scenario: setting a comma-separated list of tags adds to any existing tags on the resource + Given user "Alice" has created the following tags for folder "folderMain" of the space "use-tag": + | finance,hr | + When user "Alice" creates the following tags for folder "folderMain" of space "use-tag": + | engineering,finance,qa | + Then the HTTP status code should be "200" + When user "Alice" sends PROPFIND request from the space "use-tag" to the resource "folderMain" using the WebDAV API + Then the HTTP status code should be "207" + And the "PROPFIND" response should contain a space "use-tag" with these key and value pairs: + | key | value | + | oc:tags | engineering,finance,hr,qa | + When user "Alice" lists all available tags via the GraphApi + Then the HTTP status code should be "200" + And the response should contain following tags: + | engineering | + | finance | + | hr | + | qa | diff --git a/tests/acceptance/features/apiSpaces/tusUpload.feature b/tests/acceptance/features/apiSpaces/tusUpload.feature index 4c12bce01..da0ebbfb4 100644 --- a/tests/acceptance/features/apiSpaces/tusUpload.feature +++ b/tests/acceptance/features/apiSpaces/tusUpload.feature @@ -8,7 +8,7 @@ Feature: upload resources using TUS protocol Given these users have been created with default attributes and without skeleton files: | username | | Alice | - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And using spaces DAV path diff --git a/tests/acceptance/features/apiSpaces/uploadSpaces.feature b/tests/acceptance/features/apiSpaces/uploadSpaces.feature index dc2451faf..3c3a32458 100644 --- a/tests/acceptance/features/apiSpaces/uploadSpaces.feature +++ b/tests/acceptance/features/apiSpaces/uploadSpaces.feature @@ -13,7 +13,7 @@ Feature: Upload files into a space | Alice | | Brian | | Bob | - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "Project Ceres" of type "project" with quota "2000" And using spaces DAV path diff --git a/tests/acceptance/features/apiSpacesShares/changingFilesShare.feature b/tests/acceptance/features/apiSpacesShares/changingFilesShare.feature index d7f52757f..21eb1cb83 100644 --- a/tests/acceptance/features/apiSpacesShares/changingFilesShare.feature +++ b/tests/acceptance/features/apiSpacesShares/changingFilesShare.feature @@ -1,8 +1,8 @@ -@api +@api Feature: change shared resource - As a user + As a user I want to change the shared resource - So that I can organize them as per my necessity + So that I can organize them as per my necessity Background: Given using spaces DAV path @@ -30,8 +30,9 @@ Feature: change shared resource | from_alice.txt | - Scenario: overwrite a received file share - Given user "Alice" has uploaded file with content "old content version 1" to "/textfile1.txt" + Scenario Outline: overwrite a received file share + Given the administrator has assigned the role "" to user "Brian" using the Graph API + And user "Alice" has uploaded file with content "old content version 1" to "/textfile1.txt" And user "Alice" has uploaded file with content "old content version 2" to "/textfile1.txt" And user "Alice" has shared file "/textfile1.txt" with user "Brian" And user "Brian" has accepted share "/textfile1.txt" offered by user "Alice" @@ -49,3 +50,9 @@ Feature: change shared resource And the downloaded content should be "old content version 2" When user "Brian" tries to get version of the file "/textfile1.txt" with the index "1" of the space "Shares" using the WebDAV API Then the HTTP status code should be "403" + Examples: + | userRole | + | Admin | + | Space Admin | + | User | + | User Light | diff --git a/tests/acceptance/features/apiSpacesShares/copySpaces.feature b/tests/acceptance/features/apiSpacesShares/copySpaces.feature index bf100aa58..aaa0d8925 100644 --- a/tests/acceptance/features/apiSpacesShares/copySpaces.feature +++ b/tests/acceptance/features/apiSpacesShares/copySpaces.feature @@ -13,7 +13,7 @@ Feature: copy file Scenario Outline: copying a file within a same project space with role manager and editor - Given the administrator has given "Alice" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "Project" with the default quota using the GraphApi And user "Alice" has created a folder "/newfolder" in space "Project" And user "Alice" has uploaded a file inside space "Project" with content "some content" to "/insideSpace.txt" @@ -32,7 +32,7 @@ Feature: copy file Scenario: copying a file within a same project space with role viewer - Given the administrator has given "Alice" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "Project" with the default quota using the GraphApi And user "Alice" has created a folder "/newfolder" in space "Project" And user "Alice" has uploaded a file inside space "Project" with content "some content" to "insideSpace.txt" @@ -46,7 +46,7 @@ Feature: copy file Scenario Outline: user copies a file from a project space with a different role to a project space with the manager role - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project1" with the default quota using the GraphApi And user "Brian" has created a space "Project2" with the default quota using the GraphApi And user "Brian" has uploaded a file inside space "Project1" with content "Project1 content" to "/project1.txt" @@ -70,7 +70,7 @@ Feature: copy file Scenario Outline: user copies a file from a project space with a different role to a project space with a viewer role - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project1" with the default quota using the GraphApi And user "Brian" has created a space "Project2" with the default quota using the GraphApi And user "Brian" has uploaded a file inside space "Project1" with content "Project1 content" to "/project1.txt" @@ -91,7 +91,7 @@ Feature: copy file Scenario Outline: user copies a file from project space with different role to personal space - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project" with the default quota using the GraphApi And user "Brian" has uploaded a file inside space "Project" with content "Project content" to "/project.txt" And user "Brian" has shared a space "Project" with settings: @@ -110,7 +110,7 @@ Feature: copy file Scenario Outline: user copies a file from project space with different role to share space with editor role - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project" with the default quota using the GraphApi And user "Brian" has created folder "/testshare" And user "Brian" has uploaded a file inside space "Project" with content "Project content" to "/project.txt" @@ -132,7 +132,7 @@ Feature: copy file Scenario Outline: user copies a file from project space with different role to Shares with viewer role - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project" with the default quota using the GraphApi And user "Brian" has created folder "/testshare" And user "Brian" has uploaded a file inside space "Project" with content "Project content" to "/project.txt" @@ -153,7 +153,7 @@ Feature: copy file Scenario Outline: user copies a file from personal space to project space with different role - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project" with the default quota using the GraphApi And user "Brian" has shared a space "Project" with settings: | shareWith | Alice | @@ -171,7 +171,7 @@ Feature: copy file Scenario: user copies a file from personal space to project space with role viewer - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project" with the default quota using the GraphApi And user "Brian" has shared a space "Project" with settings: | shareWith | Alice | @@ -207,7 +207,7 @@ Feature: copy file Scenario Outline: user copies a file from share space with different role to personal space - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created folder "/testshare" And user "Brian" has uploaded file with content "testshare content" to "/testshare/testshare.txt" And user "Brian" has shared folder "/testshare" with user "Alice" with permissions "" @@ -224,7 +224,7 @@ Feature: copy file Scenario Outline: user copies a file from share space with different role to project space with different role - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project" with the default quota using the GraphApi And user "Brian" has shared a space "Project" with settings: | shareWith | Alice | @@ -247,7 +247,7 @@ Feature: copy file Scenario Outline: user copies a file from share space with different role to project space with role viewer - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project" with the default quota using the GraphApi And user "Brian" has shared a space "Project" with settings: | shareWith | Alice | @@ -309,7 +309,7 @@ Feature: copy file Scenario Outline: copying a folder within the same project space with different role - Given the administrator has given "Alice" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "Project" with the default quota using the GraphApi And user "Alice" has created a folder "/folder1" in space "Project" And user "Alice" has created a folder "/folder2" in space "Project" @@ -329,7 +329,7 @@ Feature: copy file Scenario Outline: user copies a folder from a project space with different role to a project space with different role - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project1" with the default quota using the GraphApi And user "Brian" has created a space "Project2" with the default quota using the GraphApi And user "Brian" has created a folder "/folder1" in space "Project1" @@ -356,7 +356,7 @@ Feature: copy file Scenario Outline: user copies a folder from project space with different role to personal space - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project" with the default quota using the GraphApi And user "Brian" has created a folder "/folder1" in space "Project" And user "Brian" has uploaded a file inside space "Project" with content "some content" to "/folder1/demo.txt" @@ -375,7 +375,7 @@ Feature: copy file Scenario Outline: Uuer copies a folder from project space with different role to share space with different role - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project" with the default quota using the GraphApi And user "Brian" has created folder "/testshare" And user "Brian" has created a folder "/folder1" in space "Project" @@ -400,7 +400,7 @@ Feature: copy file Scenario Outline: user copies a folder from personal space to project space with different role - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project" with the default quota using the GraphApi And user "Brian" has shared a space "Project" with settings: | shareWith | Alice | @@ -435,7 +435,7 @@ Feature: copy file Scenario Outline: user copies a folder from share space with different role to personal space - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created folder "/testshare" And user "Brian" has uploaded file with content "testshare content" to "/testshare/testshare.txt" And user "Brian" has shared folder "/testshare" with user "Alice" with permissions "" @@ -452,7 +452,7 @@ Feature: copy file Scenario Outline: user copies a folder from share space with different role to project space with different role - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project" with the default quota using the GraphApi And user "Brian" has shared a space "Project" with settings: | shareWith | Alice | @@ -475,7 +475,7 @@ Feature: copy file Scenario Outline: user copies a folder from share space with different role to project space with role viewer - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project" with the default quota using the GraphApi And user "Brian" has shared a space "Project" with settings: | shareWith | Alice | @@ -736,7 +736,7 @@ Feature: copy file Scenario: copying a file with an option "keep both" inside of the project space - Given the administrator has given "Alice" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "Project" with the default quota using the GraphApi And user "Alice" has created a folder "/newfolder" in space "Project" And user "Alice" has uploaded a file inside space "Project" with content "some content" to "/newfolder/insideSpace.txt" @@ -750,7 +750,7 @@ Feature: copy file @issue-4797 Scenario: copying a file with an option "replace" inside of the project space - Given the administrator has given "Alice" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "Project" with the default quota using the GraphApi And user "Alice" has created a folder "/newfolder" in space "Project" And user "Alice" has uploaded a file inside space "Project" with content "old content version 1" to "/newfolder/insideSpace.txt" @@ -769,7 +769,7 @@ Feature: copy file Scenario: copying a file from Personal to Shares with an option "keep both" - Given the administrator has given "Alice" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "Project" with the default quota using the GraphApi And user "Alice" has created a folder "/newfolder" in space "Project" And user "Alice" has uploaded a file inside space "Project" with content "some content" to "/newfolder/personal.txt" @@ -789,9 +789,9 @@ Feature: copy file | newfolder/personal.txt | | newfolder/personal (1).txt | - @issue-4797 + Scenario: copying a file from Personal to Shares with an option "replace" - Given the administrator has given "Alice" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "Project" with the default quota using the GraphApi And user "Alice" has created a folder "/newfolder" in space "Project" And user "Alice" has uploaded a file inside space "Project" with content "old content version 1" to "/newfolder/personal.txt" @@ -811,7 +811,4 @@ Feature: copy file Then the HTTP status code should be "200" And the downloaded content should be "old content version 2" And for user "Brian" the content of the file "/newfolder/personal.txt" of the space "Shares" should be "new content" - When user "Brian" downloads version of the file "/newfolder/personal.txt" with the index "2" of the space "Shares" using the WebDAV API - Then the HTTP status code should be "200" - And the downloaded content should be "old content version 1" And as "Brian" file "insideSpace.txt" should not exist in the trashbin of the space "Personal" diff --git a/tests/acceptance/features/apiSpacesShares/moveSpaces.feature b/tests/acceptance/features/apiSpacesShares/moveSpaces.feature index cc899e56a..3dfaf0d67 100644 --- a/tests/acceptance/features/apiSpacesShares/moveSpaces.feature +++ b/tests/acceptance/features/apiSpacesShares/moveSpaces.feature @@ -13,7 +13,7 @@ Feature: move (rename) file Scenario Outline: moving a file within same space project with role manager and editor - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project" with the default quota using the GraphApi And user "Brian" has created a folder "newfolder" in space "Project" And user "Brian" has uploaded a file inside space "Project" with content "some content" to "insideSpace.txt" @@ -33,7 +33,7 @@ Feature: move (rename) file Scenario: moving a file within same space project with role viewer - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project" with the default quota using the GraphApi And user "Brian" has created a folder "newfolder" in space "Project" And user "Brian" has uploaded a file inside space "Project" with content "some content" to "insideSpace.txt" @@ -49,7 +49,7 @@ Feature: move (rename) file Scenario Outline: user moves a file from a space project with different a role to a space project with different role - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project1" with the default quota using the GraphApi And user "Brian" has created a space "Project2" with the default quota using the GraphApi And user "Brian" has uploaded a file inside space "Project1" with content "Project1 content" to "project1.txt" @@ -79,7 +79,7 @@ Feature: move (rename) file Scenario Outline: user moves a file from a space project with different role to a space personal - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project" with the default quota using the GraphApi And user "Brian" has uploaded a file inside space "Project" with content "Project content" to "project.txt" And user "Brian" has shared a space "Project" with settings: @@ -99,7 +99,7 @@ Feature: move (rename) file Scenario Outline: user moves a file from space project with different role to space Shares with different role (permission) - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project" with the default quota using the GraphApi And user "Brian" has created folder "/testshare" And user "Brian" has uploaded a file inside space "Project" with content "Project content" to "project.txt" @@ -125,7 +125,7 @@ Feature: move (rename) file Scenario Outline: user moves a file from space personal to space project with different role - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project" with the default quota using the GraphApi And user "Brian" has shared a space "Project" with settings: | shareWith | Alice | @@ -181,7 +181,7 @@ Feature: move (rename) file Scenario Outline: user moves a file from space Shares with different role (permissions) to space project with different role - Given the administrator has given "Brian" the role "Space Admin" using the settings api + Given the administrator has assigned the role "Space Admin" to user "Brian" using the Graph API And user "Brian" has created a space "Project" with the default quota using the GraphApi And user "Brian" has shared a space "Project" with settings: | shareWith | Alice | @@ -318,22 +318,18 @@ Feature: move (rename) file And for user "Brian" the space "Personal" should not contain these entries: | /testshare/testsubfolder | - @issue-4797 + Scenario: overwriting a file while moving Given user "Brian" has created folder "/folder" - And user "Brian" has uploaded file with content "old content version 1" to "/folder/testfile.txt" - And user "Brian" has uploaded file with content "old content version 2" to "/folder/testfile.txt" - And user "Brian" has uploaded file with content "new data" to "/testfile.txt" + And user "Brian" has uploaded file with content "some content" to "/folder/testfile.txt" + And user "Brian" has uploaded file with content "old data version 1" to "/testfile.txt" + And user "Brian" has uploaded file with content "new data version 2" to "/testfile.txt" When user "Brian" overwrites file "/testfile.txt" from space "Personal" to "folder/testfile.txt" inside space "Personal" while moving using the WebDAV API Then the HTTP status code should be "204" - And the content of file "/folder/testfile.txt" for user "Brian" should be "new data" + And the content of file "/folder/testfile.txt" for user "Brian" should be "new data version 2" And for user "Brian" the space "Personal" should not contain these entries: | /testfile.txt | When user "Brian" downloads version of the file "/folder/testfile.txt" with the index "1" of the space "Personal" using the WebDAV API Then the HTTP status code should be "200" - And the downloaded content should be "old content version 2" - When user "Brian" downloads version of the file "/folder/testfile.txt" with the index "2" of the space "Personal" using the WebDAV API - Then the HTTP status code should be "200" - And the downloaded content should be "old content version 1" + And the downloaded content should be "old data version 1" And as "Brian" file "testfile.txt" should exist in the trashbin of the space "Personal" - diff --git a/tests/acceptance/features/apiSpacesShares/publicLinkDownload.feature b/tests/acceptance/features/apiSpacesShares/publicLinkDownload.feature index e33cd4243..c2744d5f3 100644 --- a/tests/acceptance/features/apiSpacesShares/publicLinkDownload.feature +++ b/tests/acceptance/features/apiSpacesShares/publicLinkDownload.feature @@ -10,7 +10,7 @@ Feature: Public can download folders from project space public link | Alice | | Brian | And using spaces DAV path - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "new-space" with the default quota using the GraphApi diff --git a/tests/acceptance/features/apiSpacesShares/shareSpaces.feature b/tests/acceptance/features/apiSpacesShares/shareSpaces.feature index 51a6f07fd..386754003 100644 --- a/tests/acceptance/features/apiSpacesShares/shareSpaces.feature +++ b/tests/acceptance/features/apiSpacesShares/shareSpaces.feature @@ -1,4 +1,4 @@ -@api +@api Feature: Share spaces As the owner of a space I want to be able to add members to a space, and to remove access for them @@ -13,7 +13,7 @@ Feature: Share spaces | Alice | | Brian | | Bob | - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "share space" with the default quota using the GraphApi And using spaces DAV path @@ -39,7 +39,7 @@ Feature: Share spaces | role | viewer | Then the user "Alice" should have a space called "share space" granted to user "Brian" with role "viewer" - @skipOnStable2.0 + Scenario: user can see that the group has been granted access Given group "sales" has been created When user "Alice" shares a space "share space" with settings: @@ -193,7 +193,7 @@ Feature: Share spaces | viewer | manager | | viewer | editor | - @skipOnStable2.0 + Scenario Outline: user shares a space with a group Given group "group2" has been created And the administrator has added a user "Brian" to the group "group2" using GraphApi @@ -211,7 +211,7 @@ Feature: Share spaces | editor | | viewer | - @skipOnStable2.0 + Scenario Outline: user has no access to the space if access for the group has been removed Given group "group2" has been created And the administrator has added a user "Brian" to the group "group2" using GraphApi @@ -228,7 +228,7 @@ Feature: Share spaces | editor | | viewer | - @skipOnStable2.0 + Scenario: user has no access to the space if he has been removed from the group Given group "group2" has been created And the administrator has added a user "Brian" to the group "group2" using GraphApi @@ -244,7 +244,7 @@ Feature: Share spaces And the user "Brian" should not have a space called "share space" But the user "Bob" should have a space called "share space" - @skipOnStable2.0 + Scenario: users don't have access to the space if the group has been deleted Given group "group2" has been created And the administrator has added a user "Brian" to the group "group2" using GraphApi @@ -258,7 +258,7 @@ Feature: Share spaces And the user "Bob" should not have a space called "share space" And the user "Brian" should not have a space called "share space" - @skipOnStable2.0 + Scenario: user increases permissions for one member of the group or for the entire group Given group "sales" has been created And the administrator has added a user "Brian" to the group "sales" using GraphApi @@ -275,7 +275,7 @@ Feature: Share spaces When user "Brian" uploads a file inside space "share space" with content "Test" to "test.txt" using the WebDAV API Then the HTTP status code should be "201" - @skipOnStable2.0 + Scenario: user increases permissions for the group, so the user's permissions are increased Given group "sales" has been created And the administrator has added a user "Brian" to the group "sales" using GraphApi @@ -292,7 +292,7 @@ Feature: Share spaces When user "Brian" uploads a file inside space "share space" with content "Test" to "test.txt" using the WebDAV API Then the HTTP status code should be "201" - @skipOnStable2.0 + Scenario Outline: space Admin can share a space to the user with an expiration date When user "Alice" shares a space "share space" with settings: | shareWith | Brian | @@ -323,7 +323,7 @@ Feature: Share spaces | editor | | viewer | - @skipOnStable2.0 + Scenario Outline: update the expiration date of a space in user share Given user "Alice" has shared a space "share space" with settings: | shareWith | Brian | @@ -381,7 +381,7 @@ Feature: Share spaces | editor | | viewer | - @skipOnStable2.0 + Scenario Outline: delete the expiration date of a space in group share Given group "sales" has been created And the administrator has added a user "Brian" to the group "sales" using GraphApi @@ -403,7 +403,7 @@ Feature: Share spaces | editor | | viewer | - @skipOnStable2.0 + Scenario Outline: check the end of expiration of a space in user share Given user "Alice" has shared a space "share space" with settings: | shareWith | Brian | @@ -418,7 +418,7 @@ Feature: Share spaces | editor | | viewer | - @skipOnStable2.0 + Scenario Outline: check the end of expiration of a space in group share Given group "sales" has been created And the administrator has added a user "Brian" to the group "sales" using GraphApi @@ -438,7 +438,7 @@ Feature: Share spaces Scenario Outline: user cannot share the personal space to an other user - Given the administrator has given "Brian" the role "" using the settings api + Given the administrator has assigned the role "" to user "Brian" using the Graph API And user "Brian" shares a space "Brian Murphy" with settings: | shareWith | Bob | | role | viewer | @@ -462,3 +462,21 @@ Feature: Share spaces Then the HTTP status code should be "400" And the OCS status message should be "can not add members to personal spaces" And the user "Brian" should not have a space called "Alice Hansen" + + + Scenario: last space manager cannot change his role + Given user "Alice" has shared a space "share space" with settings: + | shareWith | Brian | + | role | manager | + When user "Alice" updates the space "share space" with settings: + | shareWith | Alice | + | role | editor | + Then the HTTP status code should be "200" + And the OCS status code should be "200" + When user "Brian" updates the space "share space" with settings: + | shareWith | Brian | + | role | editor | + Then the HTTP status code should be "403" + And the OCS status code should be "403" + And the user "Alice" should have a space called "share space" granted to "Brian" with role "manager" + And the user "Alice" should have a space called "share space" granted to "Alice" with role "editor" diff --git a/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature b/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature index fa18c8e81..a8ca3f065 100644 --- a/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature +++ b/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature @@ -13,7 +13,7 @@ Feature: Share spaces via link | Alice | | Brian | And using spaces DAV path - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "share space" with the default quota using the GraphApi And user "Alice" has uploaded a file inside space "share space" with content "some content" to "test.txt" diff --git a/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpace.feature b/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpace.feature index fbf101423..101bdda2a 100644 --- a/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpace.feature +++ b/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpace.feature @@ -14,7 +14,7 @@ Feature: Share a file or folder that is inside a space | Brian | | Bob | And using spaces DAV path - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "share sub-item" with the default quota using the GraphApi And user "Alice" has created a folder "folder" in space "share sub-item" And user "Alice" has uploaded a file inside space "share sub-item" with content "some content" to "file.txt" diff --git a/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature b/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature index c4004ac47..82b8ca511 100644 --- a/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature +++ b/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature @@ -1,22 +1,22 @@ -@api +@api Feature: Share a file or folder that is inside a space via public link - As a user with manager space role - I want to be able to share the data inside the space via public link - So that an anonymous user can have access to certain resources + As a user with manager space role + I want to be able to share the data inside the space via public link + So that an anonymous user can have access to certain resources - folder permissions: - | role | permissions | - | internal | 0 | - | viewer | 1 | - | uploader | 4 | - | contributor | 5 | - | editor | 15 | + folder permissions: + | role | permissions | + | internal | 0 | + | viewer | 1 | + | uploader | 4 | + | contributor | 5 | + | editor | 15 | - file permissions: - | role | permissions | - | internal | 0 | - | viewer | 1 | - | editor | 3 | + file permissions: + | role | permissions | + | internal | 0 | + | viewer | 1 | + | editor | 3 | Note - this feature is run in CI with ACCOUNTS_HASH_DIFFICULTY set to the default for production See https://github.com/owncloud/ocis/issues/1542 and https://github.com/owncloud/ocis/pull/839 @@ -27,7 +27,7 @@ Feature: Share a file or folder that is inside a space via public link | Alice | | Brian | And using spaces DAV path - And the administrator has given "Alice" the role "Space Admin" using the settings api + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API And user "Alice" has created a space "share sub-item" with the default quota using the GraphApi And user "Alice" has created a folder "folder" in space "share sub-item" And user "Alice" has uploaded a file inside space "share sub-item" with content "some content" to "folder/file.txt" @@ -44,7 +44,7 @@ Feature: Share a file or folder that is inside a space via public link Then the HTTP status code should be "200" And the OCS status code should be "200" And the OCS status message should be "OK" - And the fields of the last response to user "Alice" should include + And the fields of the last response to user "Alice" and space "share sub-item" should include | item_type | | | mimetype | | | file_target | | @@ -53,6 +53,8 @@ Feature: Share a file or folder that is inside a space via public link | share_type | public_link | | displayname_owner | %displayname% | | name | | + | uid_file_owner | %space_id% | + | space_id | %space_id% | Examples: | entity | file_target | permissions | password | name | expireDate | item_type | mimetype | | folder | /folder | 0 | | link | | folder | httpd/unix-directory | @@ -79,7 +81,7 @@ Feature: Share a file or folder that is inside a space via public link Then the HTTP status code should be "200" And the OCS status code should be "200" And the OCS status message should be "OK" - And the fields of the last response to user "Brian" should include + And the fields of the last response to user "Brian" and space "share sub-item" should include | item_type | | | mimetype | | | file_target | | @@ -87,6 +89,8 @@ Feature: Share a file or folder that is inside a space via public link | share_type | public_link | | displayname_owner | %displayname% | | name | public link | + | uid_file_owner | %space_id% | + | space_id | %space_id% | Examples: | entity | file_target | item_type | mimetype | | folder | /folder | folder | httpd/unix-directory | @@ -115,7 +119,7 @@ Feature: Share a file or folder that is inside a space via public link | folder/file.txt | viewer | - Scenario Outline: user creates a new public link share of a file with edit permissions + Scenario Outline: user creates a new public link share of a file inside the personal space with edit permissions Given using OCS API version "" And user "Alice" has uploaded file with content "Random data" to "/file.txt" When user "Alice" creates a public link share using the sharing API with settings @@ -150,17 +154,18 @@ Feature: Share a file or folder that is inside a space via public link | path | folder/file.txt | | shareType | 3 | | permissions | 1 | - Then the fields of the last response to user "Alice" should include - | item_type | file | - | mimetype | text/plain | - | file_target | /file.txt | - | path | folder/file.txt | - | permissions | 1 | - | share_type | public_link | - | displayname_file_owner | %displayname% | - | displayname_owner | %displayname% | - | uid_file_owner | %username% | - | uid_owner | %username% | + Then the fields of the last response to user "Alice" and space "share sub-item" should include + | item_type | file | + | mimetype | text/plain | + | file_target | /file.txt | + | path | /folder/file.txt | + | permissions | 1 | + | share_type | public_link | + | displayname_file_owner | | + | displayname_owner | %displayname% | + | uid_owner | %username% | + | uid_file_owner | %space_id% | + | space_id | %space_id% | And for user "Brian" the space "share sub-item" should contain the last created public link of the file "folder/file.txt" Examples: | spaceRole | diff --git a/tests/acceptance/features/bootstrap/ArchiverContext.php b/tests/acceptance/features/bootstrap/ArchiverContext.php index e9415c12b..fbd93b3d2 100644 --- a/tests/acceptance/features/bootstrap/ArchiverContext.php +++ b/tests/acceptance/features/bootstrap/ArchiverContext.php @@ -28,6 +28,7 @@ use TestHelpers\HttpRequestHelper; use TestHelpers\SetupHelper; use wapmorgan\UnifiedArchive\UnifiedArchive; use PHPUnit\Framework\Assert; +use \Psr\Http\Message\ResponseInterface; require_once 'bootstrap.php'; @@ -118,18 +119,7 @@ class ArchiverContext implements Context { foreach ($headersTable as $row) { $headers[$row['header']] = $row ['value']; } - - $user = $this->featureContext->getActualUsername($user); - $queryString = $this->getArchiverQueryString($user, $resource, $addressType); - $this->featureContext->setResponse( - HttpRequestHelper::get( - $this->featureContext->getBaseUrl() . '/archiver?' . $queryString, - '', - $user, - $this->featureContext->getPasswordForUser($user), - $headers - ) - ); + $this->featureContext->setResponse($this->downloadArchive($user, $resource, $addressType, null, $headers)); } /** @@ -151,15 +141,36 @@ class ArchiverContext implements Context { string $owner, string $addressType ): void { + $this->featureContext->setResponse($this->downloadArchive($downloader, $resource, $addressType, $owner)); + } + + /** + * @param string $downloader + * @param string $resource + * @param string $addressType + * @param string|null $owner + * @param array|null $headers + * + * @return ResponseInterface + * @throws GuzzleException + * @throws JsonException + */ + public function downloadArchive( + string $downloader, + string $resource, + string $addressType, + ?string $owner = null, + ?array $headers = null + ): ResponseInterface { + $owner = $owner ?? $downloader; $downloader = $this->featureContext->getActualUsername($downloader); $queryString = $this->getArchiverQueryString($owner, $resource, $addressType); - $this->featureContext->setResponse( - HttpRequestHelper::get( - $this->featureContext->getBaseUrl() . '/archiver?' . $queryString, - '', - $downloader, - $this->featureContext->getPasswordForUser($downloader), - ) + return HttpRequestHelper::get( + $this->featureContext->getBaseUrl() . '/archiver?' . $queryString, + '', + $downloader, + $this->featureContext->getPasswordForUser($downloader), + $headers ); } diff --git a/tests/acceptance/features/bootstrap/FeatureContext.php b/tests/acceptance/features/bootstrap/FeatureContext.php index 254c89a60..60d8a6d49 100644 --- a/tests/acceptance/features/bootstrap/FeatureContext.php +++ b/tests/acceptance/features/bootstrap/FeatureContext.php @@ -52,6 +52,7 @@ class FeatureContext extends BehatVariablesContext { use Provisioning; use Sharing; use WebDav; + use JsonAssertions; /** * Unix timestamp seconds @@ -161,6 +162,7 @@ class FeatureContext extends BehatVariablesContext { public OCSContext $ocsContext; public AuthContext $authContext; public GraphContext $graphContext; + public SpacesContext $spacesContext; public AppConfigurationContext $appConfigurationContext; private array $initialTrustedServer; @@ -1360,7 +1362,7 @@ class FeatureContext extends BehatVariablesContext { PyStringNode $schemaString ): void { $jsonResponse = $this->getJsonDecodedResponseBodyContent(); - JsonAssertions::assertJsonDocumentMatchesSchema( + $this->assertJsonDocumentMatchesSchema( $jsonResponse->ocs->data, $this->getJSONSchema($schemaString) ); @@ -1377,7 +1379,7 @@ class FeatureContext extends BehatVariablesContext { */ public function theDataOfTheResponseShouldMatch(PyStringNode $schemaString): void { $responseBody = $this->getJsonDecodedResponseBodyContent(); - JsonAssertions::assertJsonDocumentMatchesSchema( + $this->assertJsonDocumentMatchesSchema( $responseBody, $this->getJSONSchema($schemaString) ); @@ -3231,6 +3233,12 @@ class FeatureContext extends BehatVariablesContext { $suiteName = $scope->getSuite()->getName(); $featureFileName = \basename($featureFile); + if (!OcisHelper::isTestingOnReva()) { + $this->spacesContext = new SpacesContext(); + $this->spacesContext->setUpScenario($scope); + $environment->registerContext($this->spacesContext); + } + if ($this->sendScenarioLineReferencesInXRequestId()) { $this->scenarioString = $suiteName . '/' . $featureFileName . ':' . $scenarioLine; } else { diff --git a/tests/acceptance/features/bootstrap/FilesVersionsContext.php b/tests/acceptance/features/bootstrap/FilesVersionsContext.php index 3e796e08c..b18f2591e 100644 --- a/tests/acceptance/features/bootstrap/FilesVersionsContext.php +++ b/tests/acceptance/features/bootstrap/FilesVersionsContext.php @@ -26,6 +26,7 @@ use Behat\Gherkin\Node\TableNode; use PHPUnit\Framework\Assert; use TestHelpers\HttpRequestHelper; use TestHelpers\WebDavHelper; +use Psr\Http\Message\ResponseInterface; require_once 'bootstrap.php'; @@ -55,20 +56,7 @@ class FilesVersionsContext implements Context { * @throws Exception */ public function userTriesToGetFileVersions(string $user, string $file, string $fileOwner):void { - $user = $this->featureContext->getActualUsername($user); - $fileOwner = $this->featureContext->getActualUsername($fileOwner); - $fileId = $this->featureContext->getFileIdForPath($fileOwner, $file); - Assert::assertNotNull($fileId, __METHOD__ . " fileid of file $file user $fileOwner not found (the file may not exist)"); - $response = $this->featureContext->makeDavRequest( - $user, - "PROPFIND", - $this->getVersionsPathForFileId($fileId), - null, - null, - null, - '2' - ); - $this->featureContext->setResponse($response, $user); + $this->featureContext->setResponse($this->getFileVersions($user, $file, $fileOwner)); } /** @@ -81,10 +69,28 @@ class FilesVersionsContext implements Context { * @throws Exception */ public function userGetsFileVersions(string $user, string $file):void { + $this->featureContext->setResponse($this->getFileVersions($user, $file)); + } + + /** + * @param string $user + * @param string $file + * @param string|null $fileOwner + * + * @return ResponseInterface + * @throws JsonException + * @throws GuzzleException + */ + public function getFileVersions( + string $user, + string $file, + ?string $fileOwner = null + ): ResponseInterface { $user = $this->featureContext->getActualUsername($user); - $fileId = $this->featureContext->getFileIdForPath($user, $file); - Assert::assertNotNull($fileId, __METHOD__ . " fileid of file $file user $user not found (the file may not exist)"); - $response = $this->featureContext->makeDavRequest( + $fileOwner = $fileOwner ? $this->featureContext->getActualUsername($fileOwner) : $user; + $fileId = $this->featureContext->getFileIdForPath($fileOwner, $file); + Assert::assertNotNull($fileId, __METHOD__ . " fileid of file $file user $fileOwner not found (the file may not exist)"); + return $this->featureContext->makeDavRequest( $user, "PROPFIND", $this->getVersionsPathForFileId($fileId), @@ -93,7 +99,6 @@ class FilesVersionsContext implements Context { null, '2' ); - $this->featureContext->setResponse($response, $user); } /** diff --git a/tests/acceptance/features/bootstrap/GraphContext.php b/tests/acceptance/features/bootstrap/GraphContext.php index fb58da2da..acadf6010 100644 --- a/tests/acceptance/features/bootstrap/GraphContext.php +++ b/tests/acceptance/features/bootstrap/GraphContext.php @@ -13,7 +13,6 @@ use Behat\Behat\Hook\Scope\BeforeScenarioScope; use Behat\Gherkin\Node\PyStringNode; use Behat\Gherkin\Node\TableNode; use GuzzleHttp\Exception\GuzzleException; -use Helmich\JsonAssert\JsonAssertions; use Psr\Http\Message\ResponseInterface; use TestHelpers\GraphHelper; use TestHelpers\WebDavHelper; @@ -264,33 +263,6 @@ class GraphContext implements Context { $this->featureContext->thenTheHTTPStatusCodeShouldBe(200); } - /** - * @param $requestingUser - * @param $targetUser - * - * @return void - * @throws JsonException - * @throws GuzzleException - */ - public function userHasRetrievedUserUsingTheGraphApi( - $requestingUser, - $targetUser - ): void { - $requester = $this->featureContext->getActualUsername($requestingUser); - $requesterPassword = $this->featureContext->getPasswordForUser($requestingUser); - $user = $this->featureContext->getActualUsername($targetUser); - $userId = $this->featureContext->getAttributeOfCreatedUser($user, "id"); - $response = GraphHelper::getUser( - $this->featureContext->getBaseUrl(), - $this->featureContext->getStepLineRef(), - $requester, - $requesterPassword, - $userId - ); - $this->featureContext->setResponse($response); - $this->featureContext->thenTheHTTPStatusCodeShouldBe(200); - } - /** * @param string $groupId * @param string|null $user @@ -1980,6 +1952,20 @@ class GraphContext implements Context { ); } + /** + * @Then /^the Graph API response should have no role$/ + * + * @return void + * @throws Exception + * @throws GuzzleException + */ + public function theGraphApiResponseShouldHaveNoRole(): void { + Assert::assertEmpty( + $this->featureContext->getJsonDecodedResponse($this->featureContext->getResponse())['value'], + "the user has a role, but should not" + ); + } + /** * @When user :user gets details of the group :groupName using the Graph API * @@ -2035,7 +2021,7 @@ class GraphContext implements Context { !$shouldContain && $userOrGroupFound, 'Response contains user or group "' . $userOrGroup . '" but should not have.' ); - JsonAssertions::assertJsonDocumentMatchesSchema( + $this->featureContext->assertJsonDocumentMatchesSchema( $responseBody, $this->featureContext->getJSONSchema($schemaString) ); @@ -2371,7 +2357,7 @@ class GraphContext implements Context { "Response does not contain event type '" . $eventType . "'." ); } - JsonAssertions::assertJsonDocumentMatchesSchema( + $this->featureContext->assertJsonDocumentMatchesSchema( $actualResponseToAssert, $this->featureContext->getJSONSchema($schemaString) ); @@ -2393,7 +2379,7 @@ class GraphContext implements Context { "Response does not contain key 'user'" ); } - JsonAssertions::assertJsonDocumentMatchesSchema( + $this->featureContext->assertJsonDocumentMatchesSchema( $actualResponseToAssert->user, $this->featureContext->getJSONSchema($schemaString) ); @@ -2424,8 +2410,26 @@ class GraphContext implements Context { } /** - * @When user :user unassigns the role of user :ofUser using the Graph API - * @When user :user tries to unassign the role of user :ofUser using the Graph API + * @param string $user + * + * @return ResponseInterface + * @throws GuzzleException + */ + public function getAssignedRole(string $user) { + $userId = $this->featureContext->getAttributeOfCreatedUser($user, 'id') ?? $this->featureContext->getUserIdByUserName($user); + return ( + GraphHelper::getAssignedRole( + $this->featureContext->getBAseUrl(), + $this->featureContext->getStepLineRef(), + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $userId + ) + ); + } + + /** + * @When /^user "([^"]*)" (?:unassigns|tries to unassign) the role of user "([^"]*)" using the Graph API$/ * * @param string $user * @param string $ofUser @@ -2438,15 +2442,7 @@ class GraphContext implements Context { public function theUserUnassignsTheRoleOfUserUsingTheGraphApi(string $user, string $ofUser): void { $userId = $this->featureContext->getAttributeOfCreatedUser($ofUser, 'id') ?? $ofUser; $credentials = $this->getAdminOrUserCredentials($user); - - $response = GraphHelper::getAssignedRole( - $this->featureContext->getBaseUrl(), - $this->featureContext->getStepLineRef(), - $this->featureContext->getAdminUsername(), - $this->featureContext->getAdminPassword(), - $userId - ); - $appRoleAssignmentId = $this->featureContext->getJsonDecodedResponse($response)["value"][0]["id"]; + $appRoleAssignmentId = $this->featureContext->getJsonDecodedResponse($this->getAssignedRole($ofUser))["value"][0]["id"]; $this->featureContext->setResponse( GraphHelper::unassignRole( @@ -2471,16 +2467,7 @@ class GraphContext implements Context { * @throws Exception */ public function userShouldHaveTheRoleAssigned(string $user, string $role): void { - $userId = $this->featureContext->getAttributeOfCreatedUser($user, 'id') ?? $user; - $response = GraphHelper::getAssignedRole( - $this->featureContext->getBaseUrl(), - $this->featureContext->getStepLineRef(), - $this->featureContext->getAdminUserName(), - $this->featureContext->getAdminPassword(), - $userId - ); - - $jsonDecodedResponse = $this->featureContext->getJsonDecodedResponse($response)['value'][0]; + $jsonDecodedResponse = $this->featureContext->getJsonDecodedResponse($this->getAssignedRole($user))['value'][0]; if (empty($this->appEntity)) { $this->setApplicationEntity(); } @@ -2503,16 +2490,7 @@ class GraphContext implements Context { * @throws Exception */ public function userShouldNotHaveAnyRoleAssigned(string $user): void { - $userId = $this->featureContext->getAttributeOfCreatedUser($user, 'id') ?? $user; - $response = GraphHelper::getAssignedRole( - $this->featureContext->getBaseUrl(), - $this->featureContext->getStepLineRef(), - $this->featureContext->getAdminUserName(), - $this->featureContext->getAdminPassword(), - $userId - ); - - $jsonDecodedResponse = $this->featureContext->getJsonDecodedResponse($response)['value']; + $jsonDecodedResponse = $this->featureContext->getJsonDecodedResponse($this->getAssignedRole($user))['value']; Assert::assertEmpty( $jsonDecodedResponse, __METHOD__ diff --git a/tests/acceptance/features/bootstrap/NotificationContext.php b/tests/acceptance/features/bootstrap/NotificationContext.php index 5e17e82e7..235371448 100644 --- a/tests/acceptance/features/bootstrap/NotificationContext.php +++ b/tests/acceptance/features/bootstrap/NotificationContext.php @@ -10,10 +10,10 @@ use Behat\Behat\Context\Context; use Behat\Behat\Hook\Scope\BeforeScenarioScope; use TestHelpers\OcsApiHelper; use Behat\Gherkin\Node\PyStringNode; -use Helmich\JsonAssert\JsonAssertions; use TestHelpers\EmailHelper; use PHPUnit\Framework\Assert; use TestHelpers\GraphHelper; +use Behat\Gherkin\Node\TableNode; require_once 'bootstrap.php'; @@ -81,7 +81,7 @@ class NotificationContext implements Context { } /** - * @Then /^user "([^"]*)" lists all notifications$/ + * @When /^user "([^"]*)" lists all notifications$/ * * @param string $user * @@ -115,6 +115,28 @@ class NotificationContext implements Context { Assert::assertNull($notifications, "response should not contain any notification"); } + /** + * @Then /^user "([^"]*)" should have "([^"]*)" notifications$/ + * + * @param string $user + * @param int $numberOfNotification + * + * @return void + * @throws Exception + */ + public function userShouldHaveNotifications(string $user, int $numberOfNotification): void { + if (!isset($this->featureContext->getJsonDecodedResponseBodyContent()->ocs->data)) { + throw new Exception("Notification is empty"); + } + $responseBody = $this->featureContext->getJsonDecodedResponseBodyContent()->ocs->data; + $actualNumber = \count($responseBody); + Assert::assertEquals( + $numberOfNotification, + $actualNumber, + "Expected number of notification was '$numberOfNotification', but got '$actualNumber'" + ); + } + /** * @Then /^the JSON response should contain a notification message with the subject "([^"]*)" and the message-details should match$/ * @@ -128,6 +150,30 @@ class NotificationContext implements Context { string $subject, PyStringNode $schemaString ): void { + $responseBody = $this->filterResponseAccordingToNotificationSubject($subject); + // substitute the value here + $schemaString = $schemaString->getRaw(); + $schemaString = $this->featureContext->substituteInLineCodes( + $schemaString, + $this->featureContext->getCurrentUser(), + [], + [], + null, + $this->getUserRecipient(), + ); + $this->featureContext->assertJsonDocumentMatchesSchema( + $responseBody, + $this->featureContext->getJSONSchema($schemaString) + ); + } + + /** + * @param string $subject + * + * @return object + */ + public function filterResponseAccordingToNotificationSubject(string $subject): object { + $responseBody = null; if (isset($this->featureContext->getJsonDecodedResponseBodyContent()->ocs->data)) { $responseBody = $this->featureContext->getJsonDecodedResponseBodyContent()->ocs->data; foreach ($responseBody as $value) { @@ -141,20 +187,27 @@ class NotificationContext implements Context { } else { $responseBody = $this->featureContext->getJsonDecodedResponseBodyContent(); } + return $responseBody; + } - // substitute the value here - $schemaString = $schemaString->getRaw(); - $schemaString = $this->featureContext->substituteInLineCodes( - $schemaString, - $this->featureContext->getCurrentUser(), - [], - [], - null, - $this->getUserRecipient(), - ); - JsonAssertions::assertJsonDocumentMatchesSchema( - $responseBody, - $this->featureContext->getJSONSchema($schemaString) + /** + * @Then user :user should get a notification with subject :subject and message: + * + * @param string $user + * @param string $subject + * @param TableNode $table + * + * @return void + */ + public function userShouldGetANotificationWithMessage(string $user, string $subject, TableNode $table):void { + $this->userListAllNotifications($user); + $this->featureContext->theHTTPStatusCodeShouldBe(200); + $actualMessage = str_replace(["\r", "\n"], " ", $this->filterResponseAccordingToNotificationSubject($subject)->message); + $expectedMessage = $table->getColumnsHash()[0]['message']; + Assert::assertSame( + $expectedMessage, + $actualMessage, + __METHOD__ . "expected message to be '$expectedMessage' but found'$actualMessage'" ); } diff --git a/tests/acceptance/features/bootstrap/OcisConfigContext.php b/tests/acceptance/features/bootstrap/OcisConfigContext.php index ad48236bc..b1ebcc5f2 100644 --- a/tests/acceptance/features/bootstrap/OcisConfigContext.php +++ b/tests/acceptance/features/bootstrap/OcisConfigContext.php @@ -21,6 +21,7 @@ */ use Behat\Behat\Context\Context; +use GuzzleHttp\Exception\GuzzleException; use TestHelpers\OcisConfigHelper; use PHPUnit\Framework\Assert; @@ -52,23 +53,24 @@ class OcisConfigContext implements Context { } /** - * @Given cors allowed origins has been set to :allowedOrigins + * @Given the config :configVariable has been set to :configValue * - * @param string $allowedOrigins + * @param string $configVariable + * @param string $configValue * * @return void * @throws GuzzleException */ - public function corsAllowedOriginsHasBeenSet(string $allowedOrigins): void { + public function theConfigHasBeenSetTo(string $configVariable, string $configValue): void { $envs = [ - "OCIS_CORS_ALLOW_ORIGINS" => $allowedOrigins, + $configVariable => $configValue, ]; $response = OcisConfigHelper::reConfigureOcis($envs); Assert::assertEquals( 200, $response->getStatusCode(), - "Failed to set OCIS_CORS_ALLOW_ORIGINS=" . $allowedOrigins + "Failed to set config $configVariable=$configValue" ); } diff --git a/tests/acceptance/features/bootstrap/Provisioning.php b/tests/acceptance/features/bootstrap/Provisioning.php index 7845bfc26..41d121df2 100644 --- a/tests/acceptance/features/bootstrap/Provisioning.php +++ b/tests/acceptance/features/bootstrap/Provisioning.php @@ -1458,96 +1458,6 @@ trait Provisioning { $this->manuallyAddSkeletonFilesForUser($user, $password); } - /** - * @When /^the groupadmin "([^"]*)" sends a user creation request for user "([^"]*)" password "([^"]*)" group "([^"]*)" using the provisioning API$/ - * - * @param string $groupadmin - * @param string $userToCreate - * @param string $password - * @param string $group - * - * @return void - * @throws Exception - */ - public function theGroupAdminCreatesUserPasswordGroupUsingTheProvisioningApi( - string $groupadmin, - string $userToCreate, - string $password, - string $group - ):void { - $userToCreate = $this->getActualUsername($userToCreate); - $password = $this->getActualPassword($password); - $email = $userToCreate . '@owncloud.com'; - $bodyTable = new TableNode( - [ - ['userid', $userToCreate], - ['password', $userToCreate], - ['username', $userToCreate], - ['email', $email], - ['groups[]', $group], - ] - ); - $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( - $groupadmin, - "POST", - "/cloud/users", - $bodyTable - ); - $this->addUserToCreatedUsersList( - $userToCreate, - $password, - null, - $email, - null, - $this->theHTTPStatusCodeWasSuccess() - ); - $this->manuallyAddSkeletonFilesForUser($userToCreate, $password); - } - - /** - * @When /^the groupadmin "([^"]*)" tries to create new user "([^"]*)" password "([^"]*)" in other group "([^"]*)" using the provisioning API$/ - * - * @param string $groupadmin - * @param string $userToCreate - * @param string|null $password - * @param string $group - * - * @return void - */ - public function theGroupAdminCreatesUserPasswordInOtherGroupUsingTheProvisioningApi( - string $groupadmin, - string $userToCreate, - ?string $password, - string $group - ):void { - $userToCreate = $this->getActualUsername($userToCreate); - $password = $this->getActualPassword($password); - $email = $userToCreate . '@owncloud.com'; - $bodyTable = new TableNode( - [ - ['userid', $userToCreate], - ['password', $userToCreate], - ['username', $userToCreate], - ['email', $email], - ['groups[]', $group], - ] - ); - $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( - $groupadmin, - "POST", - "/cloud/users", - $bodyTable - ); - $this->addUserToCreatedUsersList( - $userToCreate, - $password, - null, - $email, - null, - $this->theHTTPStatusCodeWasSuccess() - ); - } - /** * @param string $username * @param string|null $password @@ -2420,25 +2330,6 @@ trait Provisioning { ); } - /** - * @Given /^the administrator has retrieved the information of user "([^"]*)"$/ - * - * @param string $user - * - * @return void - * @throws JsonException - */ - public function adminHasRetrievedTheInformationOfUserUsingTheProvisioningApi( - string $user - ):void { - if (OcisHelper::isTestingWithGraphApi()) { - $this->graphContext->adminHasRetrievedUserUsingTheGraphApi($user); - } else { - $this->retrieveUserInformationAsAdminUsingProvisioningApi($user); - $this->theHTTPStatusCodeShouldBeSuccess(); - } - } - /** * @param string $requestingUser * @param string $targetUser @@ -2480,33 +2371,6 @@ trait Provisioning { ); } - /** - * @Given /^user "([^"]*)" has retrieved the information of user "([^"]*)"$/ - * - * @param string $requestingUser - * @param string $targetUser - * - * @return void - * @throws JsonException - */ - public function userHasRetrievedTheInformationOfUserUsingTheProvisioningApi( - string $requestingUser, - string $targetUser - ):void { - if (OcisHelper::isTestingWithGraphApi()) { - $this->graphContext->userHasRetrievedUserUsingTheGraphApi( - $requestingUser, - $targetUser - ); - } else { - $this->userRetrieveUserInformationUsingProvisioningApi( - $requestingUser, - $targetUser - ); - $this->theHTTPStatusCodeShouldBeSuccess(); - } - } - /** * @Then /^user "([^"]*)" should exist$/ * diff --git a/tests/acceptance/features/bootstrap/SettingsContext.php b/tests/acceptance/features/bootstrap/SettingsContext.php index 4801eb09b..61081029e 100644 --- a/tests/acceptance/features/bootstrap/SettingsContext.php +++ b/tests/acceptance/features/bootstrap/SettingsContext.php @@ -13,6 +13,7 @@ use Behat\Behat\Context\Context; use GuzzleHttp\Exception\GuzzleException; use Behat\Behat\Hook\Scope\BeforeScenarioScope; use PHPUnit\Framework\Assert; +use Psr\Http\Message\ResponseInterface; require_once 'bootstrap.php'; @@ -89,13 +90,10 @@ class SettingsContext implements Context { * @throws GuzzleException * @throws Exception */ - public function sendRequestAssignmentsList(string $user, string $userId): void { + public function sendRequestAssignmentsList(string $user, string $userId): ResponseInterface { $fullUrl = $this->baseUrl . $this->settingsUrl . "assignments-list"; $body = json_encode(["account_uuid" => $userId], JSON_THROW_ON_ERROR); - - $this->featureContext->setResponse( - $this->spacesContext->sendPostRequestToUrl($fullUrl, $user, $this->featureContext->getPasswordForUser($user), $body) - ); + return $this->spacesContext->sendPostRequestToUrl($fullUrl, $user, $this->featureContext->getPasswordForUser($user), $body); } /** @@ -222,7 +220,7 @@ class SettingsContext implements Context { */ public function userGetAssignmentsList(string $user): void { $userId = $this->featureContext->getAttributeOfCreatedUser($user, 'id'); - $this->sendRequestAssignmentsList($user, $userId); + $this->featureContext->setResponse($this->sendRequestAssignmentsList($user, $userId)); } /** @@ -238,10 +236,14 @@ class SettingsContext implements Context { */ public function userShouldHaveRole(string $user, string $role): void { $userId = $this->featureContext->getAttributeOfCreatedUser($user, 'id'); - $this->sendRequestAssignmentsList($this->featureContext->getAdminUserName(), $userId); - $rawBody = $this->featureContext->getResponse()->getBody()->getContents(); - $assignmentRoleId = \json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR)["assignments"][0]["roleId"]; - Assert::assertEquals($this->userGetRoleIdByRoleName($this->featureContext->getAdminUserName(), $role), $assignmentRoleId, "user $user has no role $role"); + $response = $this->sendRequestAssignmentsList($this->featureContext->getAdminUserName(), $userId); + $assignmentResponse = $this->featureContext->getJsonDecodedResponseBodyContent($response); + if (isset($assignmentResponse->assignments[0]->roleId)) { + $actualRoleId = $assignmentResponse->assignments[0]->roleId; + Assert::assertEquals($this->userGetRoleIdByRoleName($this->featureContext->getAdminUserName(), $role), $actualRoleId, "user $user has no role $role"); + } else { + Assert::fail("Response should contain user role but not found.\n" . json_encode($assignmentResponse)); + } } /** @@ -330,7 +332,7 @@ class SettingsContext implements Context { public function getSettingLanguageValue(string $user): string { $this->sendRequestGetSettingsValuesList($user); $body = json_decode((string)$this->featureContext->getResponse()->getBody(), true, 512, JSON_THROW_ON_ERROR); - + // if no language is set, the request body is empty return English as the default language if (empty($body)) { return "en"; diff --git a/tests/acceptance/features/bootstrap/Sharing.php b/tests/acceptance/features/bootstrap/Sharing.php index 013099414..15eecacaa 100644 --- a/tests/acceptance/features/bootstrap/Sharing.php +++ b/tests/acceptance/features/bootstrap/Sharing.php @@ -26,6 +26,7 @@ use Behat\Gherkin\Node\PyStringNode; use Behat\Gherkin\Node\TableNode; use Psr\Http\Message\ResponseInterface; use PHPUnit\Framework\Assert; +use PHPUnit\Framework\ExpectationFailedException; use TestHelpers\OcsApiHelper; use TestHelpers\SharingHelper; use TestHelpers\HttpRequestHelper; @@ -2354,20 +2355,24 @@ trait Sharing { } /** - * @When user :user gets all shares shared by him/her using the sharing API - * * @param string $user + * @param string|null $endpointPath * - * @return void + * @return ResponseInterface + * @throws JsonException + * @throws GuzzleException */ - public function userGetsAllSharesSharedByHimUsingTheSharingApi(string $user):void { + public function getAllShares( + string $user, + ?string $endpointPath = null + ): ResponseInterface { $user = $this->getActualUsername($user); - $this->response = OcsApiHelper::sendRequest( + return OcsApiHelper::sendRequest( $this->getBaseUrl(), $user, $this->getPasswordForUser($user), "GET", - $this->getSharesEndpointPath(), + $this->getSharesEndpointPath($endpointPath), $this->getStepLineRef(), [], $this->ocsApiVersion @@ -2375,12 +2380,23 @@ trait Sharing { } /** - * @When the administrator gets all shares shared by him/her using the sharing API + * @When /^user "([^"]*)" gets all shares shared by (?:him|her) using the sharing API$/ + * + * @param string $user + * + * @return void + */ + public function userGetsAllSharesSharedByHimUsingTheSharingApi(string $user):void { + $this->setResponse($this->getAllshares($user)); + } + + /** + * @When /^the administrator gets all shares shared by (?:him|her) using the sharing API$/ * * @return void */ public function theAdministratorGetsAllSharesSharedByHimUsingTheSharingApi():void { - $this->userGetsAllSharesSharedByHimUsingTheSharingApi($this->getAdminUsername()); + $this->setResponse($this->getAllShares($this->getAdminUsername())); } /** @@ -2392,7 +2408,6 @@ trait Sharing { * @return void */ public function userGetsFilteredSharesSharedByHimUsingTheSharingApi(string $user, string $shareType):void { - $user = $this->getActualUsername($user); if ($shareType === 'public link') { $shareType = 'public_link'; } @@ -2401,16 +2416,7 @@ trait Sharing { } else { $rawShareTypes = SharingHelper::SHARE_TYPES[$shareType]; } - $this->response = OcsApiHelper::sendRequest( - $this->getBaseUrl(), - $user, - $this->getPasswordForUser($user), - "GET", - $this->getSharesEndpointPath("?share_types=" . $rawShareTypes), - $this->getStepLineRef(), - [], - $this->ocsApiVersion - ); + $this->setResponse($this->getAllShares($user, "?share_types=" . $rawShareTypes)); } /** @@ -2422,17 +2428,7 @@ trait Sharing { * @return void */ public function userGetsAllTheSharesFromTheFileUsingTheSharingApi(string $user, string $path):void { - $user = $this->getActualUsername($user); - $this->response = OcsApiHelper::sendRequest( - $this->getBaseUrl(), - $user, - $this->getPasswordForUser($user), - "GET", - $this->getSharesEndpointPath("?path=$path"), - $this->getStepLineRef(), - [], - $this->ocsApiVersion - ); + $this->setResponse($this->getAllShares($user, "?path=$path")); } /** @@ -2447,17 +2443,7 @@ trait Sharing { string $user, string $path ):void { - $userActual = $this->getActualUsername($user); - $this->response = OcsApiHelper::sendRequest( - $this->getBaseUrl(), - $userActual, - $this->getPasswordForUser($user), - "GET", - $this->getSharesEndpointPath("?reshares=true&path=$path"), - $this->getStepLineRef(), - [], - $this->ocsApiVersion - ); + $this->setResponse($this->getAllShares($user, "?reshares=true&path=$path")); } /** @@ -2469,17 +2455,7 @@ trait Sharing { * @return void */ public function userGetsAllTheSharesInsideTheFolderUsingTheSharingApi(string $user, string $path):void { - $user = $this->getActualUsername($user); - $this->response = OcsApiHelper::sendRequest( - $this->getBaseUrl(), - $user, - $this->getPasswordForUser($user), - "GET", - $this->getSharesEndpointPath("?path=$path&subfiles=true"), - $this->getStepLineRef(), - [], - $this->ocsApiVersion - ); + $this->setResponse($this->getAllShares($user, "?path=$path&subfiles=true")); } /** @@ -2776,6 +2752,69 @@ trait Sharing { } } + /** + * @Then the fields of the last response to user :user and space :space should include + * + * @param string $user + * @param string $space + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function checkFieldsOfSpaceSharingResponse(string $user, string $space, ?TableNode $body):void { + $this->verifyTableNodeColumnsCount($body, 2); + + $bodyRows = $body->getRowsHash(); + $userRelatedFieldNames = [ + "owner", + "user", + "uid_owner", + "uid_file_owner", + "share_with", + "displayname_file_owner", + "displayname_owner", + "additional_info_owner", + "additional_info_file_owner", + "space_id" + ]; + + $this->setLastPublicShareData($this->getResponseXml(null, __METHOD__)); + foreach ($bodyRows as $field => $value) { + if (\in_array($field, $userRelatedFieldNames)) { + $value = $this->substituteInLineCodes( + $value, + $user, + [], + [ + [ + "code" => "%space_id%", + "function" => + [$this->spacesContext, "getSpaceIdByName"], + "parameter" => [$user, $space] + ], + ], + null, + null + ); + if ($field === "uid_file_owner") { + $value = (explode("$", $value))[1]; + } + if ($field === "space_id") { + $exploadedSpaceId = explode("$", $value); + $value = $exploadedSpaceId[0] . "$" . $exploadedSpaceId[1] . "!" . $exploadedSpaceId[1]; + } + } + $value = $this->getActualUsername($value); + $value = $this->replaceValuesFromTable($field, $value); + + Assert::assertTrue( + $this->isFieldInResponse($field, $value, true, $this->getLastPublicShareData()->data[0]), + "$field doesn't have value '$value'" + ); + } + } + /** * @Then /^the fields of the last response (?:to|about) user "([^"]*)" sharing with (?:user|group) "([^"]*)" should include$/ * @@ -3449,7 +3488,7 @@ trait Sharing { Assert::assertEquals($row['path'], $share['path']); $found = true; break; - } catch (PHPUnit\Framework\ExpectationFailedException $e) { + } catch (ExpectationFailedException $e) { } } if (!$found) { @@ -3490,6 +3529,25 @@ trait Sharing { * @throws JsonException */ public function userHasUnsharedResourceSharedTo(string $sharer, string $path, string $sharee): void { + $this->userUnsharesResourceSharedTo($sharer, $path, $sharee); + $this->ocsContext->assertOCSResponseIndicatesSuccess( + 'The ocs share response does not indicate success.', + ); + $this->emptyLastHTTPStatusCodesArray(); + $this->emptyLastOCSStatusCodesArray(); + } + + /** + * @When /^user "([^"]*)" unshares (?:folder|file|entity) "([^"]*)" shared to "([^"]*)"$/ + * + * @param string $sharer + * @param string $path + * @param string $sharee + * + * @return void + * @throws JsonException + */ + public function userUnsharesResourceSharedTo(string $sharer, string $path, string $sharee): void { $sharer = $this->getActualUsername($sharer); $sharee = $this->getActualUsername($sharee); @@ -3511,12 +3569,6 @@ trait Sharing { 'DELETE', '/apps/files_sharing/api/v' . $this->sharingApiVersion . '/shares/' . $shareId ); - - $this->ocsContext->assertOCSResponseIndicatesSuccess( - 'The ocs share response does not indicate success.', - ); - $this->emptyLastHTTPStatusCodesArray(); - $this->emptyLastOCSStatusCodesArray(); } /** diff --git a/tests/acceptance/features/bootstrap/SpacesContext.php b/tests/acceptance/features/bootstrap/SpacesContext.php index 4608d43f0..732dda646 100644 --- a/tests/acceptance/features/bootstrap/SpacesContext.php +++ b/tests/acceptance/features/bootstrap/SpacesContext.php @@ -27,7 +27,6 @@ use Behat\Behat\Hook\Scope\BeforeScenarioScope; use Behat\Gherkin\Node\PyStringNode; use Behat\Gherkin\Node\TableNode; use GuzzleHttp\Exception\GuzzleException; -use Helmich\JsonAssert\JsonAssertions; use Psr\Http\Message\ResponseInterface; use TestHelpers\HttpRequestHelper; use TestHelpers\WebDavHelper; @@ -205,6 +204,20 @@ class SpacesContext implements Context { return $spaces[$spaceName]; } + /** + * The method finds available spaces to the user and returns the spaceId by spaceName + * + * @param string $user + * @param string $spaceName + * + * @return string + * @throws GuzzleException + */ + public function getSpaceIdByName(string $user, string $spaceName): string { + $space = $this->getSpaceByName($user, $spaceName); + return $space["id"]; + } + /** * This method sets space id by Space Name * This is currently used to set space id from ocis in core so that we can reuse available resource (code) and avoid duplication @@ -379,14 +392,20 @@ class SpacesContext implements Context { public function setUpScenario(BeforeScenarioScope $scope): void { // Get the environment $environment = $scope->getEnvironment(); + // register new context + $this->trashbinContext = new TrashbinContext(); + $this->webDavPropertiesContext = new WebDavPropertiesContext(); + $this->favoritesContext = new FavoritesContext(); + $this->checksumContext = new ChecksumContext(); + $this->filesVersionsContext = new FilesVersionsContext(); + $environment->registerContext($this->trashbinContext); + $environment->registerContext($this->webDavPropertiesContext); + $environment->registerContext($this->favoritesContext); + $environment->registerContext($this->checksumContext); + $environment->registerContext($this->filesVersionsContext); // Get all the contexts you need in this context $this->featureContext = $environment->getContext('FeatureContext'); $this->ocsContext = $environment->getContext('OCSContext'); - $this->trashbinContext = $environment->getContext('TrashbinContext'); - $this->webDavPropertiesContext = $environment->getContext('WebDavPropertiesContext'); - $this->favoritesContext = $environment->getContext('FavoritesContext'); - $this->checksumContext = $environment->getContext('ChecksumContext'); - $this->filesVersionsContext = $environment->getContext('FilesVersionsContext'); // Run the BeforeScenario function in OCSContext to set it up correctly $this->ocsContext->before($scope); $this->baseUrl = \trim($this->featureContext->getBaseUrl(), "/"); @@ -572,43 +591,13 @@ class SpacesContext implements Context { } /** + * @When /^user "([^"]*)" (?:creates|tries to create) a space "([^"]*)" of type "([^"]*)" with quota "([^"]*)" using the Graph API$/ * @When /^user "([^"]*)" (?:creates|tries to create) a space "([^"]*)" of type "([^"]*)" with the default quota using the Graph API$/ * * @param string $user * @param string $spaceName * @param string $spaceType - * - * @return void - * - * @throws GuzzleException - * @throws Exception - */ - public function theUserCreatesASpaceUsingTheGraphApi( - string $user, - string $spaceName, - string $spaceType - ): void { - $space = ["Name" => $spaceName, "driveType" => $spaceType]; - $body = json_encode($space, JSON_THROW_ON_ERROR); - $this->featureContext->setResponse( - GraphHelper::createSpace( - $this->featureContext->getBaseUrl(), - $user, - $this->featureContext->getPasswordForUser($user), - $body - ) - ); - - $this->setSpaceCreator($spaceName, $user); - } - - /** - * @When /^user "([^"]*)" creates a space "([^"]*)" of type "([^"]*)" with quota "([^"]*)" using the Graph API$/ - * - * @param string $user - * @param string $spaceName - * @param string $spaceType - * @param int $quota + * @param int|null $quota * * @return void * @@ -619,7 +608,7 @@ class SpacesContext implements Context { string $user, string $spaceName, string $spaceType, - int $quota + ?int $quota = null ): void { $space = ["Name" => $spaceName, "driveType" => $spaceType, "quota" => ["total" => $quota]]; $body = json_encode($space); @@ -742,36 +731,6 @@ class SpacesContext implements Context { WebDavHelper::$SPACE_ID_FROM_OCIS = ''; } - /** - * @Then /^the space "([^"]*)" should (not|)\s?contain these (?:files|entries):$/ - * - * @param string $spaceName - * @param string $shouldOrNot (not|) - * @param TableNode $expectedFiles - * - * @return void - * - * @throws Exception|GuzzleException - */ - public function theSpaceShouldContainEntries( - string $spaceName, - string $shouldOrNot, - TableNode $expectedFiles - ): void { - $spaceCreator = $this->getSpaceCreator($spaceName); - $space = $this->getSpaceByName($spaceCreator, $spaceName); - $this->theUserListsTheContentOfAPersonalSpaceRootUsingTheWebDAvApi( - $spaceCreator, - $spaceName - ); - WebDavHelper::$SPACE_ID_FROM_OCIS = $space['id']; - $this->featureContext->propfindResultShouldContainEntries( - $shouldOrNot, - $expectedFiles, - ); - WebDavHelper::$SPACE_ID_FROM_OCIS = ''; - } - /** * @Then /^for user "([^"]*)" the space "([^"]*)" should (not|)\s?contain these (?:files|entries):$/ * @@ -921,7 +880,7 @@ class SpacesContext implements Context { $userName, ); - JsonAssertions::assertJsonDocumentMatchesSchema( + $this->featureContext->assertJsonDocumentMatchesSchema( $responseBody, $this->featureContext->getJSONSchema($schemaString) ); @@ -1229,6 +1188,28 @@ class SpacesContext implements Context { $this->featureContext->uploadFileWithContent($user, $content, $destination); } + /** + * @When /^user "([^"]*)" uploads a file "([^"]*)" to "([^"]*)" in space "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $source + * @param string $destination + * @param string $spaceName + * + * @return void + * @throws GuzzleException + * @throws Exception + */ + public function theUserUploadsALocalFileToSpace( + string $user, + string $source, + string $destination, + string $spaceName + ): void { + $this->setSpaceIDByName($user, $spaceName); + $this->featureContext->userUploadsAFileTo($user, $source, $destination); + } + /** * @When /^user "([^"]*)" uploads a file inside space "([^"]*)" owned by the user "([^"]*)" with content "([^"]*)" to "([^"]*)" using the WebDAV API$/ * @@ -1253,6 +1234,41 @@ class SpacesContext implements Context { $this->featureContext->uploadFileWithContent($user, $content, $destination); } + /** + * @param string $user + * @param string $spaceName + * @param array $bodyData + * @param string $owner + * + * @return ResponseInterface + * @throws GuzzleException + * @throws JsonException + */ + public function updateSpace( + string $user, + string $spaceName, + array $bodyData, + string $owner = '' + ): ResponseInterface { + if ($spaceName === "non-existing") { + // check sending invalid data + $spaceId = "39c49dd3-1f24-4687-97d1-42df43f71713"; + } else { + $space = $this->getSpaceByName(($owner !== "") ? $owner : $user, $spaceName); + $spaceId = $space["id"]; + } + + $body = json_encode($bodyData, JSON_THROW_ON_ERROR); + + return GraphHelper::updateSpace( + $this->featureContext->getBaseUrl(), + $user, + $this->featureContext->getPasswordForUser($user), + $body, + $spaceId + ); + } + /** * @When /^user "([^"]*)" (?:changes|tries to change) the name of the "([^"]*)" space to "([^"]*)"$/ * @When /^user "([^"]*)" (?:changes|tries to change) the name of the "([^"]*)" space to "([^"]*)" owned by user "([^"]*)"$/ @@ -1272,26 +1288,8 @@ class SpacesContext implements Context { string $newName, string $owner = '' ): void { - if ($spaceName === "non-existing") { - // check sending invalid data - $spaceId = "39c49dd3-1f24-4687-97d1-42df43f71713"; - } else { - $space = $this->getSpaceByName(($owner !== "") ? $owner : $user, $spaceName); - $spaceId = $space["id"]; - } - $bodyData = ["Name" => $newName]; - $body = json_encode($bodyData, JSON_THROW_ON_ERROR); - - $this->featureContext->setResponse( - GraphHelper::updateSpace( - $this->featureContext->getBaseUrl(), - $user, - $this->featureContext->getPasswordForUser($user), - $body, - $spaceId - ) - ); + $this->featureContext->setResponse($this->updateSpace($user, $spaceName, $bodyData, $owner)); } /** @@ -1313,26 +1311,8 @@ class SpacesContext implements Context { string $newDescription, string $owner = '' ): void { - if ($spaceName === "non-existing") { - // check sending invalid data - $spaceId = "39c49dd3-1f24-4687-97d1-42df43f71713"; - } else { - $space = $this->getSpaceByName(($owner !== "") ? $owner : $user, $spaceName); - $spaceId = $space["id"]; - } - $bodyData = ["description" => $newDescription]; - $body = json_encode($bodyData, JSON_THROW_ON_ERROR); - - $this->featureContext->setResponse( - GraphHelper::updateSpace( - $this->featureContext->getBaseUrl(), - $user, - $this->featureContext->getPasswordForUser($user), - $body, - $spaceId - ) - ); + $this->featureContext->setResponse($this->updateSpace($user, $spaceName, $bodyData, $owner)); } /** @@ -1377,26 +1357,8 @@ class SpacesContext implements Context { int $newQuota, string $owner = '' ): void { - if ($spaceName === "non-existing") { - // check sending invalid data - $spaceId = "39c49dd3-1f24-4687-97d1-42df43f71713"; - } else { - $space = $this->getSpaceByName(($owner !== "") ? $owner : $user, $spaceName); - $spaceId = $space["id"]; - } - $bodyData = ["quota" => ["total" => $newQuota]]; - $body = json_encode($bodyData, JSON_THROW_ON_ERROR); - - $this->featureContext->setResponse( - GraphHelper::updateSpace( - $this->featureContext->getBaseUrl(), - $user, - $this->featureContext->getPasswordForUser($user), - $body, - $spaceId - ) - ); + $this->featureContext->setResponse($this->updateSpace($user, $spaceName, $bodyData, $owner)); } /** @@ -1495,8 +1457,8 @@ class SpacesContext implements Context { * * @param string $user * @param string $spaceName - * @param string $spaceType - * @param int $quota + * @param string|null $spaceType + * @param int|null $quota * * @return void * @throws GuzzleException @@ -1508,15 +1470,8 @@ class SpacesContext implements Context { int $quota ): void { $space = ["Name" => $spaceName, "driveType" => $spaceType, "quota" => ["total" => $quota]]; - $body = json_encode($space); - $this->featureContext->setResponse( - GraphHelper::createSpace( - $this->featureContext->getBaseUrl(), - $user, - $this->featureContext->getPasswordForUser($user), - $body - ) - ); + $this->featureContext->setResponse($this->createSpace($user, $space)); + $this->setSpaceCreator($spaceName, $user); $this->featureContext->theHTTPStatusCodeShouldBe( 201, "Expected response status code should be 201 (Created)" @@ -1539,15 +1494,7 @@ class SpacesContext implements Context { string $spaceName ): void { $space = ["Name" => $spaceName]; - $body = json_encode($space, JSON_THROW_ON_ERROR); - $this->featureContext->setResponse( - GraphHelper::createSpace( - $this->featureContext->getBaseUrl(), - $user, - $this->featureContext->getPasswordForUser($user), - $body - ) - ); + $this->featureContext->setResponse($this->createSpace($user, $space)); $this->setSpaceCreator($spaceName, $user); $this->featureContext->theHTTPStatusCodeShouldBe( 201, @@ -1555,6 +1502,27 @@ class SpacesContext implements Context { ); } + /** + * @param string $user + * @param string $space + * + * @return ResponseInterface + * @throws GuzzleException + * @throws JsonException + */ + public function createSpace( + string $user, + array $space + ): ResponseInterface { + $body = json_encode($space, JSON_THROW_ON_ERROR); + return GraphHelper::createSpace( + $this->featureContext->getBaseUrl(), + $user, + $this->featureContext->getPasswordForUser($user), + $body + ); + } + /** * @When /^user "([^"]*)" copies (?:file|folder) "([^"]*)" to "([^"]*)" inside space "([^"]*)" using the WebDAV API$/ * @@ -2133,6 +2101,28 @@ class SpacesContext implements Context { $this->ocsContext->theOCSStatusCodeShouldBe($expectedOCSStatus, "Expected OCS response status code $expectedOCSStatus"); } + /** + * @Given user :user has unshared a space :spaceName shared with :recipient + * + * @param string $user + * @param string $spaceName + * @param string $recipient + * + * @return void + * @throws GuzzleException + */ + public function userHasUnsharedASpaceSharedWith( + string $user, + string $spaceName, + string $recipient + ): void { + $this->sendUnshareSpaceRequest($user, $spaceName, $recipient); + $this->featureContext->theHTTPStatusCodeShouldBe( + 200, + "Expected response status code should be 200" + ); + } + /** * @When /^user "([^"]*)" unshares a space "([^"]*)" to (?:user|group) "([^"]*)"$/ * @@ -2188,6 +2178,26 @@ class SpacesContext implements Context { ); } + /** + * @Given /^user "([^"]*)" has deleted a space "([^"]*)"$/ + * + * @param string $user + * @param string $spaceName + * + * @return void + * @throws GuzzleException + */ + public function userHasDeletedASpaceOwnedByUser( + string $user, + string $spaceName + ):void { + $this->sendDeleteSpaceRequest($user, $spaceName); + $this->featureContext->theHTTPStatusCodeShouldBe( + 204, + "Expected response status code should be 200" + ); + } + /** * @When /^user "([^"]*)" disables a space "([^"]*)"$/ * @When /^user "([^"]*)" (?:disables|tries to disable) a space "([^"]*)" owned by user "([^"]*)"$/ @@ -2685,42 +2695,15 @@ class SpacesContext implements Context { } /** - * @Then /^these etags should have changed$/ + * @Then /^these etags (should|should not) have changed$/ * + * @param string $action * @param TableNode $table * * @return void * @throws GuzzleException */ - public function theseEtagsShouldHaveChanged(TableNode $table): void { - $this->featureContext->verifyTableNodeColumns($table, ["user", "path", "space"]); - $this->featureContext->verifyTableNodeColumnsCount($table, 3); - $unchangedEtagCount = 0; - $unchangedEtagMessage = __METHOD__; - foreach ($table->getColumnsHash() as $row) { - $user = $row['user']; - $path = $row['path']; - $space = $row['space']; - $etag = $this->userGetsEtagOfElementInASpace($user, $space, $path); - $storedEtag = $this->getStoredEtagForPathInSpaceOfAUser($user, $space, $path); - if ($etag === $storedEtag) { - $unchangedEtagCount++; - $unchangedEtagMessage .= "\nExpected etag of element '$path' for user '$user' in space '$space' to change, but it did not."; - } - } - - Assert::assertEquals(0, $unchangedEtagCount, $unchangedEtagMessage); - } - - /** - * @Then /^these etags should not have changed$/ - * - * @param TableNode $table - * - * @return void - * @throws GuzzleException - */ - public function theseEtagsShouldNotHaveChanged(TableNode $table): void { + public function theseEtagsShouldShouldNotHaveChanged(string $action, TableNode $table): void { $this->featureContext->verifyTableNodeColumns($table, ["user", "path", "space"]); $this->featureContext->verifyTableNodeColumnsCount($table, 3); $changedEtagCount = 0; @@ -2729,11 +2712,15 @@ class SpacesContext implements Context { $user = $row['user']; $path = $row['path']; $space = $row['space']; - $actualEtag = $this->userGetsEtagOfElementInASpace($user, $space, $path); + $etag = $this->userGetsEtagOfElementInASpace($user, $space, $path); $storedEtag = $this->getStoredEtagForPathInSpaceOfAUser($user, $space, $path); - if ($actualEtag !== $storedEtag) { + if ($action === 'should' && $etag === $storedEtag) { $changedEtagCount++; - $changedEtagMessage .= "\nExpected etag of element '$path' for user '$user' in space '$space' not to change, but it did."; + $changedEtagMessage .= "\nExpected etag of element '$path' for user '$user' in space '$space' to change, but it did not."; + } + if ($action === 'should not' && $etag !== $storedEtag) { + $changedEtagCount++; + $changedEtagMessage .= "\nExpected etag of element '$path' for user '$user' in space '$space' to change, but it did not."; } } @@ -3154,6 +3141,15 @@ class SpacesContext implements Context { case "oc:privatelink": Assert::assertEquals($this->getPrivateLink($user, $spaceNameOrMountPoint), $responseValue, 'cannot find private link for space or resource in the response'); break; + case "oc:tags": + // The value should be a comma-separated string of tag names. + // We do not care what order they happen to be in, so compare as sorted lists. + $expectedTags = \explode(",", $value); + $expectedTags = \sort($expectedTags); + $actualTags = \explode(",", $responseValue); + $actualTags = \sort($actualTags); + Assert::assertEquals($expectedTags, $actualTags, "wrong $findItem in the response"); + break; default: Assert::assertEquals($value, $responseValue, "wrong $findItem in the response"); break; diff --git a/tests/acceptance/features/bootstrap/TrashbinContext.php b/tests/acceptance/features/bootstrap/TrashbinContext.php index d3213d8f5..1eb3310af 100644 --- a/tests/acceptance/features/bootstrap/TrashbinContext.php +++ b/tests/acceptance/features/bootstrap/TrashbinContext.php @@ -586,7 +586,10 @@ class TrashbinContext implements Context { $numItemsDeleted = 0; foreach ($listing as $entry) { - if ($entry['original-location'] === $originalPath) { + // The entry for the trashbin root can have original-location null. + // That is reasonable, because the trashbin root is not something that can be restored. + $originalLocation = $entry['original-location'] ?? ''; + if (\trim($originalLocation, '/') === $originalPath) { $trashItemHRef = $this->convertTrashbinHref($entry['href']); $response = $this->featureContext->makeDavRequest( $asUser, diff --git a/tests/acceptance/features/bootstrap/WebDav.php b/tests/acceptance/features/bootstrap/WebDav.php index 2bf821330..93993edc2 100644 --- a/tests/acceptance/features/bootstrap/WebDav.php +++ b/tests/acceptance/features/bootstrap/WebDav.php @@ -1184,7 +1184,13 @@ trait WebDav { public function contentOfFileForUserShouldBe(string $fileName, string $user, string $content):void { $user = $this->getActualUsername($user); $this->downloadFileAsUserUsingPassword($user, $fileName); - $this->downloadedContentShouldBe($content); + $actualStatus = $this->response->getStatusCode(); + if ($actualStatus !== 200) { + throw new Exception( + "Expected status code to be '200', but got '$actualStatus'" + ); + } + $this->checkDownloadedContentMatches($content); } /** @@ -3334,78 +3340,71 @@ trait WebDav { * @return void */ public function fileHasBeenDeleted(string $file, string $user):void { - $this->userHasDeletedFile($user, "deleted", "file", $file); + $this->userHasDeletedResource($user, "deleted", "file", $file); } /** - * @When /^user "([^"]*)" (?:deletes|unshares) (?:file|folder) "([^"]*)" using the WebDAV API$/ + * @When user :user deletes file/folder :resource using the WebDAV API * * @param string $user - * @param string $file + * @param string $resource * * @return void */ - public function userDeletesFile(string $user, string $file):void { + public function userDeletesFile(string $user, string $resource):void { $user = $this->getActualUsername($user); $this->pauseUploadDelete(); - $this->response = $this->makeDavRequest($user, 'DELETE', $file, []); + $this->response = $this->makeDavRequest($user, 'DELETE', $resource, []); $this->lastUploadDeleteTime = \time(); $this->pushToLastHttpStatusCodesArray((string) $this->getResponse()->getStatusCode()); } /** - * @Given /^user "([^"]*)" has (deleted|unshared) (?:file|folder|entity) "([^"]*)"$/ + * @Given /^user "([^"]*)" has deleted (?:file|folder|entity) "([^"]*)"$/ * * @param string $user - * @param string $deletedOrUnshared - * @param string $entry + * @param string $resource * * @return void * @throws Exception */ - public function userHasDeletedFile(string $user, string $deletedOrUnshared, string $entry):void { + public function userHasDeletedResource(string $user, string $resource):void { $user = $this->getActualUsername($user); - $this->userDeletesFile($user, $entry); + $this->userDeletesFile($user, $resource); // If the file or folder was there and got deleted then we get a 204 // That is good and the expected status // If the file or folder was already not there then we get a 404 // That is not expected. Scenarios that use "Given user has deleted..." // should only be using such steps when it is a file that exists and needs // to be deleted. - if ($deletedOrUnshared === "deleted") { - $deleteText = "delete"; - } else { - $deleteText = "unshare"; - } $this->theHTTPStatusCodeShouldBe( ["204"], - "HTTP status code was not 204 while trying to $deleteText resource '$entry' for user '$user'" + "HTTP status code was not 204 while trying to delete resource '$resource' for user '$user'" ); $this->emptyLastHTTPStatusCodesArray(); } /** - * @Given /^user "([^"]*)" has (deleted|unshared) the following (?:files|folders|resources)$/ + * @Given /^user "([^"]*)" has deleted the following (?:files|folders|resources)$/ * * @param string $user - * @param string $deletedOrUnshared * @param TableNode $table * * @return void * @throws Exception */ - public function userHasDeletedFollowingFiles(string $user, string $deletedOrUnshared, TableNode $table):void { + public function userHasDeletedFollowingFiles(string $user, TableNode $table):void { $this->verifyTableNodeColumns($table, ["path"]); $paths = $table->getHash(); foreach ($paths as $file) { - $this->userHasDeletedFile($user, $deletedOrUnshared, $file["path"]); + $this->userHasDeletedResource($user, $file["path"]); } } /** - * @When /^user "([^"]*)" (?:deletes|unshares) the following (?:files|folders)$/ + * @When /^user "([^"]*)" deletes the following (?:files|folders)$/ * * @param string $user * @param TableNode $table @@ -3425,7 +3424,7 @@ trait WebDav { } /** - * @When /^the user (?:deletes|unshares) (?:file|folder) "([^"]*)" using the WebDAV API$/ + * @When /^the user deletes (?:file|folder) "([^"]*)" using the WebDAV API$/ * * @param string $file * @@ -3436,20 +3435,19 @@ trait WebDav { } /** - * @Given /^the user has (deleted|unshared) (file|folder) "([^"]*)"$/ + * @Given /^the user has deleted (file|folder) "([^"]*)"$/ * - * @param string $deletedOrUnshared * @param string $fileOrFolder * @param string $file * * @return void */ - public function theUserHasDeletedFile(string $deletedOrUnshared, string $fileOrFolder, string $file):void { - $this->userHasDeletedFile($this->getCurrentUser(), $deletedOrUnshared, $fileOrFolder, $file); + public function theUserHasDeletedFile(string $fileOrFolder, string $file):void { + $this->userHasDeletedResource($this->getCurrentUser(), $fileOrFolder, $file); } /** - * @When /^user "([^"]*)" (?:deletes|unshares) these (?:files|folders|entries) without delays using the WebDAV API$/ + * @When /^user "([^"]*)" deletes these (?:files|folders|entries) without delays using the WebDAV API$/ * * @param string $user * @param TableNode $table of files or folders to delete @@ -3469,7 +3467,7 @@ trait WebDav { } /** - * @When /^the user (?:deletes|unshares) these (?:files|folders|entries) without delays using the WebDAV API$/ + * @When /^the user deletes these (?:files|folders|entries) without delays using the WebDAV API$/ * * @param TableNode $table of files or folders to delete * @@ -3481,7 +3479,7 @@ trait WebDav { } /** - * @When /^user "([^"]*)" on "(LOCAL|REMOTE)" (?:deletes|unshares) (?:file|folder) "([^"]*)" using the WebDAV API$/ + * @When /^user "([^"]*)" on "(LOCAL|REMOTE)" deletes (?:file|folder) "([^"]*)" using the WebDAV API$/ * * @param string $user * @param string $server @@ -3496,30 +3494,24 @@ trait WebDav { } /** - * @Given /^user "([^"]*)" on "(LOCAL|REMOTE)" has (deleted|unshared) (file|folder) "([^"]*)"$/ + * @Given /^user "([^"]*)" on "(LOCAL|REMOTE)" has deleted (file|folder) "([^"]*)"$/ * * @param string $user * @param string $server - * @param string $deletedOrUnshared * @param string $fileOrFolder * @param string $entry * * @return void */ - public function userOnHasDeletedFile(string $user, string $server, string $deletedOrUnshared, string $fileOrFolder, string $entry):void { + public function userOnHasDeletedFile(string $user, string $server, string $fileOrFolder, string $entry):void { $this->userOnDeletesFile($user, $server, $entry); // If the file was there and got deleted then we get a 204 // If the file was already not there then we get a 404 // Either way, the outcome of the "given" step is OK - if ($deletedOrUnshared === "deleted") { - $deleteText = "delete"; - } else { - $deleteText = "unshare"; - } $this->theHTTPStatusCodeShouldBe( ["204", "404"], - "HTTP status code was not 204 or 404 while trying to $deleteText $fileOrFolder '$entry' for user '$user' on server '$server'" + "HTTP status code was not 204 or 404 while trying to delete $fileOrFolder '$entry' for user '$user' on server '$server'" ); } @@ -3706,38 +3698,6 @@ trait WebDav { } } - /** - * Old style chunking upload - * - * @Given user :user has uploaded the following :total chunks to :file with old chunking - * - * @param string $user - * @param string $total - * @param string $file - * @param TableNode $chunkDetails table of 2 columns, chunk number and chunk - * content with following headings, e.g. - * | number | content | - * | 1 | first data | - * | 2 | followed by second data | - * Chunks may be numbered out-of-order if desired. - * - * @return void - * @throws Exception - */ - public function userHasUploadedTheFollowingTotalChunksUsingOldChunking( - string $user, - string $total, - string $file, - TableNode $chunkDetails - ):void { - $this->verifyTableNodeColumns($chunkDetails, ['number', 'content']); - foreach ($chunkDetails->getHash() as $chunkDetail) { - $chunkNumber = (int) $chunkDetail['number']; - $chunkContent = $chunkDetail['content']; - $this->userHasUploadedChunkedFile($user, $chunkNumber, (int) $total, $chunkContent, $file); - } - } - /** * Old style chunking upload * @@ -3769,37 +3729,6 @@ trait WebDav { ); } - /** - * Old style chunking upload - * - * @Given user :user has uploaded the following chunks to :file with old chunking - * - * @param string $user - * @param string $file - * @param TableNode $chunkDetails table of 2 columns, chunk number and chunk - * content with headings, e.g. - * | number | content | - * | 1 | first data | - * | 2 | followed by second data | - * Chunks may be numbered out-of-order if desired. - * - * @return void - * @throws Exception - */ - public function userHasUploadedTheFollowingChunksUsingOldChunking( - string $user, - string $file, - TableNode $chunkDetails - ):void { - $total = \count($chunkDetails->getRows()); - $this->userHasUploadedTheFollowingTotalChunksUsingOldChunking( - $user, - (string) $total, - $file, - $chunkDetails - ); - } - /** * Old style chunking upload * @@ -4513,7 +4442,7 @@ trait WebDav { foreach ($elementList as $element) { $element = \substr((string)$element, \strlen($davPrefix)); if ($checkEachDelete) { - $this->userHasDeletedFile($user, "deleted", "file", $element); + $this->userHasDeletedResource($user, "deleted", "file", $element); } else { $this->userDeletesFile($user, $element); } diff --git a/tests/acceptance/features/bootstrap/WebDavPropertiesContext.php b/tests/acceptance/features/bootstrap/WebDavPropertiesContext.php index 966561560..beef5ebe5 100644 --- a/tests/acceptance/features/bootstrap/WebDavPropertiesContext.php +++ b/tests/acceptance/features/bootstrap/WebDavPropertiesContext.php @@ -673,12 +673,7 @@ class WebDavPropertiesContext implements Context { __METHOD__ ); } - $xmlPart = $resXml->xpath($xpath); - Assert::assertTrue( - isset($xmlPart[0]), - "Cannot find item with xpath \"$xpath\"" - ); - $value = $xmlPart[0]->__toString(); + $value = $this->getXmlItemByXpath($resXml, $xpath); $user = $this->featureContext->getActualUsername($user); $expectedValue = $this->featureContext->substituteInLineCodes( $expectedValue, @@ -718,12 +713,7 @@ class WebDavPropertiesContext implements Context { __METHOD__ ); } - $xmlPart = $resXml->xpath($xpath); - Assert::assertTrue( - isset($xmlPart[0]), - "Cannot find item with xpath \"$xpath\"" - ); - $value = $xmlPart[0]->__toString(); + $value = $this->getXmlItemByXpath($resXml, $xpath); $user = $this->featureContext->getActualUsername($user); $expectedValue1 = $this->featureContext->substituteInLineCodes( $expectedValue1, @@ -744,6 +734,25 @@ class WebDavPropertiesContext implements Context { Assert::assertTrue($isExpectedValueInMessage, "The actual value \"$value\" is not one of the expected values: \"$expectedValue1\" or \"$expectedValue2\""); } + /** + * @param $xmlResponse + * @param string $xpath + * + * @return string + * @throws Exception + */ + public function getXmlItemByXpath( + SimpleXMLElement $xmlResponse, + string $xpath + ): string { + $xmlPart = $xmlResponse->xpath($xpath); + Assert::assertTrue( + isset($xmlPart[0]), + "Cannot find item with xpath \"$xpath\"" + ); + return $xmlPart[0]->__toString(); + } + /** * @Then the value of the item :xpath in the response should match :value * @@ -1284,28 +1293,6 @@ class WebDavPropertiesContext implements Context { Assert::assertEquals(0, $changedEtagCount, $changedEtagMessage); } - /** - * @Then the etag of element :path of user :user should not have changed - * - * @param string $path - * @param string $user - * - * @return void - * @throws Exception - */ - public function etagOfElementOfUserShouldNotHaveChanged(string $path, string $user):void { - $user = $this->featureContext->getActualUsername($user); - $actualEtag = $this->getCurrentEtagOfElement($path, $user); - $storedEtag = $this->getStoredEtagOfElement($path, $user, __METHOD__); - Assert::assertEquals( - $storedEtag, - $actualEtag, - __METHOD__ - . " The etag of element '$path' of user '$user' was not expected to change." - . " The stored etag was '$storedEtag' but got '$actualEtag' from the response" - ); - } - /** * @Then these etags should have changed: * @@ -1335,65 +1322,36 @@ class WebDavPropertiesContext implements Context { } /** - * @Then the etag of element :path of user :user should have changed + * @Then /^the etag of element "([^"]*)" of user "([^"]*)" (should|should not) have changed$/ * * @param string $path * @param string $user + * @param string $shouldShouldNot * * @return void * @throws Exception */ - public function etagOfElementOfUserShouldHaveChanged(string $path, string $user):void { + public function etagOfElementOfUserShouldOrShouldNotHaveChanged(string $path, string $user, string $shouldShouldNot):void { $user = $this->featureContext->getActualUsername($user); $actualEtag = $this->getCurrentEtagOfElement($path, $user); $storedEtag = $this->getStoredEtagOfElement($path, $user, __METHOD__); - Assert::assertNotEquals( - $storedEtag, - $actualEtag, - __METHOD__ - . " The etag of element '$path' of user '$user' was expected to change." - . " The stored etag was '$storedEtag' and also got '$actualEtag' from the response" - ); - } - - /** - * @Then the etag of element :path of user :user on server :server should have changed - * - * @param string $path - * @param string $user - * @param string $server - * - * @return void - * @throws Exception - */ - public function theEtagOfElementOfUserOnServerShouldHaveChanged( - string $path, - string $user, - string $server - ):void { - $previousServer = $this->featureContext->usingServer($server); - $this->etagOfElementOfUserShouldHaveChanged($path, $user); - $this->featureContext->usingServer($previousServer); - } - - /** - * @Then the etag of element :path of user :user on server :server should not have changed - * - * @param string $path - * @param string $user - * @param string $server - * - * @return void - * @throws Exception - */ - public function theEtagOfElementOfUserOnServerShouldNotHaveChanged( - string $path, - string $user, - string $server - ):void { - $previousServer = $this->featureContext->usingServer($server); - $this->etagOfElementOfUserShouldNotHaveChanged($path, $user); - $this->featureContext->usingServer($previousServer); + if ($shouldShouldNot === 'should not') { + Assert::assertEquals( + $storedEtag, + $actualEtag, + __METHOD__ + . " The etag of element '$path' of user '$user' was not expected to change." + . " The stored etag was '$storedEtag' but got '$actualEtag' from the response" + ); + } else { + Assert::assertNotEquals( + $storedEtag, + $actualEtag, + __METHOD__ + . " The etag of element '$path' of user '$user' was expected to change." + . " The stored etag was '$storedEtag' and also got '$actualEtag' from the response" + ); + } } /** diff --git a/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareDefaultFolderForReceivedShares.feature b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareDefaultFolderForReceivedShares.feature index ea2a685ee..186273c87 100644 --- a/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareDefaultFolderForReceivedShares.feature +++ b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareDefaultFolderForReceivedShares.feature @@ -14,11 +14,11 @@ Feature: shares are received in the default folder for received shares And user "Alice" has created folder "FOLDER" When user "Alice" shares folder "/FOLDER" with user "Brian" using the sharing API And user "Brian" accepts share "/FOLDER" offered by user "Alice" using the sharing API - And user "Brian" unshares folder "Shares/FOLDER" using the WebDAV API + And user "Brian" declines share "/Shares/FOLDER" offered by user "Alice" using the sharing API And user "Brian" shares folder "/Shares" with user "Alice" using the sharing API Then the OCS status code of responses on each endpoint should be "" respectively And the HTTP status code of responses on each endpoint should be "" respectively Examples: - | ocs_api_version | ocs_status_code | http_status_code | - | 1 | 100, 100, 400 | 200, 200, 204, 200 | - | 2 | 200, 200, 400 | 200, 200, 204, 400 | + | ocs_api_version | ocs_status_code | http_status_code | + | 1 | 100, 100, 100, 400 | 200, 200, 200, 200 | + | 2 | 200, 200, 200, 400 | 200, 200, 200, 400 | diff --git a/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature b/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature index 3bd6210a7..0703a74ec 100644 --- a/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature +++ b/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature @@ -74,7 +74,7 @@ Feature: sharing And as "Alice" file "/shared_file.txt" should exist in the trashbin And as "Brian" file "/shared_file.txt" should exist in the trashbin - + Scenario: deleting a folder out of a share as recipient creates a backup for the owner Given using OCS API version "1" And user "Alice" has created folder "/shared" @@ -106,8 +106,8 @@ Feature: sharing And user "Carol" has stored etag of element "/PARENT" And user "Brian" has stored etag of element "/" And user "Brian" has stored etag of element "/Shares" - When user "Brian" unshares file "/Shares/parent.txt" using the WebDAV API - Then the HTTP status code should be "204" + When user "Brian" declines share "/Shares/parent.txt" offered by user "Carol" using the sharing API + Then the HTTP status code should be "200" And the etag of element "/" of user "Brian" should have changed And the etag of element "/Shares" of user "Brian" should have changed And the etag of element "/PARENT" of user "Carol" should not have changed @@ -226,3 +226,17 @@ Feature: sharing | ocs_api_version | ocs_status_code | http_status_code | | 1 | 404 | 200 | | 2 | 404 | 404 | + + + Scenario Outline: unshare a shared resources + Given using OCS API version "" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Alice" unshares file "textfile0.txt" shared to "Brian" + Then the OCS status code should be "" + And the HTTP status code should be "" + And as "Brian" file "/Shares/textfile0.txt" should not exist + Examples: + | ocs_api_version | ocs_status_code | http_status_code | + | 1 | 100 | 200 | + | 2 | 200 | 200 | diff --git a/tests/acceptance/features/coreApiShareManagementToShares/mergeShare.feature b/tests/acceptance/features/coreApiShareManagementToShares/mergeShare.feature index 87d304bb7..47415b6b8 100644 --- a/tests/acceptance/features/coreApiShareManagementToShares/mergeShare.feature +++ b/tests/acceptance/features/coreApiShareManagementToShares/mergeShare.feature @@ -103,7 +103,7 @@ Feature: sharing And user "Brian" shares folder "/merge-test-inside-twogroups-perms" with group "grp2" using the sharing API Then the OCS status code of responses on all endpoints should be "100" And the HTTP status code of responses on all endpoints should be "200" - And as user "Brian" folder "/merge-test-inside-twogroups-perms" should contain a property "oc:permissions" with value "RDNVCKZ" or with value "RMDNVCKZ" + And as user "Brian" folder "/merge-test-inside-twogroups-perms" should contain a property "oc:permissions" with value "RDNVCKZP" or with value "RMDNVCKZP" And as "Brian" folder "/Shares/merge-test-inside-twogroups-perms" should not exist And as "Brian" folder "/Shares/merge-test-inside-twogroups-perms (2)" should not exist diff --git a/tests/acceptance/features/coreApiSharees/sharees.feature b/tests/acceptance/features/coreApiSharees/sharees.feature index 0f8cb1461..9dee6fb15 100644 --- a/tests/acceptance/features/coreApiSharees/sharees.feature +++ b/tests/acceptance/features/coreApiSharees/sharees.feature @@ -161,45 +161,6 @@ Feature: search sharees | 2 | 200 | 200 | - Scenario Outline: federated sharee for files - Given using OCS API version "" - When user "Alice" gets the sharees using the sharing API with parameters - | search | test@localhost | - | itemType | file | - Then the OCS status code should be "" - And the HTTP status code should be "" - And the "exact users" sharees returned should be empty - And the "users" sharees returned should be empty - And the "exact groups" sharees returned should be empty - And the "groups" sharees returned should be empty - And the "exact remotes" sharees returned should be - | test@localhost | 6 | test@localhost | - And the "remotes" sharees returned should be empty - Examples: - | ocs-api-version | ocs-status | http-status | - | 1 | 100 | 200 | - | 2 | 200 | 200 | - - - Scenario Outline: federated sharee for calendars not allowed - Given using OCS API version "" - When user "Alice" gets the sharees using the sharing API with parameters - | search | test@localhost | - | itemType | calendar | - Then the OCS status code should be "" - And the HTTP status code should be "" - And the "exact users" sharees returned should be empty - And the "users" sharees returned should be empty - And the "exact groups" sharees returned should be empty - And the "groups" sharees returned should be empty - And the "exact remotes" sharees returned should be empty - And the "remotes" sharees returned should be empty - Examples: - | ocs-api-version | ocs-status | http-status | - | 1 | 100 | 200 | - | 2 | 200 | 200 | - - Scenario Outline: enumerate only group members - only show partial results from member of groups Given using OCS API version "" And these users have been created with default attributes and without skeleton files: diff --git a/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature b/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature index 859630a15..1fa366193 100644 --- a/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature +++ b/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature @@ -14,7 +14,7 @@ Feature: files and folders can be deleted from the trashbin And user "Alice" has uploaded file with content "to delete" to "/PARENT/CHILD/child.txt" @smokeTest - Scenario Outline: Trashbin can be emptied + Scenario Outline: trashbin can be emptied Given using DAV path And user "Alice" has uploaded file with content "file with comma" to "sample,0.txt" And user "Alice" has uploaded file with content "file with comma" to "sample,1.txt" @@ -85,7 +85,7 @@ Feature: files and folders can be deleted from the trashbin | spaces | - Scenario Outline: User tries to delete another user's trashbin + Scenario Outline: user tries to delete another user's trashbin Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Alice" has deleted file "/textfile0.txt" @@ -105,7 +105,7 @@ Feature: files and folders can be deleted from the trashbin | spaces | 404 | - Scenario Outline: User tries to delete trashbin file using invalid password + Scenario Outline: user tries to delete trashbin file using invalid password Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Alice" has deleted file "/textfile0.txt" @@ -128,7 +128,7 @@ Feature: files and folders can be deleted from the trashbin | spaces | - Scenario Outline: User tries to delete trashbin file using no password + Scenario Outline: user tries to delete trashbin file using no password Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Alice" has deleted file "/textfile0.txt" diff --git a/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature b/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature index cf9749e81..98b79afb5 100644 --- a/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature +++ b/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature @@ -159,7 +159,7 @@ Feature: files and folders exist in the trashbin after being deleted | spaces | @issue-3561 - Scenario Outline: Listing other user's trashbin is prohibited + Scenario Outline: listing other user's trashbin is prohibited Given using DAV path And user "testtrashbin100" has been created with default attributes and without skeleton files And user "testtrashbin100" has uploaded file "filesForUpload/textfile.txt" to "/textfile1.txt" @@ -177,7 +177,7 @@ Feature: files and folders exist in the trashbin after being deleted | spaces | 404 | @issue-3561 @smokeTest - Scenario Outline: Listing other user's trashbin is prohibited with multiple files on trashbin + Scenario Outline: listing other user's trashbin is prohibited with multiple files on trashbin Given using DAV path And user "testtrashbin101" has been created with default attributes and without skeleton files And user "testtrashbin101" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" @@ -198,7 +198,7 @@ Feature: files and folders exist in the trashbin after being deleted | spaces | 404 | @issue-3561 @provisioning_api-app-required - Scenario Outline: Listing other user's trashbin is prohibited for newly recreated user with same name + Scenario Outline: listing other user's trashbin is prohibited for newly recreated user with same name Given using DAV path And user "testtrashbin102" has been created with default attributes and without skeleton files And user "testtrashbin102" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" @@ -224,7 +224,7 @@ Feature: files and folders exist in the trashbin after being deleted | spaces | 404 | @issue-3561 - Scenario Outline: Listing other user's empty unused trashbin is prohibited + Scenario Outline: listing other user's empty unused trashbin is prohibited Given using DAV path And user "testtrashbinempty" has been created with default attributes and without skeleton files And user "testtrashbinempty" has uploaded file "filesForUpload/textfile.txt" to "/textfile1.txt" @@ -240,7 +240,7 @@ Feature: files and folders exist in the trashbin after being deleted | spaces | @issue-3561 - Scenario Outline: Listing non-existent user's trashbin is prohibited + Scenario Outline: listing non-existent user's trashbin is prohibited Given using DAV path When user "Alice" tries to list the trashbin content for user "testtrashbinnotauser" Then the HTTP status code should be "404" @@ -254,7 +254,7 @@ Feature: files and folders exist in the trashbin after being deleted | spaces | @smokeTest - Scenario Outline: Get trashbin content with wrong password + Scenario Outline: get trashbin content with wrong password Given using DAV path And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" And user "Alice" has deleted file "/textfile0.txt" @@ -273,7 +273,7 @@ Feature: files and folders exist in the trashbin after being deleted | spaces | @smokeTest - Scenario Outline: Get trashbin content without password + Scenario Outline: get trashbin content without password Given using DAV path And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" And user "Alice" has deleted file "/textfile0.txt" diff --git a/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature b/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature index 6536df189..e3ca1484c 100644 --- a/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature +++ b/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature @@ -1,5 +1,8 @@ @api Feature: using trashbin together with sharing + As a user + I want the deletion of the resources that I shared to end up in my trashbin + So that I can restore the resources that were accidentally deleted Background: Given user "Alice" has been created with default attributes and without skeleton files diff --git a/tests/acceptance/features/coreApiTrashbinRestore/trashbinRestore.feature b/tests/acceptance/features/coreApiTrashbinRestore/trashbinRestore.feature index b8209ddcf..9c8b7e10e 100644 --- a/tests/acceptance/features/coreApiTrashbinRestore/trashbinRestore.feature +++ b/tests/acceptance/features/coreApiTrashbinRestore/trashbinRestore.feature @@ -1,5 +1,5 @@ @api @issue-1324 -Feature: Restore deleted files/folders +Feature: restore deleted files/folders As a user I would like to restore files/folders So that I can recover accidentally deleted files/folders in ownCloud @@ -9,7 +9,7 @@ Feature: Restore deleted files/folders And user "Alice" has uploaded file with content "file to delete" to "/textfile0.txt" @smokeTest - Scenario Outline: A deleted file can be restored + Scenario Outline: deleted file can be restored Given using DAV path And user "Alice" has created folder "/FOLDER" And user "Alice" has created folder "/PARENT" @@ -35,7 +35,7 @@ Feature: Restore deleted files/folders | new | - Scenario Outline: A file deleted from a folder can be restored to the original folder + Scenario Outline: file deleted from a folder can be restored to the original folder Given using DAV path And user "Alice" has created folder "/new-folder" And user "Alice" has moved file "/textfile0.txt" to "/new-folder/new-file.txt" @@ -51,7 +51,7 @@ Feature: Restore deleted files/folders | new | - Scenario Outline: A file deleted from a folder is restored to the original folder if the original folder was deleted and restored + Scenario Outline: file deleted from a folder is restored to the original folder if the original folder was deleted and restored Given using DAV path And user "Alice" has created folder "/new-folder" And user "Alice" has moved file "/textfile0.txt" to "/new-folder/new-file.txt" @@ -69,7 +69,7 @@ Feature: Restore deleted files/folders | new | - Scenario Outline: a file is deleted and restored to a new destination + Scenario Outline: file is deleted and restored to a new destination Given using DAV path And user "Alice" has created folder "/PARENT" And user "Alice" has created folder "/PARENT/CHILD" @@ -111,7 +111,7 @@ Feature: Restore deleted files/folders | new | "/PARENT/.hiddenfile0.txt" | ".hiddenfile0.txt" | - Scenario Outline: A file deleted from a folder is restored to the original folder if the original folder was deleted and recreated + Scenario Outline: file deleted from a folder is restored to the original folder if the original folder was deleted and recreated Given using DAV path And user "Alice" has created folder "/new-folder" And user "Alice" has moved file "/textfile0.txt" to "/new-folder/new-file.txt" @@ -131,7 +131,7 @@ Feature: Restore deleted files/folders | new | @smokeTest - Scenario Outline: A deleted file cannot be restored by a different user + Scenario Outline: deleted file cannot be restored by a different user Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Alice" has deleted file "/textfile0.txt" @@ -146,7 +146,7 @@ Feature: Restore deleted files/folders | new | 404 | @smokeTest - Scenario Outline: A deleted file cannot be restored with invalid password + Scenario Outline: deleted file cannot be restored with invalid password Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Alice" has deleted file "/textfile0.txt" @@ -161,7 +161,7 @@ Feature: Restore deleted files/folders | new | @smokeTest - Scenario Outline: A deleted file cannot be restored without using a password + Scenario Outline: deleted file cannot be restored without using a password Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Alice" has deleted file "/textfile0.txt" @@ -176,7 +176,7 @@ Feature: Restore deleted files/folders | new | - Scenario Outline: Files with strange names can be restored + Scenario Outline: files with strange names can be restored Given using DAV path And user "Alice" has uploaded file with content "file original content" to "" And user "Alice" has deleted file "" @@ -195,7 +195,7 @@ Feature: Restore deleted files/folders | new | sample,1.txt | - Scenario Outline: A file deleted from a multi level sub-folder can be restored to the original folder + Scenario Outline: file deleted from a multi level sub-folder can be restored to the original folder Given using DAV path And user "Alice" has created folder "/new-folder" And user "Alice" has created folder "/new-folder/folder1/" @@ -213,7 +213,7 @@ Feature: Restore deleted files/folders | new | - Scenario Outline: A deleted multi level folder can be restored including the content + Scenario Outline: deleted multi level folder can be restored including the content Given using DAV path And user "Alice" has created folder "/new-folder" And user "Alice" has created folder "/new-folder/folder1/" @@ -231,7 +231,7 @@ Feature: Restore deleted files/folders | new | - Scenario Outline: A subfolder from a deleted multi level folder can be restored including the content + Scenario Outline: subfolder from a deleted multi level folder can be restored including the content Given using DAV path And user "Alice" has created folder "/new-folder" And user "Alice" has created folder "/new-folder/folder1" @@ -251,7 +251,7 @@ Feature: Restore deleted files/folders | new | - Scenario Outline: A file from a deleted multi level sub-folder can be restored + Scenario Outline: file from a deleted multi level sub-folder can be restored Given using DAV path And user "Alice" has created folder "/new-folder" And user "Alice" has created folder "/new-folder/folder1/" @@ -271,7 +271,7 @@ Feature: Restore deleted files/folders | new | - Scenario Outline: A deleted hidden file can be restored + Scenario Outline: deleted hidden file can be restored Given using DAV path And user "Alice" has created folder "/FOLDER" And user "Alice" has uploaded the following files with content "hidden file" @@ -383,7 +383,7 @@ Feature: Restore deleted files/folders | new | - Scenario Outline: A deleted file inside a nested folder can be restored to a different location + Scenario Outline: deleted file inside a nested folder can be restored to a different location Given using DAV path And user "Alice" has created folder "/parent_folder" And user "Alice" has created folder "/parent_folder/sub" @@ -401,7 +401,7 @@ Feature: Restore deleted files/folders | new | - Scenario Outline: A deleted file inside a nested folder cannot be restored to the original location if the location doesn't exist + Scenario Outline: deleted file inside a nested folder cannot be restored to the original location if the location doesn't exist Given using DAV path And user "Alice" has created folder "/parent_folder" And user "Alice" has created folder "/parent_folder/sub" @@ -420,7 +420,7 @@ Feature: Restore deleted files/folders | new | - Scenario Outline: A deleted file inside a nested folder can be restored to the original location if the location exists + Scenario Outline: deleted file inside a nested folder can be restored to the original location if the location exists Given using DAV path And user "Alice" has created folder "/parent_folder" And user "Alice" has created folder "/parent_folder/sub" @@ -444,7 +444,7 @@ Feature: Restore deleted files/folders | new | - Scenario Outline: A deleted file inside a nested folder cannot be restored without the destination + Scenario Outline: deleted file inside a nested folder cannot be restored without the destination Given using DAV path And user "Alice" has created folder "/parent_folder" And user "Alice" has created folder "/parent_folder/sub" @@ -463,7 +463,7 @@ Feature: Restore deleted files/folders | new | - Scenario Outline: A deleted file cannot be restored without the destination + Scenario Outline: deleted file cannot be restored without the destination Given using DAV path And user "Alice" has uploaded file with content "parent text" to "/parent.txt" And user "Alice" has deleted file "parent.txt" diff --git a/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature b/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature index 0e013e401..a5cab861f 100644 --- a/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature +++ b/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature @@ -1,5 +1,8 @@ @api @issue-2914 Feature: file versions remember the author of each version + As a user + I want to know the author of each version of a file + So that I can better understand the version history and choose a version to restore Background: Given using OCS API version "2" diff --git a/tests/acceptance/features/coreApiVersions/fileVersions.feature b/tests/acceptance/features/coreApiVersions/fileVersions.feature index 98e3f4bdd..548df6b27 100644 --- a/tests/acceptance/features/coreApiVersions/fileVersions.feature +++ b/tests/acceptance/features/coreApiVersions/fileVersions.feature @@ -1,6 +1,8 @@ @api - Feature: dav-versions + As a user + I want the versions of files to be available + So that I can manage the changes made to the files Background: Given using OCS API version "2" @@ -8,13 +10,13 @@ Feature: dav-versions And user "Alice" has been created with default attributes and without skeleton files - Scenario: Upload file and no version is available + Scenario: upload file and no version is available When user "Alice" uploads file "filesForUpload/davtest.txt" to "/davtest.txt" using the WebDAV API Then the HTTP status code should be "201" And the version folder of file "/davtest.txt" for user "Alice" should contain "0" elements - Scenario: Upload file and no version is available using various chunking methods (except new chunking) + Scenario: upload file and no version is available using various chunking methods (except new chunking) When user "Alice" uploads file "filesForUpload/davtest.txt" to filenames based on "/davtest.txt" with all mechanisms except new chunking using the WebDAV API Then the HTTP status code should be "200" And the version folder of file "/davtest.txt-olddav-regular" for user "Alice" should contain "0" elements @@ -22,7 +24,7 @@ Feature: dav-versions And the version folder of file "/davtest.txt-olddav-oldchunking" for user "Alice" should contain "0" elements @smokeTest - Scenario: Upload a file twice and versions are available + Scenario: upload a file twice and versions are available When user "Alice" uploads file "filesForUpload/davtest.txt" to "/davtest.txt" using the WebDAV API And user "Alice" uploads file "filesForUpload/davtest.txt" to "/davtest.txt" using the WebDAV API Then the HTTP status code of responses on each endpoint should be "201, 204" respectively @@ -30,7 +32,7 @@ Feature: dav-versions And the content length of file "/davtest.txt" with version index "1" for user "Alice" in versions folder should be "8" - Scenario: Upload a file twice and versions are available using various chunking methods (except new chunking) + Scenario: upload a file twice and versions are available using various chunking methods (except new chunking) When user "Alice" uploads file "filesForUpload/davtest.txt" to filenames based on "/davtest.txt" with all mechanisms except new chunking using the WebDAV API And user "Alice" uploads file "filesForUpload/davtest.txt" to filenames based on "/davtest.txt" with all mechanisms except new chunking using the WebDAV API Then the HTTP status code of responses on all endpoints should be "200" @@ -39,7 +41,7 @@ Feature: dav-versions And the version folder of file "/davtest.txt-olddav-oldchunking" for user "Alice" should contain "1" element @smokeTest - Scenario: Remove a file + Scenario: remove a file Given user "Alice" has uploaded file "filesForUpload/davtest.txt" to "/davtest.txt" And user "Alice" has uploaded file "filesForUpload/davtest.txt" to "/davtest.txt" And the version folder of file "/davtest.txt" for user "Alice" should contain "1" element @@ -49,7 +51,7 @@ Feature: dav-versions And the version folder of file "/davtest.txt" for user "Alice" should contain "0" elements @smokeTest - Scenario: Restore a file and check, if the content is now in the current file + Scenario: restore a file and check its content Given user "Alice" has uploaded file with content "Test Content." to "/davtest.txt" And user "Alice" has uploaded file with content "Content Test Updated." to "/davtest.txt" And the version folder of file "/davtest.txt" for user "Alice" should contain "1" element @@ -58,7 +60,7 @@ Feature: dav-versions And the content of file "/davtest.txt" for user "Alice" should be "Test Content." @smokeTest @skipOnStorage:ceph @skipOnStorage:scality - Scenario: Restore a file back to bigger content and check, if the content is now in the current file + Scenario: restore a file back to bigger content and check its content Given user "Alice" has uploaded file with content "Back To The Future." to "/davtest.txt" And user "Alice" has uploaded file with content "Update Content." to "/davtest.txt" And the version folder of file "/davtest.txt" for user "Alice" should contain "1" element @@ -67,7 +69,7 @@ Feature: dav-versions And the content of file "/davtest.txt" for user "Alice" should be "Back To The Future." @smokeTest @skipOnStorage:ceph - Scenario Outline: Uploading a chunked file does create the correct version that can be restored + Scenario Outline: uploading a chunked file does create the correct version that can be restored Given using DAV path And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt" When user "Alice" uploads file "filesForUpload/davtest.txt" to "/textfile0.txt" in 2 chunks using the WebDAV API @@ -83,7 +85,7 @@ Feature: dav-versions | old | 201 | @skipOnStorage:ceph @skipOnStorage:scality - Scenario: Restore a file and check, if the content and correct checksum is now in the current file + Scenario: restore a file and check the content and checksum Given user "Alice" has uploaded file with content "AAAAABBBBBCCCCC" and checksum "MD5:45a72715acdd5019c5be30bdbb75233e" to "/davtest.txt" And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/davtest.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a" And the version folder of file "/davtest.txt" for user "Alice" should contain "1" element @@ -93,7 +95,7 @@ Feature: dav-versions And as user "Alice" the webdav checksum of "/davtest.txt" via propfind should match "SHA1:acfa6b1565f9710d4d497c6035d5c069bd35a8e8 MD5:45a72715acdd5019c5be30bdbb75233e ADLER32:1ecd03df" - Scenario: User cannot access meta folder of a file which is owned by somebody else + Scenario: user cannot access meta folder of a file which is owned by somebody else Given user "Brian" has been created with default attributes and without skeleton files And user "Alice" has uploaded file with content "123" to "/davtest.txt" And we save it into "FILEID" @@ -101,13 +103,13 @@ Feature: dav-versions Then the HTTP status code should be "400" or "404" - Scenario: User cannot access meta folder of a file which does not exist + Scenario: user cannot access meta folder of a file which does not exist Given user "Brian" has been created with default attributes and without skeleton files When user "Brian" sends HTTP method "PROPFIND" to URL "/remote.php/dav/meta/MTI4NGQyMzgtYWE5Mi00MmNlLWJkYzQtMGIwMDAwMDA5MTU2OjhjY2QyNzUxLTkwYTQtNDBmMi1iOWYzLTYxZWRmODQ0MjFmNA==" Then the HTTP status code should be "400" or "404" - Scenario Outline: User cannot access meta folder of a file with invalid fileid + Scenario Outline: user cannot access meta folder of a file with invalid fileid Given user "Brian" has been created with default attributes and without skeleton files When user "Brian" sends HTTP method "PROPFIND" to URL "/remote.php/dav/meta//v" Then the HTTP status code should be "400" or "404" @@ -119,7 +121,7 @@ Feature: dav-versions | MTI4NGQyMzgtYWE5Mi00MmNlLWJkxzQtMGIwMDAwMDA5MTU2OjhjY2QyNzUxLTkwYTQtNDBmMi1iOWYzLTYxZWRmODQ0MjFmNA== | 1284d238-aa92-42ce-bd�4-0b0000009156:8ccd2751-90a4-40f2-b9f3-61edf84421f4 | with : and � sign | - Scenario: the version history is preserved when a file is renamed + Scenario: version history is preserved when a file is renamed Given user "Alice" has uploaded file with content "old content" to "/textfile.txt" And user "Alice" has uploaded file with content "new content" to "/textfile.txt" And user "Alice" has moved file "/textfile.txt" to "/renamedfile.txt" @@ -128,7 +130,7 @@ Feature: dav-versions And the content of file "/renamedfile.txt" for user "Alice" should be "old content" - Scenario: User can access version number after moving a file + Scenario: user can access version number after moving a file Given user "Alice" has created folder "testFolder" And user "Alice" has uploaded file with content "uploaded content" to "textfile0.txt" And user "Alice" has uploaded file with content "version 1" to "textfile0.txt" @@ -140,14 +142,14 @@ Feature: dav-versions And the number of versions should be "3" - Scenario: Original file has version number 0 + Scenario: original file has version number 0 Given user "Alice" has uploaded file with content "uploaded content" to "textfile0.txt" When user "Alice" gets the number of versions of file "textfile0.txt" Then the HTTP status code should be "207" And the number of versions should be "0" - Scenario: the number of etag elements in response changes according to version of the file + Scenario: number of etag elements in response changes according to version of the file Given user "Alice" has uploaded file with content "uploaded content" to "textfile0.txt" And user "Alice" has uploaded file with content "version 1" to "textfile0.txt" And user "Alice" has uploaded file with content "version 2" to "textfile0.txt" @@ -194,20 +196,20 @@ Feature: dav-versions And the downloaded content should be "uploaded content" - Scenario: User can retrieve meta information of a root folder + Scenario: user can retrieve meta information of a root folder When user "Alice" retrieves the meta information of file "/" using the meta API Then the HTTP status code should be "207" And the single response should contain a property "oc:meta-path-for-user" with value "/" - Scenario: User can retrieve meta information of a file + Scenario: user can retrieve meta information of a file Given user "Alice" has uploaded file with content "123" to "/davtest.txt" When user "Alice" retrieves the meta information of file "/davtest.txt" using the meta API Then the HTTP status code should be "207" And the single response should contain a property "oc:meta-path-for-user" with value "/davtest.txt" - Scenario: User can retrieve meta information of a file inside folder + Scenario: user can retrieve meta information of a file inside folder Given user "Alice" has created folder "testFolder" And user "Alice" has uploaded file with content "123" to "/testFolder/davtest.txt" When user "Alice" retrieves the meta information of file "/testFolder/davtest.txt" using the meta API @@ -215,7 +217,7 @@ Feature: dav-versions And the single response should contain a property "oc:meta-path-for-user" with value "/testFolder/davtest.txt" - Scenario: User cannot retrieve meta information of a file which is owned by somebody else + Scenario: user cannot retrieve meta information of a file which is owned by somebody else Given user "Brian" has been created with default attributes and without skeleton files And user "Alice" has uploaded file with content "123" to "/davtest.txt " And we save it into "FILEID" @@ -223,7 +225,7 @@ Feature: dav-versions Then the HTTP status code should be "404" - Scenario Outline: User cannot retrieve meta information of a file that does not exist + Scenario Outline: user cannot retrieve meta information of a file that does not exist When user "Alice" retrieves the meta information of fileId "" using the meta API Then the HTTP status code should be "400" or "404" Examples: @@ -234,7 +236,7 @@ Feature: dav-versions | MTI4NGQyMzgtYWE5Mi00MmNlLWJkxzQtMGIwMDAwMDA5MTU2OjhjY2QyNzUxLTkwYTQtNDBmMi1iOWYzLTYxZWRmODQ0MjFmNA== | 1284d238-aa92-42ce-bd�4-0b0000009156:8ccd2751-90a4-40f2-b9f3-61edf84421f4 | with : and � sign | - Scenario: File versions sets back after getting deleted and restored from trashbin + Scenario: file versions sets back after getting deleted and restored from trashbin Given user "Alice" has uploaded file with content "Old Test Content." to "/davtest.txt" And user "Alice" has uploaded file with content "New Test Content." to "/davtest.txt" And the version folder of file "/davtest.txt" for user "Alice" should contain "1" element @@ -250,14 +252,14 @@ Feature: dav-versions And the content of file "/davtest.txt" for user "Alice" should be "Old Test Content." - Scenario: Upload the same file twice with the same mtime and a version is available + Scenario: upload the same file twice with the same mtime and a version is available Given user "Alice" has uploaded file "filesForUpload/textfile.txt" to "file.txt" with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the WebDAV API When user "Alice" uploads file "filesForUpload/textfile.txt" to "file.txt" with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the WebDAV API Then the HTTP status code should be "204" And the version folder of file "/file.txt" for user "Alice" should contain "1" element - Scenario: Upload the same file more than twice with the same mtime and only one version is available + Scenario: upload the same file more than twice with the same mtime and only one version is available Given user "Alice" has uploaded file "filesForUpload/textfile.txt" to "file.txt" with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the WebDAV API And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "file.txt" with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the WebDAV API When user "Alice" uploads file "filesForUpload/textfile.txt" to "file.txt" with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the WebDAV API @@ -265,7 +267,7 @@ Feature: dav-versions And the version folder of file "/file.txt" for user "Alice" should contain "1" element - Scenario: Upload the same file twice with the same mtime and no version after restoring + Scenario: upload the same file twice with the same mtime and no version after restoring Given user "Alice" has uploaded file "filesForUpload/textfile.txt" to "file.txt" with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the WebDAV API And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "file.txt" with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the WebDAV API When user "Alice" restores version index "1" of file "/file.txt" using the WebDAV API @@ -369,7 +371,7 @@ Feature: dav-versions And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Carol" should be "First content" - Scenario Outline: Moving a file (with versions) into a shared folder as the sharee and as the sharer + Scenario Outline: moving a file (with versions) into a shared folder as the sharee and as the sharer Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Brian" has created folder "/testshare" @@ -394,7 +396,7 @@ Feature: dav-versions | new | Brian | /testshare | - Scenario Outline: Moving a file (with versions) out of a shared folder as the sharee and as the sharer + Scenario Outline: moving a file (with versions) out of a shared folder as the sharee and as the sharer Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Brian" has created folder "/testshare" @@ -419,7 +421,7 @@ Feature: dav-versions | new | Brian | /testshare | - Scenario: Receiver tries to get file versions of unshared file from the sharer + Scenario: sharee tries to get file versions of file not shared by the sharer Given user "Brian" has been created with default attributes and without skeleton files And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt" And user "Alice" has uploaded file with content "textfile1" to "textfile1.txt" @@ -430,7 +432,7 @@ Feature: dav-versions And the value of the item "//s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\NotFound" @skipOnStorage:ceph - Scenario: Receiver tries get file versions of shared file from the sharer + Scenario: receiver tries get file versions of shared file from the sharer Given user "Brian" has been created with default attributes and without skeleton files And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt" And user "Alice" has uploaded file with content "version 1" to "textfile0.txt" @@ -442,7 +444,7 @@ Feature: dav-versions Then the HTTP status code should be "403" - Scenario: Receiver tries get file versions of shared file before receiving it + Scenario: receiver tries get file versions of shared file before receiving it Given user "Brian" has been created with default attributes and without skeleton files And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt" And user "Alice" has uploaded file with content "version 1" to "textfile0.txt" diff --git a/tests/acceptance/features/coreApiWebdavDelete/deleteFolderContents.feature b/tests/acceptance/features/coreApiWebdavDelete/deleteFolderContents.feature index 95f6a00c4..d77ca4313 100644 --- a/tests/acceptance/features/coreApiWebdavDelete/deleteFolderContents.feature +++ b/tests/acceptance/features/coreApiWebdavDelete/deleteFolderContents.feature @@ -8,7 +8,7 @@ Feature: delete folder contents Given user "Alice" has been created with default attributes and without skeleton files - Scenario Outline: Removing everything of a folder + Scenario Outline: removing everything from a folder Given using DAV path And user "Alice" has created folder "/PARENT/" And user "Alice" has created folder "/FOLDER/" diff --git a/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature b/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature index 68d928a55..b188a358e 100644 --- a/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature +++ b/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature @@ -1,5 +1,8 @@ @api Feature: propagation of etags when deleting a file or folder + As a client app + I want metadata (etags) of parent folders to change when a file or folder is deleted + So that the client app can know to re-scan and sync the content of the folder(s) Background: Given user "Alice" has been created with default attributes and without skeleton files @@ -82,7 +85,7 @@ Feature: propagation of etags when deleting a file or folder | spaces | - Scenario Outline: as share receiver deleting a file changes the etags of all parents for all collaborators + Scenario Outline: sharee deleting a file changes the etags of all parents for all collaborators Given user "Brian" has been created with default attributes and without skeleton files And using DAV path And user "Alice" has created folder "/upload/sub" @@ -113,7 +116,7 @@ Feature: propagation of etags when deleting a file or folder | new | - Scenario Outline: as sharer deleting a file changes the etags of all parents for all collaborators + Scenario Outline: sharer deleting a file changes the etags of all parents for all collaborators Given user "Brian" has been created with default attributes and without skeleton files And using DAV path And user "Alice" has created folder "/upload/sub" @@ -144,7 +147,7 @@ Feature: propagation of etags when deleting a file or folder | new | @issue-product-280 - Scenario Outline: as share receiver deleting a folder changes the etags of all parents for all collaborators + Scenario Outline: sharee deleting a folder changes the etags of all parents for all collaborators Given user "Brian" has been created with default attributes and without skeleton files And using DAV path And user "Alice" has created folder "/upload/sub" @@ -175,7 +178,7 @@ Feature: propagation of etags when deleting a file or folder | new | @issue-product-280 - Scenario Outline: as sharer deleting a folder changes the etags of all parents for all collaborators + Scenario Outline: sharer deleting a folder changes the etags of all parents for all collaborators Given user "Brian" has been created with default attributes and without skeleton files And using DAV path And user "Alice" has created folder "/upload/sub" diff --git a/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature b/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature index 715e8df47..0c12125b7 100644 --- a/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature +++ b/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature @@ -1,5 +1,8 @@ @api Feature: propagation of etags when moving files or folders + As a client app + I want metadata (etags) of parent folder(s) to change when a file or folder is moved + So that the client app can know to re-scan and sync the content of the folder(s) Background: Given user "Alice" has been created with default attributes and without skeleton files @@ -155,7 +158,7 @@ Feature: propagation of etags when moving files or folders | spaces | - Scenario Outline: as share receiver renaming a file inside a folder changes its etag for all collaborators + Scenario Outline: sharee renaming a file inside a folder changes its etag for all collaborators Given user "Brian" has been created with default attributes and without skeleton files And using DAV path And user "Alice" has created folder "/upload" @@ -182,7 +185,7 @@ Feature: propagation of etags when moving files or folders | new | - Scenario Outline: as sharer renaming a file inside a folder changes its etag for all collaborators + Scenario Outline: sharer renaming a file inside a folder changes its etag for all collaborators Given user "Brian" has been created with default attributes and without skeleton files And using DAV path And user "Alice" has created folder "/upload" @@ -209,7 +212,7 @@ Feature: propagation of etags when moving files or folders | new | @issue-product-280 - Scenario Outline: as sharer moving a file from one folder to an other changes the etags of both folders for all collaborators + Scenario Outline: sharer moving a file from one folder to an other changes the etags of both folders for all collaborators Given user "Brian" has been created with default attributes and without skeleton files And using DAV path And user "Alice" has created folder "/src" @@ -243,7 +246,7 @@ Feature: propagation of etags when moving files or folders | new | - Scenario Outline: as share receiver moving a file from one folder to an other changes the etags of both folders for all collaborators + Scenario Outline: sharee moving a file from one folder to an other changes the etags of both folders for all collaborators Given user "Brian" has been created with default attributes and without skeleton files And using DAV path And user "Alice" has created folder "/src" @@ -277,7 +280,7 @@ Feature: propagation of etags when moving files or folders | new | @issue-product-280 - Scenario Outline: as sharer moving a folder from one folder to an other changes the etags of both folders for all collaborators + Scenario Outline: sharer moving a folder from one folder to an other changes the etags of both folders for all collaborators Given user "Brian" has been created with default attributes and without skeleton files And using DAV path And user "Alice" has created folder "/src" @@ -311,7 +314,7 @@ Feature: propagation of etags when moving files or folders | new | - Scenario Outline: as share receiver moving a folder from one folder to an other changes the etags of both folders for all collaborators + Scenario Outline: sharee moving a folder from one folder to an other changes the etags of both folders for all collaborators Given user "Brian" has been created with default attributes and without skeleton files And using DAV path And user "Alice" has created folder "/src" diff --git a/tests/acceptance/features/coreApiWebdavEtagPropagation2/copyFileFolder.feature b/tests/acceptance/features/coreApiWebdavEtagPropagation2/copyFileFolder.feature index df6b6308e..5b9851646 100644 --- a/tests/acceptance/features/coreApiWebdavEtagPropagation2/copyFileFolder.feature +++ b/tests/acceptance/features/coreApiWebdavEtagPropagation2/copyFileFolder.feature @@ -1,5 +1,8 @@ @api Feature: propagation of etags when copying files or folders + As a client app + I want metadata (etags) of parent folders to change when a file of folder is copied + So that the client app can know to re-scan and sync the content of the folder(s) Background: Given user "Alice" has been created with default attributes and without skeleton files @@ -152,7 +155,7 @@ Feature: propagation of etags when copying files or folders | spaces | - Scenario Outline: as share receiver copying a file inside a folder changes its etag for all collaborators + Scenario Outline: sharee copying a file inside a folder changes its etag for all collaborators Given user "Brian" has been created with default attributes and without skeleton files And using DAV path And user "Alice" has created folder "/upload" @@ -189,7 +192,7 @@ Feature: propagation of etags when copying files or folders | new | @issue-product-280 - Scenario Outline: as sharer copying a file inside a folder changes its etag for all collaborators + Scenario Outline: sharer copying a file inside a folder changes its etag for all collaborators Given user "Brian" has been created with default attributes and without skeleton files And using DAV path And user "Alice" has created folder "/upload" diff --git a/tests/acceptance/features/coreApiWebdavEtagPropagation2/createFolder.feature b/tests/acceptance/features/coreApiWebdavEtagPropagation2/createFolder.feature index dee0b7532..89179697e 100644 --- a/tests/acceptance/features/coreApiWebdavEtagPropagation2/createFolder.feature +++ b/tests/acceptance/features/coreApiWebdavEtagPropagation2/createFolder.feature @@ -1,5 +1,8 @@ @api Feature: propagation of etags when creating folders + As a client app + I want metadata (etags) of parent folders to change when a sub-folder is created + So that the client app can know to re-scan and sync the content of the folder(s) Background: Given user "Alice" has been created with default attributes and without skeleton files @@ -52,7 +55,7 @@ Feature: propagation of etags when creating folders | spaces | @issue-product-280 - Scenario Outline: as share receiver creating a folder inside a folder received as a share changes its etag for all collaborators + Scenario Outline: sharee creating a folder inside a folder received as a share changes its etag for all collaborators Given user "Brian" has been created with default attributes and without skeleton files And using DAV path And user "Alice" has created folder "/folder" @@ -78,7 +81,7 @@ Feature: propagation of etags when creating folders | new | @issue-product-280 - Scenario Outline: as a sharer creating a folder inside a shared folder changes etag for all collaborators + Scenario Outline: sharer creating a folder inside a shared folder changes etag for all collaborators Given user "Brian" has been created with default attributes and without skeleton files And using DAV path And user "Alice" has created folder "/folder" diff --git a/tests/acceptance/features/coreApiWebdavEtagPropagation2/restoreFromTrash.feature b/tests/acceptance/features/coreApiWebdavEtagPropagation2/restoreFromTrash.feature index 3536355ec..928a98edb 100644 --- a/tests/acceptance/features/coreApiWebdavEtagPropagation2/restoreFromTrash.feature +++ b/tests/acceptance/features/coreApiWebdavEtagPropagation2/restoreFromTrash.feature @@ -1,5 +1,8 @@ @api Feature: propagation of etags when restoring a file or folder from trash + As a client app + I want metadata (etags) of parent folders to change when a file or folder is restored + So that the client app can know to re-scan and sync the content of the folder(s) Background: Given user "Alice" has been created with default attributes and without skeleton files diff --git a/tests/acceptance/features/coreApiWebdavEtagPropagation2/restoreVersion.feature b/tests/acceptance/features/coreApiWebdavEtagPropagation2/restoreVersion.feature index 0188bd0d1..6afa40c68 100644 --- a/tests/acceptance/features/coreApiWebdavEtagPropagation2/restoreVersion.feature +++ b/tests/acceptance/features/coreApiWebdavEtagPropagation2/restoreVersion.feature @@ -1,5 +1,8 @@ @api @issue-product-280 Feature: propagation of etags when restoring a version of a file + As a client app + I want metadata (etags) of parent folders to change when a file version is restored + So that the client app can know to re-scan and sync the content of the folder(s) Background: Given using OCS API version "2" @@ -7,7 +10,7 @@ Feature: propagation of etags when restoring a version of a file And user "Alice" has been created with default attributes and without skeleton files @skipOnStorage:ceph @skipOnStorage:scality - Scenario: Restoring a file changes the etags of all parents + Scenario: restoring a file changes the etags of all parents Given user "Alice" has created folder "/upload" And user "Alice" has created folder "/upload/sub" And user "Alice" has uploaded file with content "uploaded content" to "/upload/sub/file.txt" diff --git a/tests/acceptance/features/coreApiWebdavEtagPropagation2/upload.feature b/tests/acceptance/features/coreApiWebdavEtagPropagation2/upload.feature index 87ec17983..fe411df36 100644 --- a/tests/acceptance/features/coreApiWebdavEtagPropagation2/upload.feature +++ b/tests/acceptance/features/coreApiWebdavEtagPropagation2/upload.feature @@ -1,5 +1,8 @@ @api Feature: propagation of etags when uploading data + As a client app + I want metadata (etags) of parent folders to change when a file is uploaded + So that the client app can know to re-scan and sync the content of the folder(s) Background: Given user "Alice" has been created with default attributes and without skeleton files @@ -51,7 +54,7 @@ Feature: propagation of etags when uploading data | spaces | @issue-product-280 - Scenario Outline: as share receiver uploading a file inside a received shared folder should update etags for all collaborators + Scenario Outline: sharee uploading a file inside a received shared folder should update etags for all collaborators Given user "Brian" has been created with default attributes and without skeleton files And using DAV path And user "Alice" has shared folder "/upload" with user "Brian" @@ -76,7 +79,7 @@ Feature: propagation of etags when uploading data | new | @issue-product-280 - Scenario Outline: as sharer uploading a file inside a shared folder should update etags for all collaborators + Scenario Outline: sharer uploading a file inside a shared folder should update etags for all collaborators Given user "Brian" has been created with default attributes and without skeleton files And using DAV path And user "Alice" has shared folder "/upload" with user "Brian" @@ -101,7 +104,7 @@ Feature: propagation of etags when uploading data | new | - Scenario Outline: as share receiver overwriting a file inside a received shared folder should update etags for all collaborators + Scenario Outline: sharee overwriting a file inside a received shared folder should update etags for all collaborators Given user "Brian" has been created with default attributes and without skeleton files And using DAV path And user "Alice" has uploaded file with content "uploaded content" to "/upload/file.txt" @@ -127,7 +130,7 @@ Feature: propagation of etags when uploading data | new | - Scenario Outline: as sharer overwriting a file inside a shared folder should update etags for all collaborators + Scenario Outline: sharer overwriting a file inside a shared folder should update etags for all collaborators Given user "Brian" has been created with default attributes and without skeleton files And using DAV path And user "Alice" has uploaded file with content "uploaded content" to "/upload/file.txt" diff --git a/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature b/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature index 6ebf271ac..2632a9f12 100644 --- a/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature +++ b/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature @@ -1,11 +1,14 @@ @api @issue-1284 Feature: there can be only one exclusive lock on a resource + As a user + I want to lock a resource + So that other users cannot access or change that resource Background: Given user "Alice" has been created with default attributes and without skeleton files - Scenario Outline: a second lock cannot be set on a folder when its exclusively locked + Scenario Outline: second lock cannot be set on a folder when its exclusively locked Given using DAV path And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" And user "Alice" has locked file "textfile0.txt" setting the following properties @@ -28,7 +31,7 @@ Feature: there can be only one exclusive lock on a resource | spaces | exclusive | - Scenario Outline: a share receiver cannot lock a resource exclusively locked by itself + Scenario Outline: sharee cannot lock a resource exclusively locked by itself Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" @@ -55,7 +58,7 @@ Feature: there can be only one exclusive lock on a resource | spaces | exclusive | - Scenario Outline: a share receiver cannot lock a resource exclusively locked by the owner + Scenario Outline: sharee cannot lock a resource exclusively locked by the owner Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" @@ -82,7 +85,7 @@ Feature: there can be only one exclusive lock on a resource | spaces | exclusive | - Scenario Outline: a share owner cannot lock a resource exclusively locked by a share receiver + Scenario Outline: sharer cannot lock a resource exclusively locked by a sharee Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" diff --git a/tests/acceptance/features/coreApiWebdavLocks/requestsWithToken.feature b/tests/acceptance/features/coreApiWebdavLocks/requestsWithToken.feature index f451e0f55..01e30903b 100644 --- a/tests/acceptance/features/coreApiWebdavLocks/requestsWithToken.feature +++ b/tests/acceptance/features/coreApiWebdavLocks/requestsWithToken.feature @@ -1,5 +1,8 @@ @api @issue-1284 Feature: actions on a locked item are possible if the token is sent with the request + As a user + I want to share the lock token of a resource + So that the users or apps can access the resource that I have locked Background: Given user "Alice" has been created with default attributes and without skeleton files diff --git a/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature b/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature index da1e1cca1..ae9620cd1 100644 --- a/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature +++ b/tests/acceptance/features/coreApiWebdavLocks2/independentLocks.feature @@ -1,6 +1,8 @@ @api @issue-1284 -Feature: independent locks - Make sure all locks are independent and don't interact with other items that have the same name +Feature: independent locks - make sure all locks are independent and don't interact with other items that have the same name + As a user + I want to lock resources independently + So that resources with same name in other parts of the file system will not be locked Background: Given user "Alice" has been created with default attributes and without skeleton files diff --git a/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature b/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature index b0e017479..5a3fd9ebb 100644 --- a/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature +++ b/tests/acceptance/features/coreApiWebdavLocks2/independentLocksShareToShares.feature @@ -1,6 +1,8 @@ @api @issue-1284 -Feature: independent locks - Make sure all locks are independent and don't interact with other items that have the same name +Feature: independent locks - make sure all locks are independent and don't interact with other items that have the same name + As a user + I want to independently lock resources shared with me + So that resources with same name in other received shares will not be locked Background: Given user "Alice" has been created with default attributes and without skeleton files diff --git a/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature b/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature index 26811a6e2..7ea61382d 100644 --- a/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature +++ b/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature @@ -1,5 +1,8 @@ @api @issue-1284 -Feature: UNLOCK locked items +Feature: Unlock locked items + As a user + I want to unlock the resources previously locked by myself + So that other users can make changes to the resources Background: Given user "Alice" has been created with default attributes and without skeleton files @@ -26,7 +29,7 @@ Feature: UNLOCK locked items | spaces | - Scenario Outline: as public unlocking a file in a share that was locked by the file owner is not possible. To unlock use the owners locktoken + Scenario Outline: public unlocking a file in a share that was locked by the file owner is not possible. To unlock use the owners locktoken Given user "Alice" has created folder "PARENT" And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" And user "Alice" has created a public link share of folder "PARENT" with change permission diff --git a/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature b/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature index 436261892..c19b342cf 100644 --- a/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature +++ b/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature @@ -1,5 +1,8 @@ @api @issue-1284 Feature: UNLOCK locked items (sharing) + As a user + I want to unlock a shared resource that has been locked by me to be restricted + So that other users cannot unlock the shared resource Background: Given these users have been created with default attributes and without skeleton files: @@ -10,7 +13,7 @@ Feature: UNLOCK locked items (sharing) And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" - Scenario Outline: as share receiver unlocking a shared file locked by the file owner is not possible. To unlock use the owners locktoken + Scenario Outline: unlocking a shared file that has been locked by the file owner is not feasible unless the owner lock tocken is used Given using DAV path And user "Alice" has locked file "PARENT/parent.txt" setting the following properties | lockscope | | @@ -34,7 +37,7 @@ Feature: UNLOCK locked items (sharing) | spaces | exclusive | /parent.txt | - Scenario Outline: as share receiver unlocking a file in a share locked by the file owner is not possible. To unlock use the owners locktoken + Scenario Outline: sharee cannot unlock a file within a shared folder when it is locked by the file owner unless the owner lock token is used Given using DAV path And user "Alice" has locked file "PARENT/parent.txt" setting the following properties | lockscope | | @@ -58,7 +61,7 @@ Feature: UNLOCK locked items (sharing) | spaces | exclusive | - Scenario Outline: as share receiver unlock a shared file + Scenario Outline: sharee unlock a shared file Given using DAV path And user "Alice" has shared file "PARENT/parent.txt" with user "Brian" And user "Brian" has accepted share "" offered by user "Alice" @@ -106,7 +109,7 @@ Feature: UNLOCK locked items (sharing) | spaces | exclusive | /parent.txt | - Scenario Outline: as owner unlocking a file in a share that was locked by the share receiver is not possible. To unlock use the receivers locktoken + Scenario Outline: unlocking a file in a shared folder, which was locked by the sharee is not possible for the owner unless the receiver's locktoken is used Given using DAV path And user "Alice" has shared folder "PARENT" with user "Brian" And user "Brian" has accepted share "/PARENT" offered by user "Alice" diff --git a/tests/acceptance/features/coreApiWebdavMove1/moveFolder.feature b/tests/acceptance/features/coreApiWebdavMove1/moveFolder.feature index a02368f72..b44b25de8 100644 --- a/tests/acceptance/features/coreApiWebdavMove1/moveFolder.feature +++ b/tests/acceptance/features/coreApiWebdavMove1/moveFolder.feature @@ -9,7 +9,7 @@ Feature: move (rename) folder And user "Alice" has been created with default attributes and without skeleton files - Scenario Outline: Renaming a folder to a backslash should return an error + Scenario Outline: renaming a folder to a backslash should return an error Given using DAV path And user "Alice" has created folder "/testshare" When user "Alice" moves folder "/testshare" to "\" using the WebDAV API @@ -27,7 +27,7 @@ Feature: move (rename) folder | spaces | - Scenario Outline: Renaming a folder beginning with a backslash should return an error + Scenario Outline: renaming a folder beginning with a backslash should return an error Given using DAV path And user "Alice" has created folder "/testshare" When user "Alice" moves folder "/testshare" to "\testshare" using the WebDAV API @@ -45,7 +45,7 @@ Feature: move (rename) folder | spaces | - Scenario Outline: Renaming a folder including a backslash encoded should return an error + Scenario Outline: renaming a folder including a backslash encoded should return an error Given using DAV path And user "Alice" has created folder "/testshare" When user "Alice" moves folder "/testshare" to "/hola\hola" using the WebDAV API @@ -63,7 +63,7 @@ Feature: move (rename) folder | spaces | - Scenario Outline: Move a folder into an other one + Scenario Outline: move a folder into an other folder Given using DAV path And user "Alice" has created folder "/testshare" And user "Alice" has created folder "/an-other-folder" @@ -84,7 +84,7 @@ Feature: move (rename) folder | spaces | - Scenario Outline: Move a folder into a nonexistent one + Scenario Outline: move a folder into a nonexistent folder Given using DAV path And user "Alice" has created folder "/testshare" When user "Alice" moves folder "/testshare" to "/not-existing/testshare" using the WebDAV API @@ -132,7 +132,7 @@ Feature: move (rename) folder | spaces | /..upload | @skipOnRevaMaster @issue-3023 - Scenario Outline: Moving a folder into a sub-folder of itself + Scenario Outline: moving a folder into a sub-folder of itself Given using DAV path And user "Alice" has created folder "PARENT" And user "Alice" has created folder "PARENT/CHILD" diff --git a/tests/acceptance/features/coreApiWebdavMove1/moveFolderToBlacklistedName.feature b/tests/acceptance/features/coreApiWebdavMove1/moveFolderToBlacklistedName.feature index 98c911c13..1038bdc50 100644 --- a/tests/acceptance/features/coreApiWebdavMove1/moveFolderToBlacklistedName.feature +++ b/tests/acceptance/features/coreApiWebdavMove1/moveFolderToBlacklistedName.feature @@ -9,7 +9,7 @@ Feature: users cannot move (rename) a folder to a blacklisted name And user "Alice" has been created with default attributes and without skeleton files - Scenario Outline: Rename a folder to a name that is banned by default + Scenario Outline: rename a folder to a name that is banned by default Given using DAV path And user "Alice" has created folder "/testshare" When user "Alice" moves folder "/testshare" to "/.htaccess" using the WebDAV API diff --git a/tests/acceptance/features/coreApiWebdavMove2/moveFile.feature b/tests/acceptance/features/coreApiWebdavMove2/moveFile.feature index 1af8b00dd..8d3da9b24 100644 --- a/tests/acceptance/features/coreApiWebdavMove2/moveFile.feature +++ b/tests/acceptance/features/coreApiWebdavMove2/moveFile.feature @@ -9,7 +9,7 @@ Feature: move (rename) file And user "Alice" has been created with default attributes and without skeleton files @smokeTest - Scenario Outline: Moving a file + Scenario Outline: moving a file Given using DAV path And user "Alice" has created folder "FOLDER" And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" @@ -29,7 +29,7 @@ Feature: move (rename) file | spaces | @smokeTest - Scenario Outline: Moving and overwriting a file + Scenario Outline: moving and overwriting a file Given using DAV path And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" And user "Alice" has uploaded file with content "ownCloud test text file 1" to "textfile1.txt" @@ -49,7 +49,7 @@ Feature: move (rename) file | spaces | - Scenario Outline: Moving (renaming) a file to be only different case + Scenario Outline: moving (renaming) a file to be only different case Given using DAV path And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" When user "Alice" moves file "/textfile0.txt" to "/TextFile0.txt" using the WebDAV API @@ -67,7 +67,7 @@ Feature: move (rename) file | spaces | @smokeTest - Scenario Outline: Moving (renaming) a file to a file with only different case to an existing file + Scenario Outline: moving (renaming) a file to a file with only different case to an existing file Given using DAV path And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" And user "Alice" has uploaded file with content "ownCloud test text file 1" to "textfile1.txt" @@ -86,7 +86,7 @@ Feature: move (rename) file | spaces | - Scenario Outline: Moving (renaming) a file to a file in a folder with only different case to an existing file + Scenario Outline: moving (renaming) a file to a file in a folder with only different case to an existing file Given using DAV path And user "Alice" has created folder "PARENT" And user "Alice" has uploaded file with content "ownCloud test text file parent" to "PARENT/parent.txt" @@ -138,7 +138,7 @@ Feature: move (rename) file | spaces | - Scenario Outline: Checking file id after a move + Scenario Outline: checking file id after a move Given using DAV path And user "Alice" has created folder "FOLDER" And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" @@ -159,7 +159,7 @@ Feature: move (rename) file | spaces | @issue-1295 - Scenario Outline: Renaming a file to a path with extension .part should not be possible + Scenario Outline: renaming a file to a path with extension .part should not be possible Given using DAV path And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "fileToRename.txt" When user "Alice" moves file "/fileToRename.txt" to "/welcome.part" using the WebDAV API @@ -278,7 +278,7 @@ Feature: move (rename) file | spaces | @smokeTest - Scenario Outline: user tries to rename a file that doesnt exist + Scenario Outline: user tries to rename a file that doesn't exist Given using DAV path When user "Alice" moves file "/doesNotExist.txt" to "/exist.txt" using the WebDAV API Then the HTTP status code should be "404" @@ -294,7 +294,7 @@ Feature: move (rename) file | spaces | - Scenario Outline: Moving a hidden file + Scenario Outline: moving a hidden file Given using DAV path And user "Alice" has created folder "FOLDER" And user "Alice" has uploaded the following files with content "hidden file" @@ -325,7 +325,7 @@ Feature: move (rename) file | spaces | - Scenario Outline: Renaming to/from a hidden file + Scenario Outline: renaming to/from a hidden file Given using DAV path And user "Alice" has uploaded the following files with content "hidden file" | path | @@ -355,7 +355,7 @@ Feature: move (rename) file | spaces | - Scenario Outline: Moving a file (deep moves with various folder and file names) + Scenario Outline: moving a file (deep moves with various folder and file names) Given using DAV path And user "Alice" has created folder "" And user "Alice" has created folder "" @@ -397,7 +397,7 @@ Feature: move (rename) file | spaces | texta | file.txt | textb | 1 | - Scenario Outline: Moving a file from a folder to the root + Scenario Outline: moving a file from a folder to the root Given using DAV path And user "Alice" has created folder "" And user "Alice" has uploaded file with content "ownCloud test text file 0" to "//" diff --git a/tests/acceptance/features/coreApiWebdavMove2/moveShareOnOcis.feature b/tests/acceptance/features/coreApiWebdavMove2/moveShareOnOcis.feature index 954a98a76..ab24a2033 100644 --- a/tests/acceptance/features/coreApiWebdavMove2/moveShareOnOcis.feature +++ b/tests/acceptance/features/coreApiWebdavMove2/moveShareOnOcis.feature @@ -1,4 +1,4 @@ -@api +@api Feature: move (rename) file As a user I want to be able to move and rename files @@ -9,7 +9,7 @@ Feature: move (rename) file And user "Alice" has been created with default attributes and without skeleton files - Scenario Outline: Moving a file into a shared folder as the sharee and as the sharer + Scenario Outline: moving a file into a shared folder as the sharee and as the sharer Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Brian" has created folder "/testshare" @@ -33,7 +33,7 @@ Feature: move (rename) file | new | Brian | /testshare | - Scenario Outline: Moving a file out of a shared folder as the sharer + Scenario Outline: moving a file out of a shared folder as the sharer Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Brian" has created folder "/testshare" @@ -55,7 +55,7 @@ Feature: move (rename) file | new | - Scenario Outline: Can not move a file out of a shared folder as the sharee + Scenario Outline: moving a file out of a shared folder as the sharee Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Brian" has created folder "/testshare" @@ -76,7 +76,7 @@ Feature: move (rename) file | new | - Scenario Outline: Moving a folder into a shared folder as the sharee and as the sharer + Scenario Outline: moving a folder into a shared folder as the sharee and as the sharer Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Brian" has created folder "/testshare" @@ -101,7 +101,7 @@ Feature: move (rename) file | new | Brian | /testshare | - Scenario Outline: Moving a folder out of a shared folder as the sharer + Scenario Outline: moving a folder out of a shared folder as the sharer Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Brian" has created the following folders @@ -126,7 +126,7 @@ Feature: move (rename) file | new | - Scenario Outline: Moving a folder out of a shared folder as the sharee + Scenario Outline: moving a folder out of a shared folder as the sharee Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Brian" has created the following folders @@ -150,7 +150,7 @@ Feature: move (rename) file | new | - Scenario Outline: Moving a file to a shared folder with no permissions + Scenario Outline: moving a file to a shared folder with no permissions Given using DAV path And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" And user "Brian" has been created with default attributes and without skeleton files @@ -170,7 +170,7 @@ Feature: move (rename) file | new | - Scenario Outline: Moving a file to overwrite a file in a shared folder with no permissions + Scenario Outline: moving a file to overwrite a file in a shared folder with no permissions Given using DAV path And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" And user "Brian" has been created with default attributes and without skeleton files @@ -192,7 +192,7 @@ Feature: move (rename) file | new | - Scenario Outline: Checking file id after a move between received shares + Scenario Outline: checking file id after a move between received shares Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Alice" has created the following folders diff --git a/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature b/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature index dd3b632d2..ccacd20a2 100644 --- a/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature +++ b/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature @@ -60,7 +60,7 @@ Feature: download file | spaces | - Scenario Outline: Get the size of a file + Scenario Outline: get the size of a file Given using DAV path And user "Alice" has uploaded file with content "This is a test file" to "test-file.txt" When user "Alice" gets the size of file "test-file.txt" using the WebDAV API @@ -78,7 +78,7 @@ Feature: download file | spaces | @issue-1316 - Scenario Outline: Get the content-length response header of a pdf file + Scenario Outline: get the content-length response header of a pdf file Given using DAV path And user "Alice" has uploaded file "filesForUpload/simple.pdf" to "/simple.pdf" When user "Alice" downloads file "/simple.pdf" using the WebDAV API @@ -97,7 +97,7 @@ Feature: download file | spaces | @issue-1316 - Scenario Outline: Get the content-length response header of an image file + Scenario Outline: get the content-length response header of an image file Given using DAV path And user "Alice" has uploaded file "filesForUpload/testavatar.png" to "/testavatar.png" When user "Alice" downloads file "/testavatar.png" using the WebDAV API @@ -116,7 +116,7 @@ Feature: download file | spaces | - Scenario Outline: Download a file with comma in the filename + Scenario Outline: download a file with comma in the filename Given using DAV path And user "Alice" has uploaded file with content "file with comma in filename" to When user "Alice" downloads file using the WebDAV API @@ -264,7 +264,7 @@ Feature: download file | spaces | @smokeTest - Scenario Outline: Downloading a file should serve security headers + Scenario Outline: downloading a file should serve security headers Given using DAV path When user "Alice" downloads file "/welcome.txt" using the WebDAV API Then the HTTP status code should be "200" diff --git a/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature b/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature index 9f1d40007..3b1c9c4d8 100644 --- a/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature +++ b/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature @@ -24,7 +24,7 @@ Feature: list files | simple-folder/simple-folder1/simple-folder2/welcome.txt | - Scenario Outline: Get the list of resources in the root folder with depth 0 + Scenario Outline: get the list of resources in the root folder with depth 0 Given using DAV path When user "Alice" lists the resources in "/" with depth "0" using the WebDAV API Then the HTTP status code should be "207" @@ -48,7 +48,7 @@ Feature: list files | spaces | - Scenario Outline: Get the list of resources in the root folder with depth 1 + Scenario Outline: get the list of resources in the root folder with depth 1 Given using DAV path When user "Alice" lists the resources in "/" with depth "1" using the WebDAV API Then the HTTP status code should be "207" @@ -74,7 +74,7 @@ Feature: list files | spaces | @depthInfinityPropfindEnabled - Scenario Outline: Get the list of resources in the root folder with depth infinity + Scenario Outline: get the list of resources in the root folder with depth infinity Given using DAV path When user "Alice" lists the resources in "/" with depth "infinity" using the WebDAV API Then the HTTP status code should be "207" @@ -103,7 +103,7 @@ Feature: list files | spaces | - Scenario Outline: Get the list of resources in a folder with depth 0 + Scenario Outline: get the list of resources in a folder with depth 0 Given using DAV path When user "Alice" lists the resources in "/simple-folder" with depth "0" using the WebDAV API Then the HTTP status code should be "207" @@ -127,7 +127,7 @@ Feature: list files | spaces | - Scenario Outline: Get the list of resources in a folder with depth 1 + Scenario Outline: get the list of resources in a folder with depth 1 Given using DAV path When user "Alice" lists the resources in "/simple-folder" with depth "1" using the WebDAV API Then the HTTP status code should be "207" @@ -155,7 +155,7 @@ Feature: list files | spaces | @depthInfinityPropfindEnabled - Scenario Outline: Get the list of resources in a folder with depth infinity + Scenario Outline: get the list of resources in a folder with depth infinity Given using DAV path When user "Alice" lists the resources in "/simple-folder" with depth "infinity" using the WebDAV API Then the HTTP status code should be "207" @@ -180,7 +180,7 @@ Feature: list files | spaces | - Scenario: Get the list of resources in a folder shared through public link with depth 0 + Scenario: get the list of resources in a folder shared through public link with depth 0 Given using new DAV path And user "Alice" has created the following folders | path | @@ -203,7 +203,7 @@ Feature: list files | /simple-folder1/simple-folder2/simple-folder3/simple-folder4 | - Scenario: Get the list of resources in a folder shared through public link with depth 1 + Scenario: get the list of resources in a folder shared through public link with depth 1 Given using new DAV path And user "Alice" has created the following folders | path | @@ -228,7 +228,7 @@ Feature: list files | /simple-folder1/simple-folder2/simple-folder3/simple-folder4 | @depthInfinityPropfindEnabled - Scenario: Get the list of resources in a folder shared through public link with depth infinity + Scenario: get the list of resources in a folder shared through public link with depth infinity Given using new DAV path And user "Alice" has created the following folders | path | @@ -251,7 +251,7 @@ Feature: list files | /simple-folder1/simple-folder2/simple-folder3/simple-folder4 | - Scenario Outline: Get the list of files in the trashbin with depth 0 + Scenario Outline: get the list of files in the trashbin with depth 0 Given using DAV path And user "Alice" has deleted the following resources | path | @@ -282,7 +282,7 @@ Feature: list files | spaces | - Scenario Outline: Get the list of files in the trashbin with depth 1 + Scenario Outline: get the list of files in the trashbin with depth 1 Given using DAV path And user "Alice" has deleted the following resources | path | @@ -315,7 +315,7 @@ Feature: list files | spaces | @depthInfinityPropfindEnabled - Scenario Outline: Get the list of files in the trashbin with depth infinity + Scenario Outline: get the list of files in the trashbin with depth infinity Given using DAV path And user "Alice" has deleted the following resources | path | @@ -346,7 +346,7 @@ Feature: list files | spaces | @depthInfinityPropfindDisabled - Scenario Outline: Get the list of resources in the root folder with depth infinity when depth infinity is not allowed + Scenario Outline: get the list of resources in the root folder with depth infinity when depth infinity is not allowed Given using DAV path When user "Alice" lists the resources in "/" with depth "infinity" using the WebDAV API Then the HTTP status code should be "412" @@ -361,7 +361,7 @@ Feature: list files | spaces | @depthInfinityPropfindDisabled - Scenario Outline: Get the list of resources in a folder shared through public link with depth infinity when depth infinity is not allowed + Scenario Outline: get the list of resources in a folder shared through public link with depth infinity when depth infinity is not allowed Given using DAV path And user "Alice" has created the following folders | path | @@ -380,7 +380,7 @@ Feature: list files | spaces | @depthInfinityPropfindDisabled - Scenario Outline: Get the list of files in the trashbin with depth infinity when depth infinity is not allowed + Scenario Outline: get the list of files in the trashbin with depth infinity when depth infinity is not allowed Given using DAV path And user "Alice" has deleted the following resources | path | diff --git a/tests/acceptance/features/coreApiWebdavOperations/propfind.feature b/tests/acceptance/features/coreApiWebdavOperations/propfind.feature index 0ac7918a9..75ca8f991 100644 --- a/tests/acceptance/features/coreApiWebdavOperations/propfind.feature +++ b/tests/acceptance/features/coreApiWebdavOperations/propfind.feature @@ -1,8 +1,11 @@ @api Feature: PROPFIND + As a user + I want to retrieve all properties of a resource + So that I can get the information about a resource @issue-751 - Scenario Outline: PROPFIND to "/remote.php/dav/(files|spaces)" + Scenario Outline: send PROPFIND request to "/remote.php/dav/(files|spaces)" Given user "Alice" has been created with default attributes and without skeleton files When user "Alice" requests "" with "PROPFIND" using basic auth Then the HTTP status code should be "405" @@ -16,7 +19,7 @@ Feature: PROPFIND | /remote.php/dav/spaces | - Scenario Outline: PROPFIND to "/remote.php/dav/(files|spaces)" with depth header + Scenario Outline: send PROPFIND request to "/remote.php/dav/(files|spaces)" with depth header Given user "Alice" has been created with default attributes and without skeleton files When user "Alice" requests "" with "PROPFIND" using basic auth and with headers | header | value | diff --git a/tests/acceptance/features/coreApiWebdavOperations/refuseAccess.feature b/tests/acceptance/features/coreApiWebdavOperations/refuseAccess.feature index 815477ec7..09efa70a3 100644 --- a/tests/acceptance/features/coreApiWebdavOperations/refuseAccess.feature +++ b/tests/acceptance/features/coreApiWebdavOperations/refuseAccess.feature @@ -8,7 +8,7 @@ Feature: refuse access Given using OCS API version "1" @smokeTest - Scenario Outline: Unauthenticated call + Scenario Outline: unauthenticated call # cannot perform with spaces WebDAV due to the absence of user Given using DAV path When an unauthenticated client connects to the DAV endpoint using the WebDAV API @@ -23,7 +23,7 @@ Feature: refuse access | new | - Scenario Outline: A disabled user cannot use webdav + Scenario Outline: disabled user cannot use webdav Given using DAV path And user "Alice" has been created with default attributes and without skeleton files And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" diff --git a/tests/acceptance/features/coreApiWebdavPreviews/previews.feature b/tests/acceptance/features/coreApiWebdavPreviews/previews.feature index 9d5293c75..98c396c72 100644 --- a/tests/acceptance/features/coreApiWebdavPreviews/previews.feature +++ b/tests/acceptance/features/coreApiWebdavPreviews/previews.feature @@ -1,5 +1,8 @@ @api @preview-extension-required Feature: previews of files downloaded through the webdav API + As a user + I want to be able to download the preview of the files + So that I can view the contents of the files Background: Given user "Alice" has been created with default attributes and without skeleton files @@ -131,7 +134,7 @@ Feature: previews of files downloaded through the webdav API And as user "Brian" the preview of "/Shares/parent.txt" with width "32" and height "32" should have been changed - Scenario: it should update the preview content if the file content is updated (content with UTF chars) + Scenario: preview content should be updated if the file content is updated (content with UTF chars) Given user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/lorem.txt" And user "Alice" has uploaded file with content "सिमसिमे पानी" to "/lorem.txt" When user "Alice" downloads the preview of "/lorem.txt" with width "32" and height "32" using the WebDAV API diff --git a/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature b/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature index 2310a35f3..7cbfa9e77 100644 --- a/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature +++ b/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature @@ -12,7 +12,7 @@ Feature: copy file And user "Alice" has created folder "/FOLDER" @smokeTest - Scenario Outline: Copying a file + Scenario Outline: copying a file Given using DAV path When user "Alice" copies file "/textfile0.txt" to "/FOLDER/textfile0.txt" using the WebDAV API Then the HTTP status code should be "201" @@ -28,7 +28,7 @@ Feature: copy file | spaces | @smokeTest - Scenario Outline: Copying and overwriting a file + Scenario Outline: copying and overwriting a file Given using DAV path When user "Alice" copies file "/textfile0.txt" to "/textfile1.txt" using the WebDAV API Then the HTTP status code should be "204" @@ -44,7 +44,7 @@ Feature: copy file | spaces | - Scenario Outline: Copying a file when 2 files exist with different case + Scenario Outline: copying a file when 2 files exist with different case Given using DAV path # "/textfile1.txt" already exists in the skeleton, make another with only case differences in the file name When user "Alice" copies file "/textfile0.txt" to "/TextFile1.txt" using the WebDAV API @@ -62,7 +62,7 @@ Feature: copy file | spaces | - Scenario Outline: Copying a file to a folder with no permissions + Scenario Outline: copying a file to a folder with no permissions Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Brian" has created folder "/testshare" @@ -81,7 +81,7 @@ Feature: copy file | new | - Scenario Outline: Copying a file to overwrite a file into a folder with no permissions + Scenario Outline: copying a file to overwrite a file into a folder with no permissions Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Brian" has created folder "/testshare" @@ -102,7 +102,7 @@ Feature: copy file | new | @issue-1345 - Scenario Outline: Copying file to a path with extension .part should not be possible + Scenario Outline: copying file to a path with extension .part should not be possible Given using DAV path When user "Alice" copies file "/textfile1.txt" to "/textfile1.part" using the WebDAV API Then the HTTP status code should be "400" @@ -567,7 +567,7 @@ Feature: copy file | spaces | - Scenario Outline: Copy file into a nonexistent folder + Scenario Outline: copy file into a nonexistent folder Given using DAV path And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "fileToCopy.txt" When user "Alice" copies file "/fileToCopy.txt" to "/not-existing-folder/fileToCopy.txt" using the WebDAV API @@ -584,7 +584,7 @@ Feature: copy file | spaces | - Scenario Outline: Copy a nonexistent file into a folder + Scenario Outline: copy a nonexistent file into a folder Given using DAV path When user "Alice" copies file "/doesNotExist.txt" to "/FOLDER/doesNotExist.txt" using the WebDAV API Then the HTTP status code should be "404" @@ -600,7 +600,7 @@ Feature: copy file | spaces | - Scenario Outline: Copy a folder into a nonexistent one + Scenario Outline: copy a folder into a nonexistent one Given using DAV path And user "Alice" has created folder "/testshare" When user "Alice" copies folder "/testshare" to "/not-existing/testshare" using the WebDAV API @@ -618,7 +618,7 @@ Feature: copy file | spaces | - Scenario Outline: Copying a file into a shared folder as the sharee + Scenario Outline: copying a file into a shared folder as the sharee Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Brian" has created folder "/testshare" @@ -638,7 +638,7 @@ Feature: copy file | new | - Scenario Outline: Copying a file into a shared folder as the sharer + Scenario Outline: copying a file into a shared folder as the sharer Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Brian" has created folder "/testshare" @@ -659,7 +659,7 @@ Feature: copy file | new | - Scenario Outline: Copying a file out of a shared folder as the sharee + Scenario Outline: copying a file out of a shared folder as the sharee Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Brian" has created folder "/testshare" @@ -681,7 +681,7 @@ Feature: copy file | new | - Scenario Outline: Copying a file out of a shared folder as the sharer + Scenario Outline: copying a file out of a shared folder as the sharer Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Brian" has created folder "/testshare" @@ -703,7 +703,7 @@ Feature: copy file | new | - Scenario Outline: Copying a hidden file + Scenario Outline: copying a hidden file Given using DAV path And user "Alice" has uploaded the following files with content "hidden file" | path | @@ -730,7 +730,7 @@ Feature: copy file | spaces | - Scenario Outline: Copying a file between shares received from different users + Scenario Outline: copying a file between shares received from different users Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Carol" has been created with default attributes and without skeleton files @@ -761,7 +761,7 @@ Feature: copy file | new | - Scenario Outline: Copying a folder between shares received from different users + Scenario Outline: copying a folder between shares received from different users Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Carol" has been created with default attributes and without skeleton files @@ -793,7 +793,7 @@ Feature: copy file | new | - Scenario Outline: Copying a file to a folder that is shared with multiple users + Scenario Outline: copying a file to a folder that is shared with multiple users Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Carol" has been created with default attributes and without skeleton files @@ -822,7 +822,7 @@ Feature: copy file | new | - Scenario Outline: Copy a folder into another one + Scenario Outline: copy a folder into another one Given using DAV path And user "Alice" has created folder "/testshare" And user "Alice" has created folder "/an-other-folder" @@ -843,7 +843,7 @@ Feature: copy file | spaces | @skipOnRevaMaster @issue-3023 - Scenario Outline: Copying a folder into a sub-folder of itself + Scenario Outline: copying a folder into a sub-folder of itself Given using DAV path And user "Alice" has created folder "/PARENT" And user "Alice" has created folder "/PARENT/CHILD" @@ -860,7 +860,7 @@ Feature: copy file | spaces | - Scenario Outline: Copying a folder with a file into another folder + Scenario Outline: copying a folder with a file into another folder Given using DAV path And user "Alice" has created folder "/FOLDER1" And user "Alice" has created folder "/FOLDER2" diff --git a/tests/acceptance/features/coreApiWebdavProperties1/createFileFolder.feature b/tests/acceptance/features/coreApiWebdavProperties1/createFileFolder.feature index 526ca36f0..e3c6bdb39 100644 --- a/tests/acceptance/features/coreApiWebdavProperties1/createFileFolder.feature +++ b/tests/acceptance/features/coreApiWebdavProperties1/createFileFolder.feature @@ -46,7 +46,7 @@ Feature: create files and folder | spaces | /new&folder | @smokeTest - Scenario Outline: Creating a folder + Scenario Outline: get resourcetype property of a folder Given using DAV path And user "Alice" has created folder "/test_folder" When user "Alice" gets the following properties of folder "/test_folder" using the WebDAV API @@ -65,7 +65,7 @@ Feature: create files and folder | spaces | - Scenario Outline: Creating a folder with special chars + Scenario Outline: get resourcetype property of a folder with special chars Given using DAV path And user "Alice" has created folder "/test_folder:5" When user "Alice" gets the following properties of folder "/test_folder:5" using the WebDAV API @@ -84,7 +84,7 @@ Feature: create files and folder | spaces | @issue-1345 - Scenario Outline: Creating a directory which contains .part should not be possible + Scenario Outline: creating a directory which contains .part should not be possible Given using DAV path When user "Alice" creates folder "/folder.with.ext.part" using the WebDAV API Then the HTTP status code should be "400" @@ -142,7 +142,7 @@ Feature: create files and folder | spaces | - Scenario Outline: Create a file + Scenario Outline: create a file Given using DAV path When user "Alice" uploads file with content "some text" to "" using the WebDAV API Then the HTTP status code should be "201" diff --git a/tests/acceptance/features/coreApiWebdavProperties1/getQuota.feature b/tests/acceptance/features/coreApiWebdavProperties1/getQuota.feature index 621c30d01..9e11eb0fd 100644 --- a/tests/acceptance/features/coreApiWebdavProperties1/getQuota.feature +++ b/tests/acceptance/features/coreApiWebdavProperties1/getQuota.feature @@ -9,7 +9,7 @@ Feature: get quota And user "Alice" has been created with default attributes and small skeleton files - Scenario Outline: Retrieving folder quota when no quota is set + Scenario Outline: retrieving folder quota when no quota is set Given using DAV path When the administrator gives unlimited quota to user "Alice" using the provisioning API Then the HTTP status code should be "200" @@ -21,7 +21,7 @@ Feature: get quota | spaces | @smokeTest - Scenario Outline: Retrieving folder quota when quota is set + Scenario Outline: retrieving folder quota when quota is set Given using DAV path When the administrator sets the quota of user "Alice" to "10 MB" using the provisioning API Then the HTTP status code should be "200" @@ -33,7 +33,7 @@ Feature: get quota | spaces | - Scenario Outline: Retrieving folder quota of shared folder with quota when no quota is set for recipient + Scenario Outline: retrieving folder quota of shared folder with quota when no quota is set for recipient Given using DAV path And user "Brian" has been created with default attributes and small skeleton files And user "Alice" has been given unlimited quota @@ -56,7 +56,7 @@ Feature: get quota | spaces | - Scenario Outline: Retrieving folder quota when quota is set and a file was uploaded + Scenario Outline: retrieving folder quota when quota is set and a file was uploaded Given using DAV path And the quota of user "Alice" has been set to "1 KB" And user "Alice" has uploaded file "/prueba.txt" of size 93 bytes @@ -72,7 +72,7 @@ Feature: get quota | spaces | - Scenario Outline: Retrieving folder quota when quota is set and a file was received + Scenario Outline: retrieving folder quota when quota is set and a file was received Given using DAV path And user "Brian" has been created with default attributes and small skeleton files And the quota of user "Brian" has been set to "1 KB" diff --git a/tests/acceptance/features/coreApiWebdavProperties1/setFileProperties.feature b/tests/acceptance/features/coreApiWebdavProperties1/setFileProperties.feature index 75a396ded..8e864b90a 100644 --- a/tests/acceptance/features/coreApiWebdavProperties1/setFileProperties.feature +++ b/tests/acceptance/features/coreApiWebdavProperties1/setFileProperties.feature @@ -9,7 +9,7 @@ Feature: set file properties And user "Alice" has been created with default attributes and without skeleton files @smokeTest @issue-1263 - Scenario Outline: Setting custom DAV property and reading it + Scenario Outline: setting custom DAV property and reading it Given using DAV path And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/testcustomprop.txt" And user "Alice" has set property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testcustomprop.txt" to "veryCustomPropValue" @@ -26,7 +26,7 @@ Feature: set file properties | spaces | @issue-1297 - Scenario Outline: Setting custom complex DAV property and reading it + Scenario Outline: setting custom complex DAV property and reading it Given using DAV path And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/testcustomprop.txt" And user "Alice" has set property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testcustomprop.txt" to "" @@ -43,7 +43,7 @@ Feature: set file properties | spaces | @issue-1263 - Scenario Outline: Setting custom DAV property and reading it after the file is renamed + Scenario Outline: setting custom DAV property and reading it after the file is renamed Given using DAV path And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/testcustompropwithmove.txt" And user "Alice" has set property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testcustompropwithmove.txt" to "valueForMovetest" @@ -61,7 +61,7 @@ Feature: set file properties | spaces | @issue-1297 - Scenario Outline: Setting custom DAV property on a shared file as an owner and reading as a recipient + Scenario Outline: setting custom DAV property on a shared file as an owner and reading as a recipient Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/testcustompropshared.txt" @@ -84,7 +84,7 @@ Feature: set file properties | spaces | @issue-1263 - Scenario Outline: Setting custom DAV property using one endpoint and reading it with other endpoint + Scenario Outline: setting custom DAV property using one endpoint and reading it with other endpoint Given using DAV path And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/testnewold.txt" And user "Alice" has set property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testnewold.txt" to "lucky" diff --git a/tests/acceptance/features/coreApiWebdavProperties2/getFileProperties.feature b/tests/acceptance/features/coreApiWebdavProperties2/getFileProperties.feature index 8e486701e..ae5070eb6 100644 --- a/tests/acceptance/features/coreApiWebdavProperties2/getFileProperties.feature +++ b/tests/acceptance/features/coreApiWebdavProperties2/getFileProperties.feature @@ -9,7 +9,7 @@ Feature: get file properties And user "Alice" has been created with default attributes and without skeleton files @smokeTest - Scenario Outline: Do a PROPFIND of various file names + Scenario Outline: user sends a PROPFIND request on various file names Given using DAV path And user "Alice" has uploaded file with content "uploaded content" to "" When user "Alice" gets the properties of file "" using the WebDAV API @@ -35,7 +35,7 @@ Feature: get file properties | spaces | s,a,m,p,l,e.txt | @issue-1296 - Scenario Outline: Do a PROPFIND of various file names + Scenario Outline: user sends a PROPFIND request on various file with '?, #, &, +' in its name Given using DAV path And user "Alice" has uploaded file with content "uploaded content" to "" When user "Alice" gets the properties of file "" using the WebDAV API @@ -62,7 +62,7 @@ Feature: get file properties | spaces | /file &2.txt | dav/spaces/%spaceid%/file &2.txt | @issue-1296 - Scenario Outline: Do a PROPFIND of various folder names + Scenario Outline: user sends a PROPFIND request on various folder names Given using DAV path And user "Alice" has created folder "" And user "Alice" has uploaded file with content "uploaded content" to "/file1.txt" @@ -101,7 +101,7 @@ Feature: get file properties | spaces | /folder &2.txt | dav/spaces/%spaceid%/folder &2.txt | - Scenario Outline: do a PROPFIND of various files inside various folders + Scenario Outline: user sends a PROPFIND request on various files inside various folders Given using DAV path And user "Alice" has created folder "" And user "Alice" has uploaded file with content "uploaded content" to "/" @@ -132,7 +132,7 @@ Feature: get file properties @issue-1259 #after fixing all issues delete this Scenario and merge with the one above - Scenario Outline: do a PROPFIND of various files inside various folders with '?' character in its name + Scenario Outline: user sends a PROPFIND request on various files inside various folders with '?' character in its name Given using DAV path And user "Alice" has created folder "/folder ?2.txt" And user "Alice" has uploaded file with content "uploaded content" to "/folder ?2.txt/file ?2.txt" @@ -150,7 +150,7 @@ Feature: get file properties | spaces | - Scenario Outline: A file that is not shared does not have a share-types property + Scenario Outline: file that is not shared does not have a share-types property Given using DAV path And user "Alice" has created folder "/test" When user "Alice" gets the following properties of folder "/test" using the WebDAV API @@ -169,7 +169,7 @@ Feature: get file properties | spaces | - Scenario Outline: A file that is shared to a user has a share-types property + Scenario Outline: file that is shared to a user has a share-types property Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And user "Alice" has created folder "/test" @@ -195,7 +195,7 @@ Feature: get file properties | spaces | - Scenario Outline: A file that is shared to a group has a share-types property + Scenario Outline: file that is shared to a group has a share-types property Given using DAV path And group "grp1" has been created And user "Alice" has created folder "/test" @@ -221,7 +221,7 @@ Feature: get file properties | spaces | - Scenario Outline: A file that is shared by link has a share-types property + Scenario Outline: file that is shared by link has a share-types property Given using DAV path And user "Alice" has created folder "/test" And user "Alice" has created a public link share with settings @@ -244,7 +244,7 @@ Feature: get file properties | spaces | - Scenario Outline: A file that is shared by user,group and link has a share-types property + Scenario Outline: file that is shared by user,group and link has a share-types property Given using DAV path And user "Brian" has been created with default attributes and without skeleton files And group "grp1" has been created @@ -281,7 +281,7 @@ Feature: get file properties | spaces | @smokeTest @issue-2809 - Scenario Outline: Retrieving private link + Scenario Outline: retrieving a private link Given using DAV path And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/somefile.txt" When user "Alice" gets the following properties of file "/somefile.txt" using the WebDAV API @@ -300,7 +300,7 @@ Feature: get file properties | spaces | - Scenario Outline: Do a PROPFIND to a nonexistent URL + Scenario Outline: user sends a PROPFIND request on a nonexistent URL When user "Alice" requests "" with "PROPFIND" using basic auth Then the HTTP status code should be "404" And the value of the item "/d:error/s:message" in the response about user "Alice" should be "" or "" @@ -383,7 +383,7 @@ Feature: get file properties | spaces | - Scenario Outline: Propfind the last modified date of a folder using webdav api + Scenario Outline: user tries to get the last modified date of a folder using webdav api Given using DAV path And user "Alice" has created folder "/test" When user "Alice" gets the following properties of folder "/test" using the WebDAV API @@ -402,7 +402,7 @@ Feature: get file properties | spaces | - Scenario Outline: Propfind the content type of a folder using webdav api + Scenario Outline: user tries to get the content type of a folder using webdav api Given using DAV path And user "Alice" has created folder "/test" When user "Alice" gets the following properties of folder "/test" using the WebDAV API @@ -421,7 +421,7 @@ Feature: get file properties | spaces | - Scenario Outline: Propfind the content type of a file using webdav api + Scenario Outline: user tries to get the content type of a file using webdav api Given using DAV path And user "Alice" has uploaded file with content "uploaded content" to "file.txt" When user "Alice" gets the following properties of folder "file.txt" using the WebDAV API @@ -440,7 +440,7 @@ Feature: get file properties | spaces | - Scenario Outline: Propfind the etag of a file using webdav api + Scenario Outline: user tries to get the etag of a file using webdav api Given using DAV path And user "Alice" has uploaded file with content "uploaded content" to "file.txt" When user "Alice" gets the following properties of folder "file.txt" using the WebDAV API @@ -459,7 +459,7 @@ Feature: get file properties | spaces | - Scenario Outline: Propfind the resource type of a file using webdav api + Scenario Outline: user tries to get the resource type of a file using webdav api Given using DAV path And user "Alice" has uploaded file with content "uploaded content" to "file.txt" When user "Alice" gets the following properties of folder "file.txt" using the WebDAV API @@ -478,7 +478,7 @@ Feature: get file properties | spaces | - Scenario Outline: Propfind the size of a file using webdav api + Scenario Outline: user tries to get the size of a file using webdav api Given using DAV path And user "Alice" has uploaded file with content "uploaded content" to "file.txt" When user "Alice" gets the following properties of folder "file.txt" using the WebDAV API @@ -497,7 +497,7 @@ Feature: get file properties | spaces | - Scenario Outline: Propfind the size of a folder using webdav api + Scenario Outline: user tries to get the size of a folder using webdav api Given using DAV path And user "Alice" has created folder "/test" When user "Alice" gets the following properties of folder "/test" using the WebDAV API @@ -516,7 +516,7 @@ Feature: get file properties | spaces | - Scenario Outline: Propfind the file id of a file using webdav api + Scenario Outline: user tries to get the file id of a file using webdav api Given using DAV path And user "Alice" has uploaded file with content "uploaded content" to "file.txt" When user "Alice" gets the following properties of folder "file.txt" using the WebDAV API @@ -535,7 +535,7 @@ Feature: get file properties | spaces | - Scenario Outline: Propfind the file id of a folder using webdav api + Scenario Outline: user tries to get the file id of a folder using webdav api Given using DAV path And user "Alice" has created folder "/test" When user "Alice" gets the following properties of folder "/test" using the WebDAV API @@ -554,7 +554,7 @@ Feature: get file properties | spaces | - Scenario Outline: Propfind the owner display name of a file using webdav api + Scenario Outline: user tries to get the owner display name of a file using webdav api Given using DAV path And user "Alice" has uploaded file with content "uploaded content" to "file.txt" When user "Alice" gets the following properties of file "file.txt" using the WebDAV API @@ -573,7 +573,7 @@ Feature: get file properties | spaces | - Scenario Outline: Propfind the owner display name of a folder using webdav api + Scenario Outline: user tries to get the owner display name of a folder using webdav api Given using DAV path And user "Alice" has created folder "/test" When user "Alice" gets the following properties of folder "/test" using the WebDAV API @@ -592,7 +592,7 @@ Feature: get file properties | spaces | - Scenario Outline: Propfind the permissions on a file using webdav api + Scenario Outline: user tries to get the permissions on a file using webdav api Given using DAV path And user "Alice" has uploaded file with content "uploaded content" to "file.txt" When user "Alice" gets the following properties of folder "file.txt" using the WebDAV API @@ -611,7 +611,7 @@ Feature: get file properties | spaces | - Scenario Outline: Propfind the permissions on a folder using webdav api + Scenario Outline: user tries to get the permissions on a folder using webdav api Given using DAV path And user "Alice" has created folder "/test" When user "Alice" gets the following properties of folder "/test" using the WebDAV API @@ -630,7 +630,7 @@ Feature: get file properties | spaces | - Scenario Outline: propfind the size of a folder with files using webdav api + Scenario Outline: user tries to get the size of a folder with files using webdav api Given using DAV path And user "Alice" has created folder "/test" And user "Alice" has uploaded file with content "a" to "/test/file.txt" diff --git a/tests/acceptance/features/coreApiWebdavUpload1/uploadFile.feature b/tests/acceptance/features/coreApiWebdavUpload1/uploadFile.feature index 1cc07fb6b..3aef89d8c 100644 --- a/tests/acceptance/features/coreApiWebdavUpload1/uploadFile.feature +++ b/tests/acceptance/features/coreApiWebdavUpload1/uploadFile.feature @@ -167,7 +167,7 @@ Feature: upload file | spaces | @issue-1345 - Scenario Outline: Uploading file to path with extension .part should not be possible + Scenario Outline: uploading file to path with extension .part should not be possible Given using DAV path When user "Alice" uploads file "filesForUpload/textfile.txt" to "/textfile.part" using the WebDAV API Then the HTTP status code should be "400" diff --git a/tests/acceptance/features/coreApiWebdavUpload2/uploadFileUsingOldChunking.feature b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileUsingOldChunking.feature index 279d0e7c0..aa9378e99 100644 --- a/tests/acceptance/features/coreApiWebdavUpload2/uploadFileUsingOldChunking.feature +++ b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileUsingOldChunking.feature @@ -9,7 +9,7 @@ Feature: upload file using old chunking And user "Alice" has been created with default attributes and without skeleton files - Scenario Outline: Upload chunked file asc + Scenario Outline: upload chunked file in ascending order Given using DAV path When user "Alice" uploads the following "3" chunks to "/myChunkedFile.txt" with old chunking and using the WebDAV API | number | content | @@ -31,7 +31,7 @@ Feature: upload file using old chunking | spaces | - Scenario Outline: Upload chunked file desc + Scenario Outline: upload chunked file in descending order Given using DAV path When user "Alice" uploads the following "3" chunks to "/myChunkedFile.txt" with old chunking and using the WebDAV API | number | content | @@ -51,7 +51,7 @@ Feature: upload file using old chunking | spaces | - Scenario Outline: Upload chunked file random + Scenario Outline: upload chunked file in random order Given using DAV path When user "Alice" uploads the following "3" chunks to "/myChunkedFile.txt" with old chunking and using the WebDAV API | number | content | @@ -71,7 +71,7 @@ Feature: upload file using old chunking | spaces | - Scenario Outline: Checking file id after a move overwrite using old chunking endpoint + Scenario Outline: checking file id after a move overwrite using old chunking endpoint Given using DAV path And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" And user "Alice" has copied file "/textfile0.txt" to "/existingFile.txt" @@ -98,7 +98,7 @@ Feature: upload file using old chunking # This smokeTest scenario does ordinary checks for chunked upload, # without adjusting the log level. This allows it to run in test environments # where the log level has been fixed and cannot be changed. - Scenario Outline: Chunked upload files with difficult name + Scenario Outline: chunked upload files with difficult name Given using DAV path When user "Alice" uploads file "filesForUpload/textfile.txt" to "/" in 3 chunks using the WebDAV API Then the HTTP status code should be "201" @@ -121,7 +121,7 @@ Feature: upload file using old chunking | spaces | 0 | - Scenario Outline: Upload chunked file with old chunking with lengthy filenames + Scenario Outline: upload chunked file with old chunking with lengthy filenames Given using DAV path When user "Alice" uploads the following chunks to "नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-12345678910.txt" with old chunking and using the WebDAV API | number | content | diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature index ac143ae02..06e9623a4 100644 --- a/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature @@ -1,11 +1,14 @@ @api Feature: checksums + As a user + I want to upload resources with checksum + So that the integrity of the uploaded resources is maintained Background: Given user "Alice" has been created with default attributes and without skeleton files - Scenario Outline: Uploading a file with checksum should work + Scenario Outline: uploading a file with checksum should work Given using DAV path And user "Alice" has created a new TUS resource on the WebDAV API with these headers: | Upload-Length | 5 | @@ -28,7 +31,7 @@ Feature: checksums | spaces | SHA1 8cb2237d0679ca88db6464eac60da96345513964 | - Scenario Outline: Uploading a file with checksum should return the checksum in the propfind + Scenario Outline: uploading a file with checksum should return the checksum in the propfind Given using DAV path And user "Alice" has created a new TUS resource on the WebDAV API with these headers: | Upload-Length | 5 | @@ -49,7 +52,7 @@ Feature: checksums | spaces | - Scenario Outline: Uploading a file with checksum should return the checksum in the download header + Scenario Outline: uploading a file with checksum should return the checksum in the download header Given using DAV path And user "Alice" has created a new TUS resource on the WebDAV API with these headers: | Upload-Length | 5 | @@ -70,7 +73,7 @@ Feature: checksums | spaces | - Scenario Outline: Uploading a file with incorrect checksum should not work + Scenario Outline: uploading a file with incorrect checksum should not work Given using DAV path And user "Alice" has created a new TUS resource on the WebDAV API with these headers: | Upload-Length | 5 | @@ -93,7 +96,7 @@ Feature: checksums | spaces | SHA1 8cb2237d0679ca88db6464eac60da96345513963 | - Scenario Outline: Uploading a chunked file with correct checksum should work + Scenario Outline: uploading a chunked file with correct checksum should work Given using DAV path And user "Alice" has created a new TUS resource on the WebDAV API with these headers: | Upload-Length | 10 | @@ -114,7 +117,7 @@ Feature: checksums | spaces | - Scenario Outline: Uploading a chunked file with correct checksum should return the checksum in the propfind + Scenario Outline: uploading a chunked file with correct checksum should return the checksum in the propfind Given using DAV path And user "Alice" has created a new TUS resource on the WebDAV API with these headers: | Upload-Length | 10 | @@ -136,7 +139,7 @@ Feature: checksums | spaces | - Scenario Outline: Uploading a chunked file with checksum should return the checksum in the download header + Scenario Outline: uploading a chunked file with checksum should return the checksum in the download header Given using DAV path And user "Alice" has created a new TUS resource on the WebDAV API with these headers: | Upload-Length | 10 | @@ -158,7 +161,7 @@ Feature: checksums | spaces | - Scenario Outline: Uploading second chunk of file with incorrect checksum should not work + Scenario Outline: uploading second chunk of file with incorrect checksum should not work Given using DAV path And user "Alice" has created a new TUS resource on the WebDAV API with these headers: | Upload-Length | 10 | @@ -179,7 +182,7 @@ Feature: checksums | spaces | - Scenario Outline: Uploading a file with correct checksum and overwriting an existing file should return the checksum for new data in the propfind + Scenario Outline: uploading a file with correct checksum and overwriting an existing file should return the checksum for new data in the propfind Given using DAV path And user "Alice" has created a new TUS resource on the WebDAV API with these headers: | Upload-Length | 10 | @@ -208,7 +211,7 @@ Feature: checksums | spaces | SHA1 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d | - Scenario Outline: Uploading a file with correct checksum and overwriting an existing file with invalid checksum should not work + Scenario Outline: uploading a file with correct checksum and overwriting an existing file with invalid checksum should not work Given using DAV path And user "Alice" has created a new TUS resource on the WebDAV API with these headers: | Upload-Length | 10 | @@ -235,7 +238,7 @@ Feature: checksums | spaces | SHA1 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434a | - Scenario Outline: Overwriting an existing file with new data and checksum should return the checksum of new data in the propfind + Scenario Outline: overwriting an existing file with new data and checksum should return the checksum of new data in the propfind Given using DAV path And user "Alice" has created a new TUS resource on the WebDAV API with these headers: | Upload-Length | 5 | @@ -264,7 +267,7 @@ Feature: checksums | spaces | SHA1 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d | - Scenario Outline: Overwriting an existing file with new data and invalid checksum should not work + Scenario Outline: overwriting an existing file with new data and invalid checksum should not work Given using DAV path And user "Alice" has created a new TUS resource on the WebDAV API with these headers: | Upload-Length | 5 | diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/creationWithUploadExtension.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/creationWithUploadExtension.feature index 6bcc82791..65cabe6e2 100644 --- a/tests/acceptance/features/coreApiWebdavUploadTUS/creationWithUploadExtension.feature +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/creationWithUploadExtension.feature @@ -1,5 +1,8 @@ @api Feature: tests of the creation extension see https://tus.io/protocols/resumable-upload.html#creation-with-upload + As a user + I want to be able to include parts of upload while creating resources + So that I can provide basic information about the resources to the server Background: Given user "Alice" has been created with default attributes and without skeleton files diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/lowLevelCreationExtension.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/lowLevelCreationExtension.feature index 78b9d359d..3ccafad69 100644 --- a/tests/acceptance/features/coreApiWebdavUploadTUS/lowLevelCreationExtension.feature +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/lowLevelCreationExtension.feature @@ -1,5 +1,8 @@ @api Feature: low level tests of the creation extension see https://tus.io/protocols/resumable-upload.html#creation + As a user + I want to able to create resources + So that I can manage my resources Background: Given user "Alice" has been created with default attributes and without skeleton files diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/lowLevelUpload.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/lowLevelUpload.feature index b2c9b2a0b..fb015b2ce 100644 --- a/tests/acceptance/features/coreApiWebdavUploadTUS/lowLevelUpload.feature +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/lowLevelUpload.feature @@ -1,5 +1,8 @@ @api Feature: low level tests for upload of chunks + As a user + I want to be able to upload resources in chunks + So that I can manage my resources Background: Given user "Alice" has been created with default attributes and without skeleton files diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature index b19ce14b9..8f036896f 100644 --- a/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature @@ -1,5 +1,8 @@ @api Feature: OPTIONS request + As a user + I want to check OPTIONS request + So that I can get information about communication options for target resource Background: Given user "Alice" has been created with default attributes and without skeleton files diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature index 6afc36a05..77cc56b3d 100644 --- a/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature @@ -75,7 +75,7 @@ Feature: upload file | spaces | /?fi=le&%#2 . txt | # %ab ab?=ed | - Scenario Outline: Upload chunked file with TUS + Scenario Outline: upload chunked file with TUS Given using DAV path When user "Alice" uploads file with content "uploaded content" in 3 chunks to "/myChunkedFile.txt" using the TUS protocol on the WebDAV API Then the content of file "/myChunkedFile.txt" for user "Alice" should be "uploaded content" @@ -90,7 +90,7 @@ Feature: upload file | spaces | - Scenario Outline: Upload 1 byte chunks with TUS + Scenario Outline: upload 1 byte chunks with TUS Given using DAV path When user "Alice" uploads file with content "0123456789" in 10 chunks to "/myChunkedFile.txt" using the TUS protocol on the WebDAV API Then the content of file "/myChunkedFile.txt" for user "Alice" should be "0123456789" @@ -105,7 +105,7 @@ Feature: upload file | spaces | - Scenario Outline: Upload to overwriting a file + Scenario Outline: upload to overwriting a file Given using DAV path And user "Alice" has uploaded file with content "original content" to "textfile.txt" When user "Alice" uploads file with content "overwritten content" to "textfile.txt" using the TUS protocol on the WebDAV API diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToMoveFolder.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToMoveFolder.feature index 4b14d8cac..16a447afc 100644 --- a/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToMoveFolder.feature +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToMoveFolder.feature @@ -8,7 +8,7 @@ Feature: move folders Given user "Alice" has been created with default attributes and without skeleton files - Scenario Outline: Uploading file into a moved folder + Scenario Outline: uploading file into a moved folder Given using DAV path And user "Alice" has created folder "/test" And user "Alice" has created folder "/test-moved" diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature index b6ff91fe7..95fc18bdb 100644 --- a/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature @@ -1,6 +1,8 @@ -@api - +@api Feature: upload file to shared folder + As a user + I want to upload files on a shared folder + So that other user with access on the shared folder can access the resource Background: Given these users have been created with default attributes and without skeleton files: @@ -9,7 +11,7 @@ Feature: upload file to shared folder | Brian | - Scenario Outline: Uploading file to a received share folder + Scenario Outline: uploading file to a received share folder Given using DAV path And user "Alice" has created folder "/FOLDER" And user "Alice" has shared folder "/FOLDER" with user "Brian" @@ -23,7 +25,7 @@ Feature: upload file to shared folder | new | - Scenario Outline: Uploading file to a user read/write share folder works + Scenario Outline: uploading file to a user read/write share folder works Given using DAV path And user "Alice" has created folder "/FOLDER" And user "Alice" has shared folder "/FOLDER" with user "Brian" with permissions "change" @@ -37,7 +39,7 @@ Feature: upload file to shared folder | new | - Scenario Outline: Uploading a file into a group share as share receiver + Scenario Outline: uploading a file into a group share as share receiver Given using DAV path And group "grp1" has been created And user "Brian" has been added to group "grp1" @@ -53,7 +55,7 @@ Feature: upload file to shared folder | new | - Scenario Outline: Overwrite file to a received share folder + Scenario Outline: overwrite file to a received share folder Given using DAV path And user "Alice" has created folder "/FOLDER" And user "Alice" has uploaded file with content "original content" to "/FOLDER/textfile.txt" @@ -81,7 +83,7 @@ Feature: upload file to shared folder | new | - Scenario Outline: Upload a file to shared folder with checksum should return the checksum in the propfind for sharee + Scenario Outline: upload a file to shared folder with checksum should return the checksum in the propfind for sharee Given using DAV path And user "Alice" has created folder "/FOLDER" And user "Alice" has shared folder "/FOLDER" with user "Brian" @@ -100,7 +102,7 @@ Feature: upload file to shared folder | new | - Scenario Outline: Upload a file to shared folder with checksum should return the checksum in the download header for sharee + Scenario Outline: upload a file to shared folder with checksum should return the checksum in the download header for sharee Given using DAV path And user "Alice" has created folder "/FOLDER" And user "Alice" has shared folder "/FOLDER" with user "Brian" @@ -119,7 +121,7 @@ Feature: upload file to shared folder | new | - Scenario Outline: Sharer shares a file with correct checksum should return the checksum in the propfind for sharee + Scenario Outline: sharer shares a file with correct checksum should return the checksum in the propfind for sharee Given using DAV path And user "Alice" has created a new TUS resource on the WebDAV API with these headers: | Upload-Length | 5 | @@ -137,7 +139,7 @@ Feature: upload file to shared folder | new | - Scenario Outline: Sharer shares a file with correct checksum should return the checksum in the download header for sharee + Scenario Outline: sharer shares a file with correct checksum should return the checksum in the download header for sharee Given using DAV path And user "Alice" has created a new TUS resource on the WebDAV API with these headers: | Upload-Length | 5 | @@ -155,7 +157,7 @@ Feature: upload file to shared folder | new | - Scenario Outline: Sharee uploads a file to a received share folder with correct checksum + Scenario Outline: sharee uploads a file to a received share folder with correct checksum Given using DAV path And user "Alice" has created folder "/FOLDER" And user "Alice" has shared folder "/FOLDER" with user "Brian" @@ -175,7 +177,7 @@ Feature: upload file to shared folder | new | - Scenario Outline: Sharee uploads a file to a received share folder with wrong checksum should not work + Scenario Outline: sharee uploads a file to a received share folder with wrong checksum should not work Given using DAV path And user "Alice" has created folder "/FOLDER" And user "Alice" has shared folder "/FOLDER" with user "Brian" @@ -194,7 +196,7 @@ Feature: upload file to shared folder | new | - Scenario Outline: Sharer uploads a file to shared folder with wrong correct checksum should not work + Scenario Outline: sharer uploads a file to shared folder with wrong correct checksum should not work Given using DAV path And user "Alice" has created folder "/FOLDER" And user "Alice" has shared folder "/FOLDER" with user "Brian" @@ -213,7 +215,7 @@ Feature: upload file to shared folder | new | - Scenario Outline: Sharer uploads a chunked file with correct checksum and share it with sharee should work + Scenario Outline: sharer uploads a chunked file with correct checksum and share it with sharee should work Given using DAV path And user "Alice" has created a new TUS resource on the WebDAV API with these headers: | Upload-Length | 10 | @@ -231,7 +233,7 @@ Feature: upload file to shared folder | new | - Scenario Outline: Sharee uploads a chunked file with correct checksum to a received share folder should work + Scenario Outline: sharee uploads a chunked file with correct checksum to a received share folder should work Given using DAV path And user "Alice" has created folder "/FOLDER" And user "Alice" has shared folder "/FOLDER" with user "Brian" @@ -252,7 +254,7 @@ Feature: upload file to shared folder | new | - Scenario Outline: Sharer uploads a file with checksum and as a sharee overwrites the shared file with new data and correct checksum + Scenario Outline: sharer uploads a file with checksum and as a sharee overwrites the shared file with new data and correct checksum Given using DAV path And user "Alice" has created a new TUS resource on the WebDAV API with these headers: | Upload-Length | 16 | @@ -273,7 +275,7 @@ Feature: upload file to shared folder | new | - Scenario Outline: Sharer uploads a file with checksum and as a sharee overwrites the shared file with new data and invalid checksum + Scenario Outline: sharer uploads a file with checksum and as a sharee overwrites the shared file with new data and invalid checksum Given using DAV path And user "Alice" has created a new TUS resource on the WebDAV API with these headers: | Upload-Length | 16 | diff --git a/tests/acceptance/filesForUpload/'single'quotes.txt b/tests/acceptance/filesForUpload/'single'quotes.txt deleted file mode 100644 index 96abcb4f0..000000000 --- a/tests/acceptance/filesForUpload/'single'quotes.txt +++ /dev/null @@ -1,9 +0,0 @@ -a file with a single quote i its name - -Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, -totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae -dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, -sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est -qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora -incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum -exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? \ No newline at end of file diff --git a/tests/acceptance/filesForUpload/filesWithVirus/eicar.com b/tests/acceptance/filesForUpload/filesWithVirus/eicar.com new file mode 100644 index 000000000..a2463df6d --- /dev/null +++ b/tests/acceptance/filesForUpload/filesWithVirus/eicar.com @@ -0,0 +1 @@ +X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H* \ No newline at end of file diff --git a/tests/acceptance/filesForUpload/filesWithVirus/eicar_com.zip b/tests/acceptance/filesForUpload/filesWithVirus/eicar_com.zip new file mode 100644 index 000000000..e7e8cefb2 Binary files /dev/null and b/tests/acceptance/filesForUpload/filesWithVirus/eicar_com.zip differ diff --git a/tests/acceptance/filesForUpload/new-'single'quotes.txt b/tests/acceptance/filesForUpload/new-'single'quotes.txt deleted file mode 100644 index 603f2187a..000000000 --- a/tests/acceptance/filesForUpload/new-'single'quotes.txt +++ /dev/null @@ -1,9 +0,0 @@ -a file with a single quote i its name, but different to the original one - -Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, -totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae -dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, -sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est -qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora -incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum -exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? \ No newline at end of file diff --git a/vendor-bin/behat/composer.json b/vendor-bin/behat/composer.json index 2da5e9ae2..ba9779e16 100644 --- a/vendor-bin/behat/composer.json +++ b/vendor-bin/behat/composer.json @@ -15,13 +15,13 @@ "ciaranmcnulty/behat-stepthroughextension" : "dev-master", "rdx/behat-variables": "^1.2", "sensiolabs/behat-page-object-extension": "^2.3", - "symfony/translation": "^4.4", + "symfony/translation": "^5.4", "sabre/xml": "^2.2", - "guzzlehttp/guzzle": "^7.5", + "guzzlehttp/guzzle": "^7.7", "phpunit/phpunit": "^9.6", "laminas/laminas-ldap": "^2.15", "ankitpokhrel/tus-php": "^2.3", - "wapmorgan/unified-archive": "^1.1.3", + "wapmorgan/unified-archive": "^1.1.10", "helmich/phpunit-json-assert": "^3.4" } } diff --git a/vendor/github.com/BurntSushi/toml/decode.go b/vendor/github.com/BurntSushi/toml/decode.go index 0ca1dc4fe..4d38f3bfc 100644 --- a/vendor/github.com/BurntSushi/toml/decode.go +++ b/vendor/github.com/BurntSushi/toml/decode.go @@ -91,7 +91,7 @@ const ( // UnmarshalText method. See the Unmarshaler example for a demonstration with // email addresses. // -// ### Key mapping +// # Key mapping // // TOML keys can map to either keys in a Go map or field names in a Go struct. // The special `toml` struct tag can be used to map TOML keys to struct fields @@ -248,7 +248,7 @@ func (md *MetaData) unify(data interface{}, rv reflect.Value) error { case reflect.Bool: return md.unifyBool(data, rv) case reflect.Interface: - if rv.NumMethod() > 0 { // Only support empty interfaces are supported. + if rv.NumMethod() > 0 { /// Only empty interfaces are supported. return md.e("unsupported type %s", rv.Type()) } return md.unifyAnything(data, rv) diff --git a/vendor/github.com/BurntSushi/toml/deprecated.go b/vendor/github.com/BurntSushi/toml/deprecated.go index c6af3f239..b9e309717 100644 --- a/vendor/github.com/BurntSushi/toml/deprecated.go +++ b/vendor/github.com/BurntSushi/toml/deprecated.go @@ -5,17 +5,25 @@ import ( "io" ) +// TextMarshaler is an alias for encoding.TextMarshaler. +// // Deprecated: use encoding.TextMarshaler type TextMarshaler encoding.TextMarshaler +// TextUnmarshaler is an alias for encoding.TextUnmarshaler. +// // Deprecated: use encoding.TextUnmarshaler type TextUnmarshaler encoding.TextUnmarshaler +// PrimitiveDecode is an alias for MetaData.PrimitiveDecode(). +// // Deprecated: use MetaData.PrimitiveDecode. func PrimitiveDecode(primValue Primitive, v interface{}) error { md := MetaData{decoded: make(map[string]struct{})} return md.unify(primValue.undecoded, rvalue(v)) } +// DecodeReader is an alias for NewDecoder(r).Decode(v). +// // Deprecated: use NewDecoder(reader).Decode(&value). func DecodeReader(r io.Reader, v interface{}) (MetaData, error) { return NewDecoder(r).Decode(v) } diff --git a/vendor/github.com/BurntSushi/toml/encode.go b/vendor/github.com/BurntSushi/toml/encode.go index 930e1d521..9cd25d757 100644 --- a/vendor/github.com/BurntSushi/toml/encode.go +++ b/vendor/github.com/BurntSushi/toml/encode.go @@ -136,7 +136,8 @@ func NewEncoder(w io.Writer) *Encoder { // document. func (enc *Encoder) Encode(v interface{}) error { rv := eindirect(reflect.ValueOf(v)) - if err := enc.safeEncode(Key([]string{}), rv); err != nil { + err := enc.safeEncode(Key([]string{}), rv) + if err != nil { return err } return enc.w.Flush() @@ -457,6 +458,16 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) { frv := eindirect(rv.Field(i)) + if is32Bit { + // Copy so it works correct on 32bit archs; not clear why this + // is needed. See #314, and https://www.reddit.com/r/golang/comments/pnx8v4 + // This also works fine on 64bit, but 32bit archs are somewhat + // rare and this is a wee bit faster. + copyStart := make([]int, len(start)) + copy(copyStart, start) + start = copyStart + } + // Treat anonymous struct fields with tag names as though they are // not anonymous, like encoding/json does. // @@ -471,17 +482,7 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) { if typeIsTable(tomlTypeOfGo(frv)) { fieldsSub = append(fieldsSub, append(start, f.Index...)) } else { - // Copy so it works correct on 32bit archs; not clear why this - // is needed. See #314, and https://www.reddit.com/r/golang/comments/pnx8v4 - // This also works fine on 64bit, but 32bit archs are somewhat - // rare and this is a wee bit faster. - if is32Bit { - copyStart := make([]int, len(start)) - copy(copyStart, start) - fieldsDirect = append(fieldsDirect, append(copyStart, f.Index...)) - } else { - fieldsDirect = append(fieldsDirect, append(start, f.Index...)) - } + fieldsDirect = append(fieldsDirect, append(start, f.Index...)) } } } @@ -490,24 +491,27 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) { writeFields := func(fields [][]int) { for _, fieldIndex := range fields { fieldType := rt.FieldByIndex(fieldIndex) - fieldVal := eindirect(rv.FieldByIndex(fieldIndex)) - - if isNil(fieldVal) { /// Don't write anything for nil fields. - continue - } + fieldVal := rv.FieldByIndex(fieldIndex) opts := getOptions(fieldType.Tag) if opts.skip { continue } + if opts.omitempty && isEmpty(fieldVal) { + continue + } + + fieldVal = eindirect(fieldVal) + + if isNil(fieldVal) { /// Don't write anything for nil fields. + continue + } + keyName := fieldType.Name if opts.name != "" { keyName = opts.name } - if opts.omitempty && enc.isEmpty(fieldVal) { - continue - } if opts.omitzero && isZero(fieldVal) { continue } @@ -649,7 +653,7 @@ func isZero(rv reflect.Value) bool { return false } -func (enc *Encoder) isEmpty(rv reflect.Value) bool { +func isEmpty(rv reflect.Value) bool { switch rv.Kind() { case reflect.Array, reflect.Slice, reflect.Map, reflect.String: return rv.Len() == 0 @@ -664,13 +668,15 @@ func (enc *Encoder) isEmpty(rv reflect.Value) bool { // type b struct{ s []string } // s := a{field: b{s: []string{"AAA"}}} for i := 0; i < rv.NumField(); i++ { - if !enc.isEmpty(rv.Field(i)) { + if !isEmpty(rv.Field(i)) { return false } } return true case reflect.Bool: return !rv.Bool() + case reflect.Ptr: + return rv.IsNil() } return false } @@ -693,8 +699,11 @@ func (enc *Encoder) newline() { // v v v v vv // key = {k = 1, k2 = 2} func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) { + /// Marshaler used on top-level document; call eElement() to just call + /// Marshal{TOML,Text}. if len(key) == 0 { - encPanic(errNoKey) + enc.eElement(val) + return } enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1)) enc.eElement(val) diff --git a/vendor/github.com/BurntSushi/toml/error.go b/vendor/github.com/BurntSushi/toml/error.go index f4f390e64..efd68865b 100644 --- a/vendor/github.com/BurntSushi/toml/error.go +++ b/vendor/github.com/BurntSushi/toml/error.go @@ -84,7 +84,7 @@ func (pe ParseError) Error() string { pe.Position.Line, pe.LastKey, msg) } -// ErrorWithUsage() returns the error with detailed location context. +// ErrorWithPosition returns the error with detailed location context. // // See the documentation on [ParseError]. func (pe ParseError) ErrorWithPosition() string { @@ -124,7 +124,7 @@ func (pe ParseError) ErrorWithPosition() string { return b.String() } -// ErrorWithUsage() returns the error with detailed location context and usage +// ErrorWithUsage returns the error with detailed location context and usage // guidance. // // See the documentation on [ParseError]. diff --git a/vendor/github.com/BurntSushi/toml/lex.go b/vendor/github.com/BurntSushi/toml/lex.go index d4d70871d..3545a6ad6 100644 --- a/vendor/github.com/BurntSushi/toml/lex.go +++ b/vendor/github.com/BurntSushi/toml/lex.go @@ -46,12 +46,13 @@ func (p Position) String() string { } type lexer struct { - input string - start int - pos int - line int - state stateFn - items chan item + input string + start int + pos int + line int + state stateFn + items chan item + tomlNext bool // Allow for backing up up to 4 runes. This is necessary because TOML // contains 3-rune tokens (""" and '''). @@ -87,13 +88,14 @@ func (lx *lexer) nextItem() item { } } -func lex(input string) *lexer { +func lex(input string, tomlNext bool) *lexer { lx := &lexer{ - input: input, - state: lexTop, - items: make(chan item, 10), - stack: make([]stateFn, 0, 10), - line: 1, + input: input, + state: lexTop, + items: make(chan item, 10), + stack: make([]stateFn, 0, 10), + line: 1, + tomlNext: tomlNext, } return lx } @@ -408,7 +410,7 @@ func lexTableNameEnd(lx *lexer) stateFn { // Lexes only one part, e.g. only 'a' inside 'a.b'. func lexBareName(lx *lexer) stateFn { r := lx.next() - if isBareKeyChar(r) { + if isBareKeyChar(r, lx.tomlNext) { return lexBareName } lx.backup() @@ -618,6 +620,9 @@ func lexInlineTableValue(lx *lexer) stateFn { case isWhitespace(r): return lexSkip(lx, lexInlineTableValue) case isNL(r): + if lx.tomlNext { + return lexSkip(lx, lexInlineTableValue) + } return lx.errorPrevLine(errLexInlineTableNL{}) case r == '#': lx.push(lexInlineTableValue) @@ -640,6 +645,9 @@ func lexInlineTableValueEnd(lx *lexer) stateFn { case isWhitespace(r): return lexSkip(lx, lexInlineTableValueEnd) case isNL(r): + if lx.tomlNext { + return lexSkip(lx, lexInlineTableValueEnd) + } return lx.errorPrevLine(errLexInlineTableNL{}) case r == '#': lx.push(lexInlineTableValueEnd) @@ -648,6 +656,9 @@ func lexInlineTableValueEnd(lx *lexer) stateFn { lx.ignore() lx.skip(isWhitespace) if lx.peek() == '}' { + if lx.tomlNext { + return lexInlineTableValueEnd + } return lx.errorf("trailing comma not allowed in inline tables") } return lexInlineTableValue @@ -770,8 +781,8 @@ func lexRawString(lx *lexer) stateFn { } } -// lexMultilineRawString consumes a raw string. Nothing can be escaped in such -// a string. It assumes that the beginning ''' has already been consumed and +// lexMultilineRawString consumes a raw string. Nothing can be escaped in such a +// string. It assumes that the beginning triple-' has already been consumed and // ignored. func lexMultilineRawString(lx *lexer) stateFn { r := lx.next() @@ -828,6 +839,11 @@ func lexMultilineStringEscape(lx *lexer) stateFn { func lexStringEscape(lx *lexer) stateFn { r := lx.next() switch r { + case 'e': + if !lx.tomlNext { + return lx.error(errLexEscape{r}) + } + fallthrough case 'b': fallthrough case 't': @@ -846,6 +862,11 @@ func lexStringEscape(lx *lexer) stateFn { fallthrough case '\\': return lx.pop() + case 'x': + if !lx.tomlNext { + return lx.error(errLexEscape{r}) + } + return lexHexEscape case 'u': return lexShortUnicodeEscape case 'U': @@ -854,6 +875,19 @@ func lexStringEscape(lx *lexer) stateFn { return lx.error(errLexEscape{r}) } +func lexHexEscape(lx *lexer) stateFn { + var r rune + for i := 0; i < 2; i++ { + r = lx.next() + if !isHexadecimal(r) { + return lx.errorf( + `expected two hexadecimal digits after '\x', but got %q instead`, + lx.current()) + } + } + return lx.pop() +} + func lexShortUnicodeEscape(lx *lexer) stateFn { var r rune for i := 0; i < 4; i++ { @@ -1225,7 +1259,23 @@ func isOctal(r rune) bool { return r >= '0' && r <= '7' } func isHexadecimal(r rune) bool { return (r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F') } -func isBareKeyChar(r rune) bool { + +func isBareKeyChar(r rune, tomlNext bool) bool { + if tomlNext { + return (r >= 'A' && r <= 'Z') || + (r >= 'a' && r <= 'z') || + (r >= '0' && r <= '9') || + r == '_' || r == '-' || + r == 0xb2 || r == 0xb3 || r == 0xb9 || (r >= 0xbc && r <= 0xbe) || + (r >= 0xc0 && r <= 0xd6) || (r >= 0xd8 && r <= 0xf6) || (r >= 0xf8 && r <= 0x037d) || + (r >= 0x037f && r <= 0x1fff) || + (r >= 0x200c && r <= 0x200d) || (r >= 0x203f && r <= 0x2040) || + (r >= 0x2070 && r <= 0x218f) || (r >= 0x2460 && r <= 0x24ff) || + (r >= 0x2c00 && r <= 0x2fef) || (r >= 0x3001 && r <= 0xd7ff) || + (r >= 0xf900 && r <= 0xfdcf) || (r >= 0xfdf0 && r <= 0xfffd) || + (r >= 0x10000 && r <= 0xeffff) + } + return (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || diff --git a/vendor/github.com/BurntSushi/toml/meta.go b/vendor/github.com/BurntSushi/toml/meta.go index 71847a041..2e78b24e9 100644 --- a/vendor/github.com/BurntSushi/toml/meta.go +++ b/vendor/github.com/BurntSushi/toml/meta.go @@ -106,7 +106,7 @@ func (k Key) maybeQuoted(i int) string { return `""` } for _, c := range k[i] { - if !isBareKeyChar(c) { + if !isBareKeyChar(c, false) { return `"` + dblQuotedReplacer.Replace(k[i]) + `"` } } diff --git a/vendor/github.com/BurntSushi/toml/parse.go b/vendor/github.com/BurntSushi/toml/parse.go index d2542d6f9..9c1915369 100644 --- a/vendor/github.com/BurntSushi/toml/parse.go +++ b/vendor/github.com/BurntSushi/toml/parse.go @@ -2,6 +2,7 @@ package toml import ( "fmt" + "os" "strconv" "strings" "time" @@ -15,6 +16,7 @@ type parser struct { context Key // Full key for the current hash in scope. currentKey string // Base key name for everything except hashes. pos Position // Current position in the TOML file. + tomlNext bool ordered []Key // List of keys in the order that they appear in the TOML data. @@ -29,6 +31,8 @@ type keyInfo struct { } func parse(data string) (p *parser, err error) { + _, tomlNext := os.LookupEnv("BURNTSUSHI_TOML_110") + defer func() { if r := recover(); r != nil { if pErr, ok := r.(ParseError); ok { @@ -41,9 +45,12 @@ func parse(data string) (p *parser, err error) { }() // Read over BOM; do this here as the lexer calls utf8.DecodeRuneInString() - // which mangles stuff. - if strings.HasPrefix(data, "\xff\xfe") || strings.HasPrefix(data, "\xfe\xff") { + // which mangles stuff. UTF-16 BOM isn't strictly valid, but some tools add + // it anyway. + if strings.HasPrefix(data, "\xff\xfe") || strings.HasPrefix(data, "\xfe\xff") { // UTF-16 data = data[2:] + } else if strings.HasPrefix(data, "\xef\xbb\xbf") { // UTF-8 + data = data[3:] } // Examine first few bytes for NULL bytes; this probably means it's a UTF-16 @@ -65,9 +72,10 @@ func parse(data string) (p *parser, err error) { p = &parser{ keyInfo: make(map[string]keyInfo), mapping: make(map[string]interface{}), - lx: lex(data), + lx: lex(data, tomlNext), ordered: make([]Key, 0), implicits: make(map[string]struct{}), + tomlNext: tomlNext, } for { item := p.next() @@ -194,12 +202,12 @@ func (p *parser) topLevel(item item) { for i := range context { p.addImplicitContext(append(p.context, context[i:i+1]...)) } + p.ordered = append(p.ordered, p.context.add(p.currentKey)) /// Set value. vItem := p.next() val, typ := p.value(vItem, false) p.set(p.currentKey, val, typ, vItem.pos) - p.ordered = append(p.ordered, p.context.add(p.currentKey)) /// Remove the context we added (preserving any context from [tbl] lines). p.context = outerContext @@ -236,7 +244,7 @@ func (p *parser) value(it item, parentIsArray bool) (interface{}, tomlType) { case itemString: return p.replaceEscapes(it, it.val), p.typeOfPrimitive(it) case itemMultilineString: - return p.replaceEscapes(it, stripFirstNewline(p.stripEscapedNewlines(it.val))), p.typeOfPrimitive(it) + return p.replaceEscapes(it, p.stripEscapedNewlines(stripFirstNewline(it.val))), p.typeOfPrimitive(it) case itemRawString: return it.val, p.typeOfPrimitive(it) case itemRawMultilineString: @@ -331,11 +339,17 @@ func (p *parser) valueFloat(it item) (interface{}, tomlType) { var dtTypes = []struct { fmt string zone *time.Location + next bool }{ - {time.RFC3339Nano, time.Local}, - {"2006-01-02T15:04:05.999999999", internal.LocalDatetime}, - {"2006-01-02", internal.LocalDate}, - {"15:04:05.999999999", internal.LocalTime}, + {time.RFC3339Nano, time.Local, false}, + {"2006-01-02T15:04:05.999999999", internal.LocalDatetime, false}, + {"2006-01-02", internal.LocalDate, false}, + {"15:04:05.999999999", internal.LocalTime, false}, + + // tomlNext + {"2006-01-02T15:04Z07:00", time.Local, true}, + {"2006-01-02T15:04", internal.LocalDatetime, true}, + {"15:04", internal.LocalTime, true}, } func (p *parser) valueDatetime(it item) (interface{}, tomlType) { @@ -346,6 +360,9 @@ func (p *parser) valueDatetime(it item) (interface{}, tomlType) { err error ) for _, dt := range dtTypes { + if dt.next && !p.tomlNext { + continue + } t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone) if err == nil { ok = true @@ -384,6 +401,7 @@ func (p *parser) valueArray(it item) (interface{}, tomlType) { // // Not entirely sure how to best store this; could use "key[0]", // "key[1]" notation, or maybe store it on the Array type? + _ = types } return array, tomlArray } @@ -426,11 +444,11 @@ func (p *parser) valueInlineTable(it item, parentIsArray bool) (interface{}, tom for i := range context { p.addImplicitContext(append(p.context, context[i:i+1]...)) } + p.ordered = append(p.ordered, p.context.add(p.currentKey)) /// Set the value. val, typ := p.value(p.next(), false) p.set(p.currentKey, val, typ, it.pos) - p.ordered = append(p.ordered, p.context.add(p.currentKey)) hash[p.currentKey] = val /// Restore context. @@ -551,7 +569,6 @@ func (p *parser) addContext(key Key, array bool) { func (p *parser) set(key string, val interface{}, typ tomlType, pos Position) { p.setValue(key, val) p.setType(key, typ, pos) - } // setValue sets the given key to the given value in the current context. @@ -632,14 +649,11 @@ func (p *parser) setType(key string, typ tomlType, pos Position) { // Implicit keys need to be created when tables are implied in "a.b.c.d = 1" and // "[a.b.c]" (the "a", "b", and "c" hashes are never created explicitly). -func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = struct{}{} } -func (p *parser) removeImplicit(key Key) { delete(p.implicits, key.String()) } -func (p *parser) isImplicit(key Key) bool { _, ok := p.implicits[key.String()]; return ok } -func (p *parser) isArray(key Key) bool { return p.keyInfo[key.String()].tomlType == tomlArray } -func (p *parser) addImplicitContext(key Key) { - p.addImplicit(key) - p.addContext(key, false) -} +func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = struct{}{} } +func (p *parser) removeImplicit(key Key) { delete(p.implicits, key.String()) } +func (p *parser) isImplicit(key Key) bool { _, ok := p.implicits[key.String()]; return ok } +func (p *parser) isArray(key Key) bool { return p.keyInfo[key.String()].tomlType == tomlArray } +func (p *parser) addImplicitContext(key Key) { p.addImplicit(key); p.addContext(key, false) } // current returns the full key name of the current context. func (p *parser) current() string { @@ -662,49 +676,54 @@ func stripFirstNewline(s string) string { return s } -// Remove newlines inside triple-quoted strings if a line ends with "\". +// stripEscapedNewlines removes whitespace after line-ending backslashes in +// multiline strings. +// +// A line-ending backslash is an unescaped \ followed only by whitespace until +// the next newline. After a line-ending backslash, all whitespace is removed +// until the next non-whitespace character. func (p *parser) stripEscapedNewlines(s string) string { - split := strings.Split(s, "\n") - if len(split) < 1 { - return s - } + var b strings.Builder + var i int + for { + ix := strings.Index(s[i:], `\`) + if ix < 0 { + b.WriteString(s) + return b.String() + } + i += ix - escNL := false // Keep track of the last non-blank line was escaped. - for i, line := range split { - line = strings.TrimRight(line, " \t\r") - - if len(line) == 0 || line[len(line)-1] != '\\' { - split[i] = strings.TrimRight(split[i], "\r") - if !escNL && i != len(split)-1 { - split[i] += "\n" + if len(s) > i+1 && s[i+1] == '\\' { + // Escaped backslash. + i += 2 + continue + } + // Scan until the next non-whitespace. + j := i + 1 + whitespaceLoop: + for ; j < len(s); j++ { + switch s[j] { + case ' ', '\t', '\r', '\n': + default: + break whitespaceLoop } + } + if j == i+1 { + // Not a whitespace escape. + i++ continue } - - escBS := true - for j := len(line) - 1; j >= 0 && line[j] == '\\'; j-- { - escBS = !escBS - } - if escNL { - line = strings.TrimLeft(line, " \t\r") - } - escNL = !escBS - - if escBS { - split[i] += "\n" + if !strings.Contains(s[i:j], "\n") { + // This is not a line-ending backslash. + // (It's a bad escape sequence, but we can let + // replaceEscapes catch it.) + i++ continue } - - if i == len(split)-1 { - p.panicf("invalid escape: '\\ '") - } - - split[i] = line[:len(line)-1] // Remove \ - if len(split)-1 > i { - split[i+1] = strings.TrimLeft(split[i+1], " \t\r") - } + b.WriteString(s[:i]) + s = s[j:] + i = 0 } - return strings.Join(split, "") } func (p *parser) replaceEscapes(it item, str string) string { @@ -743,12 +762,23 @@ func (p *parser) replaceEscapes(it item, str string) string { case 'r': replaced = append(replaced, rune(0x000D)) r += 1 + case 'e': + if p.tomlNext { + replaced = append(replaced, rune(0x001B)) + r += 1 + } case '"': replaced = append(replaced, rune(0x0022)) r += 1 case '\\': replaced = append(replaced, rune(0x005C)) r += 1 + case 'x': + if p.tomlNext { + escaped := p.asciiEscapeToUnicode(it, s[r+1:r+3]) + replaced = append(replaced, escaped) + r += 3 + } case 'u': // At this point, we know we have a Unicode escape of the form // `uXXXX` at [r, r+5). (Because the lexer guarantees this diff --git a/vendor/github.com/cs3org/reva/v2/cmd/revad/runtime/option.go b/vendor/github.com/cs3org/reva/v2/cmd/revad/runtime/option.go index 44be8f959..daa1654e5 100644 --- a/vendor/github.com/cs3org/reva/v2/cmd/revad/runtime/option.go +++ b/vendor/github.com/cs3org/reva/v2/cmd/revad/runtime/option.go @@ -19,8 +19,8 @@ package runtime import ( - "github.com/cs3org/reva/v2/pkg/registry" "github.com/rs/zerolog" + "go-micro.dev/v4/registry" ) // Option defines a single option function. diff --git a/vendor/github.com/cs3org/reva/v2/cmd/revad/runtime/runtime.go b/vendor/github.com/cs3org/reva/v2/cmd/revad/runtime/runtime.go index d161a23b4..ef99449fb 100644 --- a/vendor/github.com/cs3org/reva/v2/cmd/revad/runtime/runtime.go +++ b/vendor/github.com/cs3org/reva/v2/cmd/revad/runtime/runtime.go @@ -30,12 +30,11 @@ import ( "github.com/cs3org/reva/v2/cmd/revad/internal/grace" "github.com/cs3org/reva/v2/pkg/logger" - "github.com/cs3org/reva/v2/pkg/registry/memory" + "github.com/cs3org/reva/v2/pkg/registry" "github.com/cs3org/reva/v2/pkg/rgrpc" "github.com/cs3org/reva/v2/pkg/rhttp" "github.com/cs3org/reva/v2/pkg/sharedconf" rtrace "github.com/cs3org/reva/v2/pkg/trace" - "github.com/cs3org/reva/v2/pkg/utils" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -55,19 +54,8 @@ func RunWithOptions(mainConf map[string]interface{}, pidFile string, opts ...Opt parseSharedConfOrDie(mainConf["shared"]) coreConf := parseCoreConfOrDie(mainConf["core"]) - // TODO: one can pass the options from the config file to registry.New() and initialize a registry based upon config files. - if options.Registry != nil { - utils.GlobalRegistry = options.Registry - } else if _, ok := mainConf["registry"]; ok { - for _, services := range mainConf["registry"].(map[string]interface{}) { - for sName, nodes := range services.(map[string]interface{}) { - for _, instance := range nodes.([]interface{}) { - if err := utils.GlobalRegistry.Add(memory.NewService(sName, instance.(map[string]interface{})["nodes"].([]interface{}))); err != nil { - panic(err) - } - } - } - } + if err := registry.Init(options.Registry); err != nil { + panic(err) } run(mainConf, coreConf, options.Logger, pidFile) diff --git a/vendor/github.com/cs3org/reva/v2/internal/grpc/interceptors/eventsmiddleware/conversion.go b/vendor/github.com/cs3org/reva/v2/internal/grpc/interceptors/eventsmiddleware/conversion.go index 98647c631..a4ff32f62 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/grpc/interceptors/eventsmiddleware/conversion.go +++ b/vendor/github.com/cs3org/reva/v2/internal/grpc/interceptors/eventsmiddleware/conversion.go @@ -37,6 +37,7 @@ func ContainerCreated(r *provider.CreateContainerResponse, req *provider.CreateC SpaceOwner: spaceOwner, Executant: executant, Ref: req.Ref, + Timestamp: utils.TSNow(), } } @@ -166,6 +167,8 @@ func LinkAccessFailed(r *link.GetPublicShareByTokenResponse, req *link.GetPublic Executant: executant, Status: r.Status.Code, Message: r.Status.Message, + Timestamp: utils.TSNow(), + Token: req.Token, } if r.Share != nil { e.ShareID = r.Share.Id @@ -180,6 +183,7 @@ func LinkRemoved(r *link.RemovePublicShareResponse, req *link.RemovePublicShareR Executant: executant, ShareID: req.Ref.GetId(), ShareToken: req.Ref.GetToken(), + Timestamp: utils.TSNow(), } } @@ -189,6 +193,7 @@ func FileTouched(r *provider.TouchFileResponse, req *provider.TouchFileRequest, SpaceOwner: spaceOwner, Executant: executant, Ref: req.Ref, + Timestamp: utils.TSNow(), } } @@ -198,6 +203,7 @@ func FileUploaded(r *provider.InitiateFileUploadResponse, req *provider.Initiate SpaceOwner: spaceOwner, Executant: executant, Ref: req.Ref, + Timestamp: utils.TSNow(), } } @@ -206,6 +212,7 @@ func FileDownloaded(r *provider.InitiateFileDownloadResponse, req *provider.Init return events.FileDownloaded{ Executant: executant, Ref: req.Ref, + Timestamp: utils.TSNow(), } } @@ -221,6 +228,7 @@ func ItemTrashed(r *provider.DeleteResponse, req *provider.DeleteRequest, spaceO SpaceId: req.Ref.GetResourceId().GetSpaceId(), OpaqueId: opaqueID, }, + Timestamp: utils.TSNow(), } } @@ -231,6 +239,7 @@ func ItemMoved(r *provider.MoveResponse, req *provider.MoveRequest, spaceOwner, Executant: executant, Ref: req.Destination, OldReference: req.Source, + Timestamp: utils.TSNow(), } } @@ -239,6 +248,7 @@ func ItemPurged(r *provider.PurgeRecycleResponse, req *provider.PurgeRecycleRequ return events.ItemPurged{ Executant: executant, Ref: req.Ref, + Timestamp: utils.TSNow(), } } @@ -254,6 +264,7 @@ func ItemRestored(r *provider.RestoreRecycleItemResponse, req *provider.RestoreR Ref: ref, OldReference: req.Ref, Key: req.Key, + Timestamp: utils.TSNow(), } } @@ -264,6 +275,7 @@ func FileVersionRestored(r *provider.RestoreFileVersionResponse, req *provider.R Executant: executant, Ref: req.Ref, Key: req.Key, + Timestamp: utils.TSNow(), } } @@ -288,6 +300,7 @@ func SpaceRenamed(r *provider.UpdateStorageSpaceResponse, req *provider.UpdateSt ID: r.StorageSpace.Id, Owner: extractOwner(r.StorageSpace.Owner), Name: r.StorageSpace.Name, + Timestamp: utils.TSNow(), } } @@ -297,6 +310,7 @@ func SpaceUpdated(r *provider.UpdateStorageSpaceResponse, req *provider.UpdateSt Executant: executant, ID: r.StorageSpace.Id, Space: r.StorageSpace, + Timestamp: utils.TSNow(), } } @@ -306,6 +320,7 @@ func SpaceEnabled(r *provider.UpdateStorageSpaceResponse, req *provider.UpdateSt Executant: executant, ID: r.StorageSpace.Id, Owner: extractOwner(r.StorageSpace.Owner), + Timestamp: utils.TSNow(), } } diff --git a/vendor/github.com/cs3org/reva/v2/internal/grpc/services/gateway/authprovider.go b/vendor/github.com/cs3org/reva/v2/internal/grpc/services/gateway/authprovider.go index 52875480e..59214afff 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/grpc/services/gateway/authprovider.go +++ b/vendor/github.com/cs3org/reva/v2/internal/grpc/services/gateway/authprovider.go @@ -44,6 +44,7 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest // find auth provider c, err := s.findAuthProvider(ctx, req.Type) if err != nil { + log.Err(err).Str("type", req.Type).Msg("error getting auth provider client") return &gateway.AuthenticateResponse{ Status: status.NewInternal(ctx, "error getting auth provider client"), }, nil diff --git a/vendor/github.com/cs3org/reva/v2/internal/grpc/services/publicstorageprovider/publicstorageprovider.go b/vendor/github.com/cs3org/reva/v2/internal/grpc/services/publicstorageprovider/publicstorageprovider.go index afff2e729..48fb77791 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/grpc/services/publicstorageprovider/publicstorageprovider.go +++ b/vendor/github.com/cs3org/reva/v2/internal/grpc/services/publicstorageprovider/publicstorageprovider.go @@ -59,8 +59,8 @@ type config struct { } type service struct { - conf *config - gateway gateway.GatewayAPIClient + conf *config + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] } func (s *service) Close() error { @@ -91,14 +91,14 @@ func New(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error) { return nil, err } - gateway, err := pool.GetGatewayServiceClient(c.GatewayAddr) + gatewaySelector, err := pool.GatewaySelector(c.GatewayAddr) if err != nil { return nil, err } service := &service{ - conf: c, - gateway: gateway, + conf: c, + gatewaySelector: gatewaySelector, } return service, nil @@ -114,7 +114,11 @@ func (s *service) SetArbitraryMetadata(ctx context.Context, req *provider.SetArb Status: st, }, nil } - return s.gateway.SetArbitraryMetadata(ctx, &provider.SetArbitraryMetadataRequest{Opaque: req.Opaque, Ref: ref, ArbitraryMetadata: req.ArbitraryMetadata}) + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + return gatewayClient.SetArbitraryMetadata(ctx, &provider.SetArbitraryMetadataRequest{Opaque: req.Opaque, Ref: ref, ArbitraryMetadata: req.ArbitraryMetadata}) } func (s *service) UnsetArbitraryMetadata(ctx context.Context, req *provider.UnsetArbitraryMetadataRequest) (*provider.UnsetArbitraryMetadataResponse, error) { @@ -132,7 +136,11 @@ func (s *service) SetLock(ctx context.Context, req *provider.SetLockRequest) (*p Status: st, }, nil } - return s.gateway.SetLock(ctx, &provider.SetLockRequest{Opaque: req.Opaque, Ref: ref, Lock: req.Lock}) + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + return gatewayClient.SetLock(ctx, &provider.SetLockRequest{Opaque: req.Opaque, Ref: ref, Lock: req.Lock}) } // GetLock returns an existing lock on the given reference @@ -146,7 +154,11 @@ func (s *service) GetLock(ctx context.Context, req *provider.GetLockRequest) (*p Status: st, }, nil } - return s.gateway.GetLock(ctx, &provider.GetLockRequest{Opaque: req.Opaque, Ref: ref}) + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + return gatewayClient.GetLock(ctx, &provider.GetLockRequest{Opaque: req.Opaque, Ref: ref}) } // RefreshLock refreshes an existing lock on the given reference @@ -160,7 +172,11 @@ func (s *service) RefreshLock(ctx context.Context, req *provider.RefreshLockRequ Status: st, }, nil } - return s.gateway.RefreshLock(ctx, &provider.RefreshLockRequest{Opaque: req.Opaque, Ref: ref, Lock: req.Lock}) + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + return gatewayClient.RefreshLock(ctx, &provider.RefreshLockRequest{Opaque: req.Opaque, Ref: ref, Lock: req.Lock}) } // Unlock removes an existing lock from the given reference @@ -174,7 +190,11 @@ func (s *service) Unlock(ctx context.Context, req *provider.UnlockRequest) (*pro Status: st, }, nil } - return s.gateway.Unlock(ctx, &provider.UnlockRequest{Opaque: req.Opaque, Ref: ref, Lock: req.Lock}) + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + return gatewayClient.Unlock(ctx, &provider.UnlockRequest{Opaque: req.Opaque, Ref: ref, Lock: req.Lock}) } func (s *service) InitiateFileDownload(ctx context.Context, req *provider.InitiateFileDownloadRequest) (*provider.InitiateFileDownloadResponse, error) { @@ -265,7 +285,11 @@ func (s *service) initiateFileDownload(ctx context.Context, req *provider.Initia Ref: cs3Ref, } - dRes, err := s.gateway.InitiateFileDownload(ctx, dReq) + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + dRes, err := gatewayClient.InitiateFileDownload(ctx, dReq) if err != nil { return &provider.InitiateFileDownloadResponse{ Status: status.NewInternal(ctx, "initiateFileDownload: error calling InitiateFileDownload"), @@ -319,7 +343,11 @@ func (s *service) InitiateFileUpload(ctx context.Context, req *provider.Initiate Opaque: req.Opaque, } - uRes, err := s.gateway.InitiateFileUpload(ctx, uReq) + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + uRes, err := gatewayClient.InitiateFileUpload(ctx, uReq) if err != nil { return &provider.InitiateFileUploadResponse{ Status: status.NewInternal(ctx, "InitiateFileUpload: error calling InitiateFileUpload"), @@ -543,7 +571,11 @@ func (s *service) CreateContainer(ctx context.Context, req *provider.CreateConta var res *provider.CreateContainerResponse // the call has to be made to the gateway instead of the storage. - res, err = s.gateway.CreateContainer(ctx, &provider.CreateContainerRequest{ + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + res, err = gatewayClient.CreateContainer(ctx, &provider.CreateContainerRequest{ Ref: cs3Ref, }) if err != nil { @@ -568,7 +600,11 @@ func (s *service) TouchFile(ctx context.Context, req *provider.TouchFileRequest) Status: st, }, nil } - return s.gateway.TouchFile(ctx, &provider.TouchFileRequest{Opaque: req.Opaque, Ref: ref}) + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + return gatewayClient.TouchFile(ctx, &provider.TouchFileRequest{Opaque: req.Opaque, Ref: ref}) } func (s *service) Delete(ctx context.Context, req *provider.DeleteRequest) (*provider.DeleteResponse, error) { @@ -596,7 +632,11 @@ func (s *service) Delete(ctx context.Context, req *provider.DeleteRequest) (*pro var res *provider.DeleteResponse // the call has to be made to the gateway instead of the storage. - res, err = s.gateway.Delete(ctx, &provider.DeleteRequest{ + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + res, err = gatewayClient.Delete(ctx, &provider.DeleteRequest{ Ref: cs3Ref, }) if err != nil { @@ -658,7 +698,11 @@ func (s *service) Move(ctx context.Context, req *provider.MoveRequest) (*provide var res *provider.MoveResponse // the call has to be made to the gateway instead of the storage. - res, err = s.gateway.Move(ctx, &provider.MoveRequest{ + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + res, err = gatewayClient.Move(ctx, &provider.MoveRequest{ Source: cs3RefSource, Destination: cs3RefDestination, }) @@ -721,7 +765,11 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide Path: utils.MakeRelativePath(req.Ref.Path), } - statResponse, err := s.gateway.Stat(ctx, &provider.StatRequest{Ref: ref}) + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + statResponse, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: ref}) if err != nil { return &provider.StatResponse{ Status: status.NewInternal(ctx, "Stat: error calling Stat for ref:"+req.Ref.String()), @@ -796,7 +844,11 @@ func (s *service) ListContainer(ctx context.Context, req *provider.ListContainer }, nil } - listContainerR, err := s.gateway.ListContainer( + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + listContainerR, err := gatewayClient.ListContainer( ctx, &provider.ListContainerRequest{ Ref: &provider.Reference{ @@ -926,7 +978,11 @@ func (s *service) resolveToken(ctx context.Context, token string) (*link.PublicS return nil, nil, publicShareResponse.Status, nil } - sRes, err := s.gateway.Stat(ctx, &provider.StatRequest{ + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, nil, nil, err + } + sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{ Ref: &provider.Reference{ ResourceId: publicShareResponse.GetShare().GetResourceId(), }, diff --git a/vendor/github.com/cs3org/reva/v2/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go b/vendor/github.com/cs3org/reva/v2/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go index 709631a04..4b1a12f8b 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go +++ b/vendor/github.com/cs3org/reva/v2/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go @@ -62,8 +62,8 @@ type config struct { } type service struct { - gateway gateway.GatewayAPIClient - sharesProviderClient collaboration.CollaborationAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + sharingCollaborationSelector pool.Selectable[collaboration.CollaborationAPIClient] } func (s *service) Close() error { @@ -86,24 +86,24 @@ func NewDefault(m map[string]interface{}, _ *grpc.Server) (rgrpc.Service, error) return nil, err } - gateway, err := pool.GetGatewayServiceClient(sharedconf.GetGatewaySVC(c.GatewayAddr)) + gatewaySelector, err := pool.GatewaySelector(sharedconf.GetGatewaySVC(c.GatewayAddr)) if err != nil { return nil, err } - client, err := pool.GetUserShareProviderClient(sharedconf.GetGatewaySVC(c.UserShareProviderEndpoint)) + sharingCollaborationSelector, err := pool.SharingCollaborationSelector(sharedconf.GetGatewaySVC(c.UserShareProviderEndpoint)) if err != nil { return nil, errors.Wrap(err, "sharesstorageprovider: error getting UserShareProvider client") } - return New(gateway, client) + return New(gatewaySelector, sharingCollaborationSelector) } // New returns a new instance of the SharesStorageProvider service -func New(gateway gateway.GatewayAPIClient, c collaboration.CollaborationAPIClient) (rgrpc.Service, error) { +func New(gatewaySelector pool.Selectable[gateway.GatewayAPIClient], sharingCollaborationSelector pool.Selectable[collaboration.CollaborationAPIClient]) (rgrpc.Service, error) { s := &service{ - gateway: gateway, - sharesProviderClient: c, + gatewaySelector: gatewaySelector, + sharingCollaborationSelector: sharingCollaborationSelector, } return s, nil } @@ -123,7 +123,12 @@ func (s *service) SetArbitraryMetadata(ctx context.Context, req *provider.SetArb }, nil } - return s.gateway.SetArbitraryMetadata(ctx, &provider.SetArbitraryMetadataRequest{ + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + + return gatewayClient.SetArbitraryMetadata(ctx, &provider.SetArbitraryMetadataRequest{ Opaque: req.Opaque, Ref: buildReferenceInShare(req.Ref, receivedShare), ArbitraryMetadata: req.ArbitraryMetadata, @@ -145,7 +150,12 @@ func (s *service) UnsetArbitraryMetadata(ctx context.Context, req *provider.Unse }, nil } - return s.gateway.UnsetArbitraryMetadata(ctx, &provider.UnsetArbitraryMetadataRequest{ + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + + return gatewayClient.UnsetArbitraryMetadata(ctx, &provider.UnsetArbitraryMetadataRequest{ Opaque: req.Opaque, Ref: buildReferenceInShare(req.Ref, receivedShare), ArbitraryMetadataKeys: req.ArbitraryMetadataKeys, @@ -167,7 +177,12 @@ func (s *service) InitiateFileDownload(ctx context.Context, req *provider.Initia }, nil } - gwres, err := s.gateway.InitiateFileDownload(ctx, &provider.InitiateFileDownloadRequest{ + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + + gwres, err := gatewayClient.InitiateFileDownload(ctx, &provider.InitiateFileDownloadRequest{ Opaque: req.Opaque, Ref: buildReferenceInShare(req.Ref, receivedShare), LockId: req.LockId, @@ -229,7 +244,13 @@ func (s *service) InitiateFileUpload(ctx context.Context, req *provider.Initiate Status: status.NewPermissionDenied(ctx, nil, "share does not grant InitiateFileDownload permission"), }, nil } - gwres, err := s.gateway.InitiateFileUpload(ctx, &provider.InitiateFileUploadRequest{ + + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + + gwres, err := gatewayClient.InitiateFileUpload(ctx, &provider.InitiateFileUploadRequest{ Opaque: req.Opaque, Ref: buildReferenceInShare(req.Ref, receivedShare), LockId: req.LockId, @@ -513,7 +534,12 @@ func (s *service) CreateContainer(ctx context.Context, req *provider.CreateConta }, nil } - return s.gateway.CreateContainer(ctx, &provider.CreateContainerRequest{ + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + + return gatewayClient.CreateContainer(ctx, &provider.CreateContainerRequest{ Opaque: req.Opaque, Ref: buildReferenceInShare(req.Ref, receivedShare), }) @@ -548,7 +574,12 @@ func (s *service) Delete(ctx context.Context, req *provider.DeleteRequest) (*pro }, nil } - return s.gateway.Delete(ctx, &provider.DeleteRequest{ + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + + return gatewayClient.Delete(ctx, &provider.DeleteRequest{ Opaque: req.Opaque, Ref: buildReferenceInShare(req.Ref, receivedShare), }) @@ -584,7 +615,12 @@ func (s *service) Move(ctx context.Context, req *provider.MoveRequest) (*provide Path: filepath.Base(req.Destination.Path), } - _, err = s.sharesProviderClient.UpdateReceivedShare(ctx, &collaboration.UpdateReceivedShareRequest{ + sharingCollaborationClient, err := s.sharingCollaborationSelector.Next() + if err != nil { + return nil, err + } + + _, err = sharingCollaborationClient.UpdateReceivedShare(ctx, &collaboration.UpdateReceivedShareRequest{ Share: srcReceivedShare, UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state", "mount_point"}}, }) @@ -613,7 +649,12 @@ func (s *service) Move(ctx context.Context, req *provider.MoveRequest) (*provide }, nil } - return s.gateway.Move(ctx, &provider.MoveRequest{ + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + + return gatewayClient.Move(ctx, &provider.MoveRequest{ Opaque: req.Opaque, Source: buildReferenceInShare(req.Source, srcReceivedShare), Destination: buildReferenceInShare(req.Destination, dstReceivedShare), @@ -712,8 +753,13 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide }, nil } + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + // TODO return reference? - return s.gateway.Stat(ctx, &provider.StatRequest{ + return gatewayClient.Stat(ctx, &provider.StatRequest{ Opaque: req.Opaque, Ref: buildReferenceInShare(req.Ref, receivedShare), ArbitraryMetadataKeys: req.ArbitraryMetadataKeys, @@ -742,13 +788,18 @@ func (s *service) ListContainer(ctx context.Context, req *provider.ListContainer return nil, errors.Wrap(err, "sharesstorageprovider: error calling ListReceivedSharesRequest") } + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + infos := []*provider.ResourceInfo{} for _, share := range receivedShares { if share.GetState() != collaboration.ShareState_SHARE_STATE_ACCEPTED { continue } - statRes, err := s.gateway.Stat(ctx, &provider.StatRequest{ + statRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{ Opaque: req.Opaque, Ref: &provider.Reference{ ResourceId: share.Share.ResourceId, @@ -802,7 +853,12 @@ func (s *service) ListContainer(ctx context.Context, req *provider.ListContainer }, nil } - return s.gateway.ListContainer(ctx, &provider.ListContainerRequest{ + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + + return gatewayClient.ListContainer(ctx, &provider.ListContainerRequest{ Opaque: req.Opaque, Ref: buildReferenceInShare(req.Ref, receivedShare), ArbitraryMetadataKeys: req.ArbitraryMetadataKeys, @@ -824,7 +880,12 @@ func (s *service) ListFileVersions(ctx context.Context, req *provider.ListFileVe }, nil } - return s.gateway.ListFileVersions(ctx, &provider.ListFileVersionsRequest{ + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + + return gatewayClient.ListFileVersions(ctx, &provider.ListFileVersionsRequest{ Opaque: req.Opaque, Ref: buildReferenceInShare(req.Ref, receivedShare), }) @@ -846,7 +907,12 @@ func (s *service) RestoreFileVersion(ctx context.Context, req *provider.RestoreF }, nil } - return s.gateway.RestoreFileVersion(ctx, &provider.RestoreFileVersionRequest{ + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + + return gatewayClient.RestoreFileVersion(ctx, &provider.RestoreFileVersionRequest{ Opaque: req.Opaque, Ref: buildReferenceInShare(req.Ref, receivedShare), }) @@ -911,7 +977,12 @@ func (s *service) TouchFile(ctx context.Context, req *provider.TouchFileRequest) }, nil } - return s.gateway.TouchFile(ctx, &provider.TouchFileRequest{ + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + + return gatewayClient.TouchFile(ctx, &provider.TouchFileRequest{ Opaque: req.Opaque, Ref: buildReferenceInShare(req.Ref, receivedShare), }) @@ -938,10 +1009,15 @@ func (s *service) resolveAcceptedShare(ctx context.Context, ref *provider.Refere return nil, status.NewNotFound(ctx, "sharesstorageprovider: not found "+ref.String()), nil } + sharingCollaborationClient, err := s.sharingCollaborationSelector.Next() + if err != nil { + return nil, nil, err + } + // we can get the share if the reference carries a share id if ref.ResourceId.OpaqueId != utils.ShareStorageProviderID { // look up share for this resourceid - lsRes, err := s.sharesProviderClient.GetReceivedShare(ctx, &collaboration.GetReceivedShareRequest{ + lsRes, err := sharingCollaborationClient.GetReceivedShare(ctx, &collaboration.GetReceivedShareRequest{ Ref: &collaboration.ShareReference{ Spec: &collaboration.ShareReference_Id{ Id: &collaboration.ShareId{ @@ -968,7 +1044,7 @@ func (s *service) resolveAcceptedShare(ctx context.Context, ref *provider.Refere // we need to list accepted shares and match the path // look up share for this resourceid - lsRes, err := s.sharesProviderClient.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{ + lsRes, err := sharingCollaborationClient.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{ Filters: []*collaboration.Filter{ // FIXME filter by accepted ... and by mountpoint? }, @@ -997,7 +1073,12 @@ func (s *service) rejectReceivedShare(ctx context.Context, receivedShare *collab receivedShare.State = collaboration.ShareState_SHARE_STATE_REJECTED receivedShare.MountPoint = nil - res, err := s.sharesProviderClient.UpdateReceivedShare(ctx, &collaboration.UpdateReceivedShareRequest{ + sharingCollaborationClient, err := s.sharingCollaborationSelector.Next() + if err != nil { + return err + } + + res, err := sharingCollaborationClient.UpdateReceivedShare(ctx, &collaboration.UpdateReceivedShareRequest{ Share: receivedShare, UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state", "mount_point"}}, }) @@ -1009,7 +1090,12 @@ func (s *service) rejectReceivedShare(ctx context.Context, receivedShare *collab } func (s *service) fetchShares(ctx context.Context) ([]*collaboration.ReceivedShare, map[string]*provider.ResourceInfo, error) { - lsRes, err := s.sharesProviderClient.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{ + sharingCollaborationClient, err := s.sharingCollaborationSelector.Next() + if err != nil { + return nil, nil, err + } + + lsRes, err := sharingCollaborationClient.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{ // FIXME filter by received shares for resource id - listing all shares is tooo expensive! }) if err != nil { @@ -1019,6 +1105,11 @@ func (s *service) fetchShares(ctx context.Context) ([]*collaboration.ReceivedSha return nil, nil, fmt.Errorf("sharesstorageprovider: error calling ListReceivedSharesRequest") } + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, nil, err + } + shareMetaData := make(map[string]*provider.ResourceInfo, len(lsRes.Shares)) for _, rs := range lsRes.Shares { // only stat accepted shares @@ -1029,7 +1120,7 @@ func (s *service) fetchShares(ctx context.Context) ([]*collaboration.ReceivedSha // convert backwards compatible share id rs.Share.ResourceId.StorageId, rs.Share.ResourceId.SpaceId = storagespace.SplitStorageID(rs.Share.ResourceId.StorageId) } - sRes, err := s.gateway.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: rs.Share.ResourceId}}) + sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: rs.Share.ResourceId}}) if err != nil { appctx.GetLogger(ctx).Error(). Err(err). diff --git a/vendor/github.com/cs3org/reva/v2/internal/grpc/services/storageprovider/storageprovider.go b/vendor/github.com/cs3org/reva/v2/internal/grpc/services/storageprovider/storageprovider.go index 9748db5cc..e08aff6bd 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/grpc/services/storageprovider/storageprovider.go +++ b/vendor/github.com/cs3org/reva/v2/internal/grpc/services/storageprovider/storageprovider.go @@ -499,6 +499,8 @@ func (s *service) CreateStorageSpace(ctx context.Context, req *provider.CreateSt } case errtypes.AlreadyExists: st = status.NewAlreadyExists(ctx, err, "already exists") + case errtypes.BadRequest: + st = status.NewInvalid(ctx, err.Error()) default: st = status.NewInternal(ctx, "error creating space") appctx.GetLogger(ctx). diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/archiver/handler.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/archiver/handler.go index 831fb5718..12c58aaeb 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/archiver/handler.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/archiver/handler.go @@ -47,11 +47,11 @@ import ( ) type svc struct { - config *Config - gtwClient gateway.GatewayAPIClient - log *zerolog.Logger - walker walker.Walker - downloader downloader.Downloader + config *Config + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + log *zerolog.Logger + walker walker.Walker + downloader downloader.Downloader allowedFolders []*regexp.Regexp } @@ -82,7 +82,7 @@ func New(conf map[string]interface{}, log *zerolog.Logger) (global.Service, erro c.init() - gtw, err := pool.GetGatewayServiceClient(c.GatewaySvc) + gatewaySelector, err := pool.GatewaySelector(c.GatewaySvc) if err != nil { return nil, err } @@ -98,12 +98,12 @@ func New(conf map[string]interface{}, log *zerolog.Logger) (global.Service, erro } return &svc{ - config: c, - gtwClient: gtw, - downloader: downloader.NewDownloader(gtw, rhttp.Insecure(c.Insecure), rhttp.Timeout(time.Duration(c.Timeout*int64(time.Second)))), - walker: walker.NewWalker(gtw), - log: log, - allowedFolders: allowedFolderRegex, + config: c, + gatewaySelector: gatewaySelector, + downloader: downloader.NewDownloader(gatewaySelector, rhttp.Insecure(c.Insecure), rhttp.Timeout(time.Duration(c.Timeout*int64(time.Second)))), + walker: walker.NewWalker(gatewaySelector), + log: log, + allowedFolders: allowedFolderRegex, }, nil } @@ -138,10 +138,14 @@ func (s *svc) getResources(ctx context.Context, paths, ids []string) ([]*provide } + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } for _, p := range paths { // id is base64 encoded and after decoding has the form : - resp, err := s.gtwClient.Stat(ctx, &provider.StatRequest{ + resp, err := gatewayClient.Stat(ctx, &provider.StatRequest{ Ref: &provider.Reference{ Path: p, }, @@ -259,11 +263,13 @@ func (s *svc) Handler() http.Handler { rw.Header().Set("Content-Transfer-Encoding", "binary") // create the archive + var closeArchive func() if userAgent.OS == ua.Windows { - err = arch.CreateZip(ctx, rw) + closeArchive, err = arch.CreateZip(ctx, rw) } else { - err = arch.CreateTar(ctx, rw) + closeArchive, err = arch.CreateTar(ctx, rw) } + defer closeArchive() if err != nil { s.writeHTTPError(rw, err) diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/archiver/manager/archiver.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/archiver/manager/archiver.go index 341505861..803606665 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/archiver/manager/archiver.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/archiver/manager/archiver.go @@ -61,8 +61,11 @@ func NewArchiver(r []*provider.ResourceId, w walker.Walker, d downloader.Downloa } // CreateTar creates a tar and write it into the dst Writer -func (a *Archiver) CreateTar(ctx context.Context, dst io.Writer) error { +func (a *Archiver) CreateTar(ctx context.Context, dst io.Writer) (func(), error) { w := tar.NewWriter(dst) + closer := func() { + _ = w.Close() + } var filesCount, sizeFiles int64 @@ -73,6 +76,11 @@ func (a *Archiver) CreateTar(ctx context.Context, dst io.Writer) error { return err } + // when archiving a space we can omit the spaceroot + if isSpaceRoot(info) { + return nil + } + isDir := info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER filesCount++ @@ -120,16 +128,19 @@ func (a *Archiver) CreateTar(ctx context.Context, dst io.Writer) error { }) if err != nil { - return err + return closer, err } } - return w.Close() + return closer, nil } // CreateZip creates a zip and write it into the dst Writer -func (a *Archiver) CreateZip(ctx context.Context, dst io.Writer) error { +func (a *Archiver) CreateZip(ctx context.Context, dst io.Writer) (func(), error) { w := zip.NewWriter(dst) + closer := func() { + _ = w.Close() + } var filesCount, sizeFiles int64 @@ -140,6 +151,11 @@ func (a *Archiver) CreateZip(ctx context.Context, dst io.Writer) error { return err } + // when archiving a space we can omit the spaceroot + if isSpaceRoot(info) { + return nil + } + isDir := info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER filesCount++ @@ -183,9 +199,15 @@ func (a *Archiver) CreateZip(ctx context.Context, dst io.Writer) error { }) if err != nil { - return err + return closer, err } } - return w.Close() + return closer, nil +} + +func isSpaceRoot(info *provider.ResourceInfo) bool { + f := info.GetId() + s := info.GetSpace().GetRoot() + return f.GetOpaqueId() == s.GetOpaqueId() && f.GetSpaceId() == s.GetSpaceId() } diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/copy.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/copy.go index 1aa89ac6e..7b4bce24e 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/copy.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/copy.go @@ -36,6 +36,7 @@ import ( "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/v2/pkg/appctx" "github.com/cs3org/reva/v2/pkg/errtypes" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/rhttp" "github.com/cs3org/reva/v2/pkg/rhttp/router" "github.com/cs3org/reva/v2/pkg/storagespace" @@ -105,7 +106,7 @@ func (s *svc) handlePathCopy(w http.ResponseWriter, r *http.Request, ns string) sublog := appctx.GetLogger(ctx).With().Str("src", src).Str("dst", dst).Logger() - srcSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gwClient, src) + srcSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gatewaySelector, src) if err != nil { sublog.Error().Err(err).Str("path", src).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) @@ -115,7 +116,7 @@ func (s *svc) handlePathCopy(w http.ResponseWriter, r *http.Request, ns string) errors.HandleErrorStatus(&sublog, w, status) return } - dstSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gwClient, dst) + dstSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gatewaySelector, dst) if err != nil { sublog.Error().Err(err).Str("path", dst).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) @@ -131,17 +132,22 @@ func (s *svc) handlePathCopy(w http.ResponseWriter, r *http.Request, ns string) return } - if err := s.executePathCopy(ctx, s.gwClient, w, r, cp); err != nil { + if err := s.executePathCopy(ctx, s.gatewaySelector, w, r, cp); err != nil { sublog.Error().Err(err).Str("depth", cp.depth.String()).Msg("error executing path copy") w.WriteHeader(http.StatusInternalServerError) } w.WriteHeader(cp.successCode) } -func (s *svc) executePathCopy(ctx context.Context, client gateway.GatewayAPIClient, w http.ResponseWriter, r *http.Request, cp *copy) error { +func (s *svc) executePathCopy(ctx context.Context, selector pool.Selectable[gateway.GatewayAPIClient], w http.ResponseWriter, r *http.Request, cp *copy) error { log := appctx.GetLogger(ctx) log.Debug().Str("src", cp.sourceInfo.Path).Str("dst", cp.destination.Path).Msg("descending") + client, err := selector.Next() + if err != nil { + return err + } + var fileid string if cp.sourceInfo.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { // create dir @@ -192,7 +198,7 @@ func (s *svc) executePathCopy(ctx context.Context, client gateway.GatewayAPIClie ResourceId: cp.destination.ResourceId, Path: utils.MakeRelativePath(filepath.Join(cp.destination.Path, child)), } - err := s.executePathCopy(ctx, client, w, r, ©{source: src, sourceInfo: res.Infos[i], destination: childDst, depth: cp.depth, successCode: cp.successCode}) + err := s.executePathCopy(ctx, selector, w, r, ©{source: src, sourceInfo: res.Infos[i], destination: childDst, depth: cp.depth, successCode: cp.successCode}) if err != nil { return err } @@ -354,7 +360,7 @@ func (s *svc) handleSpacesCopy(w http.ResponseWriter, r *http.Request, spaceID s return } - err = s.executeSpacesCopy(ctx, w, s.gwClient, cp) + err = s.executeSpacesCopy(ctx, w, s.gatewaySelector, cp) if err != nil { sublog.Error().Err(err).Str("depth", cp.depth.String()).Msg("error descending directory") w.WriteHeader(http.StatusInternalServerError) @@ -362,10 +368,15 @@ func (s *svc) handleSpacesCopy(w http.ResponseWriter, r *http.Request, spaceID s w.WriteHeader(cp.successCode) } -func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, client gateway.GatewayAPIClient, cp *copy) error { +func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, selector pool.Selectable[gateway.GatewayAPIClient], cp *copy) error { log := appctx.GetLogger(ctx) log.Debug().Interface("src", cp.sourceInfo).Interface("dst", cp.destination).Msg("descending") + client, err := selector.Next() + if err != nil { + return err + } + var fileid string if cp.sourceInfo.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { // create dir @@ -410,7 +421,7 @@ func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, clie ResourceId: cp.destination.ResourceId, Path: utils.MakeRelativePath(path.Join(cp.destination.Path, res.Infos[i].Path)), } - err := s.executeSpacesCopy(ctx, w, client, ©{sourceInfo: res.Infos[i], destination: childRef, depth: cp.depth, successCode: cp.successCode}) + err := s.executeSpacesCopy(ctx, w, selector, ©{sourceInfo: res.Infos[i], destination: childRef, depth: cp.depth, successCode: cp.successCode}) if err != nil { return err } @@ -528,7 +539,7 @@ func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, clie } func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Request, srcRef, dstRef *provider.Reference, log *zerolog.Logger) *copy { - isChild, err := s.referenceIsChildOf(ctx, s.gwClient, dstRef, srcRef) + isChild, err := s.referenceIsChildOf(ctx, s.gatewaySelector, dstRef, srcRef) if err != nil { switch err.(type) { case errtypes.IsNotSupported: @@ -573,8 +584,15 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re log.Debug().Bool("overwrite", overwrite).Str("depth", depth.String()).Msg("copy") + client, err := s.gatewaySelector.Next() + if err != nil { + log.Error().Err(err).Msg("error selecting next client") + w.WriteHeader(http.StatusInternalServerError) + return nil + } + srcStatReq := &provider.StatRequest{Ref: srcRef} - srcStatRes, err := s.gwClient.Stat(ctx, srcStatReq) + srcStatRes, err := client.Stat(ctx, srcStatReq) switch { case err != nil: log.Error().Err(err).Msg("error sending grpc stat request") @@ -592,7 +610,7 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re } dstStatReq := &provider.StatRequest{Ref: dstRef} - dstStatRes, err := s.gwClient.Stat(ctx, dstStatReq) + dstStatRes, err := client.Stat(ctx, dstStatReq) switch { case err != nil: log.Error().Err(err).Msg("error sending grpc stat request") @@ -621,7 +639,7 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re (dstStatRes.Info.Type == provider.ResourceType_RESOURCE_TYPE_FILE && srcStatRes.Info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER) { delReq := &provider.DeleteRequest{Ref: dstRef} - delRes, err := s.gwClient.Delete(ctx, delReq) + delRes, err := client.Delete(ctx, delReq) if err != nil { log.Error().Err(err).Msg("error sending grpc delete request") w.WriteHeader(http.StatusInternalServerError) @@ -640,7 +658,7 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re Path: utils.MakeRelativePath(p), } intStatReq := &provider.StatRequest{Ref: pRef} - intStatRes, err := s.gwClient.Stat(ctx, intStatReq) + intStatRes, err := client.Stat(ctx, intStatReq) if err != nil { log.Error().Err(err).Msg("error sending grpc stat request") w.WriteHeader(http.StatusInternalServerError) diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/dav.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/dav.go index 279534652..b3a133b24 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/dav.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/dav.go @@ -32,6 +32,7 @@ import ( "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" "github.com/cs3org/reva/v2/pkg/appctx" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/rhttp/router" "github.com/cs3org/reva/v2/pkg/utils" "google.golang.org/grpc/metadata" @@ -188,7 +189,7 @@ func (h *DavHandler) Handler(s *svc) http.Handler { var pass string var err error if _, pass, hasValidBasicAuthHeader = r.BasicAuth(); hasValidBasicAuthHeader { - res, err = handleBasicAuth(r.Context(), s.gwClient, token, pass) + res, err = handleBasicAuth(r.Context(), s.gatewaySelector, token, pass) } else { q := r.URL.Query() sig := q.Get("signature") @@ -198,7 +199,7 @@ func (h *DavHandler) Handler(s *svc) http.Handler { w.WriteHeader(http.StatusUnauthorized) return } - res, err = handleSignatureAuth(r.Context(), s.gwClient, token, sig, expiration) + res, err = handleSignatureAuth(r.Context(), s.gatewaySelector, token, sig, expiration) } switch { @@ -232,7 +233,7 @@ func (h *DavHandler) Handler(s *svc) http.Handler { r = r.WithContext(ctx) // the public share manager knew the token, but does the referenced target still exist? - sRes, err := getTokenStatInfo(ctx, s.gwClient, token) + sRes, err := getTokenStatInfo(ctx, s.gatewaySelector, token) switch { case err != nil: log.Error().Err(err).Msg("error sending grpc stat request") @@ -271,7 +272,12 @@ func (h *DavHandler) Handler(s *svc) http.Handler { }) } -func getTokenStatInfo(ctx context.Context, client gatewayv1beta1.GatewayAPIClient, token string) (*provider.StatResponse, error) { +func getTokenStatInfo(ctx context.Context, selector pool.Selectable[gatewayv1beta1.GatewayAPIClient], token string) (*provider.StatResponse, error) { + client, err := selector.Next() + if err != nil { + return nil, err + } + return client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ ResourceId: &provider.ResourceId{ StorageId: utils.PublicStorageProviderID, @@ -281,7 +287,11 @@ func getTokenStatInfo(ctx context.Context, client gatewayv1beta1.GatewayAPIClien }}) } -func handleBasicAuth(ctx context.Context, c gatewayv1beta1.GatewayAPIClient, token, pw string) (*gatewayv1beta1.AuthenticateResponse, error) { +func handleBasicAuth(ctx context.Context, selector pool.Selectable[gatewayv1beta1.GatewayAPIClient], token, pw string) (*gatewayv1beta1.AuthenticateResponse, error) { + c, err := selector.Next() + if err != nil { + return nil, err + } authenticateRequest := gatewayv1beta1.AuthenticateRequest{ Type: "publicshares", ClientId: token, @@ -291,7 +301,11 @@ func handleBasicAuth(ctx context.Context, c gatewayv1beta1.GatewayAPIClient, tok return c.Authenticate(ctx, &authenticateRequest) } -func handleSignatureAuth(ctx context.Context, c gatewayv1beta1.GatewayAPIClient, token, sig, expiration string) (*gatewayv1beta1.AuthenticateResponse, error) { +func handleSignatureAuth(ctx context.Context, selector pool.Selectable[gatewayv1beta1.GatewayAPIClient], token, sig, expiration string) (*gatewayv1beta1.AuthenticateResponse, error) { + c, err := selector.Next() + if err != nil { + return nil, err + } authenticateRequest := gatewayv1beta1.AuthenticateRequest{ Type: "publicshares", ClientId: token, diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/delete.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/delete.go index c09100037..24ed1a7ad 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/delete.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/delete.go @@ -45,7 +45,7 @@ func (s *svc) handlePathDelete(w http.ResponseWriter, r *http.Request, ns string fn := path.Join(ns, r.URL.Path) - space, rpcStatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gwClient, fn) + space, rpcStatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gatewaySelector, fn) switch { case err != nil: span.RecordError(err) @@ -73,7 +73,12 @@ func (s *svc) handleDelete(ctx context.Context, w http.ResponseWriter, r *http.R return http.StatusBadRequest, errtypes.BadRequest("invalid if header") } - res, err := s.gwClient.Delete(ctx, req) + client, err := s.gatewaySelector.Next() + if err != nil { + return http.StatusInternalServerError, errtypes.InternalError(err.Error()) + } + + res, err := client.Delete(ctx, req) switch { case err != nil: span.RecordError(err) @@ -92,7 +97,7 @@ func (s *svc) handleDelete(ctx context.Context, w http.ResponseWriter, r *http.R status = http.StatusLocked } // check if user has access to resource - sRes, err := s.gwClient.Stat(ctx, &provider.StatRequest{Ref: ref}) + sRes, err := client.Stat(ctx, &provider.StatRequest{Ref: ref}) if err != nil { span.RecordError(err) return http.StatusInternalServerError, err diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/get.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/get.go index 21b10ff7a..191d9cc04 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/get.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/get.go @@ -45,7 +45,7 @@ func (s *svc) handlePathGet(w http.ResponseWriter, r *http.Request, ns string) { sublog := appctx.GetLogger(ctx).With().Str("path", fn).Str("svc", "ocdav").Str("handler", "get").Logger() - space, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gwClient, fn) + space, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gatewaySelector, fn) if err != nil { sublog.Error().Err(err).Str("path", fn).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) @@ -60,10 +60,16 @@ func (s *svc) handlePathGet(w http.ResponseWriter, r *http.Request, ns string) { } func (s *svc) handleGet(ctx context.Context, w http.ResponseWriter, r *http.Request, ref *provider.Reference, dlProtocol string, log zerolog.Logger) { + client, err := s.gatewaySelector.Next() + if err != nil { + log.Error().Err(err).Msg("error selecting next client") + w.WriteHeader(http.StatusInternalServerError) + return + } sReq := &provider.StatRequest{ Ref: ref, } - sRes, err := s.gwClient.Stat(ctx, sReq) + sRes, err := client.Stat(ctx, sReq) if err != nil { log.Error().Err(err).Msg("error stat resource") w.WriteHeader(http.StatusInternalServerError) @@ -85,7 +91,7 @@ func (s *svc) handleGet(ctx context.Context, w http.ResponseWriter, r *http.Requ } dReq := &provider.InitiateFileDownloadRequest{Ref: ref} - dRes, err := s.gwClient.InitiateFileDownload(ctx, dReq) + dRes, err := client.InitiateFileDownload(ctx, dReq) switch { case err != nil: log.Error().Err(err).Msg("error initiating file download") diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/head.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/head.go index 702193b39..f055d18f4 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/head.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/head.go @@ -48,7 +48,7 @@ func (s *svc) handlePathHead(w http.ResponseWriter, r *http.Request, ns string) sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger() - space, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gwClient, fn) + space, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gatewaySelector, fn) if err != nil { sublog.Error().Err(err).Str("path", fn).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) @@ -63,9 +63,14 @@ func (s *svc) handlePathHead(w http.ResponseWriter, r *http.Request, ns string) } func (s *svc) handleHead(ctx context.Context, w http.ResponseWriter, r *http.Request, ref *provider.Reference, log zerolog.Logger) { - + client, err := s.gatewaySelector.Next() + if err != nil { + log.Error().Err(err).Msg("error selecting next client") + w.WriteHeader(http.StatusInternalServerError) + return + } req := &provider.StatRequest{Ref: ref} - res, err := s.gwClient.Stat(ctx, req) + res, err := client.Stat(ctx, req) if err != nil { log.Error().Err(err).Msg("error sending grpc stat request") w.WriteHeader(http.StatusInternalServerError) diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/locks.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/locks.go index d822b1055..54d69a1e2 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/locks.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/locks.go @@ -41,6 +41,7 @@ import ( "github.com/cs3org/reva/v2/pkg/appctx" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/errtypes" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/google/uuid" "go.opentelemetry.io/otel/attribute" ) @@ -159,14 +160,14 @@ type LockSystem interface { } // NewCS3LS returns a new CS3 based LockSystem. -func NewCS3LS(c gateway.GatewayAPIClient) LockSystem { +func NewCS3LS(s pool.Selectable[gateway.GatewayAPIClient]) LockSystem { return &cs3LS{ - client: c, + selector: s, } } type cs3LS struct { - client gateway.GatewayAPIClient + selector pool.Selectable[gateway.GatewayAPIClient] } func (cls *cs3LS) Confirm(ctx context.Context, now time.Time, name0, name1 string, conditions ...Condition) (func(), error) { @@ -205,7 +206,13 @@ func (cls *cs3LS) Create(ctx context.Context, now time.Time, details LockDetails Nanos: uint32(expiration.Nanosecond()), } } - res, err := cls.client.SetLock(ctx, r) + + client, err := cls.selector.Next() + if err != nil { + return "", err + } + + res, err := client.SetLock(ctx, r) if err != nil { return "", err } @@ -233,10 +240,17 @@ func (cls *cs3LS) Unlock(ctx context.Context, now time.Time, ref *provider.Refer User: u.Id, }, } - res, err := cls.client.Unlock(ctx, r) + + client, err := cls.selector.Next() if err != nil { return err } + + res, err := client.Unlock(ctx, r) + if err != nil { + return err + } + switch res.Status.Code { case rpc.Code_CODE_OK: return nil @@ -388,7 +402,7 @@ func (s *svc) handleLock(w http.ResponseWriter, r *http.Request, ns string) (ret fn := path.Join(ns, r.URL.Path) // TODO do we still need to jail if we query the registry about the spaces? // TODO instead of using a string namespace ns pass in the space with the request? - ref, cs3Status, err := spacelookup.LookupReferenceForPath(ctx, s.gwClient, fn) + ref, cs3Status, err := spacelookup.LookupReferenceForPath(ctx, s.gatewaySelector, fn) if err != nil { return http.StatusInternalServerError, err } @@ -566,7 +580,7 @@ func (s *svc) handleUnlock(w http.ResponseWriter, r *http.Request, ns string) (s fn := path.Join(ns, r.URL.Path) // TODO do we still need to jail if we query the registry about the spaces? // TODO instead of using a string namespace ns pass in the space with the request? - ref, cs3Status, err := spacelookup.LookupReferenceForPath(ctx, s.gwClient, fn) + ref, cs3Status, err := spacelookup.LookupReferenceForPath(ctx, s.gatewaySelector, fn) if err != nil { return http.StatusInternalServerError, err } diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/meta.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/meta.go index 69d7da344..169f9fd3f 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/meta.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/meta.go @@ -119,9 +119,14 @@ func (h *MetaHandler) handlePathForUser(w http.ResponseWriter, r *http.Request, w.WriteHeader(http.StatusBadRequest) return } - + client, err := s.gatewaySelector.Next() + if err != nil { + sublog.Error().Err(err).Msg("error selecting next client") + w.WriteHeader(http.StatusInternalServerError) + return + } pathReq := &provider.GetPathRequest{ResourceId: rid} - pathRes, err := s.gwClient.GetPath(ctx, pathReq) + pathRes, err := client.GetPath(ctx, pathReq) if err != nil { sublog.Error().Err(err).Msg("could not send GetPath grpc request: transport error") w.WriteHeader(http.StatusInternalServerError) diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/mkcol.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/mkcol.go index 0636052b0..9b445a143 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/mkcol.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/mkcol.go @@ -45,9 +45,14 @@ func (s *svc) handlePathMkcol(w http.ResponseWriter, r *http.Request, ns string) } sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger() + client, err := s.gatewaySelector.Next() + if err != nil { + return http.StatusInternalServerError, errtypes.InternalError(err.Error()) + } + // stat requested path to make sure it isn't existing yet // NOTE: It could be on another storage provider than the 'parent' of it - sr, err := s.gwClient.Stat(ctx, &provider.StatRequest{ + sr, err := client.Stat(ctx, &provider.StatRequest{ Ref: &provider.Reference{ Path: fn, }, @@ -67,7 +72,7 @@ func (s *svc) handlePathMkcol(w http.ResponseWriter, r *http.Request, ns string) parentPath := path.Dir(fn) - space, rpcStatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gwClient, parentPath) + space, rpcStatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gatewaySelector, parentPath) switch { case err != nil: return http.StatusInternalServerError, err @@ -108,8 +113,12 @@ func (s *svc) handleMkcol(ctx context.Context, w http.ResponseWriter, r *http.Re return http.StatusUnsupportedMediaType, fmt.Errorf("extended-mkcol not supported") } + client, err := s.gatewaySelector.Next() + if err != nil { + return http.StatusInternalServerError, errtypes.InternalError(err.Error()) + } req := &provider.CreateContainerRequest{Ref: childRef} - res, err := s.gwClient.CreateContainer(ctx, req) + res, err := client.CreateContainer(ctx, req) switch { case err != nil: return http.StatusInternalServerError, err @@ -123,7 +132,7 @@ func (s *svc) handleMkcol(ctx context.Context, w http.ResponseWriter, r *http.Re return http.StatusNotFound, errors.New("Resource not found") case res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED: // check if user has access to parent - sRes, err := s.gwClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ + sRes, err := client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ ResourceId: childRef.GetResourceId(), Path: utils.MakeRelativePath(path.Dir(childRef.Path)), }}) diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/move.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/move.go index 793eaa9d8..10ccb32ac 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/move.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/move.go @@ -78,7 +78,7 @@ func (s *svc) handlePathMove(w http.ResponseWriter, r *http.Request, ns string) sublog := appctx.GetLogger(ctx).With().Str("src", srcPath).Str("dst", dstPath).Logger() - srcSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gwClient, srcPath) + srcSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gatewaySelector, srcPath) if err != nil { sublog.Error().Err(err).Str("path", srcPath).Msg("failed to look up source storage space") w.WriteHeader(http.StatusInternalServerError) @@ -88,7 +88,7 @@ func (s *svc) handlePathMove(w http.ResponseWriter, r *http.Request, ns string) errors.HandleErrorStatus(&sublog, w, status) return } - dstSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gwClient, dstPath) + dstSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gatewaySelector, dstPath) if err != nil { sublog.Error().Err(err).Str("path", dstPath).Msg("failed to look up destination storage space") w.WriteHeader(http.StatusInternalServerError) @@ -141,7 +141,7 @@ func (s *svc) handleSpacesMove(w http.ResponseWriter, r *http.Request, srcSpaceI } func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Request, src, dst *provider.Reference, log zerolog.Logger) { - isChild, err := s.referenceIsChildOf(ctx, s.gwClient, dst, src) + isChild, err := s.referenceIsChildOf(ctx, s.gatewaySelector, dst, src) if err != nil { switch err.(type) { case errtypes.IsNotSupported: @@ -169,9 +169,16 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req return } + client, err := s.gatewaySelector.Next() + if err != nil { + log.Error().Err(err).Msg("error selecting next client") + w.WriteHeader(http.StatusInternalServerError) + return + } + // check src exists srcStatReq := &provider.StatRequest{Ref: src} - srcStatRes, err := s.gwClient.Stat(ctx, srcStatReq) + srcStatRes, err := client.Stat(ctx, srcStatReq) if err != nil { log.Error().Err(err).Msg("error sending grpc stat request") w.WriteHeader(http.StatusInternalServerError) @@ -190,7 +197,7 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req // check dst exists dstStatReq := &provider.StatRequest{Ref: dst} - dstStatRes, err := s.gwClient.Stat(ctx, dstStatReq) + dstStatRes, err := client.Stat(ctx, dstStatReq) if err != nil { log.Error().Err(err).Msg("error sending grpc stat request") w.WriteHeader(http.StatusInternalServerError) @@ -213,7 +220,7 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req // delete existing tree delReq := &provider.DeleteRequest{Ref: dst} - delRes, err := s.gwClient.Delete(ctx, delReq) + delRes, err := client.Delete(ctx, delReq) if err != nil { log.Error().Err(err).Msg("error sending grpc delete request") w.WriteHeader(http.StatusInternalServerError) @@ -230,7 +237,7 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req ResourceId: dst.ResourceId, Path: utils.MakeRelativePath(path.Dir(dst.Path)), }} - intStatRes, err := s.gwClient.Stat(ctx, intStatReq) + intStatRes, err := client.Stat(ctx, intStatReq) if err != nil { log.Error().Err(err).Msg("error sending grpc stat request") w.WriteHeader(http.StatusInternalServerError) @@ -250,7 +257,7 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req } mReq := &provider.MoveRequest{Source: src, Destination: dst} - mRes, err := s.gwClient.Move(ctx, mReq) + mRes, err := client.Move(ctx, mReq) if err != nil { log.Error().Err(err).Msg("error sending move grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -279,7 +286,7 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req return } - dstStatRes, err = s.gwClient.Stat(ctx, dstStatReq) + dstStatRes, err = client.Stat(ctx, dstStatReq) if err != nil { log.Error().Err(err).Msg("error sending grpc stat request") w.WriteHeader(http.StatusInternalServerError) diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/ocdav.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/ocdav.go index db25c5d7a..e0291a319 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/ocdav.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/ocdav.go @@ -144,7 +144,7 @@ type svc struct { davHandler *DavHandler favoritesManager favorite.Manager client *http.Client - gwClient gateway.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] // LockSystem is the lock management system. LockSystem LockSystem userIdentifierCache *ttlcache.Cache @@ -163,11 +163,11 @@ func getFavoritesManager(c *Config) (favorite.Manager, error) { } func getLockSystem(c *Config) (LockSystem, error) { // TODO in memory implementation - client, err := pool.GetGatewayServiceClient(c.GatewaySvc) + selector, err := pool.GatewaySelector(c.GatewaySvc) if err != nil { return nil, err } - return NewCS3LS(client), nil + return NewCS3LS(selector), nil } // New returns a new ocdav service @@ -192,7 +192,7 @@ func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error) } // NewWith returns a new ocdav service -func NewWith(conf *Config, fm favorite.Manager, ls LockSystem, _ *zerolog.Logger, gwc gateway.GatewayAPIClient) (global.Service, error) { +func NewWith(conf *Config, fm favorite.Manager, ls LockSystem, _ *zerolog.Logger, selector pool.Selectable[gateway.GatewayAPIClient]) (global.Service, error) { // be safe - init the conf again conf.init() @@ -204,7 +204,7 @@ func NewWith(conf *Config, fm favorite.Manager, ls LockSystem, _ *zerolog.Logger rhttp.Timeout(time.Duration(conf.Timeout*int64(time.Second))), rhttp.Insecure(conf.Insecure), ), - gwClient: gwc, + gatewaySelector: selector, favoritesManager: fm, LockSystem: ls, userIdentifierCache: ttlcache.NewCache(), @@ -219,9 +219,9 @@ func NewWith(conf *Config, fm favorite.Manager, ls LockSystem, _ *zerolog.Logger if err := s.davHandler.init(conf); err != nil { return nil, err } - if gwc == nil { + if selector == nil { var err error - s.gwClient, err = pool.GetGatewayServiceClient(s.c.GatewaySvc) + s.gatewaySelector, err = pool.GatewaySelector(s.c.GatewaySvc) if err != nil { return nil, err } @@ -326,7 +326,12 @@ func (s *svc) ApplyLayout(ctx context.Context, ns string, useLoggedInUserNS bool requestUsernameOrID, requestPath = router.ShiftPath(requestPath) // Check if this is a Userid - userRes, err := s.gwClient.GetUser(ctx, &userpb.GetUserRequest{ + client, err := s.gatewaySelector.Next() + if err != nil { + return "", "", err + } + + userRes, err := client.GetUser(ctx, &userpb.GetUserRequest{ UserId: &userpb.UserId{OpaqueId: requestUsernameOrID}, }) if err != nil { @@ -335,7 +340,7 @@ func (s *svc) ApplyLayout(ctx context.Context, ns string, useLoggedInUserNS bool // If it's not a userid try if it is a user name if userRes.Status.Code != rpc.Code_CODE_OK { - res, err := s.gwClient.GetUserByClaim(ctx, &userpb.GetUserByClaimRequest{ + res, err := client.GetUserByClaim(ctx, &userpb.GetUserByClaimRequest{ Claim: "username", Value: requestUsernameOrID, }) @@ -406,7 +411,11 @@ func authContextForUser(client gateway.GatewayAPIClient, userID *userpb.UserId, return granteeCtx, nil } -func (s *svc) sspReferenceIsChildOf(ctx context.Context, client gateway.GatewayAPIClient, child, parent *provider.Reference) (bool, error) { +func (s *svc) sspReferenceIsChildOf(ctx context.Context, selector pool.Selectable[gateway.GatewayAPIClient], child, parent *provider.Reference) (bool, error) { + client, err := selector.Next() + if err != nil { + return false, err + } parentStatRes, err := client.Stat(ctx, &provider.StatRequest{Ref: parent}) if err != nil { return false, err @@ -448,7 +457,7 @@ func (s *svc) sspReferenceIsChildOf(ctx context.Context, client gateway.GatewayA return strings.HasPrefix(cp, pp), nil } -func (s *svc) referenceIsChildOf(ctx context.Context, client gateway.GatewayAPIClient, child, parent *provider.Reference) (bool, error) { +func (s *svc) referenceIsChildOf(ctx context.Context, selector pool.Selectable[gateway.GatewayAPIClient], child, parent *provider.Reference) (bool, error) { if child.ResourceId.SpaceId != parent.ResourceId.SpaceId { return false, nil // Not on the same storage -> not a child } @@ -459,7 +468,12 @@ func (s *svc) referenceIsChildOf(ctx context.Context, client gateway.GatewayAPIC if child.ResourceId.SpaceId == utils.ShareStorageSpaceID || parent.ResourceId.SpaceId == utils.ShareStorageSpaceID { // the sharesstorageprovider needs some special handling - return s.sspReferenceIsChildOf(ctx, client, child, parent) + return s.sspReferenceIsChildOf(ctx, selector, child, parent) + } + + client, err := selector.Next() + if err != nil { + return false, err } // the references are on the same storage but relative to different resources diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind/propfind.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind/propfind.go index ad4c671a2..6e0de1022 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind/propfind.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind/propfind.go @@ -47,6 +47,7 @@ import ( ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/publicshare" rstatus "github.com/cs3org/reva/v2/pkg/rgrpc/status" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/rhttp/router" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" @@ -163,20 +164,17 @@ func NewMultiStatusResponseXML() *MultiStatusResponseXML { } } -// GetGatewayServiceClientFunc is a callback used to pass in a StorageProviderClient during testing -type GetGatewayServiceClientFunc func() (gateway.GatewayAPIClient, error) - // Handler handles propfind requests type Handler struct { PublicURL string - getClient GetGatewayServiceClientFunc + selector pool.Selectable[gateway.GatewayAPIClient] } // NewHandler returns a new PropfindHandler instance -func NewHandler(publicURL string, getClientFunc GetGatewayServiceClientFunc) *Handler { +func NewHandler(publicURL string, selector pool.Selectable[gateway.GatewayAPIClient]) *Handler { return &Handler{ PublicURL: publicURL, - getClient: getClientFunc, + selector: selector, } } @@ -197,7 +195,7 @@ func (p *Handler) HandlePathPropfind(w http.ResponseWriter, r *http.Request, ns } // retrieve a specific storage space - client, err := p.getClient() + client, err := p.selector.Next() if err != nil { sublog.Error().Err(err).Msg("error retrieving a gateway service client") w.WriteHeader(http.StatusInternalServerError) @@ -263,7 +261,7 @@ func (p *Handler) HandleSpacesPropfind(w http.ResponseWriter, r *http.Request, s return } - client, err := p.getClient() + client, err := p.selector.Next() if err != nil { sublog.Error().Err(err).Msg("error getting grpc client") w.WriteHeader(http.StatusInternalServerError) @@ -386,7 +384,7 @@ func (p *Handler) propfindResponse(ctx context.Context, w http.ResponseWriter, r // same as user / group shares for share indicators filters = append(filters, publicshare.ResourceIDFilter(resourceInfos[i].Id)) } - client, err := p.getClient() + client, err := p.selector.Next() if err != nil { log.Error().Err(err).Msg("error getting grpc client") w.WriteHeader(http.StatusInternalServerError) @@ -465,7 +463,7 @@ func (p *Handler) getResourceInfos(ctx context.Context, w http.ResponseWriter, r } span.SetAttributes(attribute.KeyValue{Key: "depth", Value: attribute.StringValue(depth.String())}) - client, err := p.getClient() + client, err := p.selector.Next() if err != nil { log.Error().Err(err).Msg("error getting grpc client") w.WriteHeader(http.StatusInternalServerError) @@ -675,7 +673,7 @@ func (p *Handler) getSpaceResourceInfos(ctx context.Context, w http.ResponseWrit span.SetAttributes(attribute.KeyValue{Key: "depth", Value: attribute.StringValue(depth.String())}) defer span.End() - client, err := p.getClient() + client, err := p.selector.Next() if err != nil { log.Error().Err(err).Msg("error getting grpc client") w.WriteHeader(http.StatusInternalServerError) diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/proppatch.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/proppatch.go index 9358e6b8c..492348d40 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/proppatch.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/proppatch.go @@ -55,7 +55,7 @@ func (s *svc) handlePathProppatch(w http.ResponseWriter, r *http.Request, ns str return status, err } - space, rpcStatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gwClient, fn) + space, rpcStatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gatewaySelector, fn) switch { case err != nil: return http.StatusInternalServerError, err @@ -64,9 +64,14 @@ func (s *svc) handlePathProppatch(w http.ResponseWriter, r *http.Request, ns str case rpcStatus.Code != rpc.Code_CODE_OK: return rstatus.HTTPStatusFromCode(rpcStatus.Code), errtypes.NewErrtypeFromStatus(rpcStatus) } + + client, err := s.gatewaySelector.Next() + if err != nil { + return http.StatusInternalServerError, errtypes.InternalError(err.Error()) + } // check if resource exists statReq := &provider.StatRequest{Ref: spacelookup.MakeRelativeReference(space, fn, false)} - statRes, err := s.gwClient.Stat(ctx, statReq) + statRes, err := client.Stat(ctx, statReq) switch { case err != nil: return http.StatusInternalServerError, err @@ -137,6 +142,12 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt acceptedProps := []xml.Name{} removedProps := []xml.Name{} + client, err := s.gatewaySelector.Next() + if err != nil { + log.Error().Err(err).Msg("error selecting next gateway client") + w.WriteHeader(http.StatusInternalServerError) + return nil, nil, false + } for i := range patches { if len(patches[i].Props) < 1 { continue @@ -161,7 +172,7 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt // FIXME: batch this somehow if remove { rreq.ArbitraryMetadataKeys[0] = key - res, err := s.gwClient.UnsetArbitraryMetadata(ctx, rreq) + res, err := client.UnsetArbitraryMetadata(ctx, rreq) if err != nil { log.Error().Err(err).Msg("error sending a grpc UnsetArbitraryMetadata request") w.WriteHeader(http.StatusInternalServerError) @@ -178,7 +189,7 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt m := res.Status.Message if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { // check if user has access to resource - sRes, err := s.gwClient.Stat(ctx, &provider.StatRequest{Ref: ref}) + sRes, err := client.Stat(ctx, &provider.StatRequest{Ref: ref}) if err != nil { log.Error().Err(err).Msg("error performing stat grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -200,7 +211,7 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt return nil, nil, false } if key == "http://owncloud.org/ns/favorite" { - statRes, err := s.gwClient.Stat(ctx, &provider.StatRequest{Ref: ref}) + statRes, err := client.Stat(ctx, &provider.StatRequest{Ref: ref}) if err != nil { w.WriteHeader(http.StatusInternalServerError) return nil, nil, false @@ -215,7 +226,7 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt removedProps = append(removedProps, propNameXML) } else { sreq.ArbitraryMetadata.Metadata[key] = value - res, err := s.gwClient.SetArbitraryMetadata(ctx, sreq) + res, err := client.SetArbitraryMetadata(ctx, sreq) if err != nil { log.Error().Err(err).Str("key", key).Str("value", value).Msg("error sending a grpc SetArbitraryMetadata request") w.WriteHeader(http.StatusInternalServerError) @@ -232,7 +243,7 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt m := res.Status.Message if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { // check if user has access to resource - sRes, err := s.gwClient.Stat(ctx, &provider.StatRequest{Ref: ref}) + sRes, err := client.Stat(ctx, &provider.StatRequest{Ref: ref}) if err != nil { log.Error().Err(err).Msg("error performing stat grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -258,7 +269,7 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt delete(sreq.ArbitraryMetadata.Metadata, key) if key == "http://owncloud.org/ns/favorite" { - statRes, err := s.gwClient.Stat(ctx, &provider.StatRequest{Ref: ref}) + statRes, err := client.Stat(ctx, &provider.StatRequest{Ref: ref}) if err != nil || statRes.Info == nil { w.WriteHeader(http.StatusInternalServerError) return nil, nil, false diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/put.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/put.go index f90b0d7b3..c76aafeb6 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/put.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/put.go @@ -114,7 +114,7 @@ func (s *svc) handlePathPut(w http.ResponseWriter, r *http.Request, ns string) { fn := path.Join(ns, r.URL.Path) sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger() - space, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gwClient, fn) + space, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gatewaySelector, fn) if err != nil { sublog.Error().Err(err).Str("path", fn).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) @@ -148,8 +148,14 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ return } + client, err := s.gatewaySelector.Next() + if err != nil { + log.Error().Err(err).Msg("error selecting next gateway client") + w.WriteHeader(http.StatusInternalServerError) + return + } if length == 0 { - tfRes, err := s.gwClient.TouchFile(ctx, &provider.TouchFileRequest{ + tfRes, err := client.TouchFile(ctx, &provider.TouchFileRequest{ Ref: ref, }) if err != nil { @@ -162,7 +168,7 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ w.WriteHeader(http.StatusInternalServerError) return } - sRes, err := s.gwClient.Stat(ctx, &provider.StatRequest{ + sRes, err := client.Stat(ctx, &provider.StatRequest{ Ref: ref, }) if err != nil { @@ -241,7 +247,7 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ } // where to upload the file? - uRes, err := s.gwClient.InitiateFileUpload(ctx, uReq) + uRes, err := client.InitiateFileUpload(ctx, uReq) if err != nil { log.Error().Err(err).Msg("error initiating file upload") w.WriteHeader(http.StatusInternalServerError) @@ -258,7 +264,7 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ status := http.StatusForbidden m := uRes.Status.Message // check if user has access to parent - sRes, err := s.gwClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ + sRes, err := client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ ResourceId: ref.ResourceId, Path: utils.MakeRelativePath(path.Dir(ref.Path)), }}) diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/report.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/report.go index 3cea9b1f7..02f688676 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/report.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/report.go @@ -81,9 +81,15 @@ func (s *svc) doFilterFiles(w http.ResponseWriter, r *http.Request, ff *reportFi return } + client, err := s.gatewaySelector.Next() + if err != nil { + log.Error().Err(err).Msg("error selecting next gateway client") + w.WriteHeader(http.StatusInternalServerError) + return + } infos := make([]*provider.ResourceInfo, 0, len(favorites)) for i := range favorites { - statRes, err := s.gwClient.Stat(ctx, &providerv1beta1.StatRequest{Ref: &providerv1beta1.Reference{ResourceId: favorites[i]}}) + statRes, err := client.Stat(ctx, &providerv1beta1.StatRequest{Ref: &providerv1beta1.Reference{ResourceId: favorites[i]}}) if err != nil { log.Error().Err(err).Msg("error getting resource info") continue diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go index b9f68ed22..db6b0cc2a 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go @@ -29,6 +29,7 @@ import ( storageProvider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/v2/pkg/rgrpc/status" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" "google.golang.org/protobuf/types/known/fieldmaskpb" @@ -37,8 +38,8 @@ import ( // LookupReferenceForPath returns: // a reference with root and relative path // the status and error for the lookup -func LookupReferenceForPath(ctx context.Context, client gateway.GatewayAPIClient, path string) (*storageProvider.Reference, *rpc.Status, error) { - space, cs3Status, err := LookUpStorageSpaceForPath(ctx, client, path) +func LookupReferenceForPath(ctx context.Context, selector pool.Selectable[gateway.GatewayAPIClient], path string) (*storageProvider.Reference, *rpc.Status, error) { + space, cs3Status, err := LookUpStorageSpaceForPath(ctx, selector, path) if err != nil || cs3Status.Code != rpc.Code_CODE_OK { return nil, cs3Status, err } @@ -52,7 +53,7 @@ func LookupReferenceForPath(ctx context.Context, client gateway.GatewayAPIClient // LookUpStorageSpaceForPath returns: // the storage spaces responsible for a path // the status and error for the lookup -func LookUpStorageSpaceForPath(ctx context.Context, client gateway.GatewayAPIClient, path string) (*storageProvider.StorageSpace, *rpc.Status, error) { +func LookUpStorageSpaceForPath(ctx context.Context, selector pool.Selectable[gateway.GatewayAPIClient], path string) (*storageProvider.StorageSpace, *rpc.Status, error) { // TODO add filter to only fetch spaces changed in the last 30 sec? // TODO cache space information, invalidate after ... 5min? so we do not need to fetch all spaces? // TODO use ListContainerStream to listen for changes @@ -72,6 +73,11 @@ func LookUpStorageSpaceForPath(ctx context.Context, client gateway.GatewayAPICli }, } + client, err := selector.Next() + if err != nil { + return nil, status.NewInternal(ctx, "could not select next client"), err + } + lSSRes, err := client.ListStorageSpaces(ctx, lSSReq) if err != nil || lSSRes.Status.Code != rpc.Code_CODE_OK { status := status.NewStatusFromErrType(ctx, "failed to lookup storage spaces", err) diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spaces.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spaces.go index 40c227287..b915ce63b 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spaces.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spaces.go @@ -22,7 +22,6 @@ import ( "net/http" "path" - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind" @@ -79,9 +78,7 @@ func (h *SpacesHandler) Handler(s *svc, trashbinHandler *TrashbinHandler) http.H var err error switch r.Method { case MethodPropfind: - p := propfind.NewHandler(config.PublicURL, func() (gateway.GatewayAPIClient, error) { - return s.gwClient, nil - }) + p := propfind.NewHandler(config.PublicURL, s.gatewaySelector) p.HandleSpacesPropfind(w, r, spaceID) case MethodProppatch: status, err = s.handleSpacesProppatch(w, r, spaceID) diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/tpc.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/tpc.go index 1b138c1a8..0c18979f8 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/tpc.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/tpc.go @@ -37,6 +37,7 @@ import ( "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" "github.com/cs3org/reva/v2/pkg/appctx" "github.com/cs3org/reva/v2/pkg/errtypes" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/rhttp" ) @@ -134,10 +135,16 @@ func (s *svc) handleTPCPull(ctx context.Context, w http.ResponseWriter, r *http. } sublog.Debug().Bool("overwrite", overwrite).Msg("TPC Pull") + client, err := s.gatewaySelector.Next() + if err != nil { + sublog.Error().Err(err).Msg("error selecting next gateway client") + w.WriteHeader(http.StatusInternalServerError) + return + } // check if destination exists ref := &provider.Reference{Path: dst} dstStatReq := &provider.StatRequest{Ref: ref} - dstStatRes, err := s.gwClient.Stat(ctx, dstStatReq) + dstStatRes, err := client.Stat(ctx, dstStatReq) if err != nil { sublog.Error().Err(err).Msg("error sending grpc stat request") w.WriteHeader(http.StatusInternalServerError) @@ -153,7 +160,7 @@ func (s *svc) handleTPCPull(ctx context.Context, w http.ResponseWriter, r *http. return } - err = s.performHTTPPull(ctx, s.gwClient, r, w, ns) + err = s.performHTTPPull(ctx, s.gatewaySelector, r, w, ns) if err != nil { sublog.Error().Err(err).Msg("error performing TPC Pull") return @@ -161,7 +168,7 @@ func (s *svc) handleTPCPull(ctx context.Context, w http.ResponseWriter, r *http. fmt.Fprintf(w, "success: Created") } -func (s *svc) performHTTPPull(ctx context.Context, client gateway.GatewayAPIClient, r *http.Request, w http.ResponseWriter, ns string) error { +func (s *svc) performHTTPPull(ctx context.Context, selector pool.Selectable[gateway.GatewayAPIClient], r *http.Request, w http.ResponseWriter, ns string) error { src := r.Header.Get("Source") dst := path.Join(ns, r.URL.Path) sublog := appctx.GetLogger(ctx) @@ -197,6 +204,12 @@ func (s *svc) performHTTPPull(ctx context.Context, client gateway.GatewayAPIClie return errtypes.InternalError(fmt.Sprintf("Remote GET returned status code %d", httpDownloadRes.StatusCode)) } + client, err := s.gatewaySelector.Next() + if err != nil { + sublog.Error().Err(err).Msg("error selecting next gateway client") + w.WriteHeader(http.StatusInternalServerError) + return errtypes.InternalError(err.Error()) + } // get upload url uReq := &provider.InitiateFileUploadRequest{ Ref: &provider.Reference{Path: dst}, @@ -287,9 +300,15 @@ func (s *svc) handleTPCPush(ctx context.Context, w http.ResponseWriter, r *http. sublog.Debug().Bool("overwrite", overwrite).Msg("TPC Push") + client, err := s.gatewaySelector.Next() + if err != nil { + sublog.Error().Err(err).Msg("error selecting next gateway client") + w.WriteHeader(http.StatusInternalServerError) + return + } ref := &provider.Reference{Path: src} srcStatReq := &provider.StatRequest{Ref: ref} - srcStatRes, err := s.gwClient.Stat(ctx, srcStatReq) + srcStatRes, err := client.Stat(ctx, srcStatReq) if err != nil { sublog.Error().Err(err).Msg("error sending grpc stat request") w.WriteHeader(http.StatusInternalServerError) @@ -305,7 +324,7 @@ func (s *svc) handleTPCPush(ctx context.Context, w http.ResponseWriter, r *http. return } - err = s.performHTTPPush(ctx, s.gwClient, r, w, srcStatRes.Info, ns) + err = s.performHTTPPush(ctx, r, w, srcStatRes.Info, ns) if err != nil { sublog.Error().Err(err).Msg("error performing TPC Push") return @@ -313,7 +332,7 @@ func (s *svc) handleTPCPush(ctx context.Context, w http.ResponseWriter, r *http. fmt.Fprintf(w, "success: Created") } -func (s *svc) performHTTPPush(ctx context.Context, client gateway.GatewayAPIClient, r *http.Request, w http.ResponseWriter, srcInfo *provider.ResourceInfo, ns string) error { +func (s *svc) performHTTPPush(ctx context.Context, r *http.Request, w http.ResponseWriter, srcInfo *provider.ResourceInfo, ns string) error { src := path.Join(ns, r.URL.Path) dst := r.Header.Get("Destination") @@ -325,6 +344,12 @@ func (s *svc) performHTTPPush(ctx context.Context, client gateway.GatewayAPIClie Ref: &provider.Reference{Path: src}, } + client, err := s.gatewaySelector.Next() + if err != nil { + sublog.Error().Err(err).Msg("error selecting next gateway client") + w.WriteHeader(http.StatusInternalServerError) + return err + } dRes, err := client.InitiateFileDownload(ctx, dReq) if err != nil { w.WriteHeader(http.StatusInternalServerError) diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/trashbin.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/trashbin.go index 3944546a0..78de68e4a 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/trashbin.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/trashbin.go @@ -111,7 +111,7 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { r.URL.Path = newPath basePath := path.Join(ns, newPath) - space, rpcstatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gwClient, basePath) + space, rpcstatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gatewaySelector, basePath) switch { case err != nil: log.Error().Err(err).Str("path", basePath).Msg("failed to look up storage space") @@ -151,7 +151,7 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { p := path.Join(ns, dst) // The destination can be in another space. E.g. the 'Shares Jail'. - space, rpcstatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gwClient, p) + space, rpcstatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gatewaySelector, p) if err != nil { log.Error().Err(err).Str("path", p).Msg("failed to look up destination storage space") w.WriteHeader(http.StatusInternalServerError) @@ -216,8 +216,14 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s return } + client, err := s.gatewaySelector.Next() + if err != nil { + sublog.Error().Err(err).Msg("error selecting next gateway client") + w.WriteHeader(http.StatusInternalServerError) + return + } // ask gateway for recycle items - getRecycleRes, err := s.gwClient.ListRecycle(ctx, &provider.ListRecycleRequest{Ref: ref, Key: path.Join(key, itemPath)}) + getRecycleRes, err := client.ListRecycle(ctx, &provider.ListRecycleRequest{Ref: ref, Key: path.Join(key, itemPath)}) if err != nil { sublog.Error().Err(err).Msg("error calling ListRecycle") w.WriteHeader(http.StatusInternalServerError) @@ -247,7 +253,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s for len(stack) > 0 { key := stack[len(stack)-1] - getRecycleRes, err := s.gwClient.ListRecycle(ctx, &provider.ListRecycleRequest{Ref: ref, Key: key}) + getRecycleRes, err := client.ListRecycle(ctx, &provider.ListRecycleRequest{Ref: ref, Key: key}) if err != nil { sublog.Error().Err(err).Msg("error calling ListRecycle") w.WriteHeader(http.StatusInternalServerError) @@ -465,8 +471,14 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc return } + client, err := s.gatewaySelector.Next() + if err != nil { + sublog.Error().Err(err).Msg("error selecting next gateway client") + w.WriteHeader(http.StatusInternalServerError) + return + } dstStatReq := &provider.StatRequest{Ref: dst} - dstStatRes, err := s.gwClient.Stat(ctx, dstStatReq) + dstStatRes, err := client.Stat(ctx, dstStatReq) if err != nil { sublog.Error().Err(err).Msg("error sending grpc stat request") w.WriteHeader(http.StatusInternalServerError) @@ -484,7 +496,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc parentRef := &provider.Reference{ResourceId: dst.ResourceId, Path: utils.MakeRelativePath(path.Dir(dst.Path))} parentStatReq := &provider.StatRequest{Ref: parentRef} - parentStatResponse, err := s.gwClient.Stat(ctx, parentStatReq) + parentStatResponse, err := client.Stat(ctx, parentStatReq) if err != nil { sublog.Error().Err(err).Msg("error sending grpc stat request") w.WriteHeader(http.StatusInternalServerError) @@ -515,7 +527,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc } // delete existing tree delReq := &provider.DeleteRequest{Ref: dst} - delRes, err := s.gwClient.Delete(ctx, delReq) + delRes, err := client.Delete(ctx, delReq) if err != nil { sublog.Error().Err(err).Msg("error sending grpc delete request") w.WriteHeader(http.StatusInternalServerError) @@ -534,7 +546,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc RestoreRef: dst, } - res, err := s.gwClient.RestoreRecycleItem(ctx, req) + res, err := client.RestoreRecycleItem(ctx, req) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc restore recycle item request") w.WriteHeader(http.StatusInternalServerError) @@ -551,7 +563,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc return } - dstStatRes, err = s.gwClient.Stat(ctx, dstStatReq) + dstStatRes, err = client.Stat(ctx, dstStatReq) if err != nil { sublog.Error().Err(err).Msg("error sending grpc stat request") w.WriteHeader(http.StatusInternalServerError) @@ -584,7 +596,13 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, Key: trashPath, } - res, err := s.gwClient.PurgeRecycle(ctx, req) + client, err := s.gatewaySelector.Next() + if err != nil { + sublog.Error().Err(err).Msg("error selecting next gateway client") + w.WriteHeader(http.StatusInternalServerError) + return + } + res, err := client.PurgeRecycle(ctx, req) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc restore recycle item request") w.WriteHeader(http.StatusInternalServerError) diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/tus.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/tus.go index 7e9148d06..8a6e6a66c 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/tus.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/tus.go @@ -114,10 +114,15 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. // TODO check Expect: 100-continue + client, err := s.gatewaySelector.Next() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } sReq := &provider.StatRequest{ Ref: ref, } - sRes, err := s.gwClient.Stat(ctx, sReq) + sRes, err := client.Stat(ctx, sReq) if err != nil { log.Error().Err(err).Msg("error sending grpc stat request") w.WriteHeader(http.StatusInternalServerError) @@ -155,7 +160,7 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. return } if uploadLength == 0 { - tfRes, err := s.gwClient.TouchFile(ctx, &provider.TouchFileRequest{ + tfRes, err := client.TouchFile(ctx, &provider.TouchFileRequest{ Ref: ref, }) if err != nil { @@ -193,7 +198,7 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. }, } - uRes, err := s.gwClient.InitiateFileUpload(ctx, uReq) + uRes, err := client.InitiateFileUpload(ctx, uReq) if err != nil { log.Error().Err(err).Msg("error initiating file upload") w.WriteHeader(http.StatusInternalServerError) @@ -284,7 +289,7 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. } } - sRes, err := s.gwClient.Stat(ctx, sReq) + sRes, err := client.Stat(ctx, sReq) if err != nil { log.Error().Err(err).Msg("error sending grpc stat request") w.WriteHeader(http.StatusInternalServerError) diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/versions.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/versions.go index 36831f0cf..ae249c1a6 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/versions.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/versions.go @@ -122,8 +122,14 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request, return } + client, err := s.gatewaySelector.Next() + if err != nil { + sublog.Error().Err(err).Msg("error selecting next gateway client") + w.WriteHeader(http.StatusInternalServerError) + return + } ref := &provider.Reference{ResourceId: rid} - res, err := s.gwClient.Stat(ctx, &provider.StatRequest{Ref: ref}) + res, err := client.Stat(ctx, &provider.StatRequest{Ref: ref}) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc stat request") w.WriteHeader(http.StatusInternalServerError) @@ -142,7 +148,7 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request, info := res.Info - lvRes, err := s.gwClient.ListFileVersions(ctx, &provider.ListFileVersionsRequest{Ref: ref}) + lvRes, err := client.ListFileVersions(ctx, &provider.ListFileVersionsRequest{Ref: ref}) if err != nil { sublog.Error().Err(err).Msg("error sending list container grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -225,7 +231,13 @@ func (h *VersionsHandler) doRestore(w http.ResponseWriter, r *http.Request, s *s Key: key, } - res, err := s.gwClient.RestoreFileVersion(ctx, req) + client, err := s.gatewaySelector.Next() + if err != nil { + sublog.Error().Err(err).Msg("error selecting next gateway client") + w.WriteHeader(http.StatusInternalServerError) + return + } + res, err := client.RestoreFileVersion(ctx, req) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc restore version request") w.WriteHeader(http.StatusInternalServerError) diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/webdav.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/webdav.go index 6a169edb6..e471bfdf4 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/webdav.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/webdav.go @@ -23,7 +23,6 @@ import ( "net/http" "path" - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind" "github.com/cs3org/reva/v2/pkg/appctx" @@ -73,9 +72,7 @@ func (h *WebDavHandler) Handler(s *svc) http.Handler { var status int // status 0 means the handler already sent the response switch r.Method { case MethodPropfind: - p := propfind.NewHandler(config.PublicURL, func() (gateway.GatewayAPIClient, error) { - return s.gwClient, nil - }) + p := propfind.NewHandler(config.PublicURL, s.gatewaySelector) p.HandlePathPropfind(w, r, ns) case MethodLock: status, err = s.handleLock(w, r, ns) diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/conversions/main.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/conversions/main.go index a0c1f6f18..06fdd6868 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/conversions/main.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/conversions/main.go @@ -129,9 +129,13 @@ type ShareData struct { // The type of the object being shared. This can be one of 'file' or 'folder'. ItemType string `json:"item_type" xml:"item_type"` // The RFC2045-compliant mimetype of the file. - MimeType string `json:"mimetype" xml:"mimetype"` - StorageID string `json:"storage_id" xml:"storage_id"` - Storage uint64 `json:"storage" xml:"storage"` + MimeType string `json:"mimetype" xml:"mimetype"` + // The space ID of the original file location + SpaceID string `json:"space_id" xml:"space_id"` + // The space alias of the original file location + SpaceAlias string `json:"space_alias" xml:"space_alias"` + StorageID string `json:"storage_id" xml:"storage_id"` + Storage uint64 `json:"storage" xml:"storage"` // The unique node id of the item being shared. ItemSource string `json:"item_source" xml:"item_source"` // The unique node id of the item being shared. For legacy reasons item_source and file_source attributes have the same value. @@ -214,7 +218,7 @@ type MatchData struct { type MatchValueData struct { ShareType int `json:"shareType" xml:"shareType"` ShareWith string `json:"shareWith" xml:"shareWith"` - ShareWithAdditionalInfo string `json:"shareWithAdditionalInfo" xml:"shareWithAdditionalInfo"` + ShareWithAdditionalInfo string `json:"shareWithAdditionalInfo" xml:"shareWithAdditionalInfo,omitempty"` UserType int `json:"userType" xml:"userType"` } @@ -345,7 +349,7 @@ func ParseTimestamp(timestampString string) (*types.Timestamp, error) { parsedTime, err = time.Parse("2006-01-02", timestampString) if err == nil { // the link needs to be valid for the whole day - parsedTime.Add(23*time.Hour + 59*time.Minute + 59*time.Second) + parsedTime = parsedTime.Add(23*time.Hour + 59*time.Minute + 59*time.Second) } } if err != nil { diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/conversions/role.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/conversions/role.go index d91c87311..eb42ca607 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/conversions/role.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/conversions/role.go @@ -98,6 +98,7 @@ func (r *Role) OCSPermissions() Permissions { // R = Shareable // M = Mounted // Z = Deniable (NEW) +// P = Purge from trashbin func (r *Role) WebDAVPermissions(isDir, isShared, isMountpoint, isPublic bool) string { var b strings.Builder if !isPublic && isShared { @@ -129,6 +130,10 @@ func (r *Role) WebDAVPermissions(isDir, isShared, isMountpoint, isPublic bool) s fmt.Fprintf(&b, "Z") } + if r.CS3ResourcePermissions().PurgeRecycle { + fmt.Fprintf(&b, "P") + } + return b.String() } @@ -231,7 +236,6 @@ func NewEditorRole(sharing bool) *Role { ListContainer: true, ListRecycle: true, Move: true, - PurgeRecycle: true, RestoreRecycleItem: true, Stat: true, }, @@ -255,7 +259,6 @@ func NewSpaceEditorRole() *Role { ListGrants: true, ListRecycle: true, Move: true, - PurgeRecycle: true, RestoreFileVersion: true, RestoreRecycleItem: true, Stat: true, @@ -400,7 +403,6 @@ func NewLegacyRoleFromOCSPermissions(p Permissions) *Role { } if p.Contain(PermissionDelete) { r.cS3ResourcePermissions.Delete = true - r.cS3ResourcePermissions.PurgeRecycle = true } if p.Contain(PermissionShare) { r.cS3ResourcePermissions.AddGrant = true @@ -446,8 +448,7 @@ func RoleFromResourcePermissions(rp *provider.ResourcePermissions, islink bool) rp.InitiateFileUpload { r.ocsPermissions |= PermissionCreate } - if rp.Delete && - rp.PurgeRecycle { + if rp.Delete { r.ocsPermissions |= PermissionDelete } if rp.AddGrant { diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees/sharees.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees/sharees.go index 55fdc73cf..cdb027c2c 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees/sharees.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees/sharees.go @@ -20,6 +20,7 @@ package sharees import ( "net/http" + "strings" grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -67,10 +68,15 @@ func (h *Handler) FindSharees(w http.ResponseWriter, r *http.Request) { log.Debug().Int("count", len(usersRes.GetUsers())).Str("search", term).Msg("users found") userMatches := make([]*conversions.MatchData, 0, len(usersRes.GetUsers())) + exactUserMatches := make([]*conversions.MatchData, 0) for _, user := range usersRes.GetUsers() { match := h.userAsMatch(user) log.Debug().Interface("user", user).Interface("match", match).Msg("mapped") - userMatches = append(userMatches, match) + if h.isExactMatch(match, term) { + exactUserMatches = append(exactUserMatches, match) + } else { + userMatches = append(userMatches, match) + } } groupsRes, err := gwc.FindGroups(r.Context(), &grouppb.FindGroupsRequest{Filter: term, SkipFetchingMembers: true}) @@ -81,16 +87,21 @@ func (h *Handler) FindSharees(w http.ResponseWriter, r *http.Request) { log.Debug().Int("count", len(groupsRes.GetGroups())).Str("search", term).Msg("groups found") groupMatches := make([]*conversions.MatchData, 0, len(groupsRes.GetGroups())) + exactGroupMatches := make([]*conversions.MatchData, 0) for _, g := range groupsRes.GetGroups() { match := h.groupAsMatch(g) log.Debug().Interface("group", g).Interface("match", match).Msg("mapped") - groupMatches = append(groupMatches, match) + if h.isExactMatch(match, term) { + exactGroupMatches = append(exactGroupMatches, match) + } else { + groupMatches = append(groupMatches, match) + } } response.WriteOCSSuccess(w, r, &conversions.ShareeData{ Exact: &conversions.ExactMatchesData{ - Users: []*conversions.MatchData{}, - Groups: []*conversions.MatchData{}, + Users: exactUserMatches, + Groups: exactGroupMatches, Remotes: []*conversions.MatchData{}, }, Users: userMatches, @@ -132,3 +143,11 @@ func (h *Handler) groupAsMatch(g *grouppb.Group) *conversions.MatchData { func (h *Handler) getAdditionalInfoAttribute(u *userpb.User) string { return templates.WithUser(u, h.additionalInfoAttribute) } + +func (h *Handler) isExactMatch(match *conversions.MatchData, term string) bool { + if match == nil || match.Value == nil { + return false + } + return strings.EqualFold(match.Value.ShareWith, term) || strings.EqualFold(match.Value.ShareWithAdditionalInfo, term) || + strings.EqualFold(match.Label, term) +} diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go index 9e2cd9398..574cc6f66 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go @@ -23,12 +23,14 @@ import ( "fmt" "net/http" "strconv" + "strings" permissionsv1beta1 "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/rs/zerolog/log" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/conversions" @@ -36,7 +38,6 @@ import ( "github.com/cs3org/reva/v2/pkg/appctx" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/publicshare" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/pkg/errors" ) @@ -127,7 +128,12 @@ func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request, } } - permissions, err := ocPublicPermToCs3(permKey, h) + // default perms: read-only + // TODO: the default might change depending on allowed permissions and configs + if permKey == nil { + permKey = &_defaultPublicLinkPermission + } + permissions, err := ocPublicPermToCs3(permKey) if err != nil { return nil, &ocsError{ Code: response.MetaBadRequest.StatusCode, @@ -135,16 +141,13 @@ func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request, Error: err, } } - if permissions == nil { - // default perms: read-only - // TODO: the default might change depending on allowed permissions and configs - permissions, err = ocPublicPermToCs3(&_defaultPublicLinkPermission, h) - if err != nil { - return nil, &ocsError{ - Code: response.MetaServerError.StatusCode, - Message: "Could not convert default permissions", - Error: err, - } + + password := strings.TrimSpace(r.FormValue("password")) + if h.enforcePassword(permKey) && len(password) == 0 { + return nil, &ocsError{ + Code: response.MetaBadRequest.StatusCode, + Message: "missing required password", + Error: errors.New("missing required password"), } } @@ -172,7 +175,7 @@ func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request, Permissions: &link.PublicSharePermissions{ Permissions: permissions, }, - Password: r.FormValue("password"), + Password: password, }, } @@ -198,7 +201,6 @@ func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request, Metadata: map[string]string{ "name": r.FormValue("name"), "quicklink": r.FormValue("quicklink"), - // "password": r.FormValue("password"), }, } @@ -275,9 +277,10 @@ func (h *Handler) listPublicShares(r *http.Request, filters []*link.ListPublicSh func (h *Handler) isPublicShare(r *http.Request, oid string) (*link.PublicShare, bool) { logger := appctx.GetLogger(r.Context()) - client, err := pool.GetGatewayServiceClient(h.gatewayAddr) + client, err := h.getClient() if err != nil { logger.Err(err) + return nil, false } psRes, err := client.GetPublicShare(r.Context(), &link.GetPublicShareRequest{ @@ -301,7 +304,7 @@ func (h *Handler) updatePublicShare(w http.ResponseWriter, r *http.Request, shar updates := []*link.UpdatePublicShareRequest_Update{} logger := appctx.GetLogger(r.Context()) - gwC, err := pool.GetGatewayServiceClient(h.gatewayAddr) + gwC, err := h.getClient() if err != nil { log.Err(err).Str("shareID", share.GetId().GetOpaqueId()).Msg("updatePublicShare") response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "error getting a connection to the gateway service", nil) @@ -377,7 +380,7 @@ func (h *Handler) updatePublicShare(w http.ResponseWriter, r *http.Request, shar } // Permissions - newPermissions, err := ocPublicPermToCs3(permKey, h) + newPermissions, err := ocPublicPermToCs3(permKey) logger.Debug().Interface("newPermissions", newPermissions).Msg("Parsed permissions") if err != nil { response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "invalid permissions", err) @@ -447,6 +450,14 @@ func (h *Handler) updatePublicShare(w http.ResponseWriter, r *http.Request, shar // Password newPassword, ok := r.Form["password"] + // enforcePassword + if h.enforcePassword(permKey) { + if (!ok && !share.PasswordProtected) || (ok && len(strings.TrimSpace(newPassword[0])) == 0) { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "missing required password", err) + return + } + } + // update or clear password if ok { updatesFound = true @@ -500,7 +511,7 @@ func (h *Handler) updatePublicShare(w http.ResponseWriter, r *http.Request, shar func (h *Handler) removePublicShare(w http.ResponseWriter, r *http.Request, share *link.PublicShare) { ctx := r.Context() - c, err := pool.GetGatewayServiceClient(h.gatewayAddr) + c, err := h.getClient() if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) return @@ -548,6 +559,39 @@ func (h *Handler) removePublicShare(w http.ResponseWriter, r *http.Request, shar response.WriteOCSSuccess(w, r, nil) } +// enforcePassword validate Password enforce based on configuration +// read_only: 1 +// read_write: 3 or 5 +// read_write_delete: 15 +// upload_only: 4 +func (h *Handler) enforcePassword(pk *int) bool { + if pk == nil { + return false + } + p, err := conversions.NewPermissions(decreasePermissionsIfNecessary(*pk)) + if err != nil { + return false + } + if h.publicPasswordEnforced.EnforcedForReadOnly && + p == conversions.PermissionRead { + return true + } + if h.publicPasswordEnforced.EnforcedForReadWrite && + (p == conversions.PermissionRead|conversions.PermissionWrite || + p == conversions.PermissionRead|conversions.PermissionCreate) { + return true + } + if h.publicPasswordEnforced.EnforcedForReadWriteDelete && + p == conversions.PermissionRead|conversions.PermissionWrite|conversions.PermissionCreate|conversions.PermissionDelete { + return true + } + if h.publicPasswordEnforced.EnforcedForUploadOnly && + p == conversions.PermissionCreate { + return true + } + return false +} + // for public links oc10 api decreases all permissions to read: stay compatible! func decreasePermissionsIfNecessary(perm int) int { if perm == int(conversions.PermissionAll) { @@ -556,7 +600,7 @@ func decreasePermissionsIfNecessary(perm int) int { return perm } -func ocPublicPermToCs3(pk *int, h *Handler) (*provider.ResourcePermissions, error) { +func ocPublicPermToCs3(pk *int) (*provider.ResourcePermissions, error) { if pk == nil { return nil, nil } diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go index 415e86558..b272c569c 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go @@ -86,6 +86,7 @@ type Handler struct { statCache cache.StatCache deniable bool resharing bool + publicPasswordEnforced passwordEnforced getClient GatewayClientGetter } @@ -103,6 +104,13 @@ type ocsError struct { Message string } +type passwordEnforced struct { + EnforcedForReadOnly bool + EnforcedForReadWrite bool + EnforcedForReadWriteDelete bool + EnforcedForUploadOnly bool +} + func getCacheWarmupManager(c *config.Config) (sharecache.Warmup, error) { if f, ok := warmupreg.NewFuncs[c.CacheWarmupDriver]; ok { return f(c.CacheWarmupDrivers[c.CacheWarmupDriver]) @@ -129,6 +137,7 @@ func (h *Handler) Init(c *config.Config) { _ = h.userIdentifierCache.SetTTL(time.Second * time.Duration(c.UserIdentifierCacheTTL)) h.deniable = c.EnableDenials h.resharing = resharing(c) + h.publicPasswordEnforced = publicPwdEnforced(c) h.statCache = cache.GetStatCache(c.StatCacheStore, c.StatCacheNodes, c.StatCacheDatabase, "stat", time.Duration(c.StatCacheTTL)*time.Second, c.StatCacheSize) if c.CacheWarmupDriver != "" { @@ -299,7 +308,8 @@ func (h *Handler) CreateShare(w http.ResponseWriter, r *http.Request) { } s := conversions.PublicShare2ShareData(share, r, h.publicURL) - h.addFileInfo(ctx, s, statRes.Info) + h.addFileInfo(ctx, s, statRes.GetInfo()) + h.addPath(ctx, s, statRes.GetInfo()) h.mapUserIds(ctx, client, s) response.WriteOCSSuccess(w, r, s) @@ -503,7 +513,7 @@ func (h *Handler) GetShare(w http.ResponseWriter, r *http.Request) { ctx := r.Context() sublog := appctx.GetLogger(ctx).With().Str("shareID", shareID).Logger() - client, err := pool.GetGatewayServiceClient(h.gatewayAddr) + client, err := h.getClient() if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) return @@ -695,7 +705,7 @@ func (h *Handler) updateShare(w http.ResponseWriter, r *http.Request, share *col ctx := r.Context() sublog := appctx.GetLogger(ctx).With().Str("shareID", share.GetId().GetOpaqueId()).Logger() - client, err := pool.GetGatewayServiceClient(h.gatewayAddr) + client, err := h.getClient() if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) return @@ -1110,7 +1120,7 @@ func (h *Handler) addFilters(w http.ResponseWriter, r *http.Request, ref *provid ctx := r.Context() // first check if the file exists - client, err := pool.GetGatewayServiceClient(h.gatewayAddr) + client, err := h.getClient() if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) return nil, nil, err @@ -1159,7 +1169,7 @@ func (h *Handler) addFileInfo(ctx context.Context, s *conversions.ShareData, inf switch { case h.sharePrefix == "/": s.FileTarget = info.Path - client, err := pool.GetGatewayServiceClient(h.gatewayAddr) + client, err := h.getClient() if err == nil { gpRes, err := client.GetPath(ctx, &provider.GetPathRequest{ ResourceId: info.Id, @@ -1209,6 +1219,27 @@ func (h *Handler) addFileInfo(ctx context.Context, s *conversions.ShareData, inf if s.UIDOwner == "" && owner != nil { s.UIDOwner = owner.GetOpaqueId() } + + if info.GetSpace().GetRoot() != nil { + s.SpaceID = storagespace.FormatResourceID(*info.GetSpace().GetRoot()) + } + s.SpaceAlias = utils.ReadPlainFromOpaque(info.GetSpace().GetOpaque(), "spaceAlias") +} + +// addPath adds the complete path of the `ResourceInfo` to the `ShareData`. It is an expensive operation and might leak data so use it with care. +func (h *Handler) addPath(ctx context.Context, s *conversions.ShareData, info *provider.ResourceInfo) { + log := appctx.GetLogger(ctx) + client, err := h.getClient() + if err != nil { + log.Error().Err(err).Msg("addPath failed: cannot get gateway client") + return + } + gpRes, err := client.GetPath(ctx, &provider.GetPathRequest{ResourceId: info.Id}) + if err != nil || gpRes.GetStatus().GetCode() != rpc.Code_CODE_OK { + log.Error().Err(err).Interface("rpc response code", gpRes.GetStatus().GetCode()).Msg("addPath failed: cannot get path") + return + } + s.Path = gpRes.GetPath() } // mustGetIdentifiers always returns a struct with identifiers, if the user or group could not be found they will all be empty @@ -1533,6 +1564,23 @@ func (h *Handler) granteeExists(ctx context.Context, g *provider.Grantee, rid *p return false, nil } +func publicPwdEnforced(c *config.Config) passwordEnforced { + enf := passwordEnforced{} + if c == nil || + c.Capabilities.Capabilities == nil || + c.Capabilities.Capabilities.FilesSharing == nil || + c.Capabilities.Capabilities.FilesSharing.Public == nil || + c.Capabilities.Capabilities.FilesSharing.Public.Password == nil || + c.Capabilities.Capabilities.FilesSharing.Public.Password.EnforcedFor == nil { + return enf + } + enf.EnforcedForReadOnly = bool(c.Capabilities.Capabilities.FilesSharing.Public.Password.EnforcedFor.ReadOnly) + enf.EnforcedForReadWrite = bool(c.Capabilities.Capabilities.FilesSharing.Public.Password.EnforcedFor.ReadWrite) + enf.EnforcedForReadWriteDelete = bool(c.Capabilities.Capabilities.FilesSharing.Public.Password.EnforcedFor.ReadWriteDelete) + enf.EnforcedForUploadOnly = bool(c.Capabilities.Capabilities.FilesSharing.Public.Password.EnforcedFor.UploadOnly) + return enf +} + // sufficientPermissions returns true if the `existing` permissions contain the `requested` permissions func sufficientPermissions(existing, requested *provider.ResourcePermissions, islink bool) bool { ep := conversions.RoleFromResourcePermissions(existing, islink).OCSPermissions() diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/spaces.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/spaces.go index 39a2cd97c..7956918ff 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/spaces.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/spaces.go @@ -44,7 +44,7 @@ import ( func (h *Handler) getGrantee(ctx context.Context, name string) (provider.Grantee, error) { log := appctx.GetLogger(ctx) - client, err := pool.GetGatewayServiceClient(h.gatewayAddr) + client, err := h.getClient() if err != nil { return provider.Grantee{}, err } @@ -96,7 +96,7 @@ func (h *Handler) addSpaceMember(w http.ResponseWriter, r *http.Request, info *p return } - client, err := pool.GetGatewayServiceClient(h.gatewayAddr) + client, err := h.getClient() if err != nil { response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "error getting gateway client", err) return @@ -125,6 +125,33 @@ func (h *Handler) addSpaceMember(w http.ResponseWriter, r *http.Request, info *p Nanos: uint32(expiration.UnixNano() % int64(time.Second)), } } + + if role.Name != conversions.RoleManager { + ref := provider.Reference{ResourceId: info.GetId()} + p, err := h.findProvider(ctx, &ref) + if err != nil { + response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "error getting storage provider", err) + return + } + + providerClient, err := h.getStorageProviderClient(p) + if err != nil { + response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "error getting storage provider client", err) + return + } + + lgRes, err := providerClient.ListGrants(ctx, &provider.ListGrantsRequest{Ref: &ref}) + if err != nil || lgRes.Status.Code != rpc.Code_CODE_OK { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error listing space grants", err) + return + } + + if !isSpaceManagerRemaining(lgRes.Grants, grantee) { + response.WriteOCSError(w, r, http.StatusForbidden, "the space must have at least one manager", nil) + return + } + } + createShareRes, err := client.CreateShare(ctx, &collaborationv1beta1.CreateShareRequest{ ResourceInfo: info, Grant: &collaborationv1beta1.ShareGrant{ @@ -199,7 +226,7 @@ func (h *Handler) removeSpaceMember(w http.ResponseWriter, r *http.Request, spac return } - gatewayClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) + gatewayClient, err := h.getClient() if err != nil { response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "error getting gateway client", err) return diff --git a/vendor/github.com/cs3org/reva/v2/pkg/app/provider/wopi/wopi.go b/vendor/github.com/cs3org/reva/v2/pkg/app/provider/wopi/wopi.go index cf7bd52b5..11744154e 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/app/provider/wopi/wopi.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/app/provider/wopi/wopi.go @@ -69,6 +69,7 @@ type config struct { JWTSecret string `mapstructure:"jwt_secret" docs:";The JWT secret to be used to retrieve the token TTL."` AppDesktopOnly bool `mapstructure:"app_desktop_only" docs:"false;Specifies if the app can be opened only on desktop."` InsecureConnections bool `mapstructure:"insecure_connections"` + AppDisableChat bool `mapstructure:"app_disable_chat"` } func parseConfig(m map[string]interface{}) (*config, error) { @@ -246,20 +247,23 @@ func (p *wopiProvider) GetAppURL(ctx context.Context, resource *provider.Resourc return nil, err } - appFullURL := result["app-url"].(string) + url, err := url.Parse(result["app-url"].(string)) + if err != nil { + return nil, err + } + urlQuery := url.Query() if language != "" { - url, err := url.Parse(appFullURL) - if err != nil { - return nil, err - } - urlQuery := url.Query() urlQuery.Set("ui", language) // OnlyOffice urlQuery.Set("lang", language) // Collabora urlQuery.Set("UI_LLCC", language) // Office365 - url.RawQuery = urlQuery.Encode() - appFullURL = url.String() } + if p.conf.AppDisableChat { + urlQuery.Set("dchat", "1") // OnlyOffice disable chat + } + + url.RawQuery = urlQuery.Encode() + appFullURL := url.String() // Depending on whether wopi server returned any form parameters or not, // we decide whether the request method is POST or GET diff --git a/vendor/github.com/cs3org/reva/v2/pkg/eosclient/eosgrpc/eoshttp.go b/vendor/github.com/cs3org/reva/v2/pkg/eosclient/eosgrpc/eoshttp.go index 591f4ae00..bf7872a14 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/eosclient/eosgrpc/eoshttp.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/eosclient/eosgrpc/eoshttp.go @@ -379,6 +379,9 @@ func (c *EOSHTTPClient) PUTFile(ctx context.Context, remoteuser string, auth eos // Execute the request. I don't like that there is no explicit timeout or buffer control on the input stream log.Debug().Str("func", "PUTFile").Msg("sending req") resp, err := c.cl.Do(req) + if resp != nil { + resp.Body.Close() + } // Let's support redirections... and if we retry we retry at the same FST if resp != nil && resp.StatusCode == 307 { @@ -471,6 +474,9 @@ func (c *EOSHTTPClient) Head(ctx context.Context, remoteuser string, auth eoscli } // Execute the request. I don't like that there is no explicit timeout or buffer control on the input stream resp, err := c.cl.Do(req) + if resp != nil { + resp.Body.Close() + } // And get an error code (if error) that is worth propagating e := c.getRespError(resp, err) diff --git a/vendor/github.com/cs3org/reva/v2/pkg/events/files.go b/vendor/github.com/cs3org/reva/v2/pkg/events/files.go index 802072ee7..cc21a4b07 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/events/files.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/events/files.go @@ -23,6 +23,7 @@ import ( user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" ) // ContainerCreated is emitted when a directory has been created @@ -31,6 +32,7 @@ type ContainerCreated struct { Executant *user.UserId Ref *provider.Reference Owner *user.UserId + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -46,6 +48,7 @@ type FileUploaded struct { Executant *user.UserId Ref *provider.Reference Owner *user.UserId + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -60,6 +63,7 @@ type FileTouched struct { SpaceOwner *user.UserId Executant *user.UserId Ref *provider.Reference + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -74,6 +78,7 @@ type FileDownloaded struct { Executant *user.UserId Ref *provider.Reference Owner *user.UserId + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -90,6 +95,7 @@ type ItemTrashed struct { ID *provider.ResourceId Ref *provider.Reference Owner *user.UserId + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -106,6 +112,7 @@ type ItemMoved struct { Ref *provider.Reference Owner *user.UserId OldReference *provider.Reference + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -121,6 +128,7 @@ type ItemPurged struct { ID *provider.ResourceId Ref *provider.Reference Owner *user.UserId + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -139,6 +147,7 @@ type ItemRestored struct { Owner *user.UserId OldReference *provider.Reference Key string + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -155,6 +164,7 @@ type FileVersionRestored struct { Ref *provider.Reference Owner *user.UserId Key string + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface diff --git a/vendor/github.com/cs3org/reva/v2/pkg/events/groups.go b/vendor/github.com/cs3org/reva/v2/pkg/events/groups.go index c6bff4a9b..b9d464562 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/events/groups.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/events/groups.go @@ -22,12 +22,14 @@ import ( "encoding/json" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" ) // GroupCreated is emitted when a group was created type GroupCreated struct { Executant *user.UserId GroupID string + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -41,6 +43,7 @@ func (GroupCreated) Unmarshal(v []byte) (interface{}, error) { type GroupDeleted struct { Executant *user.UserId GroupID string + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -55,6 +58,7 @@ type GroupMemberAdded struct { Executant *user.UserId GroupID string UserID string + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -69,6 +73,7 @@ type GroupMemberRemoved struct { Executant *user.UserId GroupID string UserID string + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -80,8 +85,9 @@ func (GroupMemberRemoved) Unmarshal(v []byte) (interface{}, error) { // GroupFeature represents a group feature type GroupFeature struct { - Name string - Value string + Name string + Value string + Timestamp *types.Timestamp } // GroupFeatureChanged is emitted when a group feature was changed @@ -89,6 +95,7 @@ type GroupFeatureChanged struct { Executant *user.UserId GroupID string Features []GroupFeature + Timestamp *types.Timestamp } // Unmarshal to fulfill unmarshaller interface diff --git a/vendor/github.com/cs3org/reva/v2/pkg/events/postprocessing.go b/vendor/github.com/cs3org/reva/v2/pkg/events/postprocessing.go index 2e4f8e57b..0b0105011 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/events/postprocessing.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/events/postprocessing.go @@ -24,6 +24,7 @@ import ( user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" ) type ( @@ -59,6 +60,7 @@ type BytesReceived struct { Filename string Filesize uint64 URL string + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -80,6 +82,7 @@ type StartPostprocessingStep struct { RevaToken string // for file retrieval in after upload case StepToStart Postprocessingstep + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -99,6 +102,7 @@ type PostprocessingStepFinished struct { Result interface{} // result information see VirusscanResult for example Error error // possible error of the step Outcome PostprocessingOutcome // some services may cause postprocessing to stop + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -128,6 +132,7 @@ type VirusscanResult struct { Scandate time.Time ResourceID *provider.ResourceId ErrorMsg string // empty when no error + Timestamp *types.Timestamp } // PostprocessingFinished is emitted by *some* service which can decide that @@ -138,6 +143,7 @@ type PostprocessingFinished struct { ExecutingUser *user.User Result map[Postprocessingstep]interface{} // it is a map[step]Event Outcome PostprocessingOutcome + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -155,7 +161,7 @@ type UploadReady struct { ExecutingUser *user.User FileRef *provider.Reference Failed bool - Timestamp time.Time + Timestamp *types.Timestamp // add reference here? We could use it to inform client pp is finished } @@ -165,3 +171,16 @@ func (UploadReady) Unmarshal(v []byte) (interface{}, error) { err := json.Unmarshal(v, &e) return e, err } + +// ResumePostprocessing can be emitted to repair broken postprocessing +type ResumePostprocessing struct { + UploadID string + Timestamp *types.Timestamp +} + +// Unmarshal to fulfill umarshaller interface +func (ResumePostprocessing) Unmarshal(v []byte) (interface{}, error) { + e := ResumePostprocessing{} + err := json.Unmarshal(v, &e) + return e, err +} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/events/sharing.go b/vendor/github.com/cs3org/reva/v2/pkg/events/sharing.go index 2551e5e3a..77c7f04be 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/events/sharing.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/events/sharing.go @@ -206,6 +206,7 @@ type LinkAccessFailed struct { Token string Status rpc.Code Message string + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -221,6 +222,7 @@ type LinkRemoved struct { // split protobuf Ref ShareID *link.PublicShareId ShareToken string + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface diff --git a/vendor/github.com/cs3org/reva/v2/pkg/events/spaces.go b/vendor/github.com/cs3org/reva/v2/pkg/events/spaces.go index 4b9817c70..03ab3ff0a 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/events/spaces.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/events/spaces.go @@ -53,6 +53,7 @@ type SpaceRenamed struct { ID *provider.StorageSpaceId Owner *user.UserId Name string + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -81,6 +82,7 @@ type SpaceEnabled struct { Executant *user.UserId ID *provider.StorageSpaceId Owner *user.UserId + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -144,6 +146,7 @@ type SpaceUpdated struct { Executant *user.UserId ID *provider.StorageSpaceId Space *provider.StorageSpace + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -162,6 +165,7 @@ type SpaceMembershipExpired struct { // split the protobuf Grantee oneof so we can use stdlib encoding/json GranteeUserID *user.UserId GranteeGroupID *group.GroupId + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface diff --git a/vendor/github.com/cs3org/reva/v2/pkg/events/tags.go b/vendor/github.com/cs3org/reva/v2/pkg/events/tags.go index 44b6adf05..0bac8263a 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/events/tags.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/events/tags.go @@ -23,6 +23,7 @@ import ( user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" ) // TagsAdded is emitted when a Tag has been added @@ -31,6 +32,7 @@ type TagsAdded struct { Tags string Ref *provider.Reference Executant *user.UserId + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -46,6 +48,7 @@ type TagsRemoved struct { Tags string Ref *provider.Reference Executant *user.UserId + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface diff --git a/vendor/github.com/cs3org/reva/v2/pkg/events/users.go b/vendor/github.com/cs3org/reva/v2/pkg/events/users.go index bf9b4fe00..1a118b719 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/events/users.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/events/users.go @@ -20,15 +20,16 @@ package events import ( "encoding/json" - "time" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" ) // UserCreated is emitted when a user was created type UserCreated struct { Executant *user.UserId UserID string + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -42,6 +43,7 @@ func (UserCreated) Unmarshal(v []byte) (interface{}, error) { type UserDeleted struct { Executant *user.UserId UserID string + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -53,8 +55,9 @@ func (UserDeleted) Unmarshal(v []byte) (interface{}, error) { // UserFeature represents a user feature type UserFeature struct { - Name string - Value string + Name string + Value string + OldValue *string } // UserFeatureChanged is emitted when a user feature was changed @@ -62,6 +65,7 @@ type UserFeatureChanged struct { Executant *user.UserId UserID string Features []UserFeature + Timestamp *types.Timestamp } // Unmarshal to fulfill umarshaller interface @@ -74,7 +78,7 @@ func (UserFeatureChanged) Unmarshal(v []byte) (interface{}, error) { // PersonalDataExtracted is emitted when a user data extraction is finished type PersonalDataExtracted struct { Executant *user.UserId - Timestamp time.Time + Timestamp *types.Timestamp ErrorMsg string } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/micro/ocdav/option.go b/vendor/github.com/cs3org/reva/v2/pkg/micro/ocdav/option.go index 762cbb8e9..5ff8644ac 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/micro/ocdav/option.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/micro/ocdav/option.go @@ -24,6 +24,7 @@ import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/storage/favorite" "github.com/rs/zerolog" "go-micro.dev/v4/broker" @@ -45,7 +46,7 @@ type Options struct { JWTSecret string FavoriteManager favorite.Manager - GatewayClient gateway.GatewayAPIClient + GatewaySelector pool.Selectable[gateway.GatewayAPIClient] TracingEnabled bool TracingInsecure bool @@ -196,10 +197,10 @@ func FavoriteManager(val favorite.Manager) Option { } } -// GatewayClient provides a function to set the GatewayClient option. -func GatewayClient(val gateway.GatewayAPIClient) Option { +// GatewaySelector provides a function to set the GatewaySelector option. +func GatewaySelector(val pool.Selectable[gateway.GatewayAPIClient]) Option { return func(o *Options) { - o.GatewayClient = val + o.GatewaySelector = val } } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/micro/ocdav/service.go b/vendor/github.com/cs3org/reva/v2/pkg/micro/ocdav/service.go index 5e384e3a6..a54f5280a 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/micro/ocdav/service.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/micro/ocdav/service.go @@ -35,6 +35,7 @@ import ( "github.com/go-chi/chi/v5/middleware" httpServer "github.com/go-micro/plugins/v4/server/http" "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "go-micro.dev/v4" @@ -68,7 +69,7 @@ func Service(opts ...Option) (micro.Service, error) { server.Version(sopts.config.VersionString), ) - revaService, err := ocdav.NewWith(&sopts.config, sopts.FavoriteManager, sopts.lockSystem, &sopts.Logger, sopts.GatewayClient) + revaService, err := ocdav.NewWith(&sopts.config, sopts.FavoriteManager, sopts.lockSystem, &sopts.Logger, sopts.GatewaySelector) if err != nil { return nil, err } @@ -137,11 +138,11 @@ func setDefaults(sopts *Options) error { sopts.Name = ServerName } if sopts.lockSystem == nil { - client, err := pool.GetGatewayServiceClient(sopts.config.GatewaySvc) + selector, err := pool.GatewaySelector(sopts.config.GatewaySvc) if err != nil { - return err + return errors.Wrap(err, "error getting gateway selector") } - sopts.lockSystem = ocdav.NewCS3LS(client) + sopts.lockSystem = ocdav.NewCS3LS(selector) } if sopts.FavoriteManager == nil { sopts.FavoriteManager, _ = memory.New(map[string]interface{}{}) diff --git a/vendor/github.com/cs3org/reva/v2/pkg/ocm/share/manager/nextcloud/nextcloud.go b/vendor/github.com/cs3org/reva/v2/pkg/ocm/share/manager/nextcloud/nextcloud.go index d186b2e02..449387c0e 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/ocm/share/manager/nextcloud/nextcloud.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/ocm/share/manager/nextcloud/nextcloud.go @@ -58,7 +58,6 @@ func randSeq(n int) string { } func init() { - rand.Seed(time.Now().UnixNano()) registry.Register("nextcloud", New) } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/permission/permission.go b/vendor/github.com/cs3org/reva/v2/pkg/permission/permission.go index c34589b24..405f72bbc 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/permission/permission.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/permission/permission.go @@ -24,9 +24,9 @@ import ( const ( // ListAllSpaces is the hardcoded name for the list all spaces permission - ListAllSpaces string = "list-all-spaces" + ListAllSpaces string = "Drives.List" // CreateSpace is the hardcoded name for the create space permission - CreateSpace string = "create-space" + CreateSpace string = "Drives.Create" // WritePublicLink is the hardcoded name for the PublicLink.Write permission WritePublicLink string = "PublicLink.Write" ) diff --git a/vendor/github.com/cs3org/reva/v2/pkg/registry/config.go b/vendor/github.com/cs3org/reva/v2/pkg/registry/config.go deleted file mode 100644 index f798b65b9..000000000 --- a/vendor/github.com/cs3org/reva/v2/pkg/registry/config.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2018-2021 CERN -// -// 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. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package registry - -import ( - "github.com/mitchellh/mapstructure" -) - -// Config configures a registry -type Config struct { - Services map[string]map[string]*service `mapstructure:"services"` -} - -// service implements the Service interface. Attributes are exported so that mapstructure can unmarshal values onto them. -type service struct { - Name string `mapstructure:"name"` - Nodes []node `mapstructure:"nodes"` -} - -type node struct { - Address string `mapstructure:"address"` - Metadata map[string]string `mapstructure:"metadata"` -} - -// ParseConfig translates Config file values into a Config struct for consumers. -func ParseConfig(m map[string]interface{}) (*Config, error) { - c := &Config{} - if err := mapstructure.Decode(m, c); err != nil { - return nil, err - } - - if len(c.Services) == 0 { - c.Services = make(map[string]map[string]*service) - } - - return c, nil -} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/registry/memory/memory.go b/vendor/github.com/cs3org/reva/v2/pkg/registry/memory/memory.go deleted file mode 100644 index aee78c9ad..000000000 --- a/vendor/github.com/cs3org/reva/v2/pkg/registry/memory/memory.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2018-2021 CERN -// -// 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. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package memory - -import ( - "fmt" - "sync" - - "github.com/cs3org/reva/v2/pkg/registry" -) - -// Registry implements the Registry interface. -type Registry struct { - // m protects async access to the services map. - sync.Mutex - // services map a service name with a set of nodes. - services map[string]registry.Service -} - -// Add implements the Registry interface. If the service is already known in this registry it will only update the nodes. -func (r *Registry) Add(svc registry.Service) error { - r.Lock() - defer r.Unlock() - - // append the nodes if the service is already registered. - if _, ok := r.services[svc.Name()]; ok { - s := service{ - name: svc.Name(), - nodes: make([]node, 0), - } - - s.mergeNodes(svc.Nodes(), r.services[svc.Name()].Nodes()) - - r.services[svc.Name()] = s - return nil - } - - r.services[svc.Name()] = svc - return nil -} - -// GetService implements the Registry interface. There is currently no load balance being done, but it should not be -// hard to add. -func (r *Registry) GetService(name string) (registry.Service, error) { - r.Lock() - defer r.Unlock() - - if service, ok := r.services[name]; ok { - return service, nil - } - - return nil, fmt.Errorf("service %v not found", name) -} - -// New returns an implementation of the Registry interface. -func New(m map[string]interface{}) registry.Registry { - // c, err := registry.ParseConfig(m) - // if err != nil { - // return nil - // } - - return &Registry{ - services: map[string]registry.Service{}, - } -} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/registry/memory/node.go b/vendor/github.com/cs3org/reva/v2/pkg/registry/memory/node.go deleted file mode 100644 index 22042306d..000000000 --- a/vendor/github.com/cs3org/reva/v2/pkg/registry/memory/node.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2018-2021 CERN -// -// 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. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package memory - -import "fmt" - -// node implements the registry.Node interface. -type node struct { - id string - address string - metadata map[string]string -} - -func (n node) Address() string { - return n.address -} - -func (n node) Metadata() map[string]string { - return n.metadata -} - -func (n node) String() string { - return fmt.Sprintf("%v-%v", n.id, n.address) -} - -func (n node) ID() string { - return n.id -} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/registry/memory/service.go b/vendor/github.com/cs3org/reva/v2/pkg/registry/memory/service.go deleted file mode 100644 index d34e92cc3..000000000 --- a/vendor/github.com/cs3org/reva/v2/pkg/registry/memory/service.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2018-2021 CERN -// -// 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. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package memory - -import "github.com/cs3org/reva/v2/pkg/registry" - -// NewService creates a new memory registry.Service. -func NewService(name string, nodes []interface{}) registry.Service { - n := make([]node, 0) - for i := 0; i < len(nodes); i++ { - n = append(n, node{ - // explicit type conversions because types are not exported to prevent from circular dependencies until released. - id: nodes[i].(map[string]interface{})["id"].(string), - address: nodes[i].(map[string]interface{})["address"].(string), - //metadata: nodes[i].(map[string]interface{})["metadata"].(map[string]string), - }) - } - - return service{ - name: name, - nodes: n, - } -} - -// service implements the Service interface -type service struct { - name string - nodes []node -} - -// Name implements the service interface. -func (s service) Name() string { - return s.name -} - -// Nodes implements the service interface. -func (s service) Nodes() []registry.Node { - ret := make([]registry.Node, 0) - for i := range s.nodes { - ret = append(ret, s.nodes[i]) - } - return ret -} - -func (s *service) mergeNodes(n1, n2 []registry.Node) { - n1 = append(n1, n2...) - for _, n := range n1 { - s.nodes = append(s.nodes, node{ - id: n.ID(), - address: n.Address(), - metadata: n.Metadata(), - }) - } -} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/registry/registry.go b/vendor/github.com/cs3org/reva/v2/pkg/registry/registry.go index 8b705a093..1595726f0 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/registry/registry.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/registry/registry.go @@ -1,4 +1,4 @@ -// Copyright 2018-2021 CERN +// Copyright 2018-2023 CERN // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,32 +18,38 @@ package registry -// Registry provides with means for dynamically registering services. -type Registry interface { - // Add registers a Service on the memoryRegistry. Repeated names is allowed, services are distinguished by their metadata. - Add(Service) error +import ( + mRegistry "go-micro.dev/v4/registry" + "go-micro.dev/v4/selector" +) - // GetService retrieves a Service and all of its nodes by Service name. It returns []*Service because we can have - // multiple versions of the same Service running alongside each others. - GetService(string) (Service, error) +var ( + // fixme: get rid of global registry + gRegistry mRegistry.Registry +) + +// Init prepares the service registry +func Init(nRegistry mRegistry.Registry) error { + // first come first serves, the first service defines the registry type. + if gRegistry == nil && nRegistry != nil { + gRegistry = nRegistry + } + + return nil } -// Service defines a service. -type Service interface { - Name() string - Nodes() []Node +// GetRegistry exposes the registry +func GetRegistry() mRegistry.Registry { + return gRegistry } -// Node defines nodes on a service. -type Node interface { - // Address where the given node is running. - Address() string +// GetNodeAddress returns a random address from the service nodes +func GetNodeAddress(services []*mRegistry.Service) (string, error) { + next := selector.Random(services) + node, err := next() + if err != nil { + return "", err + } - // metadata is used in order to differentiate services implementations. For instance an AuthProvider Service could - // have multiple implementations, basic, bearer ..., metadata would be used to select the Service type depending on - // its implementation. - Metadata() map[string]string - - // ID returns the node ID. - ID() string + return node.Address, nil } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool/client.go b/vendor/github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool/client.go new file mode 100644 index 000000000..dc22dd417 --- /dev/null +++ b/vendor/github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool/client.go @@ -0,0 +1,155 @@ +// Copyright 2018-2021 CERN +// +// 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. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package pool + +import ( + appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1" + appregistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1" + applicationauth "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" + authprovider "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" + authregistry "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + ocmcore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1" + invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" + ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" + permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" + preferences "github.com/cs3org/go-cs3apis/cs3/preferences/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" + ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" + storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + storageregistry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" + datatx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1" +) + +// GetGatewayServiceClient returns a GatewayServiceClient. +func GetGatewayServiceClient(id string, opts ...Option) (gateway.GatewayAPIClient, error) { + selector, _ := GatewaySelector(id, opts...) + return selector.Next() +} + +// GetUserProviderServiceClient returns a UserProviderServiceClient. +func GetUserProviderServiceClient(id string, opts ...Option) (user.UserAPIClient, error) { + selector, _ := IdentityUserSelector(id, opts...) + return selector.Next() +} + +// GetGroupProviderServiceClient returns a GroupProviderServiceClient. +func GetGroupProviderServiceClient(id string, opts ...Option) (group.GroupAPIClient, error) { + selector, _ := IdentityGroupSelector(id, opts...) + return selector.Next() +} + +// GetStorageProviderServiceClient returns a StorageProviderServiceClient. +func GetStorageProviderServiceClient(id string, opts ...Option) (storageprovider.ProviderAPIClient, error) { + selector, _ := StorageProviderSelector(id, opts...) + return selector.Next() +} + +// GetAuthRegistryServiceClient returns a new AuthRegistryServiceClient. +func GetAuthRegistryServiceClient(id string, opts ...Option) (authregistry.RegistryAPIClient, error) { + selector, _ := AuthRegistrySelector(id, opts...) + return selector.Next() +} + +// GetAuthProviderServiceClient returns a new AuthProviderServiceClient. +func GetAuthProviderServiceClient(id string, opts ...Option) (authprovider.ProviderAPIClient, error) { + selector, _ := AuthProviderSelector(id, opts...) + return selector.Next() +} + +// GetAppAuthProviderServiceClient returns a new AppAuthProviderServiceClient. +func GetAppAuthProviderServiceClient(id string, opts ...Option) (applicationauth.ApplicationsAPIClient, error) { + selector, _ := AuthApplicationSelector(id, opts...) + return selector.Next() +} + +// GetUserShareProviderClient returns a new UserShareProviderClient. +func GetUserShareProviderClient(id string, opts ...Option) (collaboration.CollaborationAPIClient, error) { + selector, _ := SharingCollaborationSelector(id, opts...) + return selector.Next() +} + +// GetOCMShareProviderClient returns a new OCMShareProviderClient. +func GetOCMShareProviderClient(id string, opts ...Option) (ocm.OcmAPIClient, error) { + selector, _ := SharingOCMSelector(id, opts...) + return selector.Next() +} + +// GetOCMInviteManagerClient returns a new OCMInviteManagerClient. +func GetOCMInviteManagerClient(id string, opts ...Option) (invitepb.InviteAPIClient, error) { + selector, _ := OCMInviteSelector(id, opts...) + return selector.Next() +} + +// GetPublicShareProviderClient returns a new PublicShareProviderClient. +func GetPublicShareProviderClient(id string, opts ...Option) (link.LinkAPIClient, error) { + selector, _ := SharingLinkSelector(id, opts...) + return selector.Next() +} + +// GetPreferencesClient returns a new PreferencesClient. +func GetPreferencesClient(id string, opts ...Option) (preferences.PreferencesAPIClient, error) { + selector, _ := PreferencesSelector(id, opts...) + return selector.Next() +} + +// GetPermissionsClient returns a new PermissionsClient. +func GetPermissionsClient(id string, opts ...Option) (permissions.PermissionsAPIClient, error) { + selector, _ := PermissionsSelector(id, opts...) + return selector.Next() +} + +// GetAppRegistryClient returns a new AppRegistryClient. +func GetAppRegistryClient(id string, opts ...Option) (appregistry.RegistryAPIClient, error) { + selector, _ := AppRegistrySelector(id, opts...) + return selector.Next() +} + +// GetAppProviderClient returns a new AppRegistryClient. +func GetAppProviderClient(id string, opts ...Option) (appprovider.ProviderAPIClient, error) { + selector, _ := AppProviderSelector(id, opts...) + return selector.Next() +} + +// GetStorageRegistryClient returns a new StorageRegistryClient. +func GetStorageRegistryClient(id string, opts ...Option) (storageregistry.RegistryAPIClient, error) { + selector, _ := StorageRegistrySelector(id, opts...) + return selector.Next() +} + +// GetOCMProviderAuthorizerClient returns a new OCMProviderAuthorizerClient. +func GetOCMProviderAuthorizerClient(id string, opts ...Option) (ocmprovider.ProviderAPIClient, error) { + selector, _ := OCMProviderSelector(id, opts...) + return selector.Next() +} + +// GetOCMCoreClient returns a new OCMCoreClient. +func GetOCMCoreClient(id string, opts ...Option) (ocmcore.OcmCoreAPIClient, error) { + selector, _ := OCMCoreSelector(id, opts...) + return selector.Next() +} + +// GetDataTxClient returns a new DataTxClient. +func GetDataTxClient(id string, opts ...Option) (datatx.TxAPIClient, error) { + selector, _ := TXSelector(id, opts...) + return selector.Next() +} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool/connection.go b/vendor/github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool/connection.go new file mode 100644 index 000000000..253ee7f8f --- /dev/null +++ b/vendor/github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool/connection.go @@ -0,0 +1,102 @@ +// Copyright 2018-2021 CERN +// +// 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. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package pool + +import ( + "crypto/tls" + + rtrace "github.com/cs3org/reva/v2/pkg/trace" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" +) + +var ( + maxCallRecvMsgSize = 10240000 +) + +// NewConn creates a new connection to a grpc server +// with open census tracing support. +// TODO(labkode): make grpc tls configurable. +// TODO make maxCallRecvMsgSize configurable, raised from the default 4MB to be able to list 10k files +func NewConn(address string, opts ...Option) (*grpc.ClientConn, error) { + + options := ClientOptions{} + if err := options.init(); err != nil { + return nil, err + } + + // then overwrite with supplied options + for _, opt := range opts { + opt(&options) + } + + var cred credentials.TransportCredentials + switch options.tlsMode { + case TLSOff: + cred = insecure.NewCredentials() + case TLSInsecure: + tlsConfig := tls.Config{ + InsecureSkipVerify: true, //nolint:gosec + } + cred = credentials.NewTLS(&tlsConfig) + case TLSOn: + if options.caCert != "" { + var err error + if cred, err = credentials.NewClientTLSFromFile(options.caCert, ""); err != nil { + return nil, err + } + } else { + // Use system's cert pool + cred = credentials.NewTLS(&tls.Config{}) + } + } + + conn, err := grpc.Dial( + address, + grpc.WithTransportCredentials(cred), + grpc.WithDefaultCallOptions( + grpc.MaxCallRecvMsgSize(maxCallRecvMsgSize), + ), + grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor( + otelgrpc.WithTracerProvider( + options.tracerProvider, + ), + otelgrpc.WithPropagators( + rtrace.Propagator, + ), + )), + grpc.WithUnaryInterceptor( + otelgrpc.UnaryClientInterceptor( + otelgrpc.WithTracerProvider( + options.tracerProvider, + ), + otelgrpc.WithPropagators( + rtrace.Propagator, + ), + ), + ), + ) + if err != nil { + return nil, err + } + + return conn, nil +} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool/option.go b/vendor/github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool/option.go new file mode 100644 index 000000000..2dc598b7e --- /dev/null +++ b/vendor/github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool/option.go @@ -0,0 +1,78 @@ +// Copyright 2018-2021 CERN +// +// 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. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package pool + +import ( + "github.com/cs3org/reva/v2/pkg/sharedconf" + rtrace "github.com/cs3org/reva/v2/pkg/trace" + "go-micro.dev/v4/registry" + "go.opentelemetry.io/otel/trace" +) + +// Option is used to pass client options +type Option func(opts *ClientOptions) + +// ClientOptions represent additional options (e.g. tls settings) for the grpc clients +type ClientOptions struct { + tlsMode TLSMode + caCert string + tracerProvider trace.TracerProvider + registry registry.Registry +} + +func (o *ClientOptions) init() error { + // default to shared settings + sharedOpt := sharedconf.GRPCClientOptions() + var err error + + if o.tlsMode, err = StringToTLSMode(sharedOpt.TLSMode); err != nil { + return err + } + o.caCert = sharedOpt.CACertFile + o.tracerProvider = rtrace.DefaultProvider() + return nil +} + +// WithTLSMode allows to set the TLSMode option for grpc clients +func WithTLSMode(v TLSMode) Option { + return func(o *ClientOptions) { + o.tlsMode = v + } +} + +// WithTLSCACert allows to set the CA Certificate for grpc clients +func WithTLSCACert(v string) Option { + return func(o *ClientOptions) { + o.caCert = v + } +} + +// WithTracerProvider allows to set the opentelemetry tracer provider for grpc clients +func WithTracerProvider(v trace.TracerProvider) Option { + return func(o *ClientOptions) { + o.tracerProvider = v + } +} + +// WithRegistry allows to set the registry for service lookup +func WithRegistry(v registry.Registry) Option { + return func(o *ClientOptions) { + o.registry = v + } +} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool/pool.go b/vendor/github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool/pool.go index cd506bf9d..087ae5c63 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool/pool.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool/pool.go @@ -19,50 +19,9 @@ package pool import ( - "crypto/tls" "fmt" - "sync" - - appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1" - appregistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1" - applicationauth "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" - authprovider "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" - authregistry "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1" - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" - user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - ocmcore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1" - invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" - ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" - permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" - preferences "github.com/cs3org/go-cs3apis/cs3/preferences/v1beta1" - collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" - link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" - ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" - storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - storageregistry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" - datatx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1" - "github.com/cs3org/reva/v2/pkg/sharedconf" - rtrace "github.com/cs3org/reva/v2/pkg/trace" - "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" - "go.opentelemetry.io/otel/trace" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/credentials/insecure" ) -type provider struct { - m sync.Mutex - conn map[string]interface{} -} - -func newProvider() provider { - return provider{ - sync.Mutex{}, - make(map[string]interface{}), - } -} - // TLSMode represents TLS mode for the clients type TLSMode int @@ -76,41 +35,6 @@ const ( TLSInsecure ) -// ClientOptions represent additional options (e.g. tls settings) for the grpc clients -type ClientOptions struct { - tlsMode TLSMode - caCert string - tracerProvider trace.TracerProvider -} - -// Option is used to pass client options -type Option func(opts *ClientOptions) - -// TODO(labkode): is concurrent access to the maps safe? -// var storageProviders = map[string]storageprovider.ProviderAPIClient{} -var ( - storageProviders = newProvider() - authProviders = newProvider() - appAuthProviders = newProvider() - authRegistries = newProvider() - userShareProviders = newProvider() - ocmShareProviders = newProvider() - ocmInviteManagers = newProvider() - ocmProviderAuthorizers = newProvider() - ocmCores = newProvider() - publicShareProviders = newProvider() - preferencesProviders = newProvider() - permissionsProviders = newProvider() - appRegistries = newProvider() - appProviders = newProvider() - storageRegistries = newProvider() - gatewayProviders = newProvider() - userProviders = newProvider() - groupProviders = newProvider() - dataTxs = newProvider() - maxCallRecvMsgSize = 10240000 -) - // StringToTLSMode converts the supply string into the equivalent TLSMode constant func StringToTLSMode(m string) (TLSMode, error) { switch m { @@ -124,487 +48,3 @@ func StringToTLSMode(m string) (TLSMode, error) { return TLSOff, fmt.Errorf("unknown TLS mode: '%s'. Valid values are 'on', 'off' and 'insecure'", m) } } - -func (o *ClientOptions) init() error { - // default to shared settings - sharedOpt := sharedconf.GRPCClientOptions() - var err error - - if o.tlsMode, err = StringToTLSMode(sharedOpt.TLSMode); err != nil { - return err - } - o.caCert = sharedOpt.CACertFile - o.tracerProvider = rtrace.DefaultProvider() - return nil -} - -// WithTLSMode allows to set the TLSMode option for grpc clients -func WithTLSMode(v TLSMode) Option { - return func(o *ClientOptions) { - o.tlsMode = v - } -} - -// WithTLSCACert allows to set the CA Certificate for grpc clients -func WithTLSCACert(v string) Option { - return func(o *ClientOptions) { - o.caCert = v - } -} - -// WithTracerProvider allows to set the opentelemetry tracer provider for grpc clients -func WithTracerProvider(v trace.TracerProvider) Option { - return func(o *ClientOptions) { - o.tracerProvider = v - } -} - -// NewConn creates a new connection to a grpc server -// with open census tracing support. -// TODO(labkode): make grpc tls configurable. -// TODO make maxCallRecvMsgSize configurable, raised from the default 4MB to be able to list 10k files -func NewConn(endpoint string, opts ...Option) (*grpc.ClientConn, error) { - - options := ClientOptions{} - if err := options.init(); err != nil { - return nil, err - } - - // then overwrite with supplied options - for _, opt := range opts { - opt(&options) - } - - var cred credentials.TransportCredentials - switch options.tlsMode { - case TLSOff: - cred = insecure.NewCredentials() - case TLSInsecure: - tlsConfig := tls.Config{ - InsecureSkipVerify: true, //nolint:gosec - } - cred = credentials.NewTLS(&tlsConfig) - case TLSOn: - if options.caCert != "" { - var err error - if cred, err = credentials.NewClientTLSFromFile(options.caCert, ""); err != nil { - return nil, err - } - } else { - // Use system's cert pool - cred = credentials.NewTLS(&tls.Config{}) - } - } - - conn, err := grpc.Dial( - endpoint, - grpc.WithTransportCredentials(cred), - grpc.WithDefaultCallOptions( - grpc.MaxCallRecvMsgSize(maxCallRecvMsgSize), - ), - grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor( - otelgrpc.WithTracerProvider( - options.tracerProvider, - ), - otelgrpc.WithPropagators( - rtrace.Propagator, - ), - )), - grpc.WithUnaryInterceptor( - otelgrpc.UnaryClientInterceptor( - otelgrpc.WithTracerProvider( - options.tracerProvider, - ), - otelgrpc.WithPropagators( - rtrace.Propagator, - ), - ), - ), - ) - if err != nil { - return nil, err - } - - return conn, nil -} - -// GetGatewayServiceClient returns a GatewayServiceClient. -func GetGatewayServiceClient(endpoint string, opts ...Option) (gateway.GatewayAPIClient, error) { - gatewayProviders.m.Lock() - defer gatewayProviders.m.Unlock() - - if val, ok := gatewayProviders.conn[endpoint]; ok { - return val.(gateway.GatewayAPIClient), nil - } - - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - v := gateway.NewGatewayAPIClient(conn) - gatewayProviders.conn[endpoint] = v - - return v, nil -} - -// GetUserProviderServiceClient returns a UserProviderServiceClient. -func GetUserProviderServiceClient(endpoint string, opts ...Option) (user.UserAPIClient, error) { - userProviders.m.Lock() - defer userProviders.m.Unlock() - - if val, ok := userProviders.conn[endpoint]; ok { - return val.(user.UserAPIClient), nil - } - - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - v := user.NewUserAPIClient(conn) - userProviders.conn[endpoint] = v - return v, nil -} - -// GetGroupProviderServiceClient returns a GroupProviderServiceClient. -func GetGroupProviderServiceClient(endpoint string, opts ...Option) (group.GroupAPIClient, error) { - groupProviders.m.Lock() - defer groupProviders.m.Unlock() - - if val, ok := groupProviders.conn[endpoint]; ok { - return val.(group.GroupAPIClient), nil - } - - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - v := group.NewGroupAPIClient(conn) - groupProviders.conn[endpoint] = v - return v, nil -} - -// GetStorageProviderServiceClient returns a StorageProviderServiceClient. -func GetStorageProviderServiceClient(endpoint string, opts ...Option) (storageprovider.ProviderAPIClient, error) { - storageProviders.m.Lock() - defer storageProviders.m.Unlock() - - if c, ok := storageProviders.conn[endpoint]; ok { - return c.(storageprovider.ProviderAPIClient), nil - } - - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - v := storageprovider.NewProviderAPIClient(conn) - storageProviders.conn[endpoint] = v - return v, nil -} - -// GetAuthRegistryServiceClient returns a new AuthRegistryServiceClient. -func GetAuthRegistryServiceClient(endpoint string, opts ...Option) (authregistry.RegistryAPIClient, error) { - authRegistries.m.Lock() - defer authRegistries.m.Unlock() - - // if there is already a connection to this node, use it. - if c, ok := authRegistries.conn[endpoint]; ok { - return c.(authregistry.RegistryAPIClient), nil - } - - // if not, create a new connection - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - // and memoize it - v := authregistry.NewRegistryAPIClient(conn) - authRegistries.conn[endpoint] = v - return v, nil -} - -// GetAuthProviderServiceClient returns a new AuthProviderServiceClient. -func GetAuthProviderServiceClient(endpoint string, opts ...Option) (authprovider.ProviderAPIClient, error) { - authProviders.m.Lock() - defer authProviders.m.Unlock() - - if c, ok := authProviders.conn[endpoint]; ok { - return c.(authprovider.ProviderAPIClient), nil - } - - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - v := authprovider.NewProviderAPIClient(conn) - authProviders.conn[endpoint] = v - return v, nil -} - -// GetAppAuthProviderServiceClient returns a new AppAuthProviderServiceClient. -func GetAppAuthProviderServiceClient(endpoint string, opts ...Option) (applicationauth.ApplicationsAPIClient, error) { - appAuthProviders.m.Lock() - defer appAuthProviders.m.Unlock() - - if c, ok := appAuthProviders.conn[endpoint]; ok { - return c.(applicationauth.ApplicationsAPIClient), nil - } - - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - v := applicationauth.NewApplicationsAPIClient(conn) - appAuthProviders.conn[endpoint] = v - return v, nil -} - -// GetUserShareProviderClient returns a new UserShareProviderClient. -func GetUserShareProviderClient(endpoint string, opts ...Option) (collaboration.CollaborationAPIClient, error) { - userShareProviders.m.Lock() - defer userShareProviders.m.Unlock() - - if c, ok := userShareProviders.conn[endpoint]; ok { - return c.(collaboration.CollaborationAPIClient), nil - } - - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - v := collaboration.NewCollaborationAPIClient(conn) - userShareProviders.conn[endpoint] = v - return v, nil -} - -// GetOCMShareProviderClient returns a new OCMShareProviderClient. -func GetOCMShareProviderClient(endpoint string, opts ...Option) (ocm.OcmAPIClient, error) { - ocmShareProviders.m.Lock() - defer ocmShareProviders.m.Unlock() - - if c, ok := ocmShareProviders.conn[endpoint]; ok { - return c.(ocm.OcmAPIClient), nil - } - - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - v := ocm.NewOcmAPIClient(conn) - ocmShareProviders.conn[endpoint] = v - return v, nil -} - -// GetOCMInviteManagerClient returns a new OCMInviteManagerClient. -func GetOCMInviteManagerClient(endpoint string, opts ...Option) (invitepb.InviteAPIClient, error) { - ocmInviteManagers.m.Lock() - defer ocmInviteManagers.m.Unlock() - - if c, ok := ocmInviteManagers.conn[endpoint]; ok { - return c.(invitepb.InviteAPIClient), nil - } - - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - v := invitepb.NewInviteAPIClient(conn) - ocmInviteManagers.conn[endpoint] = v - return v, nil -} - -// GetPublicShareProviderClient returns a new PublicShareProviderClient. -func GetPublicShareProviderClient(endpoint string, opts ...Option) (link.LinkAPIClient, error) { - publicShareProviders.m.Lock() - defer publicShareProviders.m.Unlock() - - if c, ok := publicShareProviders.conn[endpoint]; ok { - return c.(link.LinkAPIClient), nil - } - - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - v := link.NewLinkAPIClient(conn) - publicShareProviders.conn[endpoint] = v - return v, nil -} - -// GetPreferencesClient returns a new PreferencesClient. -func GetPreferencesClient(endpoint string, opts ...Option) (preferences.PreferencesAPIClient, error) { - preferencesProviders.m.Lock() - defer preferencesProviders.m.Unlock() - - if c, ok := preferencesProviders.conn[endpoint]; ok { - return c.(preferences.PreferencesAPIClient), nil - } - - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - v := preferences.NewPreferencesAPIClient(conn) - preferencesProviders.conn[endpoint] = v - return v, nil -} - -// GetPermissionsClient returns a new PermissionsClient. -func GetPermissionsClient(endpoint string, opts ...Option) (permissions.PermissionsAPIClient, error) { - permissionsProviders.m.Lock() - defer permissionsProviders.m.Unlock() - - if c, ok := permissionsProviders.conn[endpoint]; ok { - return c.(permissions.PermissionsAPIClient), nil - } - - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - v := permissions.NewPermissionsAPIClient(conn) - permissionsProviders.conn[endpoint] = v - return v, nil -} - -// GetAppRegistryClient returns a new AppRegistryClient. -func GetAppRegistryClient(endpoint string, opts ...Option) (appregistry.RegistryAPIClient, error) { - appRegistries.m.Lock() - defer appRegistries.m.Unlock() - - if c, ok := appRegistries.conn[endpoint]; ok { - return c.(appregistry.RegistryAPIClient), nil - } - - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - v := appregistry.NewRegistryAPIClient(conn) - appRegistries.conn[endpoint] = v - return v, nil -} - -// GetAppProviderClient returns a new AppRegistryClient. -func GetAppProviderClient(endpoint string, opts ...Option) (appprovider.ProviderAPIClient, error) { - appProviders.m.Lock() - defer appProviders.m.Unlock() - - if c, ok := appProviders.conn[endpoint]; ok { - return c.(appprovider.ProviderAPIClient), nil - } - - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - v := appprovider.NewProviderAPIClient(conn) - appProviders.conn[endpoint] = v - return v, nil -} - -// GetStorageRegistryClient returns a new StorageRegistryClient. -func GetStorageRegistryClient(endpoint string, opts ...Option) (storageregistry.RegistryAPIClient, error) { - storageRegistries.m.Lock() - defer storageRegistries.m.Unlock() - - if c, ok := storageRegistries.conn[endpoint]; ok { - return c.(storageregistry.RegistryAPIClient), nil - } - - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - v := storageregistry.NewRegistryAPIClient(conn) - storageRegistries.conn[endpoint] = v - return v, nil -} - -// GetOCMProviderAuthorizerClient returns a new OCMProviderAuthorizerClient. -func GetOCMProviderAuthorizerClient(endpoint string, opts ...Option) (ocmprovider.ProviderAPIClient, error) { - ocmProviderAuthorizers.m.Lock() - defer ocmProviderAuthorizers.m.Unlock() - - if c, ok := ocmProviderAuthorizers.conn[endpoint]; ok { - return c.(ocmprovider.ProviderAPIClient), nil - } - - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - v := ocmprovider.NewProviderAPIClient(conn) - ocmProviderAuthorizers.conn[endpoint] = v - return v, nil -} - -// GetOCMCoreClient returns a new OCMCoreClient. -func GetOCMCoreClient(endpoint string, opts ...Option) (ocmcore.OcmCoreAPIClient, error) { - ocmCores.m.Lock() - defer ocmCores.m.Unlock() - - if c, ok := ocmCores.conn[endpoint]; ok { - return c.(ocmcore.OcmCoreAPIClient), nil - } - - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - v := ocmcore.NewOcmCoreAPIClient(conn) - ocmCores.conn[endpoint] = v - return v, nil -} - -// GetDataTxClient returns a new DataTxClient. -func GetDataTxClient(endpoint string, opts ...Option) (datatx.TxAPIClient, error) { - dataTxs.m.Lock() - defer dataTxs.m.Unlock() - - if c, ok := dataTxs.conn[endpoint]; ok { - return c.(datatx.TxAPIClient), nil - } - - conn, err := NewConn(endpoint, opts...) - if err != nil { - return nil, err - } - - v := datatx.NewTxAPIClient(conn) - dataTxs.conn[endpoint] = v - return v, nil -} - -// getEndpointByName resolve service names to ip addresses present on the registry. -// func getEndpointByName(name string) (string, error) { -// if services, err := utils.GlobalRegistry.GetService(name); err == nil { -// if len(services) > 0 { -// for i := range services { -// for j := range services[i].Nodes() { -// // return the first one. This MUST be improved upon with selectors. -// return services[i].Nodes()[j].Address(), nil -// } -// } -// } -// } -// -// return "", fmt.Errorf("could not get service by name: %v", name) -// } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool/selector.go b/vendor/github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool/selector.go new file mode 100644 index 000000000..57f9c114e --- /dev/null +++ b/vendor/github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool/selector.go @@ -0,0 +1,315 @@ +// Copyright 2018-2021 CERN +// +// 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. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package pool + +import ( + "fmt" + "sync" + + appProvider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1" + appRegistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1" + authApplication "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" + authProvider "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" + authRegistry "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + identityGroup "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + identityUser "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + ocmCore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1" + ocmInvite "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" + ocmProvider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" + permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" + preferences "github.com/cs3org/go-cs3apis/cs3/preferences/v1beta1" + sharingCollaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + sharingLink "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" + sharingOCM "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" + storageProvider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + storageRegistry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" + tx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1" + "github.com/cs3org/reva/v2/pkg/registry" + "github.com/pkg/errors" + "google.golang.org/grpc" +) + +type Selectable[T any] interface { + Next(opts ...Option) (T, error) +} + +var selectors sync.Map + +// RemoveSelector removes given id from the selectors map. +func RemoveSelector(id string) { + selectors.Delete(id) +} + +func GetSelector[T any](k string, id string, f func(cc *grpc.ClientConn) T, options ...Option) *Selector[T] { + existingSelector, ok := selectors.Load(k + id) + if ok { + return existingSelector.(*Selector[T]) + } + + newSelector := &Selector[T]{ + id: id, + clientFactory: f, + options: options, + } + + selectors.Store(k+id, newSelector) + + return newSelector +} + +type Selector[T any] struct { + id string + clientFactory func(cc *grpc.ClientConn) T + clientMap sync.Map + options []Option +} + +func (s *Selector[T]) Next(opts ...Option) (T, error) { + options := ClientOptions{ + registry: registry.GetRegistry(), + } + + allOpts := append([]Option{}, s.options...) + allOpts = append(allOpts, opts...) + + for _, opt := range allOpts { + opt(&options) + } + + address := s.id + if options.registry != nil { + services, err := options.registry.GetService(s.id) + if err != nil { + return *new(T), fmt.Errorf("%s: %w", s.id, err) + } + + nodeAddress, err := registry.GetNodeAddress(services) + if err != nil { + return *new(T), fmt.Errorf("%s: %w", s.id, err) + } + + address = nodeAddress + } + + existingClient, ok := s.clientMap.Load(address) + if ok { + return existingClient.(T), nil + } + + conn, err := NewConn(address, allOpts...) + if err != nil { + return *new(T), errors.Wrap(err, fmt.Sprintf("could not create connection for %s to %s", s.id, address)) + } + + newClient := s.clientFactory(conn) + s.clientMap.Store(address, newClient) + + return newClient, nil +} + +// GatewaySelector returns a Selector[gateway.GatewayAPIClient]. +func GatewaySelector(id string, options ...Option) (*Selector[gateway.GatewayAPIClient], error) { + return GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + id, + gateway.NewGatewayAPIClient, + options..., + ), nil +} + +// IdentityUserSelector returns a Selector[identityUser.UserAPIClient]. +func IdentityUserSelector(id string, options ...Option) (*Selector[identityUser.UserAPIClient], error) { + return GetSelector[identityUser.UserAPIClient]( + "IdentityUserSelector", + id, + identityUser.NewUserAPIClient, + options..., + ), nil +} + +// IdentityGroupSelector returns a Selector[identityGroup.GroupAPIClient]. +func IdentityGroupSelector(id string, options ...Option) (*Selector[identityGroup.GroupAPIClient], error) { + return GetSelector[identityGroup.GroupAPIClient]( + "IdentityGroupSelector", + id, + identityGroup.NewGroupAPIClient, + options..., + ), nil +} + +// StorageProviderSelector returns a Selector[storageProvider.ProviderAPIClient]. +func StorageProviderSelector(id string, options ...Option) (*Selector[storageProvider.ProviderAPIClient], error) { + return GetSelector[storageProvider.ProviderAPIClient]( + "StorageProviderSelector", + id, + storageProvider.NewProviderAPIClient, + options..., + ), nil +} + +// AuthRegistrySelector returns a Selector[authRegistry.RegistryAPIClient]. +func AuthRegistrySelector(id string, options ...Option) (*Selector[authRegistry.RegistryAPIClient], error) { + return GetSelector[authRegistry.RegistryAPIClient]( + "AuthRegistrySelector", + id, + authRegistry.NewRegistryAPIClient, + options..., + ), nil +} + +// AuthProviderSelector returns a Selector[authProvider.RegistryAPIClient]. +func AuthProviderSelector(id string, options ...Option) (*Selector[authProvider.ProviderAPIClient], error) { + return GetSelector[authProvider.ProviderAPIClient]( + "AuthProviderSelector", + id, + authProvider.NewProviderAPIClient, + options..., + ), nil +} + +// AuthApplicationSelector returns a Selector[authApplication.ApplicationsAPIClient]. +func AuthApplicationSelector(id string, options ...Option) (*Selector[authApplication.ApplicationsAPIClient], error) { + return GetSelector[authApplication.ApplicationsAPIClient]( + "AuthApplicationSelector", + id, + authApplication.NewApplicationsAPIClient, + options..., + ), nil +} + +// SharingCollaborationSelector returns a Selector[sharingCollaboration.ApplicationsAPIClient]. +func SharingCollaborationSelector(id string, options ...Option) (*Selector[sharingCollaboration.CollaborationAPIClient], error) { + return GetSelector[sharingCollaboration.CollaborationAPIClient]( + "SharingCollaborationSelector", + id, + sharingCollaboration.NewCollaborationAPIClient, + options..., + ), nil +} + +// SharingOCMSelector returns a Selector[sharingOCM.OcmAPIClient]. +func SharingOCMSelector(id string, options ...Option) (*Selector[sharingOCM.OcmAPIClient], error) { + return GetSelector[sharingOCM.OcmAPIClient]( + "SharingOCMSelector", + id, + sharingOCM.NewOcmAPIClient, + options..., + ), nil +} + +// SharingLinkSelector returns a Selector[sharingLink.LinkAPIClient]. +func SharingLinkSelector(id string, options ...Option) (*Selector[sharingLink.LinkAPIClient], error) { + return GetSelector[sharingLink.LinkAPIClient]( + "SharingLinkSelector", + id, + sharingLink.NewLinkAPIClient, + options..., + ), nil +} + +// PreferencesSelector returns a Selector[preferences.PreferencesAPIClient]. +func PreferencesSelector(id string, options ...Option) (*Selector[preferences.PreferencesAPIClient], error) { + return GetSelector[preferences.PreferencesAPIClient]( + "PreferencesSelector", + id, + preferences.NewPreferencesAPIClient, + options..., + ), nil +} + +// PermissionsSelector returns a Selector[permissions.PermissionsAPIClient]. +func PermissionsSelector(id string, options ...Option) (*Selector[permissions.PermissionsAPIClient], error) { + return GetSelector[permissions.PermissionsAPIClient]( + "PermissionsSelector", + id, + permissions.NewPermissionsAPIClient, + options..., + ), nil +} + +// AppRegistrySelector returns a Selector[appRegistry.RegistryAPIClient]. +func AppRegistrySelector(id string, options ...Option) (*Selector[appRegistry.RegistryAPIClient], error) { + return GetSelector[appRegistry.RegistryAPIClient]( + "AppRegistrySelector", + id, + appRegistry.NewRegistryAPIClient, + options..., + ), nil +} + +// AppProviderSelector returns a Selector[appProvider.ProviderAPIClient]. +func AppProviderSelector(id string, options ...Option) (*Selector[appProvider.ProviderAPIClient], error) { + return GetSelector[appProvider.ProviderAPIClient]( + "AppProviderSelector", + id, + appProvider.NewProviderAPIClient, + options..., + ), nil +} + +// StorageRegistrySelector returns a Selector[storageRegistry.RegistryAPIClient]. +func StorageRegistrySelector(id string, options ...Option) (*Selector[storageRegistry.RegistryAPIClient], error) { + return GetSelector[storageRegistry.RegistryAPIClient]( + "StorageRegistrySelector", + id, + storageRegistry.NewRegistryAPIClient, + options..., + ), nil +} + +// OCMProviderSelector returns a Selector[storageRegistry.RegistryAPIClient]. +func OCMProviderSelector(id string, options ...Option) (*Selector[ocmProvider.ProviderAPIClient], error) { + return GetSelector[ocmProvider.ProviderAPIClient]( + "OCMProviderSelector", + id, + ocmProvider.NewProviderAPIClient, + options..., + ), nil +} + +// OCMCoreSelector returns a Selector[ocmCore.OcmCoreAPIClient]. +func OCMCoreSelector(id string, options ...Option) (*Selector[ocmCore.OcmCoreAPIClient], error) { + return GetSelector[ocmCore.OcmCoreAPIClient]( + "OCMCoreSelector", + id, + ocmCore.NewOcmCoreAPIClient, + options..., + ), nil +} + +// OCMInviteSelector returns a Selector[ocmInvite.InviteAPIClient]. +func OCMInviteSelector(id string, options ...Option) (*Selector[ocmInvite.InviteAPIClient], error) { + return GetSelector[ocmInvite.InviteAPIClient]( + "OCMInviteSelector", + id, + ocmInvite.NewInviteAPIClient, + options..., + ), nil +} + +// TXSelector returns a Selector[tx.TxAPIClient]. +func TXSelector(id string, options ...Option) (*Selector[tx.TxAPIClient], error) { + return GetSelector[tx.TxAPIClient]( + "TXSelector", + id, + tx.NewTxAPIClient, + options..., + ), nil +} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/rhttp/datatx/datatx.go b/vendor/github.com/cs3org/reva/v2/pkg/rhttp/datatx/datatx.go index e7c8bedcb..db6072b6d 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/rhttp/datatx/datatx.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/rhttp/datatx/datatx.go @@ -28,6 +28,7 @@ import ( "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/storage" "github.com/cs3org/reva/v2/pkg/storage/cache" + "github.com/cs3org/reva/v2/pkg/utils" ) // DataTX provides an abstraction around various data transfer protocols. @@ -46,6 +47,7 @@ func EmitFileUploadedEvent(spaceOwnerOrManager, executant *userv1beta1.UserId, r Owner: spaceOwnerOrManager, Executant: executant, Ref: ref, + Timestamp: utils.TSNow(), } return events.Publish(publisher, uploadedEv) diff --git a/vendor/github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/jsoncs3.go b/vendor/github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/jsoncs3.go index 2d301ba83..9bd39f3dc 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/jsoncs3.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/jsoncs3.go @@ -29,17 +29,20 @@ import ( "sync" "time" - "github.com/google/uuid" - "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" - "github.com/rs/zerolog/log" - "google.golang.org/genproto/protobuf/field_mask" - gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/go-micro/plugins/v4/events/natsjs" + "github.com/google/uuid" + "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "go.opentelemetry.io/otel/codes" + "golang.org/x/sync/errgroup" + "google.golang.org/genproto/protobuf/field_mask" + "github.com/cs3org/reva/v2/pkg/appctx" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/errtypes" @@ -55,7 +58,6 @@ import ( "github.com/cs3org/reva/v2/pkg/storage/utils/metadata" // nolint:staticcheck // we need the legacy package to convert V1 to V2 messages "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" - "github.com/go-micro/plugins/v4/events/natsjs" ) /* @@ -109,12 +111,16 @@ import ( - if the mtime changed we download the file to update the local cache */ +// name is the Tracer name used to identify this instrumentation library. +const tracerName = "jsoncs3" + func init() { registry.Register("jsoncs3", NewDefault) } type config struct { GatewayAddr string `mapstructure:"gateway_addr"` + MaxConcurrency int `mapstructure:"max_concurrency"` ProviderAddr string `mapstructure:"provider_addr"` ServiceUserID string `mapstructure:"service_user_id"` ServiceUserIdp string `mapstructure:"service_user_idp"` @@ -145,6 +151,8 @@ type Manager struct { initialized bool + MaxConcurrency int + gateway gatewayv1beta1.GatewayAPIClient eventStream events.Stream } @@ -205,11 +213,11 @@ func NewDefault(m map[string]interface{}) (share.Manager, error) { } } - return New(s, gc, c.CacheTTL, es) + return New(s, gc, c.CacheTTL, es, c.MaxConcurrency) } // New returns a new manager instance. -func New(s metadata.Storage, gc gatewayv1beta1.GatewayAPIClient, ttlSeconds int, es events.Stream) (*Manager, error) { +func New(s metadata.Storage, gc gatewayv1beta1.GatewayAPIClient, ttlSeconds int, es events.Stream, maxconcurrency int) (*Manager, error) { ttl := time.Duration(ttlSeconds) * time.Second return &Manager{ Cache: providercache.New(s, ttl), @@ -219,11 +227,15 @@ func New(s metadata.Storage, gc gatewayv1beta1.GatewayAPIClient, ttlSeconds int, storage: s, gateway: gc, eventStream: es, + MaxConcurrency: maxconcurrency, }, nil } -func (m *Manager) initialize() error { +func (m *Manager) initialize(ctx context.Context) error { + _, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "initialize") + defer span.End() if m.initialized { + span.SetStatus(codes.Ok, "already initialized") return nil } @@ -231,35 +243,49 @@ func (m *Manager) initialize() error { defer m.Unlock() if m.initialized { // check if initialization happened while grabbing the lock + span.SetStatus(codes.Ok, "initialized while grabbing lock") return nil } - ctx := context.Background() + ctx = context.Background() err := m.storage.Init(ctx, "jsoncs3-share-manager-metadata") if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } err = m.storage.MakeDirIfNotExist(ctx, "storages") if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } err = m.storage.MakeDirIfNotExist(ctx, "users") if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } err = m.storage.MakeDirIfNotExist(ctx, "groups") if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } m.initialized = true + span.SetStatus(codes.Ok, "initialized") return nil } // Share creates a new share func (m *Manager) Share(ctx context.Context, md *provider.ResourceInfo, g *collaboration.ShareGrant) (*collaboration.Share, error) { - if err := m.initialize(); err != nil { + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Share") + defer span.End() + if err := m.initialize(ctx); err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -270,7 +296,10 @@ func (m *Manager) Share(ctx context.Context, md *provider.ResourceInfo, g *colla // TODO: should this not already be caught at the gw level? if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && (utils.UserEqual(g.Grantee.GetUserId(), user.Id) || utils.UserEqual(g.Grantee.GetUserId(), md.Owner)) { - return nil, errtypes.BadRequest("jsoncs3: owner/creator and grantee are the same") + err := errtypes.BadRequest("jsoncs3: owner/creator and grantee are the same") + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return nil, err } // check if share already exists. @@ -280,12 +309,13 @@ func (m *Manager) Share(ctx context.Context, md *provider.ResourceInfo, g *colla Grantee: g.Grantee, } - m.Lock() - defer m.Unlock() _, err := m.getByKey(ctx, key) if err == nil { // share already exists - return nil, errtypes.AlreadyExists(key.String()) + err := errtypes.AlreadyExists(key.String()) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return nil, err } shareID := shareid.Encode(md.GetId().GetStorageId(), md.GetId().GetSpaceId(), uuid.NewString()) @@ -303,66 +333,112 @@ func (m *Manager) Share(ctx context.Context, md *provider.ResourceInfo, g *colla Mtime: ts, } - err = m.Cache.Add(ctx, md.Id.StorageId, md.Id.SpaceId, shareID, s) - if _, ok := err.(errtypes.IsPreconditionFailed); ok { - if err := m.Cache.Sync(ctx, md.Id.StorageId, md.Id.SpaceId); err != nil { - return nil, err - } - err = m.Cache.Add(ctx, md.Id.StorageId, md.Id.SpaceId, shareID, s) - // TODO try more often? - } - if err != nil { - return nil, err - } + eg, ctx := errgroup.WithContext(ctx) - err = m.CreatedCache.Add(ctx, s.GetCreator().GetOpaqueId(), shareID) - if _, ok := err.(errtypes.IsPreconditionFailed); ok { - if err := m.CreatedCache.Sync(ctx, s.GetCreator().GetOpaqueId()); err != nil { - return nil, err + eg.Go(func() error { + err := m.Cache.Add(ctx, md.Id.StorageId, md.Id.SpaceId, shareID, s) + if _, ok := err.(errtypes.IsPreconditionFailed); ok { + if err := m.Cache.Sync(ctx, md.Id.StorageId, md.Id.SpaceId); err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + + return err + } + + err = m.Cache.Add(ctx, md.Id.StorageId, md.Id.SpaceId, shareID, s) + // TODO try more often? } - err = m.CreatedCache.Add(ctx, s.GetCreator().GetOpaqueId(), shareID) - // TODO try more often? - } - if err != nil { - return nil, err - } + + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + + return err + }) + + eg.Go(func() error { + err := m.CreatedCache.Add(ctx, s.GetCreator().GetOpaqueId(), shareID) + if _, ok := err.(errtypes.IsPreconditionFailed); ok { + if err := m.CreatedCache.Sync(ctx, s.GetCreator().GetOpaqueId()); err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + + return err + } + + err = m.CreatedCache.Add(ctx, s.GetCreator().GetOpaqueId(), shareID) + // TODO try more often? + } + + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + + return err + }) spaceID := md.Id.StorageId + shareid.IDDelimiter + md.Id.SpaceId // set flag for grantee to have access to share switch g.Grantee.Type { case provider.GranteeType_GRANTEE_TYPE_USER: - userid := g.Grantee.GetUserId().GetOpaqueId() + eg.Go(func() error { + userid := g.Grantee.GetUserId().GetOpaqueId() - rs := &collaboration.ReceivedShare{ - Share: s, - State: collaboration.ShareState_SHARE_STATE_PENDING, - } - err = m.UserReceivedStates.Add(ctx, userid, spaceID, rs) - if _, ok := err.(errtypes.IsPreconditionFailed); ok { - if err := m.UserReceivedStates.Sync(ctx, s.GetCreator().GetOpaqueId()); err != nil { - return nil, err + rs := &collaboration.ReceivedShare{ + Share: s, + State: collaboration.ShareState_SHARE_STATE_PENDING, } - err = m.UserReceivedStates.Add(ctx, userid, spaceID, rs) - // TODO try more often? - } - if err != nil { - return nil, err - } + err := m.UserReceivedStates.Add(ctx, userid, spaceID, rs) + if _, ok := err.(errtypes.IsPreconditionFailed); ok { + if err := m.UserReceivedStates.Sync(ctx, s.GetCreator().GetOpaqueId()); err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return err + } + + err = m.UserReceivedStates.Add(ctx, userid, spaceID, rs) + // TODO try more often? + } + + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + + return err + }) case provider.GranteeType_GRANTEE_TYPE_GROUP: - groupid := g.Grantee.GetGroupId().GetOpaqueId() - err := m.GroupReceivedCache.Add(ctx, groupid, shareID) - if _, ok := err.(errtypes.IsPreconditionFailed); ok { - if err := m.GroupReceivedCache.Sync(ctx, groupid); err != nil { - return nil, err + eg.Go(func() error { + groupid := g.Grantee.GetGroupId().GetOpaqueId() + err := m.GroupReceivedCache.Add(ctx, groupid, shareID) + if _, ok := err.(errtypes.IsPreconditionFailed); ok { + if err := m.GroupReceivedCache.Sync(ctx, groupid); err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return err + } + + err = m.GroupReceivedCache.Add(ctx, groupid, shareID) + // TODO try more often? } - err = m.GroupReceivedCache.Add(ctx, groupid, shareID) - // TODO try more often? - } - if err != nil { - return nil, err - } + + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + + return err + }) } + if err = eg.Wait(); err != nil { + return nil, err + } + + span.SetStatus(codes.Ok, "") + return s, nil } @@ -413,12 +489,12 @@ func (m *Manager) get(ctx context.Context, ref *collaboration.ShareReference) (s // GetShare gets the information for a share by the given ref. func (m *Manager) GetShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.Share, error) { - if err := m.initialize(); err != nil { + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "GetShare") + defer span.End() + if err := m.initialize(ctx); err != nil { return nil, err } - m.Lock() - defer m.Unlock() s, err := m.get(ctx, ref) if err != nil { return nil, err @@ -463,12 +539,13 @@ func (m *Manager) GetShare(ctx context.Context, ref *collaboration.ShareReferenc // Unshare deletes a share func (m *Manager) Unshare(ctx context.Context, ref *collaboration.ShareReference) error { - if err := m.initialize(); err != nil { + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Unshare") + defer span.End() + + if err := m.initialize(ctx); err != nil { return err } - m.Lock() - defer m.Unlock() user := ctxpkg.ContextMustGetUser(ctx) s, err := m.get(ctx, ref) @@ -486,13 +563,13 @@ func (m *Manager) Unshare(ctx context.Context, ref *collaboration.ShareReference // UpdateShare updates the mode of the given share. func (m *Manager) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions, updated *collaboration.Share, fieldMask *field_mask.FieldMask) (*collaboration.Share, error) { - if err := m.initialize(); err != nil { + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "UpdateShare") + defer span.End() + + if err := m.initialize(ctx); err != nil { return nil, err } - m.Lock() - defer m.Unlock() - var toUpdate *collaboration.Share if ref != nil { @@ -541,6 +618,8 @@ func (m *Manager) UpdateShare(ctx context.Context, ref *collaboration.ShareRefer toUpdate.Mtime = utils.TSNow() // Update provider cache + unlock := m.Cache.LockSpace(toUpdate.ResourceId.SpaceId) + defer unlock() err := m.Cache.Persist(ctx, toUpdate.ResourceId.StorageId, toUpdate.ResourceId.SpaceId) // when persisting fails if _, ok := err.(errtypes.IsPreconditionFailed); ok { @@ -565,13 +644,13 @@ func (m *Manager) UpdateShare(ctx context.Context, ref *collaboration.ShareRefer // ListShares returns the shares created by the user func (m *Manager) ListShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.Share, error) { - if err := m.initialize(); err != nil { + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "ListShares") + defer span.End() + + if err := m.initialize(ctx); err != nil { return nil, err } - m.Lock() - defer m.Unlock() - user := ctxpkg.ContextMustGetUser(ctx) if len(share.FilterFiltersByType(filters, collaboration.Filter_TYPE_RESOURCE_ID)) > 0 { @@ -582,6 +661,9 @@ func (m *Manager) ListShares(ctx context.Context, filters []*collaboration.Filte } func (m *Manager) listSharesByIDs(ctx context.Context, user *userv1beta1.User, filters []*collaboration.Filter) ([]*collaboration.Share, error) { + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "listSharesByIDs") + defer span.End() + providerSpaces := make(map[string]map[string]struct{}) for _, f := range share.FilterFiltersByType(filters, collaboration.Filter_TYPE_RESOURCE_ID) { storageID := f.GetResourceId().GetStorageId() @@ -598,6 +680,8 @@ func (m *Manager) listSharesByIDs(ctx context.Context, user *userv1beta1.User, f for spaceID := range spaces { err := m.Cache.Sync(ctx, providerID, spaceID) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -645,13 +729,19 @@ func (m *Manager) listSharesByIDs(ctx context.Context, user *userv1beta1.User, f } } } + span.SetStatus(codes.Ok, "") return ss, nil } func (m *Manager) listCreatedShares(ctx context.Context, user *userv1beta1.User, filters []*collaboration.Filter) ([]*collaboration.Share, error) { + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "listCreatedShares") + defer span.End() + var ss []*collaboration.Share if err := m.CreatedCache.Sync(ctx, user.Id.OpaqueId); err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return ss, err } for ssid, spaceShareIDs := range m.CreatedCache.List(user.Id.OpaqueId) { @@ -691,19 +781,19 @@ func (m *Manager) listCreatedShares(ctx context.Context, user *userv1beta1.User, } } + span.SetStatus(codes.Ok, "") return ss, nil } // ListReceivedShares returns the list of shares the user has access to. func (m *Manager) ListReceivedShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.ReceivedShare, error) { - if err := m.initialize(); err != nil { + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "ListReceivedShares") + defer span.End() + + if err := m.initialize(ctx); err != nil { return nil, err } - m.Lock() - defer m.Unlock() - - var rss []*collaboration.ReceivedShare user := ctxpkg.ContextMustGetUser(ctx) ssids := map[string]*receivedsharecache.Space{} @@ -750,53 +840,111 @@ func (m *Manager) ListReceivedShares(ctx context.Context, filters []*collaborati } } - for ssid, rspace := range ssids { - storageID, spaceID, _ := shareid.Decode(ssid) - err := m.Cache.Sync(ctx, storageID, spaceID) - if err != nil { - continue - } - for shareID, state := range rspace.States { - s := m.Cache.Get(storageID, spaceID, shareID) - if s == nil { - continue - } - if share.IsExpired(s) { - if err := m.removeShare(ctx, s); err != nil { - log.Error().Err(err). - Msg("failed to unshare expired share") - } - if err := events.Publish(m.eventStream, events.ShareExpired{ - ShareOwner: s.GetOwner(), - ItemID: s.GetResourceId(), - ExpiredAt: time.Unix(int64(s.GetExpiration().GetSeconds()), int64(s.GetExpiration().GetNanos())), - GranteeUserID: s.GetGrantee().GetUserId(), - GranteeGroupID: s.GetGrantee().GetGroupId(), - }); err != nil { - log.Error().Err(err). - Msg("failed to publish share expired event") - } - continue - } - - if share.IsGrantedToUser(s, user) { - if share.MatchesFiltersWithState(s, state.State, filters) { - rs := &collaboration.ReceivedShare{ - Share: s, - State: state.State, - MountPoint: state.MountPoint, - } - rss = append(rss, rs) - } - } - } + numWorkers := m.MaxConcurrency + if numWorkers == 0 || len(ssids) < numWorkers { + numWorkers = len(ssids) } + type w struct { + ssid string + rspace *receivedsharecache.Space + } + work := make(chan w) + results := make(chan *collaboration.ReceivedShare) + + g, ctx := errgroup.WithContext(ctx) + + // Distribute work + g.Go(func() error { + defer close(work) + for ssid, rspace := range ssids { + select { + case work <- w{ssid, rspace}: + case <-ctx.Done(): + return ctx.Err() + } + } + return nil + }) + + // Spawn workers that'll concurrently work the queue + for i := 0; i < numWorkers; i++ { + g.Go(func() error { + for w := range work { + storageID, spaceID, _ := shareid.Decode(w.ssid) + err := m.Cache.Sync(ctx, storageID, spaceID) + if err != nil { + continue + } + for shareID, state := range w.rspace.States { + s := m.Cache.Get(storageID, spaceID, shareID) + if s == nil { + continue + } + if share.IsExpired(s) { + if err := m.removeShare(ctx, s); err != nil { + log.Error().Err(err). + Msg("failed to unshare expired share") + } + if err := events.Publish(m.eventStream, events.ShareExpired{ + ShareOwner: s.GetOwner(), + ItemID: s.GetResourceId(), + ExpiredAt: time.Unix(int64(s.GetExpiration().GetSeconds()), int64(s.GetExpiration().GetNanos())), + GranteeUserID: s.GetGrantee().GetUserId(), + GranteeGroupID: s.GetGrantee().GetGroupId(), + }); err != nil { + log.Error().Err(err). + Msg("failed to publish share expired event") + } + continue + } + + if share.IsGrantedToUser(s, user) { + if share.MatchesFiltersWithState(s, state.State, filters) { + rs := &collaboration.ReceivedShare{ + Share: s, + State: state.State, + MountPoint: state.MountPoint, + } + select { + case results <- rs: + case <-ctx.Done(): + return ctx.Err() + } + } + } + } + } + return nil + }) + } + + // Wait for things to settle down, then close results chan + go func() { + _ = g.Wait() // error is checked later + close(results) + }() + + rss := []*collaboration.ReceivedShare{} + for n := range results { + rss = append(rss, n) + } + + if err := g.Wait(); err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return nil, err + } + + span.SetStatus(codes.Ok, "") return rss, nil } // convert must be called in a lock-controlled block. func (m *Manager) convert(ctx context.Context, userID string, s *collaboration.Share) *collaboration.ReceivedShare { + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "convert") + defer span.End() + rs := &collaboration.ReceivedShare{ Share: s, State: collaboration.ShareState_SHARE_STATE_PENDING, @@ -815,7 +963,7 @@ func (m *Manager) convert(ctx context.Context, userID string, s *collaboration.S // GetReceivedShare returns the information for a received share. func (m *Manager) GetReceivedShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.ReceivedShare, error) { - if err := m.initialize(); err != nil { + if err := m.initialize(ctx); err != nil { return nil, err } @@ -823,8 +971,9 @@ func (m *Manager) GetReceivedShare(ctx context.Context, ref *collaboration.Share } func (m *Manager) getReceived(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.ReceivedShare, error) { - m.Lock() - defer m.Unlock() + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "getReceived") + defer span.End() + s, err := m.get(ctx, ref) if err != nil { return nil, err @@ -854,7 +1003,10 @@ func (m *Manager) getReceived(ctx context.Context, ref *collaboration.ShareRefer // UpdateReceivedShare updates the received share with share state. func (m *Manager) UpdateReceivedShare(ctx context.Context, receivedShare *collaboration.ReceivedShare, fieldMask *field_mask.FieldMask) (*collaboration.ReceivedShare, error) { - if err := m.initialize(); err != nil { + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "UpdateReceivedShare") + defer span.End() + + if err := m.initialize(ctx); err != nil { return nil, err } @@ -863,9 +1015,6 @@ func (m *Manager) UpdateReceivedShare(ctx context.Context, receivedShare *collab return nil, err } - m.Lock() - defer m.Unlock() - for i := range fieldMask.Paths { switch fieldMask.Paths[i] { case "state": @@ -907,7 +1056,7 @@ func updateShareID(share *collaboration.Share) { // Load imports shares and received shares from channels (e.g. during migration) func (m *Manager) Load(ctx context.Context, shareChan <-chan *collaboration.Share, receivedShareChan <-chan share.ReceivedShareWithUser) error { log := appctx.GetLogger(ctx) - if err := m.initialize(); err != nil { + if err := m.initialize(ctx); err != nil { return err } @@ -964,33 +1113,39 @@ func (m *Manager) Load(ctx context.Context, shareChan <-chan *collaboration.Shar } func (m *Manager) removeShare(ctx context.Context, s *collaboration.Share) error { - storageID, spaceID, _ := shareid.Decode(s.Id.OpaqueId) - err := m.Cache.Remove(ctx, storageID, spaceID, s.Id.OpaqueId) - if _, ok := err.(errtypes.IsPreconditionFailed); ok { - if err := m.Cache.Sync(ctx, storageID, spaceID); err != nil { - return err - } - err = m.Cache.Remove(ctx, storageID, spaceID, s.Id.OpaqueId) - // TODO try more often? - } - if err != nil { - return err - } + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "removeShare") + defer span.End() - // remove from created cache - err = m.CreatedCache.Remove(ctx, s.GetCreator().GetOpaqueId(), s.Id.OpaqueId) - if _, ok := err.(errtypes.IsPreconditionFailed); ok { - if err := m.CreatedCache.Sync(ctx, s.GetCreator().GetOpaqueId()); err != nil { - return err + eg, ctx := errgroup.WithContext(ctx) + eg.Go(func() error { + storageID, spaceID, _ := shareid.Decode(s.Id.OpaqueId) + err := m.Cache.Remove(ctx, storageID, spaceID, s.Id.OpaqueId) + if _, ok := err.(errtypes.IsPreconditionFailed); ok { + if err := m.Cache.Sync(ctx, storageID, spaceID); err != nil { + return err + } + err = m.Cache.Remove(ctx, storageID, spaceID, s.Id.OpaqueId) + // TODO try more often? } - err = m.CreatedCache.Remove(ctx, s.GetCreator().GetOpaqueId(), s.Id.OpaqueId) - // TODO try more often? - } - if err != nil { + return err - } + }) + + eg.Go(func() error { + // remove from created cache + err := m.CreatedCache.Remove(ctx, s.GetCreator().GetOpaqueId(), s.Id.OpaqueId) + if _, ok := err.(errtypes.IsPreconditionFailed); ok { + if err := m.CreatedCache.Sync(ctx, s.GetCreator().GetOpaqueId()); err != nil { + return err + } + err = m.CreatedCache.Remove(ctx, s.GetCreator().GetOpaqueId(), s.Id.OpaqueId) + // TODO try more often? + } + + return err + }) // TODO remove from grantee cache - return nil + return eg.Wait() } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/providercache/providercache.go b/vendor/github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/providercache/providercache.go index 528922ac4..08cbb0600 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/providercache/providercache.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/providercache/providercache.go @@ -25,6 +25,7 @@ import ( "os" "path" "path/filepath" + "sync" "time" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" @@ -33,10 +34,17 @@ import ( "github.com/cs3org/reva/v2/pkg/errtypes" "github.com/cs3org/reva/v2/pkg/storage/utils/metadata" "github.com/cs3org/reva/v2/pkg/utils" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" ) +// name is the Tracer name used to identify this instrumentation library. +const tracerName = "providercache" + // Cache holds share information structured by provider and space type Cache struct { + lockMap sync.Map + Providers map[string]*Spaces storage metadata.Storage @@ -95,17 +103,34 @@ func (s *Shares) UnmarshalJSON(data []byte) error { return nil } +// LockSpace locks the cache for a given space and returns an unlock function +func (c *Cache) LockSpace(spaceID string) func() { + v, _ := c.lockMap.LoadOrStore(spaceID, &sync.Mutex{}) + lock := v.(*sync.Mutex) + + lock.Lock() + return func() { lock.Unlock() } +} + // New returns a new Cache instance func New(s metadata.Storage, ttl time.Duration) Cache { return Cache{ Providers: map[string]*Spaces{}, storage: s, ttl: ttl, + lockMap: sync.Map{}, } } // Add adds a share to the cache func (c *Cache) Add(ctx context.Context, storageID, spaceID, shareID string, share *collaboration.Share) error { + unlock := c.LockSpace(spaceID) + defer unlock() + + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Add") + defer span.End() + span.SetAttributes(attribute.String("cs3.storageid", storageID), attribute.String("cs3.spaceid", spaceID), attribute.String("cs3.shareid", shareID)) + switch { case storageID == "": return fmt.Errorf("missing storage id") @@ -122,6 +147,13 @@ func (c *Cache) Add(ctx context.Context, storageID, spaceID, shareID string, sha // Remove removes a share from the cache func (c *Cache) Remove(ctx context.Context, storageID, spaceID, shareID string) error { + unlock := c.LockSpace(spaceID) + defer unlock() + + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Remove") + defer span.End() + span.SetAttributes(attribute.String("cs3.storageid", storageID), attribute.String("cs3.spaceid", spaceID), attribute.String("cs3.shareid", shareID)) + if c.Providers[storageID] == nil || c.Providers[storageID].Spaces[spaceID] == nil { return nil @@ -150,7 +182,12 @@ func (c *Cache) ListSpace(storageID, spaceID string) *Shares { // PersistWithTime persists the data of one space if it has not been modified since the given mtime func (c *Cache) PersistWithTime(ctx context.Context, storageID, spaceID string, mtime time.Time) error { + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "PersistWithTime") + defer span.End() + span.SetAttributes(attribute.String("cs3.storageid", storageID), attribute.String("cs3.spaceid", spaceID)) + if c.Providers[storageID] == nil || c.Providers[storageID].Spaces[spaceID] == nil { + span.SetStatus(codes.Ok, "no shares in provider or space") return nil } @@ -161,11 +198,15 @@ func (c *Cache) PersistWithTime(ctx context.Context, storageID, spaceID string, createdBytes, err := json.Marshal(c.Providers[storageID].Spaces[spaceID]) if err != nil { c.Providers[storageID].Spaces[spaceID].Mtime = oldMtime + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } jsonPath := spaceJSONPath(storageID, spaceID) if err := c.storage.MakeDirIfNotExist(ctx, path.Dir(jsonPath)); err != nil { c.Providers[storageID].Spaces[spaceID].Mtime = oldMtime + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } @@ -175,8 +216,11 @@ func (c *Cache) PersistWithTime(ctx context.Context, storageID, spaceID string, IfUnmodifiedSince: c.Providers[storageID].Spaces[spaceID].Mtime, }); err != nil { c.Providers[storageID].Spaces[spaceID].Mtime = oldMtime + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } + span.SetStatus(codes.Ok, "") return nil } @@ -187,15 +231,23 @@ func (c *Cache) Persist(ctx context.Context, storageID, spaceID string) error { // Sync updates the in-memory data with the data from the storage if it is outdated func (c *Cache) Sync(ctx context.Context, storageID, spaceID string) error { + unlock := c.LockSpace(spaceID) + defer unlock() + + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Sync") + defer span.End() + + span.SetAttributes(attribute.String("cs3.storageid", storageID), attribute.String("cs3.spaceid", spaceID)) + log := appctx.GetLogger(ctx).With().Str("storageID", storageID).Str("spaceID", spaceID).Logger() - log.Debug().Msg("Syncing provider cache...") var mtime time.Time if c.Providers[storageID] != nil && c.Providers[storageID].Spaces[spaceID] != nil { mtime = c.Providers[storageID].Spaces[spaceID].Mtime if time.Now().Before(c.Providers[storageID].Spaces[spaceID].nextSync) { - log.Debug().Msg("Skipping provider cache sync, it was just recently synced...") + span.AddEvent("skip sync") + span.SetStatus(codes.Ok, "") return nil } c.Providers[storageID].Spaces[spaceID].nextSync = time.Now().Add(c.ttl) @@ -207,28 +259,33 @@ func (c *Cache) Sync(ctx context.Context, storageID, spaceID string) error { info, err := c.storage.Stat(ctx, jsonPath) if err != nil { if _, ok := err.(errtypes.NotFound); ok { - log.Debug().Msg("no json file, nothing to sync") + span.AddEvent("no file") + span.SetStatus(codes.Ok, "") return nil // Nothing to sync against } if _, ok := err.(*os.PathError); ok { - log.Debug().Msg("no storage dir, nothing to sync") + span.AddEvent("no dir") + span.SetStatus(codes.Ok, "") return nil // Nothing to sync against } + span.SetStatus(codes.Error, fmt.Sprintf("Failed to stat the provider cache: %s", err.Error())) log.Error().Err(err).Msg("Failed to stat the provider cache") return err } // check mtime of /users/{userid}/created.json if utils.TSToTime(info.Mtime).After(mtime) { - log.Debug().Msg("Updating provider cache...") + span.AddEvent("updating cache") // - update cached list of created shares for the user in memory if changed createdBlob, err := c.storage.SimpleDownload(ctx, jsonPath) if err != nil { + span.SetStatus(codes.Error, fmt.Sprintf("Failed to download the provider cache: %s", err.Error())) log.Error().Err(err).Msg("Failed to download the provider cache") return err } newShares := &Shares{} err = json.Unmarshal(createdBlob, newShares) if err != nil { + span.SetStatus(codes.Error, fmt.Sprintf("Failed to unmarshal the provider cache: %s", err.Error())) log.Error().Err(err).Msg("Failed to unmarshal the provider cache") return err } @@ -236,7 +293,7 @@ func (c *Cache) Sync(ctx context.Context, storageID, spaceID string) error { c.initializeIfNeeded(storageID, spaceID) c.Providers[storageID].Spaces[spaceID] = newShares } - log.Debug().Msg("Provider cache is up to date") + span.SetStatus(codes.Ok, "") return nil } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/receivedsharecache/receivedsharecache.go b/vendor/github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/receivedsharecache/receivedsharecache.go index 0693b802c..4810f1ff1 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/receivedsharecache/receivedsharecache.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/receivedsharecache/receivedsharecache.go @@ -21,8 +21,10 @@ package receivedsharecache import ( "context" "encoding/json" + "fmt" "path" "path/filepath" + "sync" "time" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" @@ -31,12 +33,19 @@ import ( "github.com/cs3org/reva/v2/pkg/errtypes" "github.com/cs3org/reva/v2/pkg/storage/utils/metadata" "github.com/cs3org/reva/v2/pkg/utils" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" ) +// name is the Tracer name used to identify this instrumentation library. +const tracerName = "receivedsharecache" + // Cache stores the list of received shares and their states // It functions as an in-memory cache with a persistence layer // The storage is sharded by user type Cache struct { + lockMap sync.Map + ReceivedSpaces map[string]*Spaces storage metadata.Storage @@ -69,11 +78,27 @@ func New(s metadata.Storage, ttl time.Duration) Cache { ReceivedSpaces: map[string]*Spaces{}, storage: s, ttl: ttl, + lockMap: sync.Map{}, } } +func (c *Cache) lockUser(userID string) func() { + v, _ := c.lockMap.LoadOrStore(userID, &sync.Mutex{}) + lock := v.(*sync.Mutex) + + lock.Lock() + return func() { lock.Unlock() } +} + // Add adds a new entry to the cache func (c *Cache) Add(ctx context.Context, userID, spaceID string, rs *collaboration.ReceivedShare) error { + unlock := c.lockUser(userID) + defer unlock() + + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Add") + defer span.End() + span.SetAttributes(attribute.String("cs3.userid", userID), attribute.String("cs3.spaceid", spaceID)) + if c.ReceivedSpaces[userID] == nil { c.ReceivedSpaces[userID] = &Spaces{ Spaces: map[string]*Space{}, @@ -93,7 +118,7 @@ func (c *Cache) Add(ctx context.Context, userID, spaceID string, rs *collaborati MountPoint: rs.MountPoint, } - return c.Persist(ctx, userID) + return c.persist(ctx, userID) } // Get returns one entry from the cache @@ -106,13 +131,20 @@ func (c *Cache) Get(userID, spaceID, shareID string) *State { // Sync updates the in-memory data with the data from the storage if it is outdated func (c *Cache) Sync(ctx context.Context, userID string) error { + unlock := c.lockUser(userID) + defer unlock() + + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Sync") + defer span.End() + span.SetAttributes(attribute.String("cs3.userid", userID)) + log := appctx.GetLogger(ctx).With().Str("userID", userID).Logger() - log.Debug().Msg("Syncing received share cache...") var mtime time.Time if c.ReceivedSpaces[userID] != nil { if time.Now().Before(c.ReceivedSpaces[userID].nextSync) { - log.Debug().Msg("Skipping received share cache sync, it was just recently synced...") + span.AddEvent("skip sync") + span.SetStatus(codes.Ok, "") return nil } c.ReceivedSpaces[userID].nextSync = time.Now().Add(c.ttl) @@ -123,39 +155,49 @@ func (c *Cache) Sync(ctx context.Context, userID string) error { } jsonPath := userJSONPath(userID) - info, err := c.storage.Stat(ctx, jsonPath) + info, err := c.storage.Stat(ctx, jsonPath) // TODO we only need the mtime ... use fieldmask to make the request cheaper if err != nil { if _, ok := err.(errtypes.NotFound); ok { + span.AddEvent("no file") + span.SetStatus(codes.Ok, "") return nil // Nothing to sync against } + span.SetStatus(codes.Error, fmt.Sprintf("Failed to stat the received share: %s", err.Error())) log.Error().Err(err).Msg("Failed to stat the received share") return err } // check mtime of /users/{userid}/created.json if utils.TSToTime(info.Mtime).After(mtime) { - log.Debug().Msg("Updating received share cache...") + span.AddEvent("updating cache") // - update cached list of created shares for the user in memory if changed createdBlob, err := c.storage.SimpleDownload(ctx, jsonPath) if err != nil { + span.SetStatus(codes.Error, fmt.Sprintf("Failed to download the received share: %s", err.Error())) log.Error().Err(err).Msg("Failed to download the received share") return err } newSpaces := &Spaces{} err = json.Unmarshal(createdBlob, newSpaces) if err != nil { + span.SetStatus(codes.Error, fmt.Sprintf("Failed to unmarshal the received share: %s", err.Error())) log.Error().Err(err).Msg("Failed to unmarshal the received share") return err } newSpaces.Mtime = utils.TSToTime(info.Mtime) c.ReceivedSpaces[userID] = newSpaces } - log.Debug().Msg("Received share cache is up to date") + span.SetStatus(codes.Ok, "") return nil } -// Persist persists the data for one user to the storage -func (c *Cache) Persist(ctx context.Context, userID string) error { +// persist persists the data for one user to the storage +func (c *Cache) persist(ctx context.Context, userID string) error { + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Persist") + defer span.End() + span.SetAttributes(attribute.String("cs3.userid", userID)) + if c.ReceivedSpaces[userID] == nil { + span.SetStatus(codes.Ok, "no received shares") return nil } @@ -165,11 +207,15 @@ func (c *Cache) Persist(ctx context.Context, userID string) error { createdBytes, err := json.Marshal(c.ReceivedSpaces[userID]) if err != nil { c.ReceivedSpaces[userID].Mtime = oldMtime + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } jsonPath := userJSONPath(userID) if err := c.storage.MakeDirIfNotExist(ctx, path.Dir(jsonPath)); err != nil { c.ReceivedSpaces[userID].Mtime = oldMtime + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } @@ -179,8 +225,11 @@ func (c *Cache) Persist(ctx context.Context, userID string) error { IfUnmodifiedSince: c.ReceivedSpaces[userID].Mtime, }); err != nil { c.ReceivedSpaces[userID].Mtime = oldMtime + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } + span.SetStatus(codes.Ok, "") return nil } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/sharecache/sharecache.go b/vendor/github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/sharecache/sharecache.go index 7fdd7e392..f4058d459 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/sharecache/sharecache.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/sharecache/sharecache.go @@ -21,8 +21,10 @@ package sharecache import ( "context" "encoding/json" + "fmt" "path" "path/filepath" + "sync" "time" "github.com/cs3org/reva/v2/pkg/appctx" @@ -30,12 +32,19 @@ import ( "github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/shareid" "github.com/cs3org/reva/v2/pkg/storage/utils/metadata" "github.com/cs3org/reva/v2/pkg/utils" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" ) +// name is the Tracer name used to identify this instrumentation library. +const tracerName = "sharecache" + // Cache caches the list of share ids for users/groups // It functions as an in-memory cache with a persistence layer // The storage is sharded by user/group type Cache struct { + lockMap sync.Map + UserShares map[string]*UserShareCache storage metadata.Storage @@ -58,6 +67,14 @@ type SpaceShareIDs struct { IDs map[string]struct{} } +func (c *Cache) lockUser(userID string) func() { + v, _ := c.lockMap.LoadOrStore(userID, &sync.Mutex{}) + lock := v.(*sync.Mutex) + + lock.Lock() + return func() { lock.Unlock() } +} + // New returns a new Cache instance func New(s metadata.Storage, namespace, filename string, ttl time.Duration) Cache { return Cache{ @@ -66,11 +83,19 @@ func New(s metadata.Storage, namespace, filename string, ttl time.Duration) Cach namespace: namespace, filename: filename, ttl: ttl, + lockMap: sync.Map{}, } } // Add adds a share to the cache func (c *Cache) Add(ctx context.Context, userid, shareID string) error { + unlock := c.lockUser(userid) + defer unlock() + + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Add") + defer span.End() + span.SetAttributes(attribute.String("cs3.userid", userid), attribute.String("cs3.shareid", shareID)) + storageid, spaceid, _ := shareid.Decode(shareID) ssid := storageid + shareid.IDDelimiter + spaceid @@ -94,6 +119,13 @@ func (c *Cache) Add(ctx context.Context, userid, shareID string) error { // Remove removes a share for the given user func (c *Cache) Remove(ctx context.Context, userid, shareID string) error { + unlock := c.lockUser(userid) + defer unlock() + + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Remove") + defer span.End() + span.SetAttributes(attribute.String("cs3.userid", userid), attribute.String("cs3.shareid", shareID)) + storageid, spaceid, _ := shareid.Decode(shareID) ssid := storageid + shareid.IDDelimiter + spaceid @@ -133,14 +165,21 @@ func (c *Cache) List(userid string) map[string]SpaceShareIDs { // Sync updates the in-memory data with the data from the storage if it is outdated func (c *Cache) Sync(ctx context.Context, userID string) error { + unlock := c.lockUser(userID) + defer unlock() + + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Sync") + defer span.End() + span.SetAttributes(attribute.String("cs3.userid", userID)) + log := appctx.GetLogger(ctx).With().Str("userID", userID).Logger() - log.Debug().Msg("Syncing share cache...") var mtime time.Time // - do we have a cached list of created shares for the user in memory? if usc := c.UserShares[userID]; usc != nil { if time.Now().Before(c.UserShares[userID].nextSync) { - log.Debug().Msg("Skipping share cache sync, it was just recently synced...") + span.AddEvent("skip sync") + span.SetStatus(codes.Ok, "") return nil } c.UserShares[userID].nextSync = time.Now().Add(c.ttl) @@ -155,46 +194,59 @@ func (c *Cache) Sync(ctx context.Context, userID string) error { info, err := c.storage.Stat(ctx, userCreatedPath) if err != nil { if _, ok := err.(errtypes.NotFound); ok { + span.AddEvent("no file") + span.SetStatus(codes.Ok, "") return nil // Nothing to sync against } + span.SetStatus(codes.Error, fmt.Sprintf("Failed to stat the share cache: %s", err.Error())) log.Error().Err(err).Msg("Failed to stat the share cache") return err } // check mtime of /users/{userid}/created.json if utils.TSToTime(info.Mtime).After(mtime) { - log.Debug().Msg("Updating share cache...") + span.AddEvent("updating cache") // - update cached list of created shares for the user in memory if changed createdBlob, err := c.storage.SimpleDownload(ctx, userCreatedPath) if err != nil { + span.SetStatus(codes.Error, fmt.Sprintf("Failed to download the share cache: %s", err.Error())) log.Error().Err(err).Msg("Failed to download the share cache") return err } newShareCache := &UserShareCache{} err = json.Unmarshal(createdBlob, newShareCache) if err != nil { + span.SetStatus(codes.Error, fmt.Sprintf("Failed to unmarshal the share cache: %s", err.Error())) log.Error().Err(err).Msg("Failed to unmarshal the share cache") return err } newShareCache.Mtime = utils.TSToTime(info.Mtime) c.UserShares[userID] = newShareCache } - log.Debug().Msg("Share cache is up to date") + span.SetStatus(codes.Ok, "") return nil } // Persist persists the data for one user/group to the storage func (c *Cache) Persist(ctx context.Context, userid string) error { + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Persist") + defer span.End() + span.SetAttributes(attribute.String("cs3.userid", userid)) + oldMtime := c.UserShares[userid].Mtime c.UserShares[userid].Mtime = time.Now() createdBytes, err := json.Marshal(c.UserShares[userid]) if err != nil { c.UserShares[userid].Mtime = oldMtime + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } jsonPath := c.userCreatedPath(userid) if err := c.storage.MakeDirIfNotExist(ctx, path.Dir(jsonPath)); err != nil { c.UserShares[userid].Mtime = oldMtime + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } @@ -204,8 +256,11 @@ func (c *Cache) Persist(ctx context.Context, userid string) error { IfUnmodifiedSince: c.UserShares[userid].Mtime, }); err != nil { c.UserShares[userid].Mtime = oldMtime + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } + span.SetStatus(codes.Ok, "") return nil } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/share/share.go b/vendor/github.com/cs3org/reva/v2/pkg/share/share.go index 67e58ab0d..3fb48c3b0 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/share/share.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/share/share.go @@ -242,6 +242,9 @@ func FilterFiltersByType(f []*collaboration.Filter, t collaboration.Filter_Type) // IsExpired tests whether a share is expired func IsExpired(s *collaboration.Share) bool { - expiration := time.Unix(int64(s.Expiration.GetSeconds()), int64(s.Expiration.GetNanos())) - return s.Expiration != nil && expiration.Before(time.Now()) + if e := s.GetExpiration(); e != nil { + expiration := time.Unix(int64(e.Seconds), int64(e.Nanos)) + return expiration.Before(time.Now()) + } + return false } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/cache/filemetadata.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/cache/filemetadata.go index 9f8828ccc..41053b4f2 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/cache/filemetadata.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/cache/filemetadata.go @@ -39,5 +39,5 @@ func NewFileMetadataCache(store string, nodes []string, database, table string, // RemoveMetadata removes a reference from the metadata cache func (c *fileMetadataCache) RemoveMetadata(path string) error { - return c.s.Delete(path) + return c.Delete(path) } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/decomposedfs.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/decomposedfs.go index 125b2f150..d105bea91 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/decomposedfs.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/decomposedfs.go @@ -49,6 +49,7 @@ import ( "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/migrator" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/mtimesyncedcache" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree" @@ -99,7 +100,8 @@ type Decomposedfs struct { stream events.Stream cache cache.StatCache - UserCache *ttlcache.Cache + UserCache *ttlcache.Cache + spaceIDCache mtimesyncedcache.Cache[string, map[string]string] } // NewDefault returns an instance with default components @@ -127,12 +129,13 @@ func NewDefault(m map[string]interface{}, bs tree.Blobstore, es events.Stream) ( microstore.Database(o.IDCache.Database), microstore.Table(o.IDCache.Table), )) - permissionsClient, err := pool.GetPermissionsClient(o.PermissionsSVC, pool.WithTLSMode(o.PermTLSMode)) + + permissionsSelector, err := pool.PermissionsSelector(o.PermissionsSVC, pool.WithTLSMode(o.PermTLSMode)) if err != nil { return nil, err } - permissions := NewPermissions(node.NewPermissions(lu), permissionsClient) + permissions := NewPermissions(node.NewPermissions(lu), permissionsSelector) return New(o, lu, permissions, tp, es) } @@ -270,7 +273,7 @@ func (fs *Decomposedfs) Postprocessing(ch <-chan events.Event) { }, Path: utils.MakeRelativePath(filepath.Join(up.Info.MetaData["dir"], up.Info.MetaData["filename"])), }, - Timestamp: now, + Timestamp: utils.TimeToTS(now), SpaceOwner: n.SpaceOwnerOrManager(ctx), }, ); err != nil { @@ -426,7 +429,7 @@ func (fs *Decomposedfs) GetQuota(ctx context.Context, ref *provider.Reference) ( rp, err := fs.p.AssemblePermissions(ctx, n) switch { case err != nil: - return 0, 0, 0, errtypes.InternalError(err.Error()) + return 0, 0, 0, err case !rp.GetQuota && !fs.p.ListAllSpaces(ctx): f, _ := storagespace.FormatReference(ref) if rp.Stat { @@ -533,7 +536,7 @@ func (fs *Decomposedfs) GetPathByID(ctx context.Context, id *provider.ResourceId rp, err := fs.p.AssemblePermissions(ctx, n) switch { case err != nil: - return "", errtypes.InternalError(err.Error()) + return "", err case !rp.GetPath: f := storagespace.FormatResourceID(*id) if rp.Stat { @@ -580,7 +583,7 @@ func (fs *Decomposedfs) CreateDir(ctx context.Context, ref *provider.Reference) rp, err := fs.p.AssemblePermissions(ctx, n) switch { case err != nil: - return errtypes.InternalError(err.Error()) + return err case !rp.CreateContainer: f, _ := storagespace.FormatReference(ref) if rp.Stat { @@ -636,7 +639,7 @@ func (fs *Decomposedfs) TouchFile(ctx context.Context, ref *provider.Reference, rp, err := fs.p.AssemblePermissions(ctx, n) switch { case err != nil: - return errtypes.InternalError(err.Error()) + return err case !rp.InitiateFileUpload: f, _ := storagespace.FormatReference(ref) if rp.Stat { @@ -679,7 +682,7 @@ func (fs *Decomposedfs) Move(ctx context.Context, oldRef, newRef *provider.Refer rp, err := fs.p.AssemblePermissions(ctx, oldNode) switch { case err != nil: - return errtypes.InternalError(err.Error()) + return err case !rp.Move: f, _ := storagespace.FormatReference(oldRef) if rp.Stat { @@ -699,7 +702,7 @@ func (fs *Decomposedfs) Move(ctx context.Context, oldRef, newRef *provider.Refer rp, err = fs.p.AssemblePermissions(ctx, newNode) switch { case err != nil: - return errtypes.InternalError(err.Error()) + return err case oldNode.IsDir() && !rp.CreateContainer: f, _ := storagespace.FormatReference(newRef) if rp.Stat { @@ -745,7 +748,7 @@ func (fs *Decomposedfs) GetMD(ctx context.Context, ref *provider.Reference, mdKe rp, err := fs.p.AssemblePermissions(ctx, node) switch { case err != nil: - return nil, errtypes.InternalError(err.Error()) + return nil, err case !rp.Stat: f, _ := storagespace.FormatReference(ref) return nil, errtypes.NotFound(f) @@ -789,7 +792,7 @@ func (fs *Decomposedfs) ListFolder(ctx context.Context, ref *provider.Reference, rp, err := fs.p.AssemblePermissions(ctx, n) switch { case err != nil: - return nil, errtypes.InternalError(err.Error()) + return nil, err case !rp.ListContainer: f, _ := storagespace.FormatReference(ref) if rp.Stat { @@ -880,7 +883,7 @@ func (fs *Decomposedfs) Delete(ctx context.Context, ref *provider.Reference) (er rp, err := fs.p.AssemblePermissions(ctx, node) switch { case err != nil: - return errtypes.InternalError(err.Error()) + return err case !rp.Delete: f, _ := storagespace.FormatReference(ref) if rp.Stat { @@ -920,7 +923,7 @@ func (fs *Decomposedfs) Download(ctx context.Context, ref *provider.Reference) ( rp, err := fs.p.AssemblePermissions(ctx, node) switch { case err != nil: - return nil, errtypes.InternalError(err.Error()) + return nil, err case !rp.InitiateFileDownload: f, _ := storagespace.FormatReference(ref) if rp.Stat { @@ -951,7 +954,7 @@ func (fs *Decomposedfs) GetLock(ctx context.Context, ref *provider.Reference) (* rp, err := fs.p.AssemblePermissions(ctx, node) switch { case err != nil: - return nil, errtypes.InternalError(err.Error()) + return nil, err case !rp.InitiateFileDownload: f, _ := storagespace.FormatReference(ref) if rp.Stat { @@ -977,7 +980,7 @@ func (fs *Decomposedfs) SetLock(ctx context.Context, ref *provider.Reference, lo rp, err := fs.p.AssemblePermissions(ctx, node) switch { case err != nil: - return errtypes.InternalError(err.Error()) + return err case !rp.InitiateFileUpload: f, _ := storagespace.FormatReference(ref) if rp.Stat { @@ -1007,7 +1010,7 @@ func (fs *Decomposedfs) RefreshLock(ctx context.Context, ref *provider.Reference rp, err := fs.p.AssemblePermissions(ctx, node) switch { case err != nil: - return errtypes.InternalError(err.Error()) + return err case !rp.InitiateFileUpload: f, _ := storagespace.FormatReference(ref) if rp.Stat { @@ -1037,7 +1040,7 @@ func (fs *Decomposedfs) Unlock(ctx context.Context, ref *provider.Reference, loc rp, err := fs.p.AssemblePermissions(ctx, node) switch { case err != nil: - return errtypes.InternalError(err.Error()) + return err case !rp.InitiateFileUpload: // TODO do we need a dedicated permission? f, _ := storagespace.FormatReference(ref) if rp.Stat { diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/grants.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/grants.go index 0e162e6aa..3c053d71d 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/grants.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/grants.go @@ -63,7 +63,7 @@ func (fs *Decomposedfs) DenyGrant(ctx context.Context, ref *provider.Reference, switch { case err != nil: - return errtypes.InternalError(err.Error()) + return err case !rp.DenyGrant: return errtypes.PermissionDenied(filepath.Join(grantNode.ParentID, grantNode.Name)) } @@ -102,7 +102,7 @@ func (fs *Decomposedfs) AddGrant(ctx context.Context, ref *provider.Reference, g rp, err := fs.p.AssemblePermissions(ctx, grantNode) switch { case err != nil: - return errtypes.InternalError(err.Error()) + return err case !rp.AddGrant: f, _ := storagespace.FormatReference(ref) if rp.Stat { @@ -128,7 +128,7 @@ func (fs *Decomposedfs) ListGrants(ctx context.Context, ref *provider.Reference) rp, err := fs.p.AssemblePermissions(ctx, grantNode) switch { case err != nil: - return nil, errtypes.InternalError(err.Error()) + return nil, err case !rp.ListGrants && !rp.Stat: f, _ := storagespace.FormatReference(ref) return nil, errtypes.NotFound(f) @@ -186,7 +186,7 @@ func (fs *Decomposedfs) RemoveGrant(ctx context.Context, ref *provider.Reference rp, err := fs.p.AssemblePermissions(ctx, grantNode) switch { case err != nil: - return errtypes.InternalError(err.Error()) + return err case !rp.RemoveGrant: f, _ := storagespace.FormatReference(ref) if rp.Stat { @@ -262,7 +262,7 @@ func (fs *Decomposedfs) UpdateGrant(ctx context.Context, ref *provider.Reference rp, err := fs.p.AssemblePermissions(ctx, grantNode) switch { case err != nil: - return errtypes.InternalError(err.Error()) + return err case !rp.UpdateGrant: f, _ := storagespace.FormatReference(ref) if rp.Stat { diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup/lookup.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup/lookup.go index 395559eb3..c2ae2bfdc 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup/lookup.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup/lookup.go @@ -141,7 +141,7 @@ func (lu *Lookup) NodeFromID(ctx context.Context, id *provider.ResourceId) (n *n } if id.OpaqueId == "" { // The Resource references the root of a space - return lu.NodeFromSpaceID(ctx, id) + return lu.NodeFromSpaceID(ctx, id.SpaceId) } return node.ReadNode(ctx, lu, id.SpaceId, id.OpaqueId, false, nil, false) } @@ -162,9 +162,9 @@ func Pathify(id string, depth, width int) string { return b.String() } -// NodeFromSpaceID converts a resource id without an opaque id into a Node -func (lu *Lookup) NodeFromSpaceID(ctx context.Context, id *provider.ResourceId) (n *node.Node, err error) { - node, err := node.ReadNode(ctx, lu, id.SpaceId, id.OpaqueId, false, nil, false) +// NodeFromSpaceID converts a resource id into a Node +func (lu *Lookup) NodeFromSpaceID(ctx context.Context, spaceID string) (n *node.Node, err error) { + node, err := node.ReadNode(ctx, lu, spaceID, spaceID, false, nil, false) if err != nil { return nil, err } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata.go index 31c96fef0..97a35b346 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata.go @@ -52,7 +52,7 @@ func (fs *Decomposedfs) SetArbitraryMetadata(ctx context.Context, ref *provider. rp, err := fs.p.AssemblePermissions(ctx, n) switch { case err != nil: - return errtypes.InternalError(err.Error()) + return err case !rp.InitiateFileUpload: // TODO add explicit SetArbitraryMetadata grant to CS3 api, tracked in https://github.com/cs3org/cs3apis/issues/91 f, _ := storagespace.FormatReference(ref) if rp.Stat { @@ -145,7 +145,7 @@ func (fs *Decomposedfs) UnsetArbitraryMetadata(ctx context.Context, ref *provide rp, err := fs.p.AssemblePermissions(ctx, n) switch { case err != nil: - return errtypes.InternalError(err.Error()) + return err case !rp.InitiateFileUpload: // TODO use SetArbitraryMetadata grant to CS3 api, tracked in https://github.com/cs3org/cs3apis/issues/91 f, _ := storagespace.FormatReference(ref) if rp.Stat { diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/mtimesyncedcache/map.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/mtimesyncedcache/map.go new file mode 100644 index 000000000..830634fc2 --- /dev/null +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/mtimesyncedcache/map.go @@ -0,0 +1,36 @@ +package mtimesyncedcache + +import "sync" + +type Map[K comparable, V any] struct { + m sync.Map +} + +func (m *Map[K, V]) Delete(key K) { m.m.Delete(key) } + +func (m *Map[K, V]) Load(key K) (value V, ok bool) { + v, ok := m.m.Load(key) + if !ok { + return value, ok + } + return v.(V), ok +} + +func (m *Map[K, V]) LoadAndDelete(key K) (value V, loaded bool) { + v, loaded := m.m.LoadAndDelete(key) + if !loaded { + return value, loaded + } + return v.(V), loaded +} + +func (m *Map[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) { + a, loaded := m.m.LoadOrStore(key, value) + return a.(V), loaded +} + +func (m *Map[K, V]) Range(f func(key K, value V) bool) { + m.m.Range(func(key, value any) bool { return f(key.(K), value.(V)) }) +} + +func (m *Map[K, V]) Store(key K, value V) { m.m.Store(key, value) } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/mtimesyncedcache/mtimesyncedcache.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/mtimesyncedcache/mtimesyncedcache.go new file mode 100644 index 000000000..65f7bd3f8 --- /dev/null +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/mtimesyncedcache/mtimesyncedcache.go @@ -0,0 +1,59 @@ +package mtimesyncedcache + +import ( + "sync" + "time" +) + +type Cache[K comparable, T any] struct { + entries Map[K, *entry[T]] +} + +type entry[T any] struct { + mtime time.Time + value T + + mu sync.Mutex +} + +func New[K comparable, T any]() Cache[K, T] { + return Cache[K, T]{ + entries: Map[K, *entry[T]]{}, + } +} + +func (c *Cache[K, T]) Store(key K, mtime time.Time, value T) error { + c.entries.Store(key, &entry[T]{ + mtime: mtime, + value: value, + }) + return nil +} + +func (c *Cache[K, T]) Load(key K) (T, bool) { + entry, ok := c.entries.Load(key) + if !ok { + var t T + return t, false + } + return entry.value, true +} + +func (c *Cache[K, T]) LoadOrStore(key K, mtime time.Time, f func() (T, error)) (T, error) { + e, _ := c.entries.LoadOrStore(key, &entry[T]{}) + + e.mu.Lock() + defer e.mu.Unlock() + if mtime.After(e.mtime) { + e.mtime = mtime + v, err := f() + if err != nil { + var t T + return t, err + } + e.value = v + c.entries.Store(key, e) + } + + return e.value, nil +} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/node.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/node.go index 25a8a8e4a..9c52eeeee 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/node.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/node.go @@ -294,37 +294,6 @@ func ReadNode(ctx context.Context, lu PathLookup, spaceID, nodeID string, canLis } return nil, errtypes.InternalError("Missing parent ID on node") } - // TODO why do we stat the parent? to determine if the current node is in the trash we would need to traverse all parents... - // we need to traverse all parents for permissions anyway ... - // - we can compare to space root owner with the current user - // - we can compare the share permissions on the root for spaces, which would work for managers - // - for non managers / owners we need to traverse all path segments because an intermediate node might have been shared - // - if we want to support negative acls we need to traverse the path for all users (but the owner) - // for trashed items we need to check all parents - // - one of them might have the trash suffix ... - // - options: - // - move deleted nodes in a trash folder that is still part of the tree (aka freedesktop org trash spec) - // - shares should still be removed, which requires traversing all trashed children ... and it should be undoable ... - // - what if a trashed file is restored? will child items be accessible by a share? - // - compare paths of trash root items and the trashed file? - // - to determine the relative path of a file we would need to traverse all intermediate nodes anyway - // - recursively mark all children as trashed ... async ... it is ok when that is not synchronous - // - how do we pick up if an error occurs? write a journal somewhere? activity log / delta? - // - stat requests will not pick up trashed items at all - // - recursively move all children into the trash folder? - // - no need to write an additional trash entry - // - can be made more robust with a journal - // - same recursion mechanism can be used to purge items? sth we still need to do - // - flag the two above options with dtime - if !skipParentCheck { - _, err = os.Stat(n.ParentPath()) - if err != nil { - if errors.Is(err, fs.ErrNotExist) { - return nil, errtypes.NotFound(err.Error()) - } - return nil, err - } - } if revisionSuffix == "" { n.BlobID = attrs.String(prefixes.BlobIDAttr) @@ -406,11 +375,11 @@ func (n *Node) Child(ctx context.Context, name string) (*Node, error) { } // ParentWithReader returns the parent node -func (n *Node) ParentWithReader(r io.Reader) (p *Node, err error) { +func (n *Node) ParentWithReader(r io.Reader) (*Node, error) { if n.ParentID == "" { return nil, fmt.Errorf("decomposedfs: root has no parent") } - p = &Node{ + p := &Node{ SpaceID: n.SpaceID, lu: n.lu, ID: n.ParentID, @@ -418,17 +387,19 @@ func (n *Node) ParentWithReader(r io.Reader) (p *Node, err error) { } // fill metadata cache using the reader - _, _ = p.XattrsWithReader(r) - - // lookup name and parent id in extended attributes - p.ParentID, _ = p.XattrString(prefixes.ParentidAttr) - p.Name, _ = p.XattrString(prefixes.NameAttr) - - // check node exists - if _, err := os.Stat(p.InternalPath()); err == nil { - p.Exists = true + attrs, err := p.XattrsWithReader(r) + switch { + case metadata.IsNotExist(err): + return p, nil // swallow not found, the node defaults to exists = false + case err != nil: + return nil, err } - return + p.Exists = true + + p.Name = attrs.String(prefixes.NameAttr) + p.ParentID = attrs.String(prefixes.ParentidAttr) + + return p, err } // Parent returns the parent node @@ -948,11 +919,11 @@ func (n *Node) IsDisabled() bool { // GetTreeSize reads the treesize from the extended attributes func (n *Node) GetTreeSize() (treesize uint64, err error) { - s, err := n.XattrInt64(prefixes.TreesizeAttr) + s, err := n.XattrUint64(prefixes.TreesizeAttr) if err != nil { return 0, err } - return uint64(s), nil + return s, nil } // SetTreeSize writes the treesize to the extended attributes @@ -1039,7 +1010,6 @@ func (n *Node) ReadUserPermissions(ctx context.Context, u *userpb.User) (ap prov } AddPermissions(&ap, g.GetPermissions()) case metadata.IsAttrUnset(err): - err = nil appctx.GetLogger(ctx).Error().Interface("node", n).Str("grant", grantees[i]).Interface("grantees", grantees).Msg("grant vanished from node after listing") // continue with next segment default: diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/permissions.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/permissions.go index 0899ba2c6..2401510f3 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/permissions.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/permissions.go @@ -98,6 +98,16 @@ func NewPermissions(lu PathLookup) *Permissions { // AssemblePermissions will assemble the permissions for the current user on the given node, taking into account all parent nodes func (p *Permissions) AssemblePermissions(ctx context.Context, n *Node) (ap provider.ResourcePermissions, err error) { + return p.assemblePermissions(ctx, n, true) +} + +// AssembleTrashPermissions will assemble the permissions for the current user on the given node, taking into account all parent nodes +func (p *Permissions) AssembleTrashPermissions(ctx context.Context, n *Node) (ap provider.ResourcePermissions, err error) { + return p.assemblePermissions(ctx, n, false) +} + +// assemblePermissions will assemble the permissions for the current user on the given node, taking into account all parent nodes +func (p *Permissions) assemblePermissions(ctx context.Context, n *Node, failOnTrashedSubtree bool) (ap provider.ResourcePermissions, err error) { u, ok := ctxpkg.ContextGetUser(ctx) if !ok { return NoPermissions(), nil @@ -114,16 +124,9 @@ func (p *Permissions) AssemblePermissions(ctx context.Context, n *Node) (ap prov n.ID = kp[0] } - // check if the current user is the owner - if utils.UserIDEqual(u.Id, n.Owner()) { - return OwnerPermissions(), nil - } // determine root - rn := n.SpaceRoot - cn := n - ap = provider.ResourcePermissions{} // for an efficient group lookup convert the list of groups to a map @@ -154,6 +157,10 @@ func (p *Permissions) AssemblePermissions(ctx context.Context, n *Node) (ap prov // We do not have a parent, so we assume the next valid parent is the spaceRoot (which must always exist) cn = n.SpaceRoot } + if failOnTrashedSubtree && !cn.Exists { + return NoPermissions(), errtypes.NotFound(n.ID) + } + } // for the root node @@ -167,6 +174,11 @@ func (p *Permissions) AssemblePermissions(ctx context.Context, n *Node) (ap prov appctx.GetLogger(ctx).Error().Err(err).Interface("node", cn.ID).Msg("error reading root node permissions") } + // check if the current user is the owner + if utils.UserIDEqual(u.Id, n.Owner()) { + return OwnerPermissions(), nil + } + appctx.GetLogger(ctx).Debug().Interface("permissions", ap).Interface("node", n.ID).Interface("user", u).Msg("returning agregated permissions") return ap, nil } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/xattrs.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/xattrs.go index 3aa1eb3d1..d8b526ccd 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/xattrs.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/xattrs.go @@ -174,3 +174,12 @@ func (n *Node) XattrInt64(key string) (int64, error) { } return strconv.ParseInt(b, 10, 64) } + +// XattrUint64 returns the uint64 representation of an attribute +func (n *Node) XattrUint64(key string) (uint64, error) { + b, err := n.XattrString(key) + if err != nil { + return 0, err + } + return strconv.ParseUint(b, 10, 64) +} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/recycle.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/recycle.go index 18598ef98..8163f4d03 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/recycle.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/recycle.go @@ -58,14 +58,14 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference sublog := appctx.GetLogger(ctx).With().Str("space", spaceID).Str("key", key).Str("relative_path", relativePath).Logger() // check permissions - trashnode, err := fs.lu.NodeFromSpaceID(ctx, ref.ResourceId) + trashnode, err := fs.lu.NodeFromSpaceID(ctx, spaceID) if err != nil { return nil, err } - rp, err := fs.p.AssemblePermissions(ctx, trashnode) + rp, err := fs.p.AssembleTrashPermissions(ctx, trashnode) switch { case err != nil: - return nil, errtypes.InternalError(err.Error()) + return nil, err case !rp.ListRecycle: if rp.Stat { return nil, errtypes.PermissionDenied(key) @@ -300,10 +300,10 @@ func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, ref *provider.Re } // check permissions of deleted node - rp, err := fs.p.AssemblePermissions(ctx, rn) + rp, err := fs.p.AssembleTrashPermissions(ctx, rn) switch { case err != nil: - return errtypes.InternalError(err.Error()) + return err case !rp.RestoreRecycleItem: if rp.Stat { return errtypes.PermissionDenied(key) @@ -318,7 +318,7 @@ func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, ref *provider.Re pp, err := fs.p.AssemblePermissions(ctx, parent) switch { case err != nil: - return errtypes.InternalError(err.Error()) + return err case !pp.InitiateFileUpload: // share receiver cannot restore to a shared resource to which she does not have write permissions. if rp.Stat { @@ -346,10 +346,10 @@ func (fs *Decomposedfs) PurgeRecycleItem(ctx context.Context, ref *provider.Refe } // check permissions of deleted node - rp, err := fs.p.AssemblePermissions(ctx, rn) + rp, err := fs.p.AssembleTrashPermissions(ctx, rn) switch { case err != nil: - return errtypes.InternalError(err.Error()) + return err case !rp.PurgeRecycle: if rp.Stat { return errtypes.PermissionDenied(key) diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/revisions.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/revisions.go index e023ee98a..be40c727f 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/revisions.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/revisions.go @@ -57,7 +57,7 @@ func (fs *Decomposedfs) ListRevisions(ctx context.Context, ref *provider.Referen rp, err := fs.p.AssemblePermissions(ctx, n) switch { case err != nil: - return nil, errtypes.InternalError(err.Error()) + return nil, err case !rp.ListFileVersions: f, _ := storagespace.FormatReference(ref) if rp.Stat { @@ -136,7 +136,7 @@ func (fs *Decomposedfs) DownloadRevision(ctx context.Context, ref *provider.Refe rp, err := fs.p.AssemblePermissions(ctx, n) switch { case err != nil: - return nil, errtypes.InternalError(err.Error()) + return nil, err case !rp.ListFileVersions || !rp.InitiateFileDownload: // TODO add explicit permission in the CS3 api? f, _ := storagespace.FormatReference(ref) if rp.Stat { @@ -190,7 +190,7 @@ func (fs *Decomposedfs) RestoreRevision(ctx context.Context, ref *provider.Refer rp, err := fs.p.AssemblePermissions(ctx, n) switch { case err != nil: - return errtypes.InternalError(err.Error()) + return err case !rp.RestoreFileVersion: f, _ := storagespace.FormatReference(ref) if rp.Stat { @@ -327,7 +327,7 @@ func (fs *Decomposedfs) getRevisionNode(ctx context.Context, ref *provider.Refer p, err := fs.p.AssemblePermissions(ctx, n) switch { case err != nil: - return nil, errtypes.InternalError(err.Error()) + return nil, err case !hasPermission(&p): return nil, errtypes.PermissionDenied(filepath.Join(n.ParentID, n.Name)) } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spacepermissions.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spacepermissions.go index ce764f8e0..0a8d990b8 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spacepermissions.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spacepermissions.go @@ -8,6 +8,7 @@ import ( v1beta11 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" "github.com/cs3org/reva/v2/pkg/utils" "google.golang.org/grpc" @@ -16,6 +17,7 @@ import ( // PermissionsChecker defines an interface for checking permissions on a Node type PermissionsChecker interface { AssemblePermissions(ctx context.Context, n *node.Node) (ap provider.ResourcePermissions, err error) + AssembleTrashPermissions(ctx context.Context, n *node.Node) (ap provider.ResourcePermissions, err error) } // CS3PermissionsClient defines an interface for checking permissions against the CS3 permissions service @@ -25,13 +27,13 @@ type CS3PermissionsClient interface { // Permissions manages permissions type Permissions struct { - item PermissionsChecker // handles item permissions - space CS3PermissionsClient // handlers space permissions + item PermissionsChecker // handles item permissions + permissionsSelector pool.Selectable[cs3permissions.PermissionsAPIClient] // handlers space permissions } // NewPermissions returns a new Permissions instance -func NewPermissions(item PermissionsChecker, space CS3PermissionsClient) Permissions { - return Permissions{item: item, space: space} +func NewPermissions(item PermissionsChecker, permissionsSelector pool.Selectable[cs3permissions.PermissionsAPIClient]) Permissions { + return Permissions{item: item, permissionsSelector: permissionsSelector} } // AssemblePermissions is used to assemble file permissions @@ -39,9 +41,14 @@ func (p Permissions) AssemblePermissions(ctx context.Context, n *node.Node) (pro return p.item.AssemblePermissions(ctx, n) } +// AssembleTrashPermissions is used to assemble file permissions +func (p Permissions) AssembleTrashPermissions(ctx context.Context, n *node.Node) (provider.ResourcePermissions, error) { + return p.item.AssembleTrashPermissions(ctx, n) +} + // CreateSpace returns true when the user is allowed to create the space func (p Permissions) CreateSpace(ctx context.Context, spaceid string) bool { - return p.checkPermission(ctx, "create-space", spaceRef(spaceid)) + return p.checkPermission(ctx, "Drives.Create", spaceRef(spaceid)) } // SetSpaceQuota returns true when the user is allowed to change the spaces quota @@ -50,25 +57,25 @@ func (p Permissions) SetSpaceQuota(ctx context.Context, spaceid string, spaceTyp default: return false // only quotas of personal and project space may be changed case _spaceTypePersonal: - return p.checkPermission(ctx, "set-space-quota", spaceRef(spaceid)) + return p.checkPermission(ctx, "Drives.ReadWritePersonalQuota", spaceRef(spaceid)) case _spaceTypeProject: - return p.checkPermission(ctx, "Drive.ReadWriteQuota.Project", spaceRef(spaceid)) + return p.checkPermission(ctx, "Drives.ReadWriteProjectQuota", spaceRef(spaceid)) } } // ManageSpaceProperties returns true when the user is allowed to change space properties (name/subtitle) func (p Permissions) ManageSpaceProperties(ctx context.Context, spaceid string) bool { - return p.checkPermission(ctx, "Drive.ReadWrite", spaceRef(spaceid)) + return p.checkPermission(ctx, "Drives.ReadWrite", spaceRef(spaceid)) } // SpaceAbility returns true when the user is allowed to enable/disable the space func (p Permissions) SpaceAbility(ctx context.Context, spaceid string) bool { - return p.checkPermission(ctx, "Drive.ReadWriteEnabled", spaceRef(spaceid)) + return p.checkPermission(ctx, "Drives.ReadWriteEnabled", spaceRef(spaceid)) } // ListAllSpaces returns true when the user is allowed to list all spaces func (p Permissions) ListAllSpaces(ctx context.Context) bool { - return p.checkPermission(ctx, "list-all-spaces", nil) + return p.checkPermission(ctx, "Drives.List", nil) } // ListSpacesOfUser returns true when the user is allowed to list the spaces of the given user @@ -86,18 +93,23 @@ func (p Permissions) ListSpacesOfUser(ctx context.Context, userid *userv1beta1.U // DeleteAllSpaces returns true when the user is allowed to delete all spaces func (p Permissions) DeleteAllSpaces(ctx context.Context) bool { - return p.checkPermission(ctx, "delete-all-spaces", nil) + return p.checkPermission(ctx, "Drives.DeleteProject", nil) } // DeleteAllHomeSpaces returns true when the user is allowed to delete all home spaces func (p Permissions) DeleteAllHomeSpaces(ctx context.Context) bool { - return p.checkPermission(ctx, "delete-all-home-spaces", nil) + return p.checkPermission(ctx, "Drives.DeletePersonal", nil) } // checkPermission is used to check a users space permissions func (p Permissions) checkPermission(ctx context.Context, perm string, ref *provider.Reference) bool { + permissionsClient, err := p.permissionsSelector.Next() + if err != nil { + return false + } + user := ctxpkg.ContextMustGetUser(ctx) - checkRes, err := p.space.CheckPermission(ctx, &cs3permissions.CheckPermissionRequest{ + checkRes, err := permissionsClient.CheckPermission(ctx, &cs3permissions.CheckPermissionRequest{ Permission: perm, SubjectRef: &cs3permissions.SubjectReference{ Spec: &cs3permissions.SubjectReference_UserId{ diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spaces.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spaces.go index 77fda6136..96285f784 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spaces.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spaces.go @@ -27,6 +27,7 @@ import ( "path/filepath" "strconv" "strings" + "sync/atomic" "time" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -49,6 +50,7 @@ import ( "github.com/cs3org/reva/v2/pkg/utils" "github.com/google/uuid" "github.com/pkg/errors" + "golang.org/x/sync/errgroup" ) const ( @@ -299,17 +301,37 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide matches := map[string]struct{}{} if requestedUserID != nil { - path := filepath.Join(fs.o.Root, "indexes", "by-user-id", requestedUserID.GetOpaqueId(), nodeID) - m, err := filepath.Glob(path) + allMatches := map[string]string{} + indexPath := filepath.Join(fs.o.Root, "indexes", "by-user-id", requestedUserID.GetOpaqueId()) + fi, err := os.Stat(indexPath) + if err == nil { + allMatches, err = fs.spaceIDCache.LoadOrStore("by-user-id:"+requestedUserID.GetOpaqueId(), fi.ModTime(), func() (map[string]string, error) { + path := filepath.Join(fs.o.Root, "indexes", "by-user-id", requestedUserID.GetOpaqueId(), "*") + m, err := filepath.Glob(path) + if err != nil { + return nil, err + } + matches := map[string]string{} + for _, match := range m { + link, err := os.Readlink(match) + if err != nil { + continue + } + matches[match] = link + } + return matches, nil + }) + } if err != nil { return nil, err } - for _, match := range m { - link, err := os.Readlink(match) - if err != nil { - continue + + if nodeID == spaceIDAny { + for _, match := range allMatches { + matches[match] = struct{}{} } - matches[link] = struct{}{} + } else { + matches[allMatches[nodeID]] = struct{}{} } // get Groups for userid @@ -323,17 +345,37 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide } for _, group := range user.Groups { - path := filepath.Join(fs.o.Root, "indexes", "by-group-id", group, nodeID) - m, err := filepath.Glob(path) + indexPath := filepath.Join(fs.o.Root, "indexes", "by-group-id", group) + fi, err := os.Stat(indexPath) + if err != nil { + continue + } + allMatches, err := fs.spaceIDCache.LoadOrStore("by-group-id:"+group, fi.ModTime(), func() (map[string]string, error) { + path := filepath.Join(fs.o.Root, "indexes", "by-group-id", group, "*") + m, err := filepath.Glob(path) + if err != nil { + return nil, err + } + matches := map[string]string{} + for _, match := range m { + link, err := os.Readlink(match) + if err != nil { + continue + } + matches[match] = link + } + return matches, nil + }) if err != nil { return nil, err } - for _, match := range m { - link, err := os.Readlink(match) - if err != nil { - continue + + if nodeID == spaceIDAny { + for _, match := range allMatches { + matches[match] = struct{}{} } - matches[link] = struct{}{} + } else { + matches[allMatches[nodeID]] = struct{}{} } } @@ -341,17 +383,40 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide if requestedUserID == nil { for spaceType := range spaceTypes { - path := filepath.Join(fs.o.Root, "indexes", "by-type", spaceType, nodeID) - m, err := filepath.Glob(path) + indexPath := filepath.Join(fs.o.Root, "indexes", "by-type") + if spaceType != spaceTypeAny { + indexPath = filepath.Join(indexPath, spaceType) + } + fi, err := os.Stat(indexPath) + if err != nil { + continue + } + allMatches, err := fs.spaceIDCache.LoadOrStore("by-type:"+spaceType, fi.ModTime(), func() (map[string]string, error) { + path := filepath.Join(fs.o.Root, "indexes", "by-type", spaceType, "*") + m, err := filepath.Glob(path) + if err != nil { + return nil, err + } + matches := map[string]string{} + for _, match := range m { + link, err := os.Readlink(match) + if err != nil { + continue + } + matches[match] = link + } + return matches, nil + }) if err != nil { return nil, err } - for _, match := range m { - link, err := os.Readlink(match) - if err != nil { - continue + + if nodeID == spaceIDAny { + for _, match := range allMatches { + matches[match] = struct{}{} } - matches[link] = struct{}{} + } else { + matches[allMatches[nodeID]] = struct{}{} } } } @@ -366,66 +431,106 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide // But what about sharding nodes by space? // an efficient lookup would be possible if we received a spaceid&opaqueid in the request // the personal spaces must also use the nodeid and not the name + numShares := atomic.Int64{} + errg, ctx := errgroup.WithContext(ctx) + work := make(chan string, len(matches)) + results := make(chan *provider.StorageSpace, len(matches)) - numShares := 0 - - for match := range matches { - var err error - // TODO introduce metadata.IsLockFile(path) - // do not investigate flock files any further. They indicate file locks but are not relevant here. - if strings.HasSuffix(match, filelocks.LockFileSuffix) { - continue - } - // skip metadata files - if fs.lu.MetadataBackend().IsMetaFile(match) { - continue - } - // always read link in case storage space id != node id - spaceID, nodeID, err = ReadSpaceAndNodeFromIndexLink(match) - if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Str("match", match).Msg("could not read link, skipping") - continue - } - - n, err := node.ReadNode(ctx, fs.lu, spaceID, nodeID, true, nil, true) - if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Str("id", nodeID).Msg("could not read node, skipping") - continue - } - - if !n.Exists { - continue - } - - space, err := fs.storageSpaceFromNode(ctx, n, checkNodePermissions) - if err != nil { - switch err.(type) { - case errtypes.IsPermissionDenied: - // ok - case errtypes.NotFound: - // ok - default: - appctx.GetLogger(ctx).Error().Err(err).Str("id", nodeID).Msg("could not convert to storage space") + // Distribute work + errg.Go(func() error { + defer close(work) + for match := range matches { + select { + case work <- match: + case <-ctx.Done(): + return ctx.Err() } - continue } + return nil + }) - // FIXME type share evolved to grant on the edge branch ... make it configurable if the driver should support them or not for now ... ignore type share - if space.SpaceType == spaceTypeShare { - numShares++ - // do not list shares as spaces for the owner - continue - } - - // TODO apply more filters - _, ok1 := spaceTypes[spaceTypeAny] - _, ok2 := spaceTypes[space.SpaceType] - if ok1 || ok2 { - spaces = append(spaces, space) - } + // Spawn workers that'll concurrently work the queue + numWorkers := 20 + if len(matches) < numWorkers { + numWorkers = len(matches) } + for i := 0; i < numWorkers; i++ { + errg.Go(func() error { + for match := range work { + var err error + // TODO introduce metadata.IsLockFile(path) + // do not investigate flock files any further. They indicate file locks but are not relevant here. + if strings.HasSuffix(match, filelocks.LockFileSuffix) { + continue + } + // skip metadata files + if fs.lu.MetadataBackend().IsMetaFile(match) { + continue + } + // always read link in case storage space id != node id + spaceID, nodeID, err = ReadSpaceAndNodeFromIndexLink(match) + if err != nil { + appctx.GetLogger(ctx).Error().Err(err).Str("match", match).Msg("could not read link, skipping") + continue + } + + n, err := node.ReadNode(ctx, fs.lu, spaceID, nodeID, true, nil, true) + if err != nil { + appctx.GetLogger(ctx).Error().Err(err).Str("id", nodeID).Msg("could not read node, skipping") + continue + } + + if !n.Exists { + continue + } + + space, err := fs.storageSpaceFromNode(ctx, n, checkNodePermissions) + if err != nil { + switch err.(type) { + case errtypes.IsPermissionDenied: + // ok + case errtypes.NotFound: + // ok + default: + appctx.GetLogger(ctx).Error().Err(err).Str("id", nodeID).Msg("could not convert to storage space") + } + continue + } + + // FIXME type share evolved to grant on the edge branch ... make it configurable if the driver should support them or not for now ... ignore type share + if space.SpaceType == spaceTypeShare { + numShares.Add(1) + // do not list shares as spaces for the owner + continue + } + + // TODO apply more filters + _, ok1 := spaceTypes[spaceTypeAny] + _, ok2 := spaceTypes[space.SpaceType] + if ok1 || ok2 { + select { + case results <- space: + case <-ctx.Done(): + return ctx.Err() + } + } + } + return nil + }) + } + + // Wait for things to settle down, then close results chan + go func() { + _ = errg.Wait() // error is checked later + close(results) + }() + + for r := range results { + spaces = append(spaces, r) + } + // if there are no matches (or they happened to be spaces for the owner) and the node is a child return a space - if len(matches) <= numShares && nodeID != spaceID { + if int64(len(matches)) <= numShares.Load() && nodeID != spaceID { // try node id n, err := node.ReadNode(ctx, fs.lu, spaceID, nodeID, true, nil, false) // permission to read disabled space is checked in storageSpaceFromNode if err != nil { @@ -554,7 +659,7 @@ func (fs *Decomposedfs) UpdateStorageSpace(ctx context.Context, req *provider.Up sp, err := fs.p.AssemblePermissions(ctx, spaceNode) if err != nil { return &provider.UpdateStorageSpaceResponse{ - Status: &v1beta11.Status{Code: v1beta11.Code_CODE_INVALID, Message: err.Error()}, + Status: status.NewStatusFromErrType(ctx, "assembling permissions failed", err), }, nil } @@ -777,14 +882,16 @@ func (fs *Decomposedfs) linkStorageSpaceType(ctx context.Context, spaceType stri if isAlreadyExists(err) { appctx.GetLogger(ctx).Debug().Err(err).Str("space", spaceID).Str("spacetype", spaceType).Msg("symlink already exists") // FIXME: is it ok to wipe this err if the symlink already exists? - err = nil } else { // TODO how should we handle error cases here? appctx.GetLogger(ctx).Error().Err(err).Str("space", spaceID).Str("spacetype", spaceType).Msg("could not create symlink") + return err } } - return err + // touch index root to invalidate caches + now := time.Now() + return os.Chtimes(filepath.Join(fs.o.Root, "indexes", "by-type"), now, now) } func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, checkPermissions bool) (*provider.StorageSpace, error) { @@ -793,7 +900,7 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, rp, err := fs.p.AssemblePermissions(ctx, n) switch { case err != nil: - return nil, errtypes.InternalError(err.Error()) + return nil, err case !rp.Stat: return nil, errtypes.NotFound(fmt.Sprintf("space %s not found", n.ID)) } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/tree.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/tree.go index d092f52d6..c8891f16d 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/tree.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/tree.go @@ -376,7 +376,7 @@ func (t *Tree) ListFolder(ctx context.Context, n *node.Node) ([]*node.Node, erro } } - child, err := node.ReadNode(ctx, t.lookup, n.SpaceID, nodeID, false, n, true) + child, err := node.ReadNode(ctx, t.lookup, n.SpaceID, nodeID, false, n.SpaceRoot, true) if err != nil { return err } @@ -691,7 +691,12 @@ func (t *Tree) removeNode(path string, n *node.Node) error { // Propagate propagates changes to the root of the tree func (t *Tree) Propagate(ctx context.Context, n *node.Node, sizeDiff int64) (err error) { - sublog := appctx.GetLogger(ctx).With().Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Logger() + sublog := appctx.GetLogger(ctx).With(). + Str("method", "tree.Propagate"). + Str("spaceid", n.SpaceID). + Str("nodeid", n.ID). + Int64("sizeDiff", sizeDiff). + Logger() if !t.options.TreeTimeAccounting && (!t.options.TreeSizeAccounting || sizeDiff == 0) { // no propagation enabled sublog.Debug().Msg("propagation disabled or nothing to propagate") @@ -712,15 +717,22 @@ func (t *Tree) Propagate(ctx context.Context, n *node.Node, sizeDiff int64) (err var f *lockedfile.File // lock parent before reading treesize or tree time + + var parentFilename string switch t.lookup.MetadataBackend().(type) { case metadata.MessagePackBackend: - f, err = lockedfile.OpenFile(t.lookup.MetadataBackend().MetadataPath(n.ParentPath()), os.O_RDWR|os.O_CREATE, 0600) + parentFilename = t.lookup.MetadataBackend().MetadataPath(n.ParentPath()) + f, err = lockedfile.OpenFile(parentFilename, os.O_RDWR|os.O_CREATE, 0600) case metadata.XattrsBackend: // we have to use dedicated lockfiles to lock directories // this only works because the xattr backend also locks folders with separate lock files - f, err = lockedfile.OpenFile(n.ParentPath()+filelocks.LockFileSuffix, os.O_RDWR|os.O_CREATE, 0600) + parentFilename = n.ParentPath() + filelocks.LockFileSuffix + f, err = lockedfile.OpenFile(parentFilename, os.O_RDWR|os.O_CREATE, 0600) } if err != nil { + sublog.Error().Err(err). + Str("parent filename", parentFilename). + Msg("Propagation failed. Could not open metadata for parent with lock.") return err } // always log error if closing node fails @@ -733,6 +745,8 @@ func (t *Tree) Propagate(ctx context.Context, n *node.Node, sizeDiff int64) (err }() if n, err = n.ParentWithReader(f); err != nil { + sublog.Error().Err(err). + Msg("Propagation failed. Could not read parent node.") return err } @@ -788,18 +802,26 @@ func (t *Tree) Propagate(ctx context.Context, n *node.Node, sizeDiff int64) (err switch { case metadata.IsAttrUnset(err): // fallback to calculating the treesize + sublog.Warn().Msg("treesize attribute unset, falling back to calculating the treesize") newSize, err = t.calculateTreeSize(ctx, n.InternalPath()) if err != nil { return err } case err != nil: + sublog.Error().Err(err). + Msg("Faild to propagate treesize change. Error when reading the treesize attribute from parent") return err + case sizeDiff > 0: + newSize = treeSize + uint64(sizeDiff) + case uint64(-sizeDiff) > treeSize: + // The sizeDiff is larger than the current treesize. Which would result in + // a negative new treesize. Something must have gone wrong with the accounting. + // Reset the current treesize to 0. + sublog.Error().Uint64("treeSize", treeSize).Int64("sizeDiff", sizeDiff). + Msg("Error when updating treesize of parent node. Updated treesize < 0. Reestting to 0") + newSize = 0 default: - if sizeDiff > 0 { - newSize = treeSize + uint64(sizeDiff) - } else { - newSize = treeSize - uint64(-sizeDiff) - } + newSize = treeSize - uint64(-sizeDiff) } // update the tree size of the node @@ -808,12 +830,14 @@ func (t *Tree) Propagate(ctx context.Context, n *node.Node, sizeDiff int64) (err } if err = n.SetXattrs(attrs, false); err != nil { + sublog.Error().Err(err).Msg("Failed to update extend attributes of parent node") return err } // Release node lock early, ignore already closed error cerr := f.Close() if cerr != nil && !errors.Is(cerr, os.ErrClosed) { + sublog.Error().Err(cerr).Msg("Failed to close parent node and release lock") return cerr } } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload/processing.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload/processing.go index 3ba1a0445..06d92907a 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload/processing.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload/processing.go @@ -70,10 +70,7 @@ func New(ctx context.Context, info tusd.FileInfo, lu *lookup.Lookup, tp Tree, p return nil, errors.New("Decomposedfs: missing dir in metadata") } - n, err := lu.NodeFromSpaceID(ctx, &provider.ResourceId{ - SpaceId: info.Storage["SpaceRoot"], - OpaqueId: info.Storage["SpaceRoot"], - }) + n, err := lu.NodeFromSpaceID(ctx, info.Storage["SpaceRoot"]) if err != nil { return nil, errors.Wrap(err, "Decomposedfs: error getting space root node") } @@ -114,7 +111,7 @@ func New(ctx context.Context, info tusd.FileInfo, lu *lookup.Lookup, tp Tree, p rp, err := p.AssemblePermissions(ctx, checkNode) switch { case err != nil: - return nil, errtypes.InternalError(err.Error()) + return nil, err case !rp.InitiateFileUpload: return nil, errtypes.PermissionDenied(path) } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/downloader/downloader.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/downloader/downloader.go index a3795082e..b38ad80b3 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/downloader/downloader.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/downloader/downloader.go @@ -29,6 +29,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/internal/http/services/datagateway" "github.com/cs3org/reva/v2/pkg/errtypes" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/rhttp" ) @@ -39,15 +40,15 @@ type Downloader interface { } type revaDownloader struct { - gtw gateway.GatewayAPIClient - httpClient *http.Client + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + httpClient *http.Client } // NewDownloader creates a Downloader from the reva gateway -func NewDownloader(gtw gateway.GatewayAPIClient, options ...rhttp.Option) Downloader { +func NewDownloader(gatewaySelector pool.Selectable[gateway.GatewayAPIClient], options ...rhttp.Option) Downloader { return &revaDownloader{ - gtw: gtw, - httpClient: rhttp.GetHTTPClient(options...), + gatewaySelector: gatewaySelector, + httpClient: rhttp.GetHTTPClient(options...), } } @@ -62,7 +63,11 @@ func getDownloadProtocol(protocols []*gateway.FileDownloadProtocol, prot string) // Download downloads a resource given the path to the dst Writer func (r *revaDownloader) Download(ctx context.Context, id *provider.ResourceId, dst io.Writer) error { - downResp, err := r.gtw.InitiateFileDownload(ctx, &provider.InitiateFileDownloadRequest{ + gatewayClient, err := r.gatewaySelector.Next() + if err != nil { + return err + } + downResp, err := gatewayClient.InitiateFileDownload(ctx, &provider.InitiateFileDownloadRequest{ Ref: &provider.Reference{ ResourceId: id, Path: ".", diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/metadata/cs3.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/metadata/cs3.go index 76cff96d1..d271b7e77 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/metadata/cs3.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/metadata/cs3.go @@ -37,9 +37,17 @@ import ( "github.com/cs3org/reva/v2/pkg/rgrpc/status" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/utils" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" "google.golang.org/grpc/metadata" ) +var tracer trace.Tracer + +func init() { + tracer = otel.Tracer("github.com/cs3org/reva/pkg/storage/utils/metadata") +} + // CS3 represents a metadata storage with a cs3 storage backend type CS3 struct { providerAddr string @@ -75,6 +83,9 @@ func (cs3 *CS3) Backend() string { // Init creates the metadata space func (cs3 *CS3) Init(ctx context.Context, spaceid string) (err error) { + ctx, span := tracer.Start(ctx, "Init") + defer span.End() + client, err := cs3.providerClient() if err != nil { return err @@ -114,6 +125,9 @@ func (cs3 *CS3) Init(ctx context.Context, spaceid string) (err error) { // SimpleUpload uploads a file to the metadata storage func (cs3 *CS3) SimpleUpload(ctx context.Context, uploadpath string, content []byte) error { + ctx, span := tracer.Start(ctx, "SimpleUpload") + defer span.End() + return cs3.Upload(ctx, UploadRequest{ Path: uploadpath, Content: content, @@ -122,6 +136,9 @@ func (cs3 *CS3) SimpleUpload(ctx context.Context, uploadpath string, content []b // Upload uploads a file to the metadata storage func (cs3 *CS3) Upload(ctx context.Context, req UploadRequest) error { + ctx, span := tracer.Start(ctx, "Upload") + defer span.End() + client, err := cs3.providerClient() if err != nil { return err @@ -185,6 +202,9 @@ func (cs3 *CS3) Upload(ctx context.Context, req UploadRequest) error { // Stat returns the metadata for the given path func (cs3 *CS3) Stat(ctx context.Context, path string) (*provider.ResourceInfo, error) { + ctx, span := tracer.Start(ctx, "Stat") + defer span.End() + client, err := cs3.providerClient() if err != nil { return nil, err @@ -214,6 +234,9 @@ func (cs3 *CS3) Stat(ctx context.Context, path string) (*provider.ResourceInfo, // SimpleDownload reads a file from the metadata storage func (cs3 *CS3) SimpleDownload(ctx context.Context, downloadpath string) (content []byte, err error) { + ctx, span := tracer.Start(ctx, "SimpleDownload") + defer span.End() + client, err := cs3.providerClient() if err != nil { return nil, err @@ -277,6 +300,9 @@ func (cs3 *CS3) SimpleDownload(ctx context.Context, downloadpath string) (conten // Delete deletes a path func (cs3 *CS3) Delete(ctx context.Context, path string) error { + ctx, span := tracer.Start(ctx, "Delete") + defer span.End() + client, err := cs3.providerClient() if err != nil { return err @@ -304,6 +330,9 @@ func (cs3 *CS3) Delete(ctx context.Context, path string) error { // ReadDir returns the entries in a given directory func (cs3 *CS3) ReadDir(ctx context.Context, path string) ([]string, error) { + ctx, span := tracer.Start(ctx, "ReadDir") + defer span.End() + infos, err := cs3.ListDir(ctx, path) if err != nil { return nil, err @@ -318,6 +347,9 @@ func (cs3 *CS3) ReadDir(ctx context.Context, path string) ([]string, error) { // ListDir returns a list of ResourceInfos for the entries in a given directory func (cs3 *CS3) ListDir(ctx context.Context, path string) ([]*provider.ResourceInfo, error) { + ctx, span := tracer.Start(ctx, "ListDir") + defer span.End() + client, err := cs3.providerClient() if err != nil { return nil, err @@ -347,6 +379,9 @@ func (cs3 *CS3) ListDir(ctx context.Context, path string) ([]*provider.ResourceI // MakeDirIfNotExist will create a root node in the metadata storage. Requires an authenticated context. func (cs3 *CS3) MakeDirIfNotExist(ctx context.Context, folder string) error { + ctx, span := tracer.Start(ctx, "MakeDirIfNotExist") + defer span.End() + client, err := cs3.providerClient() if err != nil { return err @@ -361,40 +396,28 @@ func (cs3 *CS3) MakeDirIfNotExist(ctx context.Context, folder string) error { Path: utils.MakeRelativePath(folder), } - resp, err := client.Stat(ctx, &provider.StatRequest{ + resp, err := client.CreateContainer(ctx, &provider.CreateContainerRequest{ Ref: rootPathRef, }) - - if err != nil { - return err - } - switch { case err != nil: return err case resp.Status.Code == rpc.Code_CODE_OK: // nothing to do in this case - case resp.Status.Code == rpc.Code_CODE_NOT_FOUND: - r, err := client.CreateContainer(ctx, &provider.CreateContainerRequest{ - Ref: rootPathRef, - }) - - if err != nil { - return err - } - - if r.Status.Code != rpc.Code_CODE_OK { - return errtypes.NewErrtypeFromStatus(r.Status) - } + return nil + case resp.Status.Code == rpc.Code_CODE_ALREADY_EXISTS: + // nothing to do in this case + return nil default: return errtypes.NewErrtypeFromStatus(resp.Status) } - - return nil } // CreateSymlink creates a symlink func (cs3 *CS3) CreateSymlink(ctx context.Context, oldname, newname string) error { + ctx, span := tracer.Start(ctx, "CreateSymlink") + defer span.End() + if _, err := cs3.ResolveSymlink(ctx, newname); err == nil { return os.ErrExist } @@ -404,6 +427,9 @@ func (cs3 *CS3) CreateSymlink(ctx context.Context, oldname, newname string) erro // ResolveSymlink resolves a symlink func (cs3 *CS3) ResolveSymlink(ctx context.Context, name string) (string, error) { + ctx, span := tracer.Start(ctx, "ResolveSymlink") + defer span.End() + b, err := cs3.SimpleDownload(ctx, name) if err != nil { if errors.Is(err, errtypes.NotFound("")) { @@ -420,12 +446,17 @@ func (cs3 *CS3) providerClient() (provider.ProviderAPIClient, error) { } func (cs3 *CS3) getAuthContext(ctx context.Context) (context.Context, error) { + // we need to start a new context to get rid of an existing x-access-token in the outgoing context + authCtx := context.Background() + authCtx, span := tracer.Start(authCtx, "getAuthContext", trace.WithLinks(trace.LinkFromContext(ctx))) + defer span.End() + client, err := pool.GetGatewayServiceClient(cs3.gatewayAddr) if err != nil { return nil, err } - authCtx := ctxpkg.ContextSetUser(context.Background(), cs3.serviceUser) + authCtx = ctxpkg.ContextSetUser(authCtx, cs3.serviceUser) authRes, err := client.Authenticate(authCtx, &gateway.AuthenticateRequest{ Type: "machine", ClientId: "userid:" + cs3.serviceUser.Id.OpaqueId, diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/walker/walker.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/walker/walker.go index ab3c4ff59..acca4869e 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/walker/walker.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/walker/walker.go @@ -27,6 +27,7 @@ import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" "github.com/cs3org/reva/v2/pkg/errtypes" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" ) // WalkFunc is the type of function called by Walk to visit each file or directory @@ -46,12 +47,12 @@ type Walker interface { } type revaWalker struct { - gtw gateway.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] } // NewWalker creates a Walker object that uses the reva gateway -func NewWalker(gtw gateway.GatewayAPIClient) Walker { - return &revaWalker{gtw: gtw} +func NewWalker(gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) Walker { + return &revaWalker{gatewaySelector: gatewaySelector} } // Walk walks the file tree rooted at root, calling fn for each file or folder in the tree, including the root. @@ -95,7 +96,11 @@ func (r *revaWalker) walkRecursively(ctx context.Context, wd string, info *provi } func (r *revaWalker) readDir(ctx context.Context, id *provider.ResourceId) ([]*provider.ResourceInfo, error) { - resp, err := r.gtw.ListContainer(ctx, &provider.ListContainerRequest{Ref: &provider.Reference{ResourceId: id, Path: "."}}) + gatewayClient, err := r.gatewaySelector.Next() + if err != nil { + return nil, err + } + resp, err := gatewayClient.ListContainer(ctx, &provider.ListContainerRequest{Ref: &provider.Reference{ResourceId: id, Path: "."}}) switch { case err != nil: @@ -108,7 +113,11 @@ func (r *revaWalker) readDir(ctx context.Context, id *provider.ResourceId) ([]*p } func (r *revaWalker) stat(ctx context.Context, id *provider.ResourceId) (*provider.ResourceInfo, error) { - resp, err := r.gtw.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: id, Path: "."}}) + gatewayClient, err := r.gatewaySelector.Next() + if err != nil { + return nil, err + } + resp, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: id, Path: "."}}) switch { case err != nil: diff --git a/vendor/github.com/cs3org/reva/v2/pkg/utils/ldap/reconnect.go b/vendor/github.com/cs3org/reva/v2/pkg/utils/ldap/reconnect.go index 13a913652..684d6d60d 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/utils/ldap/reconnect.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/utils/ldap/reconnect.go @@ -163,10 +163,10 @@ func (c *ConnWithReconnect) getConnection() (*ldap.Conn, error) { } func (c *ConnWithReconnect) ldapAutoConnect(config Config) { - l, err := c.ldapConnect(config) - if err != nil { - c.logger.Debug().Err(err).Msg("autoconnect could not get ldap Connection") - } + var ( + l *ldap.Conn + err error + ) for { select { @@ -203,7 +203,7 @@ func (c *ConnWithReconnect) ldapConnect(config Config) (*ldap.Conn, error) { } if err != nil { - c.logger.Debug().Err(err).Msg("could not get ldap Connection") + c.logger.Error().Err(err).Msg("could not get ldap Connection") return nil, err } c.logger.Debug().Msg("LDAP Connected") diff --git a/vendor/github.com/cs3org/reva/v2/pkg/utils/utils.go b/vendor/github.com/cs3org/reva/v2/pkg/utils/utils.go index 8a4fb54be..c67c50dbf 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/utils/utils.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/utils/utils.go @@ -39,8 +39,6 @@ import ( userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/v2/pkg/registry" - "github.com/cs3org/reva/v2/pkg/registry/memory" "github.com/golang/protobuf/proto" "google.golang.org/protobuf/encoding/protojson" ) @@ -49,9 +47,6 @@ var ( matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") matchEmail = regexp.MustCompile(`^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$`) - // GlobalRegistry configures a service registry globally accessible. It defaults to a memory registry. The usage of - // globals is not encouraged, and this is a workaround until the PR is out of a draft state. - GlobalRegistry registry.Registry = memory.New(map[string]interface{}{}) // ShareStorageProviderID is the provider id used by the sharestorageprovider ShareStorageProviderID = "a0ca6a90-a365-4782-871e-d44447bbc668" diff --git a/vendor/github.com/fatih/color/README.md b/vendor/github.com/fatih/color/README.md index 5152bf59b..be82827ca 100644 --- a/vendor/github.com/fatih/color/README.md +++ b/vendor/github.com/fatih/color/README.md @@ -7,7 +7,6 @@ suits you. ![Color](https://user-images.githubusercontent.com/438920/96832689-03b3e000-13f4-11eb-9803-46f4c4de3406.jpg) - ## Install ```bash @@ -124,17 +123,17 @@ fmt.Println("All text will now be bold magenta.") ``` ### Disable/Enable color - + There might be a case where you want to explicitly disable/enable color output. the `go-isatty` package will automatically disable color output for non-tty output streams (for example if the output were piped directly to `less`). The `color` package also disables color output if the [`NO_COLOR`](https://no-color.org) environment -variable is set (regardless of its value). +variable is set to a non-empty string. -`Color` has support to disable/enable colors programatically both globally and +`Color` has support to disable/enable colors programmatically both globally and for single color definitions. For example suppose you have a CLI app and a -`--no-color` bool flag. You can easily disable the color output with: +`-no-color` bool flag. You can easily disable the color output with: ```go var flagNoColor = flag.Bool("no-color", false, "Disable color output") @@ -167,11 +166,10 @@ To output color in GitHub Actions (or other CI systems that support ANSI colors) * Save/Return previous values * Evaluate fmt.Formatter interface - ## Credits - * [Fatih Arslan](https://github.com/fatih) - * Windows support via @mattn: [colorable](https://github.com/mattn/go-colorable) +* [Fatih Arslan](https://github.com/fatih) +* Windows support via @mattn: [colorable](https://github.com/mattn/go-colorable) ## License diff --git a/vendor/github.com/fatih/color/color.go b/vendor/github.com/fatih/color/color.go index 98a60f3c8..889f9e77b 100644 --- a/vendor/github.com/fatih/color/color.go +++ b/vendor/github.com/fatih/color/color.go @@ -19,10 +19,10 @@ var ( // set (regardless of its value). This is a global option and affects all // colors. For more control over each color block use the methods // DisableColor() individually. - NoColor = noColorExists() || os.Getenv("TERM") == "dumb" || + NoColor = noColorIsSet() || os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) - // Output defines the standard output of the print functions. By default + // Output defines the standard output of the print functions. By default, // os.Stdout is used. Output = colorable.NewColorableStdout() @@ -35,10 +35,9 @@ var ( colorsCacheMu sync.Mutex // protects colorsCache ) -// noColorExists returns true if the environment variable NO_COLOR exists. -func noColorExists() bool { - _, exists := os.LookupEnv("NO_COLOR") - return exists +// noColorIsSet returns true if the environment variable NO_COLOR is set to a non-empty string. +func noColorIsSet() bool { + return os.Getenv("NO_COLOR") != "" } // Color defines a custom color object which is defined by SGR parameters. @@ -120,7 +119,7 @@ func New(value ...Attribute) *Color { params: make([]Attribute, 0), } - if noColorExists() { + if noColorIsSet() { c.noColor = boolPtr(true) } @@ -152,7 +151,7 @@ func (c *Color) Set() *Color { return c } - fmt.Fprintf(Output, c.format()) + fmt.Fprint(Output, c.format()) return c } @@ -164,16 +163,21 @@ func (c *Color) unset() { Unset() } -func (c *Color) setWriter(w io.Writer) *Color { +// SetWriter is used to set the SGR sequence with the given io.Writer. This is +// a low-level function, and users should use the higher-level functions, such +// as color.Fprint, color.Print, etc. +func (c *Color) SetWriter(w io.Writer) *Color { if c.isNoColorSet() { return c } - fmt.Fprintf(w, c.format()) + fmt.Fprint(w, c.format()) return c } -func (c *Color) unsetWriter(w io.Writer) { +// UnsetWriter resets all escape attributes and clears the output with the give +// io.Writer. Usually should be called after SetWriter(). +func (c *Color) UnsetWriter(w io.Writer) { if c.isNoColorSet() { return } @@ -192,20 +196,14 @@ func (c *Color) Add(value ...Attribute) *Color { return c } -func (c *Color) prepend(value Attribute) { - c.params = append(c.params, 0) - copy(c.params[1:], c.params[0:]) - c.params[0] = value -} - // Fprint formats using the default formats for its operands and writes to w. // Spaces are added between operands when neither is a string. // It returns the number of bytes written and any write error encountered. // On Windows, users should wrap w with colorable.NewColorable() if w is of // type *os.File. func (c *Color) Fprint(w io.Writer, a ...interface{}) (n int, err error) { - c.setWriter(w) - defer c.unsetWriter(w) + c.SetWriter(w) + defer c.UnsetWriter(w) return fmt.Fprint(w, a...) } @@ -227,8 +225,8 @@ func (c *Color) Print(a ...interface{}) (n int, err error) { // On Windows, users should wrap w with colorable.NewColorable() if w is of // type *os.File. func (c *Color) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { - c.setWriter(w) - defer c.unsetWriter(w) + c.SetWriter(w) + defer c.UnsetWriter(w) return fmt.Fprintf(w, format, a...) } @@ -248,8 +246,8 @@ func (c *Color) Printf(format string, a ...interface{}) (n int, err error) { // On Windows, users should wrap w with colorable.NewColorable() if w is of // type *os.File. func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) { - c.setWriter(w) - defer c.unsetWriter(w) + c.SetWriter(w) + defer c.UnsetWriter(w) return fmt.Fprintln(w, a...) } @@ -396,7 +394,7 @@ func (c *Color) DisableColor() { } // EnableColor enables the color output. Use it in conjunction with -// DisableColor(). Otherwise this method has no side effects. +// DisableColor(). Otherwise, this method has no side effects. func (c *Color) EnableColor() { c.noColor = boolPtr(false) } diff --git a/vendor/github.com/fatih/color/doc.go b/vendor/github.com/fatih/color/doc.go index 04541de78..9491ad541 100644 --- a/vendor/github.com/fatih/color/doc.go +++ b/vendor/github.com/fatih/color/doc.go @@ -5,106 +5,105 @@ that suits you. Use simple and default helper functions with predefined foreground colors: - color.Cyan("Prints text in cyan.") + color.Cyan("Prints text in cyan.") - // a newline will be appended automatically - color.Blue("Prints %s in blue.", "text") + // a newline will be appended automatically + color.Blue("Prints %s in blue.", "text") - // More default foreground colors.. - color.Red("We have red") - color.Yellow("Yellow color too!") - color.Magenta("And many others ..") + // More default foreground colors.. + color.Red("We have red") + color.Yellow("Yellow color too!") + color.Magenta("And many others ..") - // Hi-intensity colors - color.HiGreen("Bright green color.") - color.HiBlack("Bright black means gray..") - color.HiWhite("Shiny white color!") + // Hi-intensity colors + color.HiGreen("Bright green color.") + color.HiBlack("Bright black means gray..") + color.HiWhite("Shiny white color!") -However there are times where custom color mixes are required. Below are some +However, there are times when custom color mixes are required. Below are some examples to create custom color objects and use the print functions of each separate color object. - // Create a new color object - c := color.New(color.FgCyan).Add(color.Underline) - c.Println("Prints cyan text with an underline.") + // Create a new color object + c := color.New(color.FgCyan).Add(color.Underline) + c.Println("Prints cyan text with an underline.") - // Or just add them to New() - d := color.New(color.FgCyan, color.Bold) - d.Printf("This prints bold cyan %s\n", "too!.") + // Or just add them to New() + d := color.New(color.FgCyan, color.Bold) + d.Printf("This prints bold cyan %s\n", "too!.") - // Mix up foreground and background colors, create new mixes! - red := color.New(color.FgRed) + // Mix up foreground and background colors, create new mixes! + red := color.New(color.FgRed) - boldRed := red.Add(color.Bold) - boldRed.Println("This will print text in bold red.") + boldRed := red.Add(color.Bold) + boldRed.Println("This will print text in bold red.") - whiteBackground := red.Add(color.BgWhite) - whiteBackground.Println("Red text with White background.") + whiteBackground := red.Add(color.BgWhite) + whiteBackground.Println("Red text with White background.") - // Use your own io.Writer output - color.New(color.FgBlue).Fprintln(myWriter, "blue color!") + // Use your own io.Writer output + color.New(color.FgBlue).Fprintln(myWriter, "blue color!") - blue := color.New(color.FgBlue) - blue.Fprint(myWriter, "This will print text in blue.") + blue := color.New(color.FgBlue) + blue.Fprint(myWriter, "This will print text in blue.") You can create PrintXxx functions to simplify even more: - // Create a custom print function for convenient - red := color.New(color.FgRed).PrintfFunc() - red("warning") - red("error: %s", err) + // Create a custom print function for convenient + red := color.New(color.FgRed).PrintfFunc() + red("warning") + red("error: %s", err) - // Mix up multiple attributes - notice := color.New(color.Bold, color.FgGreen).PrintlnFunc() - notice("don't forget this...") + // Mix up multiple attributes + notice := color.New(color.Bold, color.FgGreen).PrintlnFunc() + notice("don't forget this...") You can also FprintXxx functions to pass your own io.Writer: - blue := color.New(FgBlue).FprintfFunc() - blue(myWriter, "important notice: %s", stars) - - // Mix up with multiple attributes - success := color.New(color.Bold, color.FgGreen).FprintlnFunc() - success(myWriter, don't forget this...") + blue := color.New(FgBlue).FprintfFunc() + blue(myWriter, "important notice: %s", stars) + // Mix up with multiple attributes + success := color.New(color.Bold, color.FgGreen).FprintlnFunc() + success(myWriter, don't forget this...") Or create SprintXxx functions to mix strings with other non-colorized strings: - yellow := New(FgYellow).SprintFunc() - red := New(FgRed).SprintFunc() + yellow := New(FgYellow).SprintFunc() + red := New(FgRed).SprintFunc() - fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error")) + fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error")) - info := New(FgWhite, BgGreen).SprintFunc() - fmt.Printf("this %s rocks!\n", info("package")) + info := New(FgWhite, BgGreen).SprintFunc() + fmt.Printf("this %s rocks!\n", info("package")) Windows support is enabled by default. All Print functions work as intended. -However only for color.SprintXXX functions, user should use fmt.FprintXXX and +However, only for color.SprintXXX functions, user should use fmt.FprintXXX and set the output to color.Output: - fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS")) + fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS")) - info := New(FgWhite, BgGreen).SprintFunc() - fmt.Fprintf(color.Output, "this %s rocks!\n", info("package")) + info := New(FgWhite, BgGreen).SprintFunc() + fmt.Fprintf(color.Output, "this %s rocks!\n", info("package")) Using with existing code is possible. Just use the Set() method to set the standard output to the given parameters. That way a rewrite of an existing code is not required. - // Use handy standard colors. - color.Set(color.FgYellow) + // Use handy standard colors. + color.Set(color.FgYellow) - fmt.Println("Existing text will be now in Yellow") - fmt.Printf("This one %s\n", "too") + fmt.Println("Existing text will be now in Yellow") + fmt.Printf("This one %s\n", "too") - color.Unset() // don't forget to unset + color.Unset() // don't forget to unset - // You can mix up parameters - color.Set(color.FgMagenta, color.Bold) - defer color.Unset() // use it in your function + // You can mix up parameters + color.Set(color.FgMagenta, color.Bold) + defer color.Unset() // use it in your function - fmt.Println("All text will be now bold magenta.") + fmt.Println("All text will be now bold magenta.") There might be a case where you want to disable color output (for example to pipe the standard output of your app to somewhere else). `Color` has support to @@ -112,24 +111,24 @@ disable colors both globally and for single color definition. For example suppose you have a CLI app and a `--no-color` bool flag. You can easily disable the color output with: - var flagNoColor = flag.Bool("no-color", false, "Disable color output") + var flagNoColor = flag.Bool("no-color", false, "Disable color output") - if *flagNoColor { - color.NoColor = true // disables colorized output - } + if *flagNoColor { + color.NoColor = true // disables colorized output + } You can also disable the color by setting the NO_COLOR environment variable to any value. It also has support for single color definitions (local). You can disable/enable color output on the fly: - c := color.New(color.FgCyan) - c.Println("Prints cyan text") + c := color.New(color.FgCyan) + c.Println("Prints cyan text") - c.DisableColor() - c.Println("This is printed without any color") + c.DisableColor() + c.Println("This is printed without any color") - c.EnableColor() - c.Println("This prints again cyan...") + c.EnableColor() + c.Println("This prints again cyan...") */ package color diff --git a/vendor/github.com/go-micro/plugins/v4/registry/kubernetes/client/watch/body.go b/vendor/github.com/go-micro/plugins/v4/registry/kubernetes/client/watch/body.go index b93e222dc..4570a3e8d 100644 --- a/vendor/github.com/go-micro/plugins/v4/registry/kubernetes/client/watch/body.go +++ b/vendor/github.com/go-micro/plugins/v4/registry/kubernetes/client/watch/body.go @@ -32,7 +32,6 @@ func (wr *bodyWatcher) Stop() { return default: wr.stop() - close(wr.results) } } @@ -51,7 +50,7 @@ func (wr *bodyWatcher) stream() { go func() { //nolint:errcheck defer wr.res.Body.Close() - + out: for { // Read a line b, err := reader.ReadBytes('\n') @@ -69,9 +68,15 @@ func (wr *bodyWatcher) stream() { if err := json.Unmarshal(b, &event); err != nil { continue } - wr.results <- event + + select { + case <-wr.ctx.Done(): + break out + case wr.results <- event: + } } + close(wr.results) // stop the watcher wr.Stop() }() diff --git a/vendor/github.com/go-micro/plugins/v4/registry/kubernetes/watcher.go b/vendor/github.com/go-micro/plugins/v4/registry/kubernetes/watcher.go index 27e594bbe..b90180845 100644 --- a/vendor/github.com/go-micro/plugins/v4/registry/kubernetes/watcher.go +++ b/vendor/github.com/go-micro/plugins/v4/registry/kubernetes/watcher.go @@ -24,6 +24,7 @@ type k8sWatcher struct { sync.RWMutex pods map[string]*client.Pod + sync.Once } // build a cache of pods when the watcher starts. @@ -164,7 +165,9 @@ func (k *k8sWatcher) Stop() { case <-k.next: return default: - close(k.next) + k.Do(func() { + close(k.next) + }) } } diff --git a/vendor/github.com/goccy/go-yaml/.codecov.yml b/vendor/github.com/goccy/go-yaml/.codecov.yml new file mode 100644 index 000000000..8364eea0b --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/.codecov.yml @@ -0,0 +1,31 @@ +codecov: + require_ci_to_pass: yes + +coverage: + precision: 2 + round: down + range: "70...100" + + status: + project: + default: + target: 75% + threshold: 2% + patch: off + changes: no + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no + +comment: + layout: "header,diff" + behavior: default + require_changes: no + +ignore: + - ast diff --git a/vendor/github.com/goccy/go-yaml/CHANGELOG.md b/vendor/github.com/goccy/go-yaml/CHANGELOG.md new file mode 100644 index 000000000..2887563f3 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/CHANGELOG.md @@ -0,0 +1,163 @@ +# 1.10.1 - 2023-03-28 + +### Features + +- Quote YAML 1.1 bools at encoding time for compatibility with other legacy parsers +- Add support of 32-bit architecture + +### Fix bugs + +- Don't trim all space characters in block style sequence +- Support strings starting with `@` + +# 1.10.0 - 2023-03-01 + +### Fix bugs + +Reversible conversion of comments was not working in various cases, which has been corrected. +**Breaking Change** exists in the comment map interface. However, if you are dealing with CommentMap directly, there is no problem. + + +# 1.9.8 - 2022-12-19 + +### Fix feature + +- Append new line at the end of file ( #329 ) + +### Fix bugs + +- Fix custom marshaler ( #333, #334 ) +- Fix behavior when struct fields conflicted( #335 ) +- Fix position calculation for literal, folded and raw folded strings ( #330 ) + +# 1.9.7 - 2022-12-03 + +### Fix bugs + +- Fix handling of quoted map key ( #328 ) +- Fix resusing process of scanning context ( #322 ) + +## v1.9.6 - 2022-10-26 + +### New Features + +- Introduce MapKeyNode interface to limit node types for map key ( #312 ) + +### Fix bugs + +- Quote strings with special characters in flow mode ( #270 ) +- typeError implements PrettyPrinter interface ( #280 ) +- Fix incorrect const type ( #284 ) +- Fix large literals type inference on 32 bits ( #293 ) +- Fix UTF-8 characters ( #294 ) +- Fix decoding of unknown aliases ( #317 ) +- Fix stream encoder for insert a separator between each encoded document ( #318 ) + +### Update + +- Update golang.org/x/sys ( #289 ) +- Update Go version in CI ( #295 ) +- Add test cases for missing keys to struct literals ( #300 ) + +## v1.9.5 - 2022-01-12 + +### New Features + +* Add UseSingleQuote option ( #265 ) + +### Fix bugs + +* Preserve defaults while decoding nested structs ( #260 ) +* Fix minor typo in decodeInit error ( #264 ) +* Handle empty sequence entries ( #275 ) +* Fix encoding of sequence with multiline string ( #276 ) +* Fix encoding of BytesMarshaler type ( #277 ) +* Fix indentState logic for multi-line value ( #278 ) + +## v1.9.4 - 2021-10-12 + +### Fix bugs + +* Keep prev/next reference between tokens containing comments when filtering comment tokens ( #257 ) +* Supports escaping reserved keywords in PathBuilder ( #258 ) + +## v1.9.3 - 2021-09-07 + +### New Features + +* Support encoding and decoding `time.Duration` fields ( #246 ) +* Allow reserved characters for key name in YAMLPath ( #251 ) +* Support getting YAMLPath from ast.Node ( #252 ) +* Support CommentToMap option ( #253 ) + +### Fix bugs + +* Fix encoding nested sequences with `yaml.IndentSequence` ( #241 ) +* Fix error reporting on inline structs in strict mode ( #244, #245 ) +* Fix encoding of large floats ( #247 ) + +### Improve workflow + +* Migrate CI from CircleCI to GitHub Action ( #249 ) +* Add workflow for ycat ( #250 ) + +## v1.9.2 - 2021-07-26 + +### Support WithComment option ( #238 ) + +`yaml.WithComment` is a option for encoding with comment. +The position where you want to add a comment is represented by YAMLPath, and it is the key of `yaml.CommentMap`. +Also, you can select `Head` comment or `Line` comment as the comment type. + +## v1.9.1 - 2021-07-20 + +### Fix DecodeFromNode ( #237 ) + +- Fix YAML handling where anchor exists + +## v1.9.0 - 2021-07-19 + +### New features + +- Support encoding of comment node ( #233 ) +- Support `yaml.NodeToValue(ast.Node, interface{}, ...DecodeOption) error` ( #236 ) + - Can convert a AST node to a value directly + +### Fix decoder for comment + +- Fix parsing of literal with comment ( #234 ) + +### Rename API ( #235 ) + +- Rename `MarshalWithContext` to `MarshalContext` +- Rename `UnmarshalWithContext` to `UnmarshalContext` + +## v1.8.10 - 2021-07-02 + +### Fixed bugs + +- Fix searching anchor by alias name ( #212 ) +- Fixing Issue 186, scanner should account for newline characters when processing multi-line text. Without this source annotations line/column number (for this and all subsequent tokens) is inconsistent with plain text editors. e.g. https://github.com/goccy/go-yaml/issues/186. This addresses the issue specifically for single and double quote text only. ( #210 ) +- Add error for unterminated flow mapping node ( #213 ) +- Handle missing required field validation ( #221 ) +- Nicely format unexpected node type errors ( #229 ) +- Support to encode map which has defined type key ( #231 ) + +### New features + +- Support sequence indentation by EncodeOption ( #232 ) + +## v1.8.9 - 2021-03-01 + +### Fixed bugs + +- Fix origin buffer for DocumentHeader and DocumentEnd and Directive +- Fix origin buffer for anchor value +- Fix syntax error about map value +- Fix parsing MergeKey ('<<') characters +- Fix encoding of float value +- Fix incorrect column annotation when single or double quotes are used + +### New features + +- Support to encode/decode of ast.Node directly diff --git a/vendor/github.com/goccy/go-yaml/LICENSE b/vendor/github.com/goccy/go-yaml/LICENSE new file mode 100644 index 000000000..04485ce65 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Masaaki Goshima + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/goccy/go-yaml/Makefile b/vendor/github.com/goccy/go-yaml/Makefile new file mode 100644 index 000000000..1b1d92397 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/Makefile @@ -0,0 +1,19 @@ +.PHONY: test +test: + go test -v -race ./... + +.PHONY: simple-test +simple-test: + go test -v ./... + +.PHONY: cover +cover: + go test -coverprofile=cover.out ./... + +.PHONY: cover-html +cover-html: cover + go tool cover -html=cover.out + +.PHONY: ycat/build +ycat/build: + go build -o ycat ./cmd/ycat diff --git a/vendor/github.com/goccy/go-yaml/README.md b/vendor/github.com/goccy/go-yaml/README.md new file mode 100644 index 000000000..945234917 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/README.md @@ -0,0 +1,370 @@ +# YAML support for the Go language + +[![PkgGoDev](https://pkg.go.dev/badge/github.com/goccy/go-yaml)](https://pkg.go.dev/github.com/goccy/go-yaml) +![Go](https://github.com/goccy/go-yaml/workflows/Go/badge.svg) +[![codecov](https://codecov.io/gh/goccy/go-yaml/branch/master/graph/badge.svg)](https://codecov.io/gh/goccy/go-yaml) +[![Go Report Card](https://goreportcard.com/badge/github.com/goccy/go-yaml)](https://goreportcard.com/report/github.com/goccy/go-yaml) + + + +# Why a new library? + +As of this writing, there already exists a de facto standard library for YAML processing for Go: [https://github.com/go-yaml/yaml](https://github.com/go-yaml/yaml). However we feel that some features are lacking, namely: + +- Pretty format for error notifications +- Direct manipulation of YAML abstract syntax tree +- Support for `Anchor` and `Alias` when marshaling +- Allow referencing elements declared in another file via anchors + +# Features + +- Pretty format for error notifications +- Supports `Scanner` or `Lexer` or `Parser` as public API +- Supports `Anchor` and `Alias` to Marshaler +- Allow referencing elements declared in another file via anchors +- Extract value or AST by YAMLPath ( YAMLPath is like a JSONPath ) + +# Installation + +```sh +go get -u github.com/goccy/go-yaml +``` + +# Synopsis + +## 1. Simple Encode/Decode + +Has an interface like `go-yaml/yaml` using `reflect` + +```go +var v struct { + A int + B string +} +v.A = 1 +v.B = "hello" +bytes, err := yaml.Marshal(v) +if err != nil { + //... +} +fmt.Println(string(bytes)) // "a: 1\nb: hello\n" +``` + +```go + yml := ` +%YAML 1.2 +--- +a: 1 +b: c +` +var v struct { + A int + B string +} +if err := yaml.Unmarshal([]byte(yml), &v); err != nil { + //... +} +``` + +To control marshal/unmarshal behavior, you can use the `yaml` tag. + +```go + yml := `--- +foo: 1 +bar: c +` +var v struct { + A int `yaml:"foo"` + B string `yaml:"bar"` +} +if err := yaml.Unmarshal([]byte(yml), &v); err != nil { + //... +} +``` + +For convenience, we also accept the `json` tag. Note that not all options from +the `json` tag will have significance when parsing YAML documents. If both +tags exist, `yaml` tag will take precedence. + +```go + yml := `--- +foo: 1 +bar: c +` +var v struct { + A int `json:"foo"` + B string `json:"bar"` +} +if err := yaml.Unmarshal([]byte(yml), &v); err != nil { + //... +} +``` + +For custom marshal/unmarshaling, implement either `Bytes` or `Interface` variant of marshaler/unmarshaler. The difference is that while `BytesMarshaler`/`BytesUnmarshaler` behaves like [`encoding/json`](https://pkg.go.dev/encoding/json) and `InterfaceMarshaler`/`InterfaceUnmarshaler` behaves like [`gopkg.in/yaml.v2`](https://pkg.go.dev/gopkg.in/yaml.v2). + +Semantically both are the same, but they differ in performance. Because indentation matters in YAML, you cannot simply accept a valid YAML fragment from a Marshaler, and expect it to work when it is attached to the parent container's serialized form. Therefore when we receive use the `BytesMarshaler`, which returns `[]byte`, we must decode it once to figure out how to make it work in the given context. If you use the `InterfaceMarshaler`, we can skip the decoding. + +If you are repeatedly marshaling complex objects, the latter is always better +performance wise. But if you are, for example, just providing a choice between +a config file format that is read only once, the former is probably easier to +code. + +## 2. Reference elements declared in another file + +`testdata` directory contains `anchor.yml` file: + +```shell +├── testdata +   └── anchor.yml +``` + +And `anchor.yml` is defined as follows: + +```yaml +a: &a + b: 1 + c: hello +``` + +Then, if `yaml.ReferenceDirs("testdata")` option is passed to `yaml.Decoder`, + `Decoder` tries to find the anchor definition from YAML files the under `testdata` directory. + +```go +buf := bytes.NewBufferString("a: *a\n") +dec := yaml.NewDecoder(buf, yaml.ReferenceDirs("testdata")) +var v struct { + A struct { + B int + C string + } +} +if err := dec.Decode(&v); err != nil { + //... +} +fmt.Printf("%+v\n", v) // {A:{B:1 C:hello}} +``` + +## 3. Encode with `Anchor` and `Alias` + +### 3.1. Explicitly declared `Anchor` name and `Alias` name + +If you want to use `anchor` or `alias`, you can define it as a struct tag. + +```go +type T struct { + A int + B string +} +var v struct { + C *T `yaml:"c,anchor=x"` + D *T `yaml:"d,alias=x"` +} +v.C = &T{A: 1, B: "hello"} +v.D = v.C +bytes, err := yaml.Marshal(v) +if err != nil { + panic(err) +} +fmt.Println(string(bytes)) +/* +c: &x + a: 1 + b: hello +d: *x +*/ +``` + +### 3.2. Implicitly declared `Anchor` and `Alias` names + +If you do not explicitly declare the anchor name, the default behavior is to +use the equivalent of `strings.ToLower($FieldName)` as the name of the anchor. + +If you do not explicitly declare the alias name AND the value is a pointer +to another element, we look up the anchor name by finding out which anchor +field the value is assigned to by looking up its pointer address. + +```go +type T struct { + I int + S string +} +var v struct { + A *T `yaml:"a,anchor"` + B *T `yaml:"b,anchor"` + C *T `yaml:"c,alias"` + D *T `yaml:"d,alias"` +} +v.A = &T{I: 1, S: "hello"} +v.B = &T{I: 2, S: "world"} +v.C = v.A // C has same pointer address to A +v.D = v.B // D has same pointer address to B +bytes, err := yaml.Marshal(v) +if err != nil { + //... +} +fmt.Println(string(bytes)) +/* +a: &a + i: 1 + s: hello +b: &b + i: 2 + s: world +c: *a +d: *b +*/ +``` + +### 3.3 MergeKey and Alias + +Merge key and alias ( `<<: *alias` ) can be used by embedding a structure with the `inline,alias` tag. + +```go +type Person struct { + *Person `yaml:",omitempty,inline,alias"` // embed Person type for default value + Name string `yaml:",omitempty"` + Age int `yaml:",omitempty"` +} +defaultPerson := &Person{ + Name: "John Smith", + Age: 20, +} +people := []*Person{ + { + Person: defaultPerson, // assign default value + Name: "Ken", // override Name property + Age: 10, // override Age property + }, + { + Person: defaultPerson, // assign default value only + }, +} +var doc struct { + Default *Person `yaml:"default,anchor"` + People []*Person `yaml:"people"` +} +doc.Default = defaultPerson +doc.People = people +bytes, err := yaml.Marshal(doc) +if err != nil { + //... +} +fmt.Println(string(bytes)) +/* +default: &default + name: John Smith + age: 20 +people: +- <<: *default + name: Ken + age: 10 +- <<: *default +*/ +``` + +## 4. Pretty Formatted Errors + +Error values produced during parsing have two extra features over regular +error values. + +First, by default, they contain extra information on the location of the error +from the source YAML document, to make it easier to find the error location. + +Second, the error messages can optionally be colorized. + +If you would like to control exactly how the output looks like, consider +using `yaml.FormatError`, which accepts two boolean values to +control turning these features on or off. + + + +## 5. Use YAMLPath + +```go +yml := ` +store: + book: + - author: john + price: 10 + - author: ken + price: 12 + bicycle: + color: red + price: 19.95 +` +path, err := yaml.PathString("$.store.book[*].author") +if err != nil { + //... +} +var authors []string +if err := path.Read(strings.NewReader(yml), &authors); err != nil { + //... +} +fmt.Println(authors) +// [john ken] +``` + +### 5.1 Print customized error with YAML source code + +```go +package main + +import ( + "fmt" + + "github.com/goccy/go-yaml" +) + +func main() { + yml := ` +a: 1 +b: "hello" +` + var v struct { + A int + B string + } + if err := yaml.Unmarshal([]byte(yml), &v); err != nil { + panic(err) + } + if v.A != 2 { + // output error with YAML source + path, err := yaml.PathString("$.a") + if err != nil { + panic(err) + } + source, err := path.AnnotateSource([]byte(yml), true) + if err != nil { + panic(err) + } + fmt.Printf("a value expected 2 but actual %d:\n%s\n", v.A, string(source)) + } +} +``` + +output result is the following: + + + + +# Tools + +## ycat + +print yaml file with color + +ycat + +### Installation + +```sh +go install github.com/goccy/go-yaml/cmd/ycat@latest +``` + +# Looking for Sponsors + +I'm looking for sponsors this library. This library is being developed as a personal project in my spare time. If you want a quick response or problem resolution when using this library in your project, please register as a [sponsor](https://github.com/sponsors/goccy). I will cooperate as much as possible. Of course, this library is developed as an MIT license, so you can use it freely for free. + +# License + +MIT diff --git a/vendor/github.com/goccy/go-yaml/ast/ast.go b/vendor/github.com/goccy/go-yaml/ast/ast.go new file mode 100644 index 000000000..f535a2465 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/ast/ast.go @@ -0,0 +1,2117 @@ +package ast + +import ( + "fmt" + "io" + "math" + "strconv" + "strings" + + "github.com/goccy/go-yaml/token" + "golang.org/x/xerrors" +) + +var ( + ErrInvalidTokenType = xerrors.New("invalid token type") + ErrInvalidAnchorName = xerrors.New("invalid anchor name") + ErrInvalidAliasName = xerrors.New("invalid alias name") +) + +// NodeType type identifier of node +type NodeType int + +const ( + // UnknownNodeType type identifier for default + UnknownNodeType NodeType = iota + // DocumentType type identifier for document node + DocumentType + // NullType type identifier for null node + NullType + // BoolType type identifier for boolean node + BoolType + // IntegerType type identifier for integer node + IntegerType + // FloatType type identifier for float node + FloatType + // InfinityType type identifier for infinity node + InfinityType + // NanType type identifier for nan node + NanType + // StringType type identifier for string node + StringType + // MergeKeyType type identifier for merge key node + MergeKeyType + // LiteralType type identifier for literal node + LiteralType + // MappingType type identifier for mapping node + MappingType + // MappingKeyType type identifier for mapping key node + MappingKeyType + // MappingValueType type identifier for mapping value node + MappingValueType + // SequenceType type identifier for sequence node + SequenceType + // AnchorType type identifier for anchor node + AnchorType + // AliasType type identifier for alias node + AliasType + // DirectiveType type identifier for directive node + DirectiveType + // TagType type identifier for tag node + TagType + // CommentType type identifier for comment node + CommentType + // CommentGroupType type identifier for comment group node + CommentGroupType +) + +// String node type identifier to text +func (t NodeType) String() string { + switch t { + case UnknownNodeType: + return "UnknownNode" + case DocumentType: + return "Document" + case NullType: + return "Null" + case BoolType: + return "Bool" + case IntegerType: + return "Integer" + case FloatType: + return "Float" + case InfinityType: + return "Infinity" + case NanType: + return "Nan" + case StringType: + return "String" + case MergeKeyType: + return "MergeKey" + case LiteralType: + return "Literal" + case MappingType: + return "Mapping" + case MappingKeyType: + return "MappingKey" + case MappingValueType: + return "MappingValue" + case SequenceType: + return "Sequence" + case AnchorType: + return "Anchor" + case AliasType: + return "Alias" + case DirectiveType: + return "Directive" + case TagType: + return "Tag" + case CommentType: + return "Comment" + case CommentGroupType: + return "CommentGroup" + } + return "" +} + +// String node type identifier to YAML Structure name +// based on https://yaml.org/spec/1.2/spec.html +func (t NodeType) YAMLName() string { + switch t { + case UnknownNodeType: + return "unknown" + case DocumentType: + return "document" + case NullType: + return "null" + case BoolType: + return "boolean" + case IntegerType: + return "int" + case FloatType: + return "float" + case InfinityType: + return "inf" + case NanType: + return "nan" + case StringType: + return "string" + case MergeKeyType: + return "merge key" + case LiteralType: + return "scalar" + case MappingType: + return "mapping" + case MappingKeyType: + return "key" + case MappingValueType: + return "value" + case SequenceType: + return "sequence" + case AnchorType: + return "anchor" + case AliasType: + return "alias" + case DirectiveType: + return "directive" + case TagType: + return "tag" + case CommentType: + return "comment" + case CommentGroupType: + return "comment" + } + return "" +} + +// Node type of node +type Node interface { + io.Reader + // String node to text + String() string + // GetToken returns token instance + GetToken() *token.Token + // Type returns type of node + Type() NodeType + // AddColumn add column number to child nodes recursively + AddColumn(int) + // SetComment set comment token to node + SetComment(*CommentGroupNode) error + // Comment returns comment token instance + GetComment() *CommentGroupNode + // GetPath returns YAMLPath for the current node + GetPath() string + // SetPath set YAMLPath for the current node + SetPath(string) + // MarshalYAML + MarshalYAML() ([]byte, error) + // already read length + readLen() int + // append read length + addReadLen(int) + // clean read length + clearLen() +} + +// MapKeyNode type for map key node +type MapKeyNode interface { + Node + // String node to text without comment + stringWithoutComment() string +} + +// ScalarNode type for scalar node +type ScalarNode interface { + MapKeyNode + GetValue() interface{} +} + +type BaseNode struct { + Path string + Comment *CommentGroupNode + read int +} + +func addCommentString(base string, node *CommentGroupNode) string { + return fmt.Sprintf("%s %s", base, node.String()) +} + +func (n *BaseNode) readLen() int { + return n.read +} + +func (n *BaseNode) clearLen() { + n.read = 0 +} + +func (n *BaseNode) addReadLen(len int) { + n.read += len +} + +// GetPath returns YAMLPath for the current node. +func (n *BaseNode) GetPath() string { + if n == nil { + return "" + } + return n.Path +} + +// SetPath set YAMLPath for the current node. +func (n *BaseNode) SetPath(path string) { + if n == nil { + return + } + n.Path = path +} + +// GetComment returns comment token instance +func (n *BaseNode) GetComment() *CommentGroupNode { + return n.Comment +} + +// SetComment set comment token +func (n *BaseNode) SetComment(node *CommentGroupNode) error { + n.Comment = node + return nil +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func readNode(p []byte, node Node) (int, error) { + s := node.String() + readLen := node.readLen() + remain := len(s) - readLen + if remain == 0 { + node.clearLen() + return 0, io.EOF + } + size := min(remain, len(p)) + for idx, b := range []byte(s[readLen : readLen+size]) { + p[idx] = byte(b) + } + node.addReadLen(size) + return size, nil +} + +// Null create node for null value +func Null(tk *token.Token) *NullNode { + return &NullNode{ + BaseNode: &BaseNode{}, + Token: tk, + } +} + +// Bool create node for boolean value +func Bool(tk *token.Token) *BoolNode { + b, _ := strconv.ParseBool(tk.Value) + return &BoolNode{ + BaseNode: &BaseNode{}, + Token: tk, + Value: b, + } +} + +// Integer create node for integer value +func Integer(tk *token.Token) *IntegerNode { + value := removeUnderScoreFromNumber(tk.Value) + switch tk.Type { + case token.BinaryIntegerType: + // skip two characters because binary token starts with '0b' + skipCharacterNum := 2 + negativePrefix := "" + if value[0] == '-' { + skipCharacterNum++ + negativePrefix = "-" + } + if len(negativePrefix) > 0 { + i, _ := strconv.ParseInt(negativePrefix+value[skipCharacterNum:], 2, 64) + return &IntegerNode{ + BaseNode: &BaseNode{}, + Token: tk, + Value: i, + } + } + i, _ := strconv.ParseUint(negativePrefix+value[skipCharacterNum:], 2, 64) + return &IntegerNode{ + BaseNode: &BaseNode{}, + Token: tk, + Value: i, + } + case token.OctetIntegerType: + // octet token starts with '0o' or '-0o' or '0' or '-0' + skipCharacterNum := 1 + negativePrefix := "" + if value[0] == '-' { + skipCharacterNum++ + if len(value) > 2 && value[2] == 'o' { + skipCharacterNum++ + } + negativePrefix = "-" + } else { + if value[1] == 'o' { + skipCharacterNum++ + } + } + if len(negativePrefix) > 0 { + i, _ := strconv.ParseInt(negativePrefix+value[skipCharacterNum:], 8, 64) + return &IntegerNode{ + BaseNode: &BaseNode{}, + Token: tk, + Value: i, + } + } + i, _ := strconv.ParseUint(value[skipCharacterNum:], 8, 64) + return &IntegerNode{ + BaseNode: &BaseNode{}, + Token: tk, + Value: i, + } + case token.HexIntegerType: + // hex token starts with '0x' or '-0x' + skipCharacterNum := 2 + negativePrefix := "" + if value[0] == '-' { + skipCharacterNum++ + negativePrefix = "-" + } + if len(negativePrefix) > 0 { + i, _ := strconv.ParseInt(negativePrefix+value[skipCharacterNum:], 16, 64) + return &IntegerNode{ + BaseNode: &BaseNode{}, + Token: tk, + Value: i, + } + } + i, _ := strconv.ParseUint(value[skipCharacterNum:], 16, 64) + return &IntegerNode{ + BaseNode: &BaseNode{}, + Token: tk, + Value: i, + } + } + if value[0] == '-' || value[0] == '+' { + i, _ := strconv.ParseInt(value, 10, 64) + return &IntegerNode{ + BaseNode: &BaseNode{}, + Token: tk, + Value: i, + } + } + i, _ := strconv.ParseUint(value, 10, 64) + return &IntegerNode{ + BaseNode: &BaseNode{}, + Token: tk, + Value: i, + } +} + +// Float create node for float value +func Float(tk *token.Token) *FloatNode { + f, _ := strconv.ParseFloat(removeUnderScoreFromNumber(tk.Value), 64) + return &FloatNode{ + BaseNode: &BaseNode{}, + Token: tk, + Value: f, + } +} + +// Infinity create node for .inf or -.inf value +func Infinity(tk *token.Token) *InfinityNode { + node := &InfinityNode{ + BaseNode: &BaseNode{}, + Token: tk, + } + switch tk.Value { + case ".inf", ".Inf", ".INF": + node.Value = math.Inf(0) + case "-.inf", "-.Inf", "-.INF": + node.Value = math.Inf(-1) + } + return node +} + +// Nan create node for .nan value +func Nan(tk *token.Token) *NanNode { + return &NanNode{ + BaseNode: &BaseNode{}, + Token: tk, + } +} + +// String create node for string value +func String(tk *token.Token) *StringNode { + return &StringNode{ + BaseNode: &BaseNode{}, + Token: tk, + Value: tk.Value, + } +} + +// Comment create node for comment +func Comment(tk *token.Token) *CommentNode { + return &CommentNode{ + BaseNode: &BaseNode{}, + Token: tk, + } +} + +func CommentGroup(comments []*token.Token) *CommentGroupNode { + nodes := []*CommentNode{} + for _, comment := range comments { + nodes = append(nodes, Comment(comment)) + } + return &CommentGroupNode{ + BaseNode: &BaseNode{}, + Comments: nodes, + } +} + +// MergeKey create node for merge key ( << ) +func MergeKey(tk *token.Token) *MergeKeyNode { + return &MergeKeyNode{ + BaseNode: &BaseNode{}, + Token: tk, + } +} + +// Mapping create node for map +func Mapping(tk *token.Token, isFlowStyle bool, values ...*MappingValueNode) *MappingNode { + node := &MappingNode{ + BaseNode: &BaseNode{}, + Start: tk, + IsFlowStyle: isFlowStyle, + Values: []*MappingValueNode{}, + } + node.Values = append(node.Values, values...) + return node +} + +// MappingValue create node for mapping value +func MappingValue(tk *token.Token, key MapKeyNode, value Node) *MappingValueNode { + return &MappingValueNode{ + BaseNode: &BaseNode{}, + Start: tk, + Key: key, + Value: value, + } +} + +// MappingKey create node for map key ( '?' ). +func MappingKey(tk *token.Token) *MappingKeyNode { + return &MappingKeyNode{ + BaseNode: &BaseNode{}, + Start: tk, + } +} + +// Sequence create node for sequence +func Sequence(tk *token.Token, isFlowStyle bool) *SequenceNode { + return &SequenceNode{ + BaseNode: &BaseNode{}, + Start: tk, + IsFlowStyle: isFlowStyle, + Values: []Node{}, + } +} + +func Anchor(tk *token.Token) *AnchorNode { + return &AnchorNode{ + BaseNode: &BaseNode{}, + Start: tk, + } +} + +func Alias(tk *token.Token) *AliasNode { + return &AliasNode{ + BaseNode: &BaseNode{}, + Start: tk, + } +} + +func Document(tk *token.Token, body Node) *DocumentNode { + return &DocumentNode{ + BaseNode: &BaseNode{}, + Start: tk, + Body: body, + } +} + +func Directive(tk *token.Token) *DirectiveNode { + return &DirectiveNode{ + BaseNode: &BaseNode{}, + Start: tk, + } +} + +func Literal(tk *token.Token) *LiteralNode { + return &LiteralNode{ + BaseNode: &BaseNode{}, + Start: tk, + } +} + +func Tag(tk *token.Token) *TagNode { + return &TagNode{ + BaseNode: &BaseNode{}, + Start: tk, + } +} + +// File contains all documents in YAML file +type File struct { + Name string + Docs []*DocumentNode +} + +// Read implements (io.Reader).Read +func (f *File) Read(p []byte) (int, error) { + for _, doc := range f.Docs { + n, err := doc.Read(p) + if err == io.EOF { + continue + } + return n, nil + } + return 0, io.EOF +} + +// String all documents to text +func (f *File) String() string { + docs := []string{} + for _, doc := range f.Docs { + docs = append(docs, doc.String()) + } + if len(docs) > 0 { + return strings.Join(docs, "\n") + "\n" + } else { + return "" + } +} + +// DocumentNode type of Document +type DocumentNode struct { + *BaseNode + Start *token.Token // position of DocumentHeader ( `---` ) + End *token.Token // position of DocumentEnd ( `...` ) + Body Node +} + +// Read implements (io.Reader).Read +func (d *DocumentNode) Read(p []byte) (int, error) { + return readNode(p, d) +} + +// Type returns DocumentNodeType +func (d *DocumentNode) Type() NodeType { return DocumentType } + +// GetToken returns token instance +func (d *DocumentNode) GetToken() *token.Token { + return d.Body.GetToken() +} + +// AddColumn add column number to child nodes recursively +func (d *DocumentNode) AddColumn(col int) { + if d.Body != nil { + d.Body.AddColumn(col) + } +} + +// String document to text +func (d *DocumentNode) String() string { + doc := []string{} + if d.Start != nil { + doc = append(doc, d.Start.Value) + } + doc = append(doc, d.Body.String()) + if d.End != nil { + doc = append(doc, d.End.Value) + } + return strings.Join(doc, "\n") +} + +// MarshalYAML encodes to a YAML text +func (d *DocumentNode) MarshalYAML() ([]byte, error) { + return []byte(d.String()), nil +} + +func removeUnderScoreFromNumber(num string) string { + return strings.ReplaceAll(num, "_", "") +} + +// NullNode type of null node +type NullNode struct { + *BaseNode + Token *token.Token +} + +// Read implements (io.Reader).Read +func (n *NullNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns NullType +func (n *NullNode) Type() NodeType { return NullType } + +// GetToken returns token instance +func (n *NullNode) GetToken() *token.Token { + return n.Token +} + +// AddColumn add column number to child nodes recursively +func (n *NullNode) AddColumn(col int) { + n.Token.AddColumn(col) +} + +// GetValue returns nil value +func (n *NullNode) GetValue() interface{} { + return nil +} + +// String returns `null` text +func (n *NullNode) String() string { + if n.Comment != nil { + return addCommentString("null", n.Comment) + } + return n.stringWithoutComment() +} + +func (n *NullNode) stringWithoutComment() string { + return "null" +} + +// MarshalYAML encodes to a YAML text +func (n *NullNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// IntegerNode type of integer node +type IntegerNode struct { + *BaseNode + Token *token.Token + Value interface{} // int64 or uint64 value +} + +// Read implements (io.Reader).Read +func (n *IntegerNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns IntegerType +func (n *IntegerNode) Type() NodeType { return IntegerType } + +// GetToken returns token instance +func (n *IntegerNode) GetToken() *token.Token { + return n.Token +} + +// AddColumn add column number to child nodes recursively +func (n *IntegerNode) AddColumn(col int) { + n.Token.AddColumn(col) +} + +// GetValue returns int64 value +func (n *IntegerNode) GetValue() interface{} { + return n.Value +} + +// String int64 to text +func (n *IntegerNode) String() string { + if n.Comment != nil { + return addCommentString(n.Token.Value, n.Comment) + } + return n.stringWithoutComment() +} + +func (n *IntegerNode) stringWithoutComment() string { + return n.Token.Value +} + +// MarshalYAML encodes to a YAML text +func (n *IntegerNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// FloatNode type of float node +type FloatNode struct { + *BaseNode + Token *token.Token + Precision int + Value float64 +} + +// Read implements (io.Reader).Read +func (n *FloatNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns FloatType +func (n *FloatNode) Type() NodeType { return FloatType } + +// GetToken returns token instance +func (n *FloatNode) GetToken() *token.Token { + return n.Token +} + +// AddColumn add column number to child nodes recursively +func (n *FloatNode) AddColumn(col int) { + n.Token.AddColumn(col) +} + +// GetValue returns float64 value +func (n *FloatNode) GetValue() interface{} { + return n.Value +} + +// String float64 to text +func (n *FloatNode) String() string { + if n.Comment != nil { + return addCommentString(n.Token.Value, n.Comment) + } + return n.stringWithoutComment() +} + +func (n *FloatNode) stringWithoutComment() string { + return n.Token.Value +} + +// MarshalYAML encodes to a YAML text +func (n *FloatNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// StringNode type of string node +type StringNode struct { + *BaseNode + Token *token.Token + Value string +} + +// Read implements (io.Reader).Read +func (n *StringNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns StringType +func (n *StringNode) Type() NodeType { return StringType } + +// GetToken returns token instance +func (n *StringNode) GetToken() *token.Token { + return n.Token +} + +// AddColumn add column number to child nodes recursively +func (n *StringNode) AddColumn(col int) { + n.Token.AddColumn(col) +} + +// GetValue returns string value +func (n *StringNode) GetValue() interface{} { + return n.Value +} + +// escapeSingleQuote escapes s to a single quoted scalar. +// https://yaml.org/spec/1.2.2/#732-single-quoted-style +func escapeSingleQuote(s string) string { + var sb strings.Builder + growLen := len(s) + // s includes also one ' from the doubled pair + 2 + // opening and closing ' + strings.Count(s, "'") // ' added by ReplaceAll + sb.Grow(growLen) + sb.WriteString("'") + sb.WriteString(strings.ReplaceAll(s, "'", "''")) + sb.WriteString("'") + return sb.String() +} + +// String string value to text with quote or literal header if required +func (n *StringNode) String() string { + switch n.Token.Type { + case token.SingleQuoteType: + quoted := escapeSingleQuote(n.Value) + if n.Comment != nil { + return addCommentString(quoted, n.Comment) + } + return quoted + case token.DoubleQuoteType: + quoted := strconv.Quote(n.Value) + if n.Comment != nil { + return addCommentString(quoted, n.Comment) + } + return quoted + } + + lbc := token.DetectLineBreakCharacter(n.Value) + if strings.Contains(n.Value, lbc) { + // This block assumes that the line breaks in this inside scalar content and the Outside scalar content are the same. + // It works mostly, but inconsistencies occur if line break characters are mixed. + header := token.LiteralBlockHeader(n.Value) + space := strings.Repeat(" ", n.Token.Position.Column-1) + values := []string{} + for _, v := range strings.Split(n.Value, lbc) { + values = append(values, fmt.Sprintf("%s %s", space, v)) + } + block := strings.TrimSuffix(strings.TrimSuffix(strings.Join(values, lbc), fmt.Sprintf("%s %s", lbc, space)), fmt.Sprintf(" %s", space)) + return fmt.Sprintf("%s%s%s", header, lbc, block) + } else if len(n.Value) > 0 && (n.Value[0] == '{' || n.Value[0] == '[') { + return fmt.Sprintf(`'%s'`, n.Value) + } + if n.Comment != nil { + return addCommentString(n.Value, n.Comment) + } + return n.Value +} + +func (n *StringNode) stringWithoutComment() string { + switch n.Token.Type { + case token.SingleQuoteType: + quoted := fmt.Sprintf(`'%s'`, n.Value) + return quoted + case token.DoubleQuoteType: + quoted := strconv.Quote(n.Value) + return quoted + } + + lbc := token.DetectLineBreakCharacter(n.Value) + if strings.Contains(n.Value, lbc) { + // This block assumes that the line breaks in this inside scalar content and the Outside scalar content are the same. + // It works mostly, but inconsistencies occur if line break characters are mixed. + header := token.LiteralBlockHeader(n.Value) + space := strings.Repeat(" ", n.Token.Position.Column-1) + values := []string{} + for _, v := range strings.Split(n.Value, lbc) { + values = append(values, fmt.Sprintf("%s %s", space, v)) + } + block := strings.TrimSuffix(strings.TrimSuffix(strings.Join(values, lbc), fmt.Sprintf("%s %s", lbc, space)), fmt.Sprintf(" %s", space)) + return fmt.Sprintf("%s%s%s", header, lbc, block) + } else if len(n.Value) > 0 && (n.Value[0] == '{' || n.Value[0] == '[') { + return fmt.Sprintf(`'%s'`, n.Value) + } + return n.Value +} + +// MarshalYAML encodes to a YAML text +func (n *StringNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// LiteralNode type of literal node +type LiteralNode struct { + *BaseNode + Start *token.Token + Value *StringNode +} + +// Read implements (io.Reader).Read +func (n *LiteralNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns LiteralType +func (n *LiteralNode) Type() NodeType { return LiteralType } + +// GetToken returns token instance +func (n *LiteralNode) GetToken() *token.Token { + return n.Start +} + +// AddColumn add column number to child nodes recursively +func (n *LiteralNode) AddColumn(col int) { + n.Start.AddColumn(col) + if n.Value != nil { + n.Value.AddColumn(col) + } +} + +// GetValue returns string value +func (n *LiteralNode) GetValue() interface{} { + return n.String() +} + +// String literal to text +func (n *LiteralNode) String() string { + origin := n.Value.GetToken().Origin + lit := strings.TrimRight(strings.TrimRight(origin, " "), "\n") + if n.Comment != nil { + return fmt.Sprintf("%s %s\n%s", n.Start.Value, n.Comment.String(), lit) + } + return fmt.Sprintf("%s\n%s", n.Start.Value, lit) +} + +func (n *LiteralNode) stringWithoutComment() string { + return n.String() +} + +// MarshalYAML encodes to a YAML text +func (n *LiteralNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// MergeKeyNode type of merge key node +type MergeKeyNode struct { + *BaseNode + Token *token.Token +} + +// Read implements (io.Reader).Read +func (n *MergeKeyNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns MergeKeyType +func (n *MergeKeyNode) Type() NodeType { return MergeKeyType } + +// GetToken returns token instance +func (n *MergeKeyNode) GetToken() *token.Token { + return n.Token +} + +// GetValue returns '<<' value +func (n *MergeKeyNode) GetValue() interface{} { + return n.Token.Value +} + +// String returns '<<' value +func (n *MergeKeyNode) String() string { + return n.stringWithoutComment() +} + +func (n *MergeKeyNode) stringWithoutComment() string { + return n.Token.Value +} + +// AddColumn add column number to child nodes recursively +func (n *MergeKeyNode) AddColumn(col int) { + n.Token.AddColumn(col) +} + +// MarshalYAML encodes to a YAML text +func (n *MergeKeyNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// BoolNode type of boolean node +type BoolNode struct { + *BaseNode + Token *token.Token + Value bool +} + +// Read implements (io.Reader).Read +func (n *BoolNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns BoolType +func (n *BoolNode) Type() NodeType { return BoolType } + +// GetToken returns token instance +func (n *BoolNode) GetToken() *token.Token { + return n.Token +} + +// AddColumn add column number to child nodes recursively +func (n *BoolNode) AddColumn(col int) { + n.Token.AddColumn(col) +} + +// GetValue returns boolean value +func (n *BoolNode) GetValue() interface{} { + return n.Value +} + +// String boolean to text +func (n *BoolNode) String() string { + if n.Comment != nil { + return addCommentString(n.Token.Value, n.Comment) + } + return n.stringWithoutComment() +} + +func (n *BoolNode) stringWithoutComment() string { + return n.Token.Value +} + +// MarshalYAML encodes to a YAML text +func (n *BoolNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// InfinityNode type of infinity node +type InfinityNode struct { + *BaseNode + Token *token.Token + Value float64 +} + +// Read implements (io.Reader).Read +func (n *InfinityNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns InfinityType +func (n *InfinityNode) Type() NodeType { return InfinityType } + +// GetToken returns token instance +func (n *InfinityNode) GetToken() *token.Token { + return n.Token +} + +// AddColumn add column number to child nodes recursively +func (n *InfinityNode) AddColumn(col int) { + n.Token.AddColumn(col) +} + +// GetValue returns math.Inf(0) or math.Inf(-1) +func (n *InfinityNode) GetValue() interface{} { + return n.Value +} + +// String infinity to text +func (n *InfinityNode) String() string { + if n.Comment != nil { + return addCommentString(n.Token.Value, n.Comment) + } + return n.stringWithoutComment() +} + +func (n *InfinityNode) stringWithoutComment() string { + return n.Token.Value +} + +// MarshalYAML encodes to a YAML text +func (n *InfinityNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// NanNode type of nan node +type NanNode struct { + *BaseNode + Token *token.Token +} + +// Read implements (io.Reader).Read +func (n *NanNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns NanType +func (n *NanNode) Type() NodeType { return NanType } + +// GetToken returns token instance +func (n *NanNode) GetToken() *token.Token { + return n.Token +} + +// AddColumn add column number to child nodes recursively +func (n *NanNode) AddColumn(col int) { + n.Token.AddColumn(col) +} + +// GetValue returns math.NaN() +func (n *NanNode) GetValue() interface{} { + return math.NaN() +} + +// String returns .nan +func (n *NanNode) String() string { + if n.Comment != nil { + return addCommentString(n.Token.Value, n.Comment) + } + return n.stringWithoutComment() +} + +func (n *NanNode) stringWithoutComment() string { + return n.Token.Value +} + +// MarshalYAML encodes to a YAML text +func (n *NanNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// MapNode interface of MappingValueNode / MappingNode +type MapNode interface { + MapRange() *MapNodeIter +} + +// MapNodeIter is an iterator for ranging over a MapNode +type MapNodeIter struct { + values []*MappingValueNode + idx int +} + +const ( + startRangeIndex = -1 +) + +// Next advances the map iterator and reports whether there is another entry. +// It returns false when the iterator is exhausted. +func (m *MapNodeIter) Next() bool { + m.idx++ + next := m.idx < len(m.values) + return next +} + +// Key returns the key of the iterator's current map node entry. +func (m *MapNodeIter) Key() MapKeyNode { + return m.values[m.idx].Key +} + +// Value returns the value of the iterator's current map node entry. +func (m *MapNodeIter) Value() Node { + return m.values[m.idx].Value +} + +// MappingNode type of mapping node +type MappingNode struct { + *BaseNode + Start *token.Token + End *token.Token + IsFlowStyle bool + Values []*MappingValueNode + FootComment *CommentGroupNode +} + +func (n *MappingNode) startPos() *token.Position { + if len(n.Values) == 0 { + return n.Start.Position + } + return n.Values[0].Key.GetToken().Position +} + +// Merge merge key/value of map. +func (n *MappingNode) Merge(target *MappingNode) { + keyToMapValueMap := map[string]*MappingValueNode{} + for _, value := range n.Values { + key := value.Key.String() + keyToMapValueMap[key] = value + } + column := n.startPos().Column - target.startPos().Column + target.AddColumn(column) + for _, value := range target.Values { + mapValue, exists := keyToMapValueMap[value.Key.String()] + if exists { + mapValue.Value = value.Value + } else { + n.Values = append(n.Values, value) + } + } +} + +// SetIsFlowStyle set value to IsFlowStyle field recursively. +func (n *MappingNode) SetIsFlowStyle(isFlow bool) { + n.IsFlowStyle = isFlow + for _, value := range n.Values { + value.SetIsFlowStyle(isFlow) + } +} + +// Read implements (io.Reader).Read +func (n *MappingNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns MappingType +func (n *MappingNode) Type() NodeType { return MappingType } + +// GetToken returns token instance +func (n *MappingNode) GetToken() *token.Token { + return n.Start +} + +// AddColumn add column number to child nodes recursively +func (n *MappingNode) AddColumn(col int) { + n.Start.AddColumn(col) + n.End.AddColumn(col) + for _, value := range n.Values { + value.AddColumn(col) + } +} + +func (n *MappingNode) flowStyleString(commentMode bool) string { + values := []string{} + for _, value := range n.Values { + values = append(values, strings.TrimLeft(value.String(), " ")) + } + mapText := fmt.Sprintf("{%s}", strings.Join(values, ", ")) + if commentMode && n.Comment != nil { + return addCommentString(mapText, n.Comment) + } + return mapText +} + +func (n *MappingNode) blockStyleString(commentMode bool) string { + values := []string{} + for _, value := range n.Values { + values = append(values, value.String()) + } + mapText := strings.Join(values, "\n") + if commentMode && n.Comment != nil { + value := values[0] + var spaceNum int + for i := 0; i < len(value); i++ { + if value[i] != ' ' { + break + } + spaceNum++ + } + comment := n.Comment.StringWithSpace(spaceNum) + return fmt.Sprintf("%s\n%s", comment, mapText) + } + return mapText +} + +// String mapping values to text +func (n *MappingNode) String() string { + if len(n.Values) == 0 { + if n.Comment != nil { + return addCommentString("{}", n.Comment) + } + return "{}" + } + + commentMode := true + if n.IsFlowStyle || len(n.Values) == 0 { + return n.flowStyleString(commentMode) + } + return n.blockStyleString(commentMode) +} + +// MapRange implements MapNode protocol +func (n *MappingNode) MapRange() *MapNodeIter { + return &MapNodeIter{ + idx: startRangeIndex, + values: n.Values, + } +} + +// MarshalYAML encodes to a YAML text +func (n *MappingNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// MappingKeyNode type of tag node +type MappingKeyNode struct { + *BaseNode + Start *token.Token + Value Node +} + +// Read implements (io.Reader).Read +func (n *MappingKeyNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns MappingKeyType +func (n *MappingKeyNode) Type() NodeType { return MappingKeyType } + +// GetToken returns token instance +func (n *MappingKeyNode) GetToken() *token.Token { + return n.Start +} + +// AddColumn add column number to child nodes recursively +func (n *MappingKeyNode) AddColumn(col int) { + n.Start.AddColumn(col) + if n.Value != nil { + n.Value.AddColumn(col) + } +} + +// String tag to text +func (n *MappingKeyNode) String() string { + return n.stringWithoutComment() +} + +func (n *MappingKeyNode) stringWithoutComment() string { + return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String()) +} + +// MarshalYAML encodes to a YAML text +func (n *MappingKeyNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// MappingValueNode type of mapping value +type MappingValueNode struct { + *BaseNode + Start *token.Token + Key MapKeyNode + Value Node + FootComment *CommentGroupNode +} + +// Replace replace value node. +func (n *MappingValueNode) Replace(value Node) error { + column := n.Value.GetToken().Position.Column - value.GetToken().Position.Column + value.AddColumn(column) + n.Value = value + return nil +} + +// Read implements (io.Reader).Read +func (n *MappingValueNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns MappingValueType +func (n *MappingValueNode) Type() NodeType { return MappingValueType } + +// GetToken returns token instance +func (n *MappingValueNode) GetToken() *token.Token { + return n.Start +} + +// AddColumn add column number to child nodes recursively +func (n *MappingValueNode) AddColumn(col int) { + n.Start.AddColumn(col) + if n.Key != nil { + n.Key.AddColumn(col) + } + if n.Value != nil { + n.Value.AddColumn(col) + } +} + +// SetIsFlowStyle set value to IsFlowStyle field recursively. +func (n *MappingValueNode) SetIsFlowStyle(isFlow bool) { + switch value := n.Value.(type) { + case *MappingNode: + value.SetIsFlowStyle(isFlow) + case *MappingValueNode: + value.SetIsFlowStyle(isFlow) + case *SequenceNode: + value.SetIsFlowStyle(isFlow) + } +} + +// String mapping value to text +func (n *MappingValueNode) String() string { + var text string + if n.Comment != nil { + text = fmt.Sprintf( + "%s\n%s", + n.Comment.StringWithSpace(n.Key.GetToken().Position.Column-1), + n.toString(), + ) + } else { + text = n.toString() + } + if n.FootComment != nil { + text += fmt.Sprintf("\n%s", n.FootComment.StringWithSpace(n.Key.GetToken().Position.Column-1)) + } + return text +} + +func (n *MappingValueNode) toString() string { + space := strings.Repeat(" ", n.Key.GetToken().Position.Column-1) + keyIndentLevel := n.Key.GetToken().Position.IndentLevel + valueIndentLevel := n.Value.GetToken().Position.IndentLevel + keyComment := n.Key.GetComment() + if _, ok := n.Value.(ScalarNode); ok { + return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) + } else if keyIndentLevel < valueIndentLevel { + if keyComment != nil { + return fmt.Sprintf( + "%s%s: %s\n%s", + space, + n.Key.stringWithoutComment(), + keyComment.String(), + n.Value.String(), + ) + } + return fmt.Sprintf("%s%s:\n%s", space, n.Key.String(), n.Value.String()) + } else if m, ok := n.Value.(*MappingNode); ok && (m.IsFlowStyle || len(m.Values) == 0) { + return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) + } else if s, ok := n.Value.(*SequenceNode); ok && (s.IsFlowStyle || len(s.Values) == 0) { + return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) + } else if _, ok := n.Value.(*AnchorNode); ok { + return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) + } else if _, ok := n.Value.(*AliasNode); ok { + return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) + } + if keyComment != nil { + return fmt.Sprintf( + "%s%s: %s\n%s", + space, + n.Key.stringWithoutComment(), + keyComment.String(), + n.Value.String(), + ) + } + if m, ok := n.Value.(*MappingNode); ok && m.Comment != nil { + return fmt.Sprintf( + "%s%s: %s", + space, + n.Key.String(), + strings.TrimLeft(n.Value.String(), " "), + ) + } + return fmt.Sprintf("%s%s:\n%s", space, n.Key.String(), n.Value.String()) +} + +// MapRange implements MapNode protocol +func (n *MappingValueNode) MapRange() *MapNodeIter { + return &MapNodeIter{ + idx: startRangeIndex, + values: []*MappingValueNode{n}, + } +} + +// MarshalYAML encodes to a YAML text +func (n *MappingValueNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// ArrayNode interface of SequenceNode +type ArrayNode interface { + ArrayRange() *ArrayNodeIter +} + +// ArrayNodeIter is an iterator for ranging over a ArrayNode +type ArrayNodeIter struct { + values []Node + idx int +} + +// Next advances the array iterator and reports whether there is another entry. +// It returns false when the iterator is exhausted. +func (m *ArrayNodeIter) Next() bool { + m.idx++ + next := m.idx < len(m.values) + return next +} + +// Value returns the value of the iterator's current array entry. +func (m *ArrayNodeIter) Value() Node { + return m.values[m.idx] +} + +// Len returns length of array +func (m *ArrayNodeIter) Len() int { + return len(m.values) +} + +// SequenceNode type of sequence node +type SequenceNode struct { + *BaseNode + Start *token.Token + End *token.Token + IsFlowStyle bool + Values []Node + ValueHeadComments []*CommentGroupNode + FootComment *CommentGroupNode +} + +// Replace replace value node. +func (n *SequenceNode) Replace(idx int, value Node) error { + if len(n.Values) <= idx { + return xerrors.Errorf( + "invalid index for sequence: sequence length is %d, but specified %d index", + len(n.Values), idx, + ) + } + column := n.Values[idx].GetToken().Position.Column - value.GetToken().Position.Column + value.AddColumn(column) + n.Values[idx] = value + return nil +} + +// Merge merge sequence value. +func (n *SequenceNode) Merge(target *SequenceNode) { + column := n.Start.Position.Column - target.Start.Position.Column + target.AddColumn(column) + for _, value := range target.Values { + n.Values = append(n.Values, value) + } +} + +// SetIsFlowStyle set value to IsFlowStyle field recursively. +func (n *SequenceNode) SetIsFlowStyle(isFlow bool) { + n.IsFlowStyle = isFlow + for _, value := range n.Values { + switch value := value.(type) { + case *MappingNode: + value.SetIsFlowStyle(isFlow) + case *MappingValueNode: + value.SetIsFlowStyle(isFlow) + case *SequenceNode: + value.SetIsFlowStyle(isFlow) + } + } +} + +// Read implements (io.Reader).Read +func (n *SequenceNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns SequenceType +func (n *SequenceNode) Type() NodeType { return SequenceType } + +// GetToken returns token instance +func (n *SequenceNode) GetToken() *token.Token { + return n.Start +} + +// AddColumn add column number to child nodes recursively +func (n *SequenceNode) AddColumn(col int) { + n.Start.AddColumn(col) + n.End.AddColumn(col) + for _, value := range n.Values { + value.AddColumn(col) + } +} + +func (n *SequenceNode) flowStyleString() string { + values := []string{} + for _, value := range n.Values { + values = append(values, value.String()) + } + return fmt.Sprintf("[%s]", strings.Join(values, ", ")) +} + +func (n *SequenceNode) blockStyleString() string { + space := strings.Repeat(" ", n.Start.Position.Column-1) + values := []string{} + if n.Comment != nil { + values = append(values, n.Comment.StringWithSpace(n.Start.Position.Column-1)) + } + + for idx, value := range n.Values { + valueStr := value.String() + splittedValues := strings.Split(valueStr, "\n") + trimmedFirstValue := strings.TrimLeft(splittedValues[0], " ") + diffLength := len(splittedValues[0]) - len(trimmedFirstValue) + if len(splittedValues) > 1 && value.Type() == StringType || value.Type() == LiteralType { + // If multi-line string, the space characters for indent have already been added, so delete them. + prefix := space + " " + for i := 1; i < len(splittedValues); i++ { + splittedValues[i] = strings.TrimPrefix(splittedValues[i], prefix) + } + } + newValues := []string{trimmedFirstValue} + for i := 1; i < len(splittedValues); i++ { + if len(splittedValues[i]) <= diffLength { + // this line is \n or white space only + newValues = append(newValues, "") + continue + } + trimmed := splittedValues[i][diffLength:] + newValues = append(newValues, fmt.Sprintf("%s %s", space, trimmed)) + } + newValue := strings.Join(newValues, "\n") + if len(n.ValueHeadComments) == len(n.Values) && n.ValueHeadComments[idx] != nil { + values = append(values, n.ValueHeadComments[idx].StringWithSpace(n.Start.Position.Column-1)) + } + values = append(values, fmt.Sprintf("%s- %s", space, newValue)) + } + if n.FootComment != nil { + values = append(values, n.FootComment.StringWithSpace(n.Start.Position.Column-1)) + } + return strings.Join(values, "\n") +} + +// String sequence to text +func (n *SequenceNode) String() string { + if n.IsFlowStyle || len(n.Values) == 0 { + return n.flowStyleString() + } + return n.blockStyleString() +} + +// ArrayRange implements ArrayNode protocol +func (n *SequenceNode) ArrayRange() *ArrayNodeIter { + return &ArrayNodeIter{ + idx: startRangeIndex, + values: n.Values, + } +} + +// MarshalYAML encodes to a YAML text +func (n *SequenceNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// AnchorNode type of anchor node +type AnchorNode struct { + *BaseNode + Start *token.Token + Name Node + Value Node +} + +func (n *AnchorNode) SetName(name string) error { + if n.Name == nil { + return ErrInvalidAnchorName + } + s, ok := n.Name.(*StringNode) + if !ok { + return ErrInvalidAnchorName + } + s.Value = name + return nil +} + +// Read implements (io.Reader).Read +func (n *AnchorNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns AnchorType +func (n *AnchorNode) Type() NodeType { return AnchorType } + +// GetToken returns token instance +func (n *AnchorNode) GetToken() *token.Token { + return n.Start +} + +// AddColumn add column number to child nodes recursively +func (n *AnchorNode) AddColumn(col int) { + n.Start.AddColumn(col) + if n.Name != nil { + n.Name.AddColumn(col) + } + if n.Value != nil { + n.Value.AddColumn(col) + } +} + +// String anchor to text +func (n *AnchorNode) String() string { + value := n.Value.String() + if len(strings.Split(value, "\n")) > 1 { + return fmt.Sprintf("&%s\n%s", n.Name.String(), value) + } else if s, ok := n.Value.(*SequenceNode); ok && !s.IsFlowStyle { + return fmt.Sprintf("&%s\n%s", n.Name.String(), value) + } else if m, ok := n.Value.(*MappingNode); ok && !m.IsFlowStyle { + return fmt.Sprintf("&%s\n%s", n.Name.String(), value) + } + return fmt.Sprintf("&%s %s", n.Name.String(), value) +} + +// MarshalYAML encodes to a YAML text +func (n *AnchorNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// AliasNode type of alias node +type AliasNode struct { + *BaseNode + Start *token.Token + Value Node +} + +func (n *AliasNode) SetName(name string) error { + if n.Value == nil { + return ErrInvalidAliasName + } + s, ok := n.Value.(*StringNode) + if !ok { + return ErrInvalidAliasName + } + s.Value = name + return nil +} + +// Read implements (io.Reader).Read +func (n *AliasNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns AliasType +func (n *AliasNode) Type() NodeType { return AliasType } + +// GetToken returns token instance +func (n *AliasNode) GetToken() *token.Token { + return n.Start +} + +// AddColumn add column number to child nodes recursively +func (n *AliasNode) AddColumn(col int) { + n.Start.AddColumn(col) + if n.Value != nil { + n.Value.AddColumn(col) + } +} + +// String alias to text +func (n *AliasNode) String() string { + return fmt.Sprintf("*%s", n.Value.String()) +} + +// MarshalYAML encodes to a YAML text +func (n *AliasNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// DirectiveNode type of directive node +type DirectiveNode struct { + *BaseNode + Start *token.Token + Value Node +} + +// Read implements (io.Reader).Read +func (n *DirectiveNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns DirectiveType +func (n *DirectiveNode) Type() NodeType { return DirectiveType } + +// GetToken returns token instance +func (n *DirectiveNode) GetToken() *token.Token { + return n.Start +} + +// AddColumn add column number to child nodes recursively +func (n *DirectiveNode) AddColumn(col int) { + if n.Value != nil { + n.Value.AddColumn(col) + } +} + +// String directive to text +func (n *DirectiveNode) String() string { + return fmt.Sprintf("%s%s", n.Start.Value, n.Value.String()) +} + +// MarshalYAML encodes to a YAML text +func (n *DirectiveNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// TagNode type of tag node +type TagNode struct { + *BaseNode + Start *token.Token + Value Node +} + +// Read implements (io.Reader).Read +func (n *TagNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns TagType +func (n *TagNode) Type() NodeType { return TagType } + +// GetToken returns token instance +func (n *TagNode) GetToken() *token.Token { + return n.Start +} + +// AddColumn add column number to child nodes recursively +func (n *TagNode) AddColumn(col int) { + n.Start.AddColumn(col) + if n.Value != nil { + n.Value.AddColumn(col) + } +} + +// String tag to text +func (n *TagNode) String() string { + return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String()) +} + +// MarshalYAML encodes to a YAML text +func (n *TagNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// CommentNode type of comment node +type CommentNode struct { + *BaseNode + Token *token.Token +} + +// Read implements (io.Reader).Read +func (n *CommentNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns TagType +func (n *CommentNode) Type() NodeType { return CommentType } + +// GetToken returns token instance +func (n *CommentNode) GetToken() *token.Token { return n.Token } + +// AddColumn add column number to child nodes recursively +func (n *CommentNode) AddColumn(col int) { + if n.Token == nil { + return + } + n.Token.AddColumn(col) +} + +// String comment to text +func (n *CommentNode) String() string { + return fmt.Sprintf("#%s", n.Token.Value) +} + +// MarshalYAML encodes to a YAML text +func (n *CommentNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// CommentGroupNode type of comment node +type CommentGroupNode struct { + *BaseNode + Comments []*CommentNode +} + +// Read implements (io.Reader).Read +func (n *CommentGroupNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// Type returns TagType +func (n *CommentGroupNode) Type() NodeType { return CommentType } + +// GetToken returns token instance +func (n *CommentGroupNode) GetToken() *token.Token { + if len(n.Comments) > 0 { + return n.Comments[0].Token + } + return nil +} + +// AddColumn add column number to child nodes recursively +func (n *CommentGroupNode) AddColumn(col int) { + for _, comment := range n.Comments { + comment.AddColumn(col) + } +} + +// String comment to text +func (n *CommentGroupNode) String() string { + values := []string{} + for _, comment := range n.Comments { + values = append(values, comment.String()) + } + return strings.Join(values, "\n") +} + +func (n *CommentGroupNode) StringWithSpace(col int) string { + values := []string{} + space := strings.Repeat(" ", col) + for _, comment := range n.Comments { + values = append(values, space+comment.String()) + } + return strings.Join(values, "\n") + +} + +// MarshalYAML encodes to a YAML text +func (n *CommentGroupNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +// Visitor has Visit method that is invokded for each node encountered by Walk. +// If the result visitor w is not nil, Walk visits each of the children of node with the visitor w, +// followed by a call of w.Visit(nil). +type Visitor interface { + Visit(Node) Visitor +} + +// Walk traverses an AST in depth-first order: It starts by calling v.Visit(node); node must not be nil. +// If the visitor w returned by v.Visit(node) is not nil, +// Walk is invoked recursively with visitor w for each of the non-nil children of node, +// followed by a call of w.Visit(nil). +func Walk(v Visitor, node Node) { + if v = v.Visit(node); v == nil { + return + } + + switch n := node.(type) { + case *CommentNode: + case *NullNode: + walkComment(v, n.BaseNode) + case *IntegerNode: + walkComment(v, n.BaseNode) + case *FloatNode: + walkComment(v, n.BaseNode) + case *StringNode: + walkComment(v, n.BaseNode) + case *MergeKeyNode: + walkComment(v, n.BaseNode) + case *BoolNode: + walkComment(v, n.BaseNode) + case *InfinityNode: + walkComment(v, n.BaseNode) + case *NanNode: + walkComment(v, n.BaseNode) + case *LiteralNode: + walkComment(v, n.BaseNode) + Walk(v, n.Value) + case *DirectiveNode: + walkComment(v, n.BaseNode) + Walk(v, n.Value) + case *TagNode: + walkComment(v, n.BaseNode) + Walk(v, n.Value) + case *DocumentNode: + walkComment(v, n.BaseNode) + Walk(v, n.Body) + case *MappingNode: + walkComment(v, n.BaseNode) + for _, value := range n.Values { + Walk(v, value) + } + case *MappingKeyNode: + walkComment(v, n.BaseNode) + Walk(v, n.Value) + case *MappingValueNode: + walkComment(v, n.BaseNode) + Walk(v, n.Key) + Walk(v, n.Value) + case *SequenceNode: + walkComment(v, n.BaseNode) + for _, value := range n.Values { + Walk(v, value) + } + case *AnchorNode: + walkComment(v, n.BaseNode) + Walk(v, n.Name) + Walk(v, n.Value) + case *AliasNode: + walkComment(v, n.BaseNode) + Walk(v, n.Value) + } +} + +func walkComment(v Visitor, base *BaseNode) { + if base == nil { + return + } + if base.Comment == nil { + return + } + Walk(v, base.Comment) +} + +type filterWalker struct { + typ NodeType + results []Node +} + +func (v *filterWalker) Visit(n Node) Visitor { + if v.typ == n.Type() { + v.results = append(v.results, n) + } + return v +} + +type parentFinder struct { + target Node +} + +func (f *parentFinder) walk(parent, node Node) Node { + if f.target == node { + return parent + } + switch n := node.(type) { + case *CommentNode: + return nil + case *NullNode: + return nil + case *IntegerNode: + return nil + case *FloatNode: + return nil + case *StringNode: + return nil + case *MergeKeyNode: + return nil + case *BoolNode: + return nil + case *InfinityNode: + return nil + case *NanNode: + return nil + case *LiteralNode: + return f.walk(node, n.Value) + case *DirectiveNode: + return f.walk(node, n.Value) + case *TagNode: + return f.walk(node, n.Value) + case *DocumentNode: + return f.walk(node, n.Body) + case *MappingNode: + for _, value := range n.Values { + if found := f.walk(node, value); found != nil { + return found + } + } + case *MappingKeyNode: + return f.walk(node, n.Value) + case *MappingValueNode: + if found := f.walk(node, n.Key); found != nil { + return found + } + return f.walk(node, n.Value) + case *SequenceNode: + for _, value := range n.Values { + if found := f.walk(node, value); found != nil { + return found + } + } + case *AnchorNode: + if found := f.walk(node, n.Name); found != nil { + return found + } + return f.walk(node, n.Value) + case *AliasNode: + return f.walk(node, n.Value) + } + return nil +} + +// Parent get parent node from child node. +func Parent(root, child Node) Node { + finder := &parentFinder{target: child} + return finder.walk(root, root) +} + +// Filter returns a list of nodes that match the given type. +func Filter(typ NodeType, node Node) []Node { + walker := &filterWalker{typ: typ} + Walk(walker, node) + return walker.results +} + +// FilterFile returns a list of nodes that match the given type. +func FilterFile(typ NodeType, file *File) []Node { + results := []Node{} + for _, doc := range file.Docs { + walker := &filterWalker{typ: typ} + Walk(walker, doc) + results = append(results, walker.results...) + } + return results +} + +type ErrInvalidMergeType struct { + dst Node + src Node +} + +func (e *ErrInvalidMergeType) Error() string { + return fmt.Sprintf("cannot merge %s into %s", e.src.Type(), e.dst.Type()) +} + +// Merge merge document, map, sequence node. +func Merge(dst Node, src Node) error { + if doc, ok := src.(*DocumentNode); ok { + src = doc.Body + } + err := &ErrInvalidMergeType{dst: dst, src: src} + switch dst.Type() { + case DocumentType: + node := dst.(*DocumentNode) + return Merge(node.Body, src) + case MappingType: + node := dst.(*MappingNode) + target, ok := src.(*MappingNode) + if !ok { + return err + } + node.Merge(target) + return nil + case SequenceType: + node := dst.(*SequenceNode) + target, ok := src.(*SequenceNode) + if !ok { + return err + } + node.Merge(target) + return nil + } + return err +} diff --git a/vendor/github.com/goccy/go-yaml/decode.go b/vendor/github.com/goccy/go-yaml/decode.go new file mode 100644 index 000000000..d3dbabcbc --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/decode.go @@ -0,0 +1,1753 @@ +package yaml + +import ( + "bytes" + "context" + "encoding" + "encoding/base64" + "fmt" + "io" + "io/ioutil" + "math" + "os" + "path/filepath" + "reflect" + "sort" + "strconv" + "time" + + "github.com/goccy/go-yaml/ast" + "github.com/goccy/go-yaml/internal/errors" + "github.com/goccy/go-yaml/parser" + "github.com/goccy/go-yaml/token" + "golang.org/x/xerrors" +) + +// Decoder reads and decodes YAML values from an input stream. +type Decoder struct { + reader io.Reader + referenceReaders []io.Reader + anchorNodeMap map[string]ast.Node + anchorValueMap map[string]reflect.Value + customUnmarshalerMap map[reflect.Type]func(interface{}, []byte) error + toCommentMap CommentMap + opts []DecodeOption + referenceFiles []string + referenceDirs []string + isRecursiveDir bool + isResolvedReference bool + validator StructValidator + disallowUnknownField bool + disallowDuplicateKey bool + useOrderedMap bool + useJSONUnmarshaler bool + parsedFile *ast.File + streamIndex int +} + +// NewDecoder returns a new decoder that reads from r. +func NewDecoder(r io.Reader, opts ...DecodeOption) *Decoder { + return &Decoder{ + reader: r, + anchorNodeMap: map[string]ast.Node{}, + anchorValueMap: map[string]reflect.Value{}, + customUnmarshalerMap: map[reflect.Type]func(interface{}, []byte) error{}, + opts: opts, + referenceReaders: []io.Reader{}, + referenceFiles: []string{}, + referenceDirs: []string{}, + isRecursiveDir: false, + isResolvedReference: false, + disallowUnknownField: false, + disallowDuplicateKey: false, + useOrderedMap: false, + } +} + +func (d *Decoder) castToFloat(v interface{}) interface{} { + switch vv := v.(type) { + case int: + return float64(vv) + case int8: + return float64(vv) + case int16: + return float64(vv) + case int32: + return float64(vv) + case int64: + return float64(vv) + case uint: + return float64(vv) + case uint8: + return float64(vv) + case uint16: + return float64(vv) + case uint32: + return float64(vv) + case uint64: + return float64(vv) + case float32: + return float64(vv) + case float64: + return vv + case string: + // if error occurred, return zero value + f, _ := strconv.ParseFloat(vv, 64) + return f + } + return 0 +} + +func (d *Decoder) mergeValueNode(value ast.Node) ast.Node { + if value.Type() == ast.AliasType { + aliasNode := value.(*ast.AliasNode) + aliasName := aliasNode.Value.GetToken().Value + return d.anchorNodeMap[aliasName] + } + return value +} + +func (d *Decoder) mapKeyNodeToString(node ast.MapKeyNode) string { + key := d.nodeToValue(node) + if key == nil { + return "null" + } + if k, ok := key.(string); ok { + return k + } + return fmt.Sprint(key) +} + +func (d *Decoder) setToMapValue(node ast.Node, m map[string]interface{}) { + d.setPathToCommentMap(node) + switch n := node.(type) { + case *ast.MappingValueNode: + if n.Key.Type() == ast.MergeKeyType { + d.setToMapValue(d.mergeValueNode(n.Value), m) + } else { + key := d.mapKeyNodeToString(n.Key) + m[key] = d.nodeToValue(n.Value) + } + case *ast.MappingNode: + for _, value := range n.Values { + d.setToMapValue(value, m) + } + case *ast.AnchorNode: + anchorName := n.Name.GetToken().Value + d.anchorNodeMap[anchorName] = n.Value + } +} + +func (d *Decoder) setToOrderedMapValue(node ast.Node, m *MapSlice) { + switch n := node.(type) { + case *ast.MappingValueNode: + if n.Key.Type() == ast.MergeKeyType { + d.setToOrderedMapValue(d.mergeValueNode(n.Value), m) + } else { + key := d.mapKeyNodeToString(n.Key) + *m = append(*m, MapItem{Key: key, Value: d.nodeToValue(n.Value)}) + } + case *ast.MappingNode: + for _, value := range n.Values { + d.setToOrderedMapValue(value, m) + } + } +} + +func (d *Decoder) setPathToCommentMap(node ast.Node) { + if d.toCommentMap == nil { + return + } + d.addHeadOrLineCommentToMap(node) + d.addFootCommentToMap(node) +} + +func (d *Decoder) addHeadOrLineCommentToMap(node ast.Node) { + sequence, ok := node.(*ast.SequenceNode) + if ok { + d.addSequenceNodeCommentToMap(sequence) + return + } + commentGroup := node.GetComment() + if commentGroup == nil { + return + } + texts := []string{} + targetLine := node.GetToken().Position.Line + minCommentLine := math.MaxInt + for _, comment := range commentGroup.Comments { + if minCommentLine > comment.Token.Position.Line { + minCommentLine = comment.Token.Position.Line + } + texts = append(texts, comment.Token.Value) + } + if len(texts) == 0 { + return + } + commentPath := node.GetPath() + if minCommentLine < targetLine { + d.addCommentToMap(commentPath, HeadComment(texts...)) + } else { + d.addCommentToMap(commentPath, LineComment(texts[0])) + } +} + +func (d *Decoder) addSequenceNodeCommentToMap(node *ast.SequenceNode) { + if len(node.ValueHeadComments) != 0 { + for idx, headComment := range node.ValueHeadComments { + if headComment == nil { + continue + } + texts := make([]string, 0, len(headComment.Comments)) + for _, comment := range headComment.Comments { + texts = append(texts, comment.Token.Value) + } + if len(texts) != 0 { + d.addCommentToMap(node.Values[idx].GetPath(), HeadComment(texts...)) + } + } + } + firstElemHeadComment := node.GetComment() + if firstElemHeadComment != nil { + texts := make([]string, 0, len(firstElemHeadComment.Comments)) + for _, comment := range firstElemHeadComment.Comments { + texts = append(texts, comment.Token.Value) + } + if len(texts) != 0 { + d.addCommentToMap(node.Values[0].GetPath(), HeadComment(texts...)) + } + } +} + +func (d *Decoder) addFootCommentToMap(node ast.Node) { + var ( + footComment *ast.CommentGroupNode + footCommentPath string = node.GetPath() + ) + switch n := node.(type) { + case *ast.SequenceNode: + if len(n.Values) != 0 { + footCommentPath = n.Values[len(n.Values)-1].GetPath() + } + footComment = n.FootComment + case *ast.MappingNode: + footComment = n.FootComment + case *ast.MappingValueNode: + footComment = n.FootComment + } + if footComment == nil { + return + } + var texts []string + for _, comment := range footComment.Comments { + texts = append(texts, comment.Token.Value) + } + if len(texts) != 0 { + d.addCommentToMap(footCommentPath, FootComment(texts...)) + } +} + +func (d *Decoder) addCommentToMap(path string, comment *Comment) { + for _, c := range d.toCommentMap[path] { + if c.Position == comment.Position { + // already added same comment + return + } + } + d.toCommentMap[path] = append(d.toCommentMap[path], comment) + sort.Slice(d.toCommentMap[path], func(i, j int) bool { + return d.toCommentMap[path][i].Position < d.toCommentMap[path][j].Position + }) +} + +func (d *Decoder) nodeToValue(node ast.Node) interface{} { + d.setPathToCommentMap(node) + switch n := node.(type) { + case *ast.NullNode: + return nil + case *ast.StringNode: + return n.GetValue() + case *ast.IntegerNode: + return n.GetValue() + case *ast.FloatNode: + return n.GetValue() + case *ast.BoolNode: + return n.GetValue() + case *ast.InfinityNode: + return n.GetValue() + case *ast.NanNode: + return n.GetValue() + case *ast.TagNode: + switch token.ReservedTagKeyword(n.Start.Value) { + case token.TimestampTag: + t, _ := d.castToTime(n.Value) + return t + case token.IntegerTag: + i, _ := strconv.Atoi(fmt.Sprint(d.nodeToValue(n.Value))) + return i + case token.FloatTag: + return d.castToFloat(d.nodeToValue(n.Value)) + case token.NullTag: + return nil + case token.BinaryTag: + b, _ := base64.StdEncoding.DecodeString(d.nodeToValue(n.Value).(string)) + return b + case token.StringTag: + return d.nodeToValue(n.Value) + case token.MappingTag: + return d.nodeToValue(n.Value) + } + case *ast.AnchorNode: + anchorName := n.Name.GetToken().Value + anchorValue := d.nodeToValue(n.Value) + d.anchorNodeMap[anchorName] = n.Value + return anchorValue + case *ast.AliasNode: + aliasName := n.Value.GetToken().Value + node := d.anchorNodeMap[aliasName] + return d.nodeToValue(node) + case *ast.LiteralNode: + return n.Value.GetValue() + case *ast.MappingKeyNode: + return d.nodeToValue(n.Value) + case *ast.MappingValueNode: + if n.Key.Type() == ast.MergeKeyType { + value := d.mergeValueNode(n.Value) + if d.useOrderedMap { + m := MapSlice{} + d.setToOrderedMapValue(value, &m) + return m + } + m := map[string]interface{}{} + d.setToMapValue(value, m) + return m + } + key := d.mapKeyNodeToString(n.Key) + if d.useOrderedMap { + return MapSlice{{Key: key, Value: d.nodeToValue(n.Value)}} + } + return map[string]interface{}{ + key: d.nodeToValue(n.Value), + } + case *ast.MappingNode: + if d.useOrderedMap { + m := make(MapSlice, 0, len(n.Values)) + for _, value := range n.Values { + d.setToOrderedMapValue(value, &m) + } + return m + } + m := make(map[string]interface{}, len(n.Values)) + for _, value := range n.Values { + d.setToMapValue(value, m) + } + return m + case *ast.SequenceNode: + v := make([]interface{}, 0, len(n.Values)) + for _, value := range n.Values { + v = append(v, d.nodeToValue(value)) + } + return v + } + return nil +} + +func (d *Decoder) resolveAlias(node ast.Node) (ast.Node, error) { + switch n := node.(type) { + case *ast.MappingNode: + for idx, v := range n.Values { + value, err := d.resolveAlias(v) + if err != nil { + return nil, err + } + n.Values[idx] = value.(*ast.MappingValueNode) + } + case *ast.TagNode: + value, err := d.resolveAlias(n.Value) + if err != nil { + return nil, err + } + n.Value = value + case *ast.MappingKeyNode: + value, err := d.resolveAlias(n.Value) + if err != nil { + return nil, err + } + n.Value = value + case *ast.MappingValueNode: + if n.Key.Type() == ast.MergeKeyType && n.Value.Type() == ast.AliasType { + value, err := d.resolveAlias(n.Value) + if err != nil { + return nil, err + } + keyColumn := n.Key.GetToken().Position.Column + requiredColumn := keyColumn + 2 + value.AddColumn(requiredColumn) + n.Value = value + } else { + key, err := d.resolveAlias(n.Key) + if err != nil { + return nil, err + } + n.Key = key.(ast.MapKeyNode) + value, err := d.resolveAlias(n.Value) + if err != nil { + return nil, err + } + n.Value = value + } + case *ast.SequenceNode: + for idx, v := range n.Values { + value, err := d.resolveAlias(v) + if err != nil { + return nil, err + } + n.Values[idx] = value + } + case *ast.AliasNode: + aliasName := n.Value.GetToken().Value + node := d.anchorNodeMap[aliasName] + if node == nil { + return nil, xerrors.Errorf("cannot find anchor by alias name %s", aliasName) + } + return d.resolveAlias(node) + } + return node, nil +} + +func (d *Decoder) getMapNode(node ast.Node) (ast.MapNode, error) { + if _, ok := node.(*ast.NullNode); ok { + return nil, nil + } + if anchor, ok := node.(*ast.AnchorNode); ok { + mapNode, ok := anchor.Value.(ast.MapNode) + if ok { + return mapNode, nil + } + return nil, errUnexpectedNodeType(anchor.Value.Type(), ast.MappingType, node.GetToken()) + } + if alias, ok := node.(*ast.AliasNode); ok { + aliasName := alias.Value.GetToken().Value + node := d.anchorNodeMap[aliasName] + if node == nil { + return nil, xerrors.Errorf("cannot find anchor by alias name %s", aliasName) + } + mapNode, ok := node.(ast.MapNode) + if ok { + return mapNode, nil + } + return nil, errUnexpectedNodeType(node.Type(), ast.MappingType, node.GetToken()) + } + mapNode, ok := node.(ast.MapNode) + if !ok { + return nil, errUnexpectedNodeType(node.Type(), ast.MappingType, node.GetToken()) + } + return mapNode, nil +} + +func (d *Decoder) getArrayNode(node ast.Node) (ast.ArrayNode, error) { + if _, ok := node.(*ast.NullNode); ok { + return nil, nil + } + if anchor, ok := node.(*ast.AnchorNode); ok { + arrayNode, ok := anchor.Value.(ast.ArrayNode) + if ok { + return arrayNode, nil + } + + return nil, errUnexpectedNodeType(anchor.Value.Type(), ast.SequenceType, node.GetToken()) + } + if alias, ok := node.(*ast.AliasNode); ok { + aliasName := alias.Value.GetToken().Value + node := d.anchorNodeMap[aliasName] + if node == nil { + return nil, xerrors.Errorf("cannot find anchor by alias name %s", aliasName) + } + arrayNode, ok := node.(ast.ArrayNode) + if ok { + return arrayNode, nil + } + return nil, errUnexpectedNodeType(node.Type(), ast.SequenceType, node.GetToken()) + } + arrayNode, ok := node.(ast.ArrayNode) + if !ok { + return nil, errUnexpectedNodeType(node.Type(), ast.SequenceType, node.GetToken()) + } + return arrayNode, nil +} + +func (d *Decoder) fileToNode(f *ast.File) ast.Node { + for _, doc := range f.Docs { + if v := d.nodeToValue(doc.Body); v != nil { + return doc.Body + } + } + return nil +} + +func (d *Decoder) convertValue(v reflect.Value, typ reflect.Type, src ast.Node) (reflect.Value, error) { + if typ.Kind() != reflect.String { + if !v.Type().ConvertibleTo(typ) { + return reflect.Zero(typ), errTypeMismatch(typ, v.Type(), src.GetToken()) + } + return v.Convert(typ), nil + } + // cast value to string + switch v.Type().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return reflect.ValueOf(fmt.Sprint(v.Int())), nil + case reflect.Float32, reflect.Float64: + return reflect.ValueOf(fmt.Sprint(v.Float())), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return reflect.ValueOf(fmt.Sprint(v.Uint())), nil + case reflect.Bool: + return reflect.ValueOf(fmt.Sprint(v.Bool())), nil + } + if !v.Type().ConvertibleTo(typ) { + return reflect.Zero(typ), errTypeMismatch(typ, v.Type(), src.GetToken()) + } + return v.Convert(typ), nil +} + +type overflowError struct { + dstType reflect.Type + srcNum string +} + +func (e *overflowError) Error() string { + return fmt.Sprintf("cannot unmarshal %s into Go value of type %s ( overflow )", e.srcNum, e.dstType) +} + +func errOverflow(dstType reflect.Type, num string) *overflowError { + return &overflowError{dstType: dstType, srcNum: num} +} + +func errTypeMismatch(dstType, srcType reflect.Type, token *token.Token) *errors.TypeError { + return &errors.TypeError{DstType: dstType, SrcType: srcType, Token: token} +} + +type unknownFieldError struct { + err error +} + +func (e *unknownFieldError) Error() string { + return e.err.Error() +} + +func errUnknownField(msg string, tk *token.Token) *unknownFieldError { + return &unknownFieldError{err: errors.ErrSyntax(msg, tk)} +} + +func errUnexpectedNodeType(actual, expected ast.NodeType, tk *token.Token) error { + return errors.ErrSyntax(fmt.Sprintf("%s was used where %s is expected", actual.YAMLName(), expected.YAMLName()), tk) +} + +type duplicateKeyError struct { + err error +} + +func (e *duplicateKeyError) Error() string { + return e.err.Error() +} + +func errDuplicateKey(msg string, tk *token.Token) *duplicateKeyError { + return &duplicateKeyError{err: errors.ErrSyntax(msg, tk)} +} + +func (d *Decoder) deleteStructKeys(structType reflect.Type, unknownFields map[string]ast.Node) error { + if structType.Kind() == reflect.Ptr { + structType = structType.Elem() + } + structFieldMap, err := structFieldMap(structType) + if err != nil { + return errors.Wrapf(err, "failed to create struct field map") + } + + for j := 0; j < structType.NumField(); j++ { + field := structType.Field(j) + if isIgnoredStructField(field) { + continue + } + + structField, exists := structFieldMap[field.Name] + if !exists { + continue + } + + if structField.IsInline { + d.deleteStructKeys(field.Type, unknownFields) + } else { + delete(unknownFields, structField.RenderName) + } + } + return nil +} + +func (d *Decoder) lastNode(node ast.Node) ast.Node { + switch n := node.(type) { + case *ast.MappingNode: + if len(n.Values) > 0 { + return d.lastNode(n.Values[len(n.Values)-1]) + } + case *ast.MappingValueNode: + return d.lastNode(n.Value) + case *ast.SequenceNode: + if len(n.Values) > 0 { + return d.lastNode(n.Values[len(n.Values)-1]) + } + } + return node +} + +func (d *Decoder) unmarshalableDocument(node ast.Node) ([]byte, error) { + var err error + node, err = d.resolveAlias(node) + if err != nil { + return nil, err + } + doc := node.String() + last := d.lastNode(node) + if last != nil && last.Type() == ast.LiteralType { + doc += "\n" + } + return []byte(doc), nil +} + +func (d *Decoder) unmarshalableText(node ast.Node) ([]byte, bool, error) { + var err error + node, err = d.resolveAlias(node) + if err != nil { + return nil, false, err + } + if node.Type() == ast.AnchorType { + node = node.(*ast.AnchorNode).Value + } + switch n := node.(type) { + case *ast.StringNode: + return []byte(n.Value), true, nil + case *ast.LiteralNode: + return []byte(n.Value.GetToken().Value), true, nil + default: + scalar, ok := n.(ast.ScalarNode) + if ok { + return []byte(fmt.Sprint(scalar.GetValue())), true, nil + } + } + return nil, false, nil +} + +type jsonUnmarshaler interface { + UnmarshalJSON([]byte) error +} + +func (d *Decoder) existsTypeInCustomUnmarshalerMap(t reflect.Type) bool { + if _, exists := d.customUnmarshalerMap[t]; exists { + return true + } + + globalCustomUnmarshalerMu.Lock() + defer globalCustomUnmarshalerMu.Unlock() + if _, exists := globalCustomUnmarshalerMap[t]; exists { + return true + } + return false +} + +func (d *Decoder) unmarshalerFromCustomUnmarshalerMap(t reflect.Type) (func(interface{}, []byte) error, bool) { + if unmarshaler, exists := d.customUnmarshalerMap[t]; exists { + return unmarshaler, exists + } + + globalCustomUnmarshalerMu.Lock() + defer globalCustomUnmarshalerMu.Unlock() + if unmarshaler, exists := globalCustomUnmarshalerMap[t]; exists { + return unmarshaler, exists + } + return nil, false +} + +func (d *Decoder) canDecodeByUnmarshaler(dst reflect.Value) bool { + ptrValue := dst.Addr() + if d.existsTypeInCustomUnmarshalerMap(ptrValue.Type()) { + return true + } + iface := ptrValue.Interface() + switch iface.(type) { + case BytesUnmarshalerContext: + return true + case BytesUnmarshaler: + return true + case InterfaceUnmarshalerContext: + return true + case InterfaceUnmarshaler: + return true + case *time.Time: + return true + case *time.Duration: + return true + case encoding.TextUnmarshaler: + return true + case jsonUnmarshaler: + return d.useJSONUnmarshaler + } + return false +} + +func (d *Decoder) decodeByUnmarshaler(ctx context.Context, dst reflect.Value, src ast.Node) error { + ptrValue := dst.Addr() + if unmarshaler, exists := d.unmarshalerFromCustomUnmarshalerMap(ptrValue.Type()); exists { + b, err := d.unmarshalableDocument(src) + if err != nil { + return errors.Wrapf(err, "failed to UnmarshalYAML") + } + if err := unmarshaler(ptrValue.Interface(), b); err != nil { + return errors.Wrapf(err, "failed to UnmarshalYAML") + } + return nil + } + iface := ptrValue.Interface() + + if unmarshaler, ok := iface.(BytesUnmarshalerContext); ok { + b, err := d.unmarshalableDocument(src) + if err != nil { + return errors.Wrapf(err, "failed to UnmarshalYAML") + } + if err := unmarshaler.UnmarshalYAML(ctx, b); err != nil { + return errors.Wrapf(err, "failed to UnmarshalYAML") + } + return nil + } + + if unmarshaler, ok := iface.(BytesUnmarshaler); ok { + b, err := d.unmarshalableDocument(src) + if err != nil { + return errors.Wrapf(err, "failed to UnmarshalYAML") + } + if err := unmarshaler.UnmarshalYAML(b); err != nil { + return errors.Wrapf(err, "failed to UnmarshalYAML") + } + return nil + } + + if unmarshaler, ok := iface.(InterfaceUnmarshalerContext); ok { + if err := unmarshaler.UnmarshalYAML(ctx, func(v interface{}) error { + rv := reflect.ValueOf(v) + if rv.Type().Kind() != reflect.Ptr { + return errors.ErrDecodeRequiredPointerType + } + if err := d.decodeValue(ctx, rv.Elem(), src); err != nil { + return errors.Wrapf(err, "failed to decode value") + } + return nil + }); err != nil { + return errors.Wrapf(err, "failed to UnmarshalYAML") + } + return nil + } + + if unmarshaler, ok := iface.(InterfaceUnmarshaler); ok { + if err := unmarshaler.UnmarshalYAML(func(v interface{}) error { + rv := reflect.ValueOf(v) + if rv.Type().Kind() != reflect.Ptr { + return errors.ErrDecodeRequiredPointerType + } + if err := d.decodeValue(ctx, rv.Elem(), src); err != nil { + return errors.Wrapf(err, "failed to decode value") + } + return nil + }); err != nil { + return errors.Wrapf(err, "failed to UnmarshalYAML") + } + return nil + } + + if _, ok := iface.(*time.Time); ok { + return d.decodeTime(ctx, dst, src) + } + + if _, ok := iface.(*time.Duration); ok { + return d.decodeDuration(ctx, dst, src) + } + + if unmarshaler, isText := iface.(encoding.TextUnmarshaler); isText { + b, ok, err := d.unmarshalableText(src) + if err != nil { + return errors.Wrapf(err, "failed to UnmarshalText") + } + if ok { + if err := unmarshaler.UnmarshalText(b); err != nil { + return errors.Wrapf(err, "failed to UnmarshalText") + } + return nil + } + } + + if d.useJSONUnmarshaler { + if unmarshaler, ok := iface.(jsonUnmarshaler); ok { + b, err := d.unmarshalableDocument(src) + if err != nil { + return errors.Wrapf(err, "failed to UnmarshalJSON") + } + jsonBytes, err := YAMLToJSON(b) + if err != nil { + return errors.Wrapf(err, "failed to convert yaml to json") + } + jsonBytes = bytes.TrimRight(jsonBytes, "\n") + if err := unmarshaler.UnmarshalJSON(jsonBytes); err != nil { + return errors.Wrapf(err, "failed to UnmarshalJSON") + } + return nil + } + } + + return xerrors.Errorf("does not implemented Unmarshaler") +} + +var ( + astNodeType = reflect.TypeOf((*ast.Node)(nil)).Elem() +) + +func (d *Decoder) decodeValue(ctx context.Context, dst reflect.Value, src ast.Node) error { + if src.Type() == ast.AnchorType { + anchorName := src.(*ast.AnchorNode).Name.GetToken().Value + if _, exists := d.anchorValueMap[anchorName]; !exists { + d.anchorValueMap[anchorName] = dst + } + } + if d.canDecodeByUnmarshaler(dst) { + if err := d.decodeByUnmarshaler(ctx, dst, src); err != nil { + return errors.Wrapf(err, "failed to decode by unmarshaler") + } + return nil + } + valueType := dst.Type() + switch valueType.Kind() { + case reflect.Ptr: + if dst.IsNil() { + return nil + } + if src.Type() == ast.NullType { + // set nil value to pointer + dst.Set(reflect.Zero(valueType)) + return nil + } + v := d.createDecodableValue(dst.Type()) + if err := d.decodeValue(ctx, v, src); err != nil { + return errors.Wrapf(err, "failed to decode ptr value") + } + dst.Set(d.castToAssignableValue(v, dst.Type())) + case reflect.Interface: + if dst.Type() == astNodeType { + dst.Set(reflect.ValueOf(src)) + return nil + } + v := reflect.ValueOf(d.nodeToValue(src)) + if v.IsValid() { + dst.Set(v) + } + case reflect.Map: + return d.decodeMap(ctx, dst, src) + case reflect.Array: + return d.decodeArray(ctx, dst, src) + case reflect.Slice: + if mapSlice, ok := dst.Addr().Interface().(*MapSlice); ok { + return d.decodeMapSlice(ctx, mapSlice, src) + } + return d.decodeSlice(ctx, dst, src) + case reflect.Struct: + if mapItem, ok := dst.Addr().Interface().(*MapItem); ok { + return d.decodeMapItem(ctx, mapItem, src) + } + return d.decodeStruct(ctx, dst, src) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v := d.nodeToValue(src) + switch vv := v.(type) { + case int64: + if !dst.OverflowInt(vv) { + dst.SetInt(vv) + return nil + } + case uint64: + if vv <= math.MaxInt64 && !dst.OverflowInt(int64(vv)) { + dst.SetInt(int64(vv)) + return nil + } + case float64: + if vv <= math.MaxInt64 && !dst.OverflowInt(int64(vv)) { + dst.SetInt(int64(vv)) + return nil + } + default: + return errTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken()) + } + return errOverflow(valueType, fmt.Sprint(v)) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + v := d.nodeToValue(src) + switch vv := v.(type) { + case int64: + if 0 <= vv && !dst.OverflowUint(uint64(vv)) { + dst.SetUint(uint64(vv)) + return nil + } + case uint64: + if !dst.OverflowUint(vv) { + dst.SetUint(vv) + return nil + } + case float64: + if 0 <= vv && vv <= math.MaxUint64 && !dst.OverflowUint(uint64(vv)) { + dst.SetUint(uint64(vv)) + return nil + } + default: + return errTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken()) + } + return errOverflow(valueType, fmt.Sprint(v)) + } + v := reflect.ValueOf(d.nodeToValue(src)) + if v.IsValid() { + convertedValue, err := d.convertValue(v, dst.Type(), src) + if err != nil { + return errors.Wrapf(err, "failed to convert value") + } + dst.Set(convertedValue) + } + return nil +} + +func (d *Decoder) createDecodableValue(typ reflect.Type) reflect.Value { + for { + if typ.Kind() == reflect.Ptr { + typ = typ.Elem() + continue + } + break + } + return reflect.New(typ).Elem() +} + +func (d *Decoder) castToAssignableValue(value reflect.Value, target reflect.Type) reflect.Value { + if target.Kind() != reflect.Ptr { + return value + } + maxTryCount := 5 + tryCount := 0 + for { + if tryCount > maxTryCount { + return value + } + if value.Type().AssignableTo(target) { + break + } + value = value.Addr() + tryCount++ + } + return value +} + +func (d *Decoder) createDecodedNewValue( + ctx context.Context, typ reflect.Type, defaultVal reflect.Value, node ast.Node, +) (reflect.Value, error) { + if node.Type() == ast.AliasType { + aliasName := node.(*ast.AliasNode).Value.GetToken().Value + newValue := d.anchorValueMap[aliasName] + if newValue.IsValid() { + return newValue, nil + } + } + if node.Type() == ast.NullType { + return reflect.Zero(typ), nil + } + newValue := d.createDecodableValue(typ) + for defaultVal.Kind() == reflect.Ptr { + defaultVal = defaultVal.Elem() + } + if defaultVal.IsValid() && defaultVal.Type().AssignableTo(newValue.Type()) { + newValue.Set(defaultVal) + } + if err := d.decodeValue(ctx, newValue, node); err != nil { + return newValue, errors.Wrapf(err, "failed to decode value") + } + return newValue, nil +} + +func (d *Decoder) keyToNodeMap(node ast.Node, ignoreMergeKey bool, getKeyOrValueNode func(*ast.MapNodeIter) ast.Node) (map[string]ast.Node, error) { + mapNode, err := d.getMapNode(node) + if err != nil { + return nil, errors.Wrapf(err, "failed to get map node") + } + keyMap := map[string]struct{}{} + keyToNodeMap := map[string]ast.Node{} + if mapNode == nil { + return keyToNodeMap, nil + } + mapIter := mapNode.MapRange() + for mapIter.Next() { + keyNode := mapIter.Key() + if keyNode.Type() == ast.MergeKeyType { + if ignoreMergeKey { + continue + } + mergeMap, err := d.keyToNodeMap(mapIter.Value(), ignoreMergeKey, getKeyOrValueNode) + if err != nil { + return nil, errors.Wrapf(err, "failed to get keyToNodeMap by MergeKey node") + } + for k, v := range mergeMap { + if err := d.validateDuplicateKey(keyMap, k, v); err != nil { + return nil, errors.Wrapf(err, "invalid struct key") + } + keyToNodeMap[k] = v + } + } else { + key, ok := d.nodeToValue(keyNode).(string) + if !ok { + return nil, errors.Wrapf(err, "failed to decode map key") + } + if err := d.validateDuplicateKey(keyMap, key, keyNode); err != nil { + return nil, errors.Wrapf(err, "invalid struct key") + } + keyToNodeMap[key] = getKeyOrValueNode(mapIter) + } + } + return keyToNodeMap, nil +} + +func (d *Decoder) keyToKeyNodeMap(node ast.Node, ignoreMergeKey bool) (map[string]ast.Node, error) { + m, err := d.keyToNodeMap(node, ignoreMergeKey, func(nodeMap *ast.MapNodeIter) ast.Node { return nodeMap.Key() }) + if err != nil { + return nil, errors.Wrapf(err, "failed to get keyToNodeMap") + } + return m, nil +} + +func (d *Decoder) keyToValueNodeMap(node ast.Node, ignoreMergeKey bool) (map[string]ast.Node, error) { + m, err := d.keyToNodeMap(node, ignoreMergeKey, func(nodeMap *ast.MapNodeIter) ast.Node { return nodeMap.Value() }) + if err != nil { + return nil, errors.Wrapf(err, "failed to get keyToNodeMap") + } + return m, nil +} + +func (d *Decoder) setDefaultValueIfConflicted(v reflect.Value, fieldMap StructFieldMap) error { + typ := v.Type() + if typ.Kind() != reflect.Struct { + return nil + } + embeddedStructFieldMap, err := structFieldMap(typ) + if err != nil { + return errors.Wrapf(err, "failed to get struct field map by embedded type") + } + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + if isIgnoredStructField(field) { + continue + } + structField := embeddedStructFieldMap[field.Name] + if !fieldMap.isIncludedRenderName(structField.RenderName) { + continue + } + // if declared same key name, set default value + fieldValue := v.Field(i) + if fieldValue.CanSet() { + fieldValue.Set(reflect.Zero(fieldValue.Type())) + } + } + return nil +} + +// This is a subset of the formats allowed by the regular expression +// defined at http://yaml.org/type/timestamp.html. +var allowedTimestampFormats = []string{ + "2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields. + "2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t". + "2006-1-2 15:4:5.999999999", // space separated with no time zone + "2006-1-2", // date only +} + +func (d *Decoder) castToTime(src ast.Node) (time.Time, error) { + if src == nil { + return time.Time{}, nil + } + v := d.nodeToValue(src) + if t, ok := v.(time.Time); ok { + return t, nil + } + s, ok := v.(string) + if !ok { + return time.Time{}, errTypeMismatch(reflect.TypeOf(time.Time{}), reflect.TypeOf(v), src.GetToken()) + } + for _, format := range allowedTimestampFormats { + t, err := time.Parse(format, s) + if err != nil { + // invalid format + continue + } + return t, nil + } + return time.Time{}, nil +} + +func (d *Decoder) decodeTime(ctx context.Context, dst reflect.Value, src ast.Node) error { + t, err := d.castToTime(src) + if err != nil { + return errors.Wrapf(err, "failed to convert to time") + } + dst.Set(reflect.ValueOf(t)) + return nil +} + +func (d *Decoder) castToDuration(src ast.Node) (time.Duration, error) { + if src == nil { + return 0, nil + } + v := d.nodeToValue(src) + if t, ok := v.(time.Duration); ok { + return t, nil + } + s, ok := v.(string) + if !ok { + return 0, errTypeMismatch(reflect.TypeOf(time.Duration(0)), reflect.TypeOf(v), src.GetToken()) + } + t, err := time.ParseDuration(s) + if err != nil { + return 0, errors.Wrapf(err, "failed to parse duration") + } + return t, nil +} + +func (d *Decoder) decodeDuration(ctx context.Context, dst reflect.Value, src ast.Node) error { + t, err := d.castToDuration(src) + if err != nil { + return errors.Wrapf(err, "failed to convert to duration") + } + dst.Set(reflect.ValueOf(t)) + return nil +} + +// getMergeAliasName support single alias only +func (d *Decoder) getMergeAliasName(src ast.Node) string { + mapNode, err := d.getMapNode(src) + if err != nil { + return "" + } + if mapNode == nil { + return "" + } + mapIter := mapNode.MapRange() + for mapIter.Next() { + key := mapIter.Key() + value := mapIter.Value() + if key.Type() == ast.MergeKeyType && value.Type() == ast.AliasType { + return value.(*ast.AliasNode).Value.GetToken().Value + } + } + return "" +} + +func (d *Decoder) decodeStruct(ctx context.Context, dst reflect.Value, src ast.Node) error { + if src == nil { + return nil + } + structType := dst.Type() + srcValue := reflect.ValueOf(src) + srcType := srcValue.Type() + if srcType.Kind() == reflect.Ptr { + srcType = srcType.Elem() + srcValue = srcValue.Elem() + } + if structType == srcType { + // dst value implements ast.Node + dst.Set(srcValue) + return nil + } + structFieldMap, err := structFieldMap(structType) + if err != nil { + return errors.Wrapf(err, "failed to create struct field map") + } + ignoreMergeKey := structFieldMap.hasMergeProperty() + keyToNodeMap, err := d.keyToValueNodeMap(src, ignoreMergeKey) + if err != nil { + return errors.Wrapf(err, "failed to get keyToValueNodeMap") + } + var unknownFields map[string]ast.Node + if d.disallowUnknownField { + unknownFields, err = d.keyToKeyNodeMap(src, ignoreMergeKey) + if err != nil { + return errors.Wrapf(err, "failed to get keyToKeyNodeMap") + } + } + + aliasName := d.getMergeAliasName(src) + var foundErr error + + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if isIgnoredStructField(field) { + continue + } + structField := structFieldMap[field.Name] + if structField.IsInline { + fieldValue := dst.FieldByName(field.Name) + if structField.IsAutoAlias { + if aliasName != "" { + newFieldValue := d.anchorValueMap[aliasName] + if newFieldValue.IsValid() { + fieldValue.Set(d.castToAssignableValue(newFieldValue, fieldValue.Type())) + } + } + continue + } + if !fieldValue.CanSet() { + return xerrors.Errorf("cannot set embedded type as unexported field %s.%s", field.PkgPath, field.Name) + } + if fieldValue.Type().Kind() == reflect.Ptr && src.Type() == ast.NullType { + // set nil value to pointer + fieldValue.Set(reflect.Zero(fieldValue.Type())) + continue + } + mapNode := ast.Mapping(nil, false) + for k, v := range keyToNodeMap { + key := &ast.StringNode{BaseNode: &ast.BaseNode{}, Value: k} + mapNode.Values = append(mapNode.Values, ast.MappingValue(nil, key, v)) + } + newFieldValue, err := d.createDecodedNewValue(ctx, fieldValue.Type(), fieldValue, mapNode) + if d.disallowUnknownField { + if err := d.deleteStructKeys(fieldValue.Type(), unknownFields); err != nil { + return errors.Wrapf(err, "cannot delete struct keys") + } + } + + if err != nil { + if foundErr != nil { + continue + } + var te *errors.TypeError + if xerrors.As(err, &te) { + if te.StructFieldName != nil { + fieldName := fmt.Sprintf("%s.%s", structType.Name(), *te.StructFieldName) + te.StructFieldName = &fieldName + } else { + fieldName := fmt.Sprintf("%s.%s", structType.Name(), field.Name) + te.StructFieldName = &fieldName + } + foundErr = te + continue + } else { + foundErr = err + } + continue + } + d.setDefaultValueIfConflicted(newFieldValue, structFieldMap) + fieldValue.Set(d.castToAssignableValue(newFieldValue, fieldValue.Type())) + continue + } + v, exists := keyToNodeMap[structField.RenderName] + if !exists { + continue + } + delete(unknownFields, structField.RenderName) + fieldValue := dst.FieldByName(field.Name) + if fieldValue.Type().Kind() == reflect.Ptr && src.Type() == ast.NullType { + // set nil value to pointer + fieldValue.Set(reflect.Zero(fieldValue.Type())) + continue + } + newFieldValue, err := d.createDecodedNewValue(ctx, fieldValue.Type(), fieldValue, v) + if err != nil { + if foundErr != nil { + continue + } + var te *errors.TypeError + if xerrors.As(err, &te) { + fieldName := fmt.Sprintf("%s.%s", structType.Name(), field.Name) + te.StructFieldName = &fieldName + foundErr = te + } else { + foundErr = err + } + continue + } + fieldValue.Set(d.castToAssignableValue(newFieldValue, fieldValue.Type())) + } + if foundErr != nil { + return errors.Wrapf(foundErr, "failed to decode value") + } + + // Ignore unknown fields when parsing an inline struct (recognized by a nil token). + // Unknown fields are expected (they could be fields from the parent struct). + if len(unknownFields) != 0 && d.disallowUnknownField && src.GetToken() != nil { + for key, node := range unknownFields { + return errUnknownField(fmt.Sprintf(`unknown field "%s"`, key), node.GetToken()) + } + } + + if d.validator != nil { + if err := d.validator.Struct(dst.Interface()); err != nil { + ev := reflect.ValueOf(err) + if ev.Type().Kind() == reflect.Slice { + for i := 0; i < ev.Len(); i++ { + fieldErr, ok := ev.Index(i).Interface().(FieldError) + if !ok { + continue + } + fieldName := fieldErr.StructField() + structField, exists := structFieldMap[fieldName] + if !exists { + continue + } + node, exists := keyToNodeMap[structField.RenderName] + if exists { + // TODO: to make FieldError message cutomizable + return errors.ErrSyntax(fmt.Sprintf("%s", err), node.GetToken()) + } else if t := src.GetToken(); t != nil && t.Prev != nil && t.Prev.Prev != nil { + // A missing required field will not be in the keyToNodeMap + // the error needs to be associated with the parent of the source node + return errors.ErrSyntax(fmt.Sprintf("%s", err), t.Prev.Prev) + } + } + } + return err + } + } + return nil +} + +func (d *Decoder) decodeArray(ctx context.Context, dst reflect.Value, src ast.Node) error { + arrayNode, err := d.getArrayNode(src) + if err != nil { + return errors.Wrapf(err, "failed to get array node") + } + if arrayNode == nil { + return nil + } + iter := arrayNode.ArrayRange() + arrayValue := reflect.New(dst.Type()).Elem() + arrayType := dst.Type() + elemType := arrayType.Elem() + idx := 0 + + var foundErr error + for iter.Next() { + v := iter.Value() + if elemType.Kind() == reflect.Ptr && v.Type() == ast.NullType { + // set nil value to pointer + arrayValue.Index(idx).Set(reflect.Zero(elemType)) + } else { + dstValue, err := d.createDecodedNewValue(ctx, elemType, reflect.Value{}, v) + if err != nil { + if foundErr == nil { + foundErr = err + } + continue + } else { + arrayValue.Index(idx).Set(d.castToAssignableValue(dstValue, elemType)) + } + } + idx++ + } + dst.Set(arrayValue) + if foundErr != nil { + return errors.Wrapf(foundErr, "failed to decode value") + } + return nil +} + +func (d *Decoder) decodeSlice(ctx context.Context, dst reflect.Value, src ast.Node) error { + arrayNode, err := d.getArrayNode(src) + if err != nil { + return errors.Wrapf(err, "failed to get array node") + } + if arrayNode == nil { + return nil + } + iter := arrayNode.ArrayRange() + sliceType := dst.Type() + sliceValue := reflect.MakeSlice(sliceType, 0, iter.Len()) + elemType := sliceType.Elem() + + var foundErr error + for iter.Next() { + v := iter.Value() + if elemType.Kind() == reflect.Ptr && v.Type() == ast.NullType { + // set nil value to pointer + sliceValue = reflect.Append(sliceValue, reflect.Zero(elemType)) + continue + } + dstValue, err := d.createDecodedNewValue(ctx, elemType, reflect.Value{}, v) + if err != nil { + if foundErr == nil { + foundErr = err + } + continue + } + sliceValue = reflect.Append(sliceValue, d.castToAssignableValue(dstValue, elemType)) + } + dst.Set(sliceValue) + if foundErr != nil { + return errors.Wrapf(foundErr, "failed to decode value") + } + return nil +} + +func (d *Decoder) decodeMapItem(ctx context.Context, dst *MapItem, src ast.Node) error { + mapNode, err := d.getMapNode(src) + if err != nil { + return errors.Wrapf(err, "failed to get map node") + } + if mapNode == nil { + return nil + } + mapIter := mapNode.MapRange() + if !mapIter.Next() { + return nil + } + key := mapIter.Key() + value := mapIter.Value() + if key.Type() == ast.MergeKeyType { + if err := d.decodeMapItem(ctx, dst, value); err != nil { + return errors.Wrapf(err, "failed to decode map with merge key") + } + return nil + } + *dst = MapItem{ + Key: d.nodeToValue(key), + Value: d.nodeToValue(value), + } + return nil +} + +func (d *Decoder) validateDuplicateKey(keyMap map[string]struct{}, key interface{}, keyNode ast.Node) error { + k, ok := key.(string) + if !ok { + return nil + } + if d.disallowDuplicateKey { + if _, exists := keyMap[k]; exists { + return errDuplicateKey(fmt.Sprintf(`duplicate key "%s"`, k), keyNode.GetToken()) + } + } + keyMap[k] = struct{}{} + return nil +} + +func (d *Decoder) decodeMapSlice(ctx context.Context, dst *MapSlice, src ast.Node) error { + mapNode, err := d.getMapNode(src) + if err != nil { + return errors.Wrapf(err, "failed to get map node") + } + if mapNode == nil { + return nil + } + mapSlice := MapSlice{} + mapIter := mapNode.MapRange() + keyMap := map[string]struct{}{} + for mapIter.Next() { + key := mapIter.Key() + value := mapIter.Value() + if key.Type() == ast.MergeKeyType { + var m MapSlice + if err := d.decodeMapSlice(ctx, &m, value); err != nil { + return errors.Wrapf(err, "failed to decode map with merge key") + } + for _, v := range m { + if err := d.validateDuplicateKey(keyMap, v.Key, value); err != nil { + return errors.Wrapf(err, "invalid map key") + } + mapSlice = append(mapSlice, v) + } + continue + } + k := d.nodeToValue(key) + if err := d.validateDuplicateKey(keyMap, k, key); err != nil { + return errors.Wrapf(err, "invalid map key") + } + mapSlice = append(mapSlice, MapItem{ + Key: k, + Value: d.nodeToValue(value), + }) + } + *dst = mapSlice + return nil +} + +func (d *Decoder) decodeMap(ctx context.Context, dst reflect.Value, src ast.Node) error { + mapNode, err := d.getMapNode(src) + if err != nil { + return errors.Wrapf(err, "failed to get map node") + } + if mapNode == nil { + return nil + } + mapType := dst.Type() + mapValue := reflect.MakeMap(mapType) + keyType := mapValue.Type().Key() + valueType := mapValue.Type().Elem() + mapIter := mapNode.MapRange() + keyMap := map[string]struct{}{} + var foundErr error + for mapIter.Next() { + key := mapIter.Key() + value := mapIter.Value() + if key.Type() == ast.MergeKeyType { + if err := d.decodeMap(ctx, dst, value); err != nil { + return errors.Wrapf(err, "failed to decode map with merge key") + } + iter := dst.MapRange() + for iter.Next() { + if err := d.validateDuplicateKey(keyMap, iter.Key(), value); err != nil { + return errors.Wrapf(err, "invalid map key") + } + mapValue.SetMapIndex(iter.Key(), iter.Value()) + } + continue + } + k := reflect.ValueOf(d.nodeToValue(key)) + if k.IsValid() && k.Type().ConvertibleTo(keyType) { + k = k.Convert(keyType) + } + if k.IsValid() { + if err := d.validateDuplicateKey(keyMap, k.Interface(), key); err != nil { + return errors.Wrapf(err, "invalid map key") + } + } + if valueType.Kind() == reflect.Ptr && value.Type() == ast.NullType { + // set nil value to pointer + mapValue.SetMapIndex(k, reflect.Zero(valueType)) + continue + } + dstValue, err := d.createDecodedNewValue(ctx, valueType, reflect.Value{}, value) + if err != nil { + if foundErr == nil { + foundErr = err + } + } + if !k.IsValid() { + // expect nil key + mapValue.SetMapIndex(d.createDecodableValue(keyType), d.castToAssignableValue(dstValue, valueType)) + continue + } + mapValue.SetMapIndex(k, d.castToAssignableValue(dstValue, valueType)) + } + dst.Set(mapValue) + if foundErr != nil { + return errors.Wrapf(foundErr, "failed to decode value") + } + return nil +} + +func (d *Decoder) fileToReader(file string) (io.Reader, error) { + reader, err := os.Open(file) + if err != nil { + return nil, errors.Wrapf(err, "failed to open file") + } + return reader, nil +} + +func (d *Decoder) isYAMLFile(file string) bool { + ext := filepath.Ext(file) + if ext == ".yml" { + return true + } + if ext == ".yaml" { + return true + } + return false +} + +func (d *Decoder) readersUnderDir(dir string) ([]io.Reader, error) { + pattern := fmt.Sprintf("%s/*", dir) + matches, err := filepath.Glob(pattern) + if err != nil { + return nil, errors.Wrapf(err, "failed to get files by %s", pattern) + } + readers := []io.Reader{} + for _, match := range matches { + if !d.isYAMLFile(match) { + continue + } + reader, err := d.fileToReader(match) + if err != nil { + return nil, errors.Wrapf(err, "failed to get reader") + } + readers = append(readers, reader) + } + return readers, nil +} + +func (d *Decoder) readersUnderDirRecursive(dir string) ([]io.Reader, error) { + readers := []io.Reader{} + if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if !d.isYAMLFile(path) { + return nil + } + reader, err := d.fileToReader(path) + if err != nil { + return errors.Wrapf(err, "failed to get reader") + } + readers = append(readers, reader) + return nil + }); err != nil { + return nil, errors.Wrapf(err, "interrupt walk in %s", dir) + } + return readers, nil +} + +func (d *Decoder) resolveReference() error { + for _, opt := range d.opts { + if err := opt(d); err != nil { + return errors.Wrapf(err, "failed to exec option") + } + } + for _, file := range d.referenceFiles { + reader, err := d.fileToReader(file) + if err != nil { + return errors.Wrapf(err, "failed to get reader") + } + d.referenceReaders = append(d.referenceReaders, reader) + } + for _, dir := range d.referenceDirs { + if !d.isRecursiveDir { + readers, err := d.readersUnderDir(dir) + if err != nil { + return errors.Wrapf(err, "failed to get readers from under the %s", dir) + } + d.referenceReaders = append(d.referenceReaders, readers...) + } else { + readers, err := d.readersUnderDirRecursive(dir) + if err != nil { + return errors.Wrapf(err, "failed to get readers from under the %s", dir) + } + d.referenceReaders = append(d.referenceReaders, readers...) + } + } + for _, reader := range d.referenceReaders { + bytes, err := ioutil.ReadAll(reader) + if err != nil { + return errors.Wrapf(err, "failed to read buffer") + } + + // assign new anchor definition to anchorMap + if _, err := d.parse(bytes); err != nil { + return errors.Wrapf(err, "failed to decode") + } + } + d.isResolvedReference = true + return nil +} + +func (d *Decoder) parse(bytes []byte) (*ast.File, error) { + var parseMode parser.Mode + if d.toCommentMap != nil { + parseMode = parser.ParseComments + } + f, err := parser.ParseBytes(bytes, parseMode) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse yaml") + } + normalizedFile := &ast.File{} + for _, doc := range f.Docs { + // try to decode ast.Node to value and map anchor value to anchorMap + if v := d.nodeToValue(doc.Body); v != nil { + normalizedFile.Docs = append(normalizedFile.Docs, doc) + } + } + return normalizedFile, nil +} + +func (d *Decoder) isInitialized() bool { + return d.parsedFile != nil +} + +func (d *Decoder) decodeInit() error { + if !d.isResolvedReference { + if err := d.resolveReference(); err != nil { + return errors.Wrapf(err, "failed to resolve reference") + } + } + var buf bytes.Buffer + if _, err := io.Copy(&buf, d.reader); err != nil { + return errors.Wrapf(err, "failed to copy from reader") + } + file, err := d.parse(buf.Bytes()) + if err != nil { + return errors.Wrapf(err, "failed to decode") + } + d.parsedFile = file + return nil +} + +func (d *Decoder) decode(ctx context.Context, v reflect.Value) error { + if len(d.parsedFile.Docs) <= d.streamIndex { + return io.EOF + } + body := d.parsedFile.Docs[d.streamIndex].Body + if body == nil { + return nil + } + if err := d.decodeValue(ctx, v.Elem(), body); err != nil { + return errors.Wrapf(err, "failed to decode value") + } + d.streamIndex++ + return nil +} + +// Decode reads the next YAML-encoded value from its input +// and stores it in the value pointed to by v. +// +// See the documentation for Unmarshal for details about the +// conversion of YAML into a Go value. +func (d *Decoder) Decode(v interface{}) error { + return d.DecodeContext(context.Background(), v) +} + +// DecodeContext reads the next YAML-encoded value from its input +// and stores it in the value pointed to by v with context.Context. +func (d *Decoder) DecodeContext(ctx context.Context, v interface{}) error { + rv := reflect.ValueOf(v) + if rv.Type().Kind() != reflect.Ptr { + return errors.ErrDecodeRequiredPointerType + } + if d.isInitialized() { + if err := d.decode(ctx, rv); err != nil { + if err == io.EOF { + return err + } + return errors.Wrapf(err, "failed to decode") + } + return nil + } + if err := d.decodeInit(); err != nil { + return errors.Wrapf(err, "failed to decodeInit") + } + if err := d.decode(ctx, rv); err != nil { + if err == io.EOF { + return err + } + return errors.Wrapf(err, "failed to decode") + } + return nil +} + +// DecodeFromNode decodes node into the value pointed to by v. +func (d *Decoder) DecodeFromNode(node ast.Node, v interface{}) error { + return d.DecodeFromNodeContext(context.Background(), node, v) +} + +// DecodeFromNodeContext decodes node into the value pointed to by v with context.Context. +func (d *Decoder) DecodeFromNodeContext(ctx context.Context, node ast.Node, v interface{}) error { + rv := reflect.ValueOf(v) + if rv.Type().Kind() != reflect.Ptr { + return errors.ErrDecodeRequiredPointerType + } + if !d.isInitialized() { + if err := d.decodeInit(); err != nil { + return errors.Wrapf(err, "failed to decodInit") + } + } + // resolve references to the anchor on the same file + d.nodeToValue(node) + if err := d.decodeValue(ctx, rv.Elem(), node); err != nil { + return errors.Wrapf(err, "failed to decode value") + } + return nil +} diff --git a/vendor/github.com/goccy/go-yaml/encode.go b/vendor/github.com/goccy/go-yaml/encode.go new file mode 100644 index 000000000..7d8d81e03 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/encode.go @@ -0,0 +1,871 @@ +package yaml + +import ( + "context" + "encoding" + "fmt" + "io" + "math" + "reflect" + "sort" + "strconv" + "strings" + "time" + + "github.com/goccy/go-yaml/ast" + "github.com/goccy/go-yaml/internal/errors" + "github.com/goccy/go-yaml/parser" + "github.com/goccy/go-yaml/printer" + "github.com/goccy/go-yaml/token" + "golang.org/x/xerrors" +) + +const ( + // DefaultIndentSpaces default number of space for indent + DefaultIndentSpaces = 2 +) + +// Encoder writes YAML values to an output stream. +type Encoder struct { + writer io.Writer + opts []EncodeOption + indent int + indentSequence bool + singleQuote bool + isFlowStyle bool + isJSONStyle bool + useJSONMarshaler bool + anchorCallback func(*ast.AnchorNode, interface{}) error + anchorPtrToNameMap map[uintptr]string + customMarshalerMap map[reflect.Type]func(interface{}) ([]byte, error) + useLiteralStyleIfMultiline bool + commentMap map[*Path][]*Comment + written bool + + line int + column int + offset int + indentNum int + indentLevel int +} + +// NewEncoder returns a new encoder that writes to w. +// The Encoder should be closed after use to flush all data to w. +func NewEncoder(w io.Writer, opts ...EncodeOption) *Encoder { + return &Encoder{ + writer: w, + opts: opts, + indent: DefaultIndentSpaces, + anchorPtrToNameMap: map[uintptr]string{}, + customMarshalerMap: map[reflect.Type]func(interface{}) ([]byte, error){}, + line: 1, + column: 1, + offset: 0, + } +} + +// Close closes the encoder by writing any remaining data. +// It does not write a stream terminating string "...". +func (e *Encoder) Close() error { + return nil +} + +// Encode writes the YAML encoding of v to the stream. +// If multiple items are encoded to the stream, +// the second and subsequent document will be preceded with a "---" document separator, +// but the first will not. +// +// See the documentation for Marshal for details about the conversion of Go values to YAML. +func (e *Encoder) Encode(v interface{}) error { + return e.EncodeContext(context.Background(), v) +} + +// EncodeContext writes the YAML encoding of v to the stream with context.Context. +func (e *Encoder) EncodeContext(ctx context.Context, v interface{}) error { + node, err := e.EncodeToNodeContext(ctx, v) + if err != nil { + return errors.Wrapf(err, "failed to encode to node") + } + if err := e.setCommentByCommentMap(node); err != nil { + return errors.Wrapf(err, "failed to set comment by comment map") + } + if !e.written { + e.written = true + } else { + // write document separator + e.writer.Write([]byte("---\n")) + } + var p printer.Printer + e.writer.Write(p.PrintNode(node)) + return nil +} + +// EncodeToNode convert v to ast.Node. +func (e *Encoder) EncodeToNode(v interface{}) (ast.Node, error) { + return e.EncodeToNodeContext(context.Background(), v) +} + +// EncodeToNodeContext convert v to ast.Node with context.Context. +func (e *Encoder) EncodeToNodeContext(ctx context.Context, v interface{}) (ast.Node, error) { + for _, opt := range e.opts { + if err := opt(e); err != nil { + return nil, errors.Wrapf(err, "failed to run option for encoder") + } + } + node, err := e.encodeValue(ctx, reflect.ValueOf(v), 1) + if err != nil { + return nil, errors.Wrapf(err, "failed to encode value") + } + return node, nil +} + +func (e *Encoder) setCommentByCommentMap(node ast.Node) error { + if e.commentMap == nil { + return nil + } + for path, comments := range e.commentMap { + n, err := path.FilterNode(node) + if err != nil { + return errors.Wrapf(err, "failed to filter node") + } + if n == nil { + continue + } + for _, comment := range comments { + commentTokens := []*token.Token{} + for _, text := range comment.Texts { + commentTokens = append(commentTokens, token.New(text, text, nil)) + } + commentGroup := ast.CommentGroup(commentTokens) + switch comment.Position { + case CommentHeadPosition: + if err := e.setHeadComment(node, n, commentGroup); err != nil { + return errors.Wrapf(err, "failed to set head comment") + } + case CommentLinePosition: + if err := e.setLineComment(node, n, commentGroup); err != nil { + return errors.Wrapf(err, "failed to set line comment") + } + case CommentFootPosition: + if err := e.setFootComment(node, n, commentGroup); err != nil { + return errors.Wrapf(err, "failed to set foot comment") + } + default: + return ErrUnknownCommentPositionType + } + } + } + return nil +} + +func (e *Encoder) setHeadComment(node ast.Node, filtered ast.Node, comment *ast.CommentGroupNode) error { + parent := ast.Parent(node, filtered) + if parent == nil { + return ErrUnsupportedHeadPositionType(node) + } + switch p := parent.(type) { + case *ast.MappingValueNode: + if err := p.SetComment(comment); err != nil { + return errors.Wrapf(err, "failed to set comment") + } + case *ast.MappingNode: + if err := p.SetComment(comment); err != nil { + return errors.Wrapf(err, "failed to set comment") + } + case *ast.SequenceNode: + if len(p.ValueHeadComments) == 0 { + p.ValueHeadComments = make([]*ast.CommentGroupNode, len(p.Values)) + } + var foundIdx int + for idx, v := range p.Values { + if v == filtered { + foundIdx = idx + break + } + } + p.ValueHeadComments[foundIdx] = comment + default: + return ErrUnsupportedHeadPositionType(node) + } + return nil +} + +func (e *Encoder) setLineComment(node ast.Node, filtered ast.Node, comment *ast.CommentGroupNode) error { + switch filtered.(type) { + case *ast.MappingValueNode, *ast.SequenceNode: + // Line comment cannot be set for mapping value node. + // It should probably be set for the parent map node + if err := e.setLineCommentToParentMapNode(node, filtered, comment); err != nil { + return errors.Wrapf(err, "failed to set line comment to parent node") + } + default: + if err := filtered.SetComment(comment); err != nil { + return errors.Wrapf(err, "failed to set comment") + } + } + return nil +} + +func (e *Encoder) setLineCommentToParentMapNode(node ast.Node, filtered ast.Node, comment *ast.CommentGroupNode) error { + parent := ast.Parent(node, filtered) + if parent == nil { + return ErrUnsupportedLinePositionType(node) + } + switch p := parent.(type) { + case *ast.MappingValueNode: + if err := p.Key.SetComment(comment); err != nil { + return errors.Wrapf(err, "failed to set comment") + } + case *ast.MappingNode: + if err := p.SetComment(comment); err != nil { + return errors.Wrapf(err, "failed to set comment") + } + default: + return ErrUnsupportedLinePositionType(parent) + } + return nil +} + +func (e *Encoder) setFootComment(node ast.Node, filtered ast.Node, comment *ast.CommentGroupNode) error { + parent := ast.Parent(node, filtered) + if parent == nil { + return ErrUnsupportedFootPositionType(node) + } + switch n := parent.(type) { + case *ast.MappingValueNode: + n.FootComment = comment + case *ast.MappingNode: + n.FootComment = comment + case *ast.SequenceNode: + n.FootComment = comment + default: + return ErrUnsupportedFootPositionType(n) + } + return nil +} + +func (e *Encoder) encodeDocument(doc []byte) (ast.Node, error) { + f, err := parser.ParseBytes(doc, 0) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse yaml") + } + for _, docNode := range f.Docs { + if docNode.Body != nil { + return docNode.Body, nil + } + } + return nil, nil +} + +func (e *Encoder) isInvalidValue(v reflect.Value) bool { + if !v.IsValid() { + return true + } + kind := v.Type().Kind() + if kind == reflect.Ptr && v.IsNil() { + return true + } + if kind == reflect.Interface && v.IsNil() { + return true + } + return false +} + +type jsonMarshaler interface { + MarshalJSON() ([]byte, error) +} + +func (e *Encoder) existsTypeInCustomMarshalerMap(t reflect.Type) bool { + if _, exists := e.customMarshalerMap[t]; exists { + return true + } + + globalCustomMarshalerMu.Lock() + defer globalCustomMarshalerMu.Unlock() + if _, exists := globalCustomMarshalerMap[t]; exists { + return true + } + return false +} + +func (e *Encoder) marshalerFromCustomMarshalerMap(t reflect.Type) (func(interface{}) ([]byte, error), bool) { + if marshaler, exists := e.customMarshalerMap[t]; exists { + return marshaler, exists + } + + globalCustomMarshalerMu.Lock() + defer globalCustomMarshalerMu.Unlock() + if marshaler, exists := globalCustomMarshalerMap[t]; exists { + return marshaler, exists + } + return nil, false +} + +func (e *Encoder) canEncodeByMarshaler(v reflect.Value) bool { + if !v.CanInterface() { + return false + } + if e.existsTypeInCustomMarshalerMap(v.Type()) { + return true + } + iface := v.Interface() + switch iface.(type) { + case BytesMarshalerContext: + return true + case BytesMarshaler: + return true + case InterfaceMarshalerContext: + return true + case InterfaceMarshaler: + return true + case time.Time: + return true + case time.Duration: + return true + case encoding.TextMarshaler: + return true + case jsonMarshaler: + return e.useJSONMarshaler + } + return false +} + +func (e *Encoder) encodeByMarshaler(ctx context.Context, v reflect.Value, column int) (ast.Node, error) { + iface := v.Interface() + + if marshaler, exists := e.marshalerFromCustomMarshalerMap(v.Type()); exists { + doc, err := marshaler(iface) + if err != nil { + return nil, errors.Wrapf(err, "failed to MarshalYAML") + } + node, err := e.encodeDocument(doc) + if err != nil { + return nil, errors.Wrapf(err, "failed to encode document") + } + return node, nil + } + + if marshaler, ok := iface.(BytesMarshalerContext); ok { + doc, err := marshaler.MarshalYAML(ctx) + if err != nil { + return nil, errors.Wrapf(err, "failed to MarshalYAML") + } + node, err := e.encodeDocument(doc) + if err != nil { + return nil, errors.Wrapf(err, "failed to encode document") + } + return node, nil + } + + if marshaler, ok := iface.(BytesMarshaler); ok { + doc, err := marshaler.MarshalYAML() + if err != nil { + return nil, errors.Wrapf(err, "failed to MarshalYAML") + } + node, err := e.encodeDocument(doc) + if err != nil { + return nil, errors.Wrapf(err, "failed to encode document") + } + return node, nil + } + + if marshaler, ok := iface.(InterfaceMarshalerContext); ok { + marshalV, err := marshaler.MarshalYAML(ctx) + if err != nil { + return nil, errors.Wrapf(err, "failed to MarshalYAML") + } + return e.encodeValue(ctx, reflect.ValueOf(marshalV), column) + } + + if marshaler, ok := iface.(InterfaceMarshaler); ok { + marshalV, err := marshaler.MarshalYAML() + if err != nil { + return nil, errors.Wrapf(err, "failed to MarshalYAML") + } + return e.encodeValue(ctx, reflect.ValueOf(marshalV), column) + } + + if t, ok := iface.(time.Time); ok { + return e.encodeTime(t, column), nil + } + + if t, ok := iface.(time.Duration); ok { + return e.encodeDuration(t, column), nil + } + + if marshaler, ok := iface.(encoding.TextMarshaler); ok { + doc, err := marshaler.MarshalText() + if err != nil { + return nil, errors.Wrapf(err, "failed to MarshalText") + } + node, err := e.encodeDocument(doc) + if err != nil { + return nil, errors.Wrapf(err, "failed to encode document") + } + return node, nil + } + + if e.useJSONMarshaler { + if marshaler, ok := iface.(jsonMarshaler); ok { + jsonBytes, err := marshaler.MarshalJSON() + if err != nil { + return nil, errors.Wrapf(err, "failed to MarshalJSON") + } + doc, err := JSONToYAML(jsonBytes) + if err != nil { + return nil, errors.Wrapf(err, "failed to convert json to yaml") + } + node, err := e.encodeDocument(doc) + if err != nil { + return nil, errors.Wrapf(err, "failed to encode document") + } + return node, nil + } + } + + return nil, xerrors.Errorf("does not implemented Marshaler") +} + +func (e *Encoder) encodeValue(ctx context.Context, v reflect.Value, column int) (ast.Node, error) { + if e.isInvalidValue(v) { + return e.encodeNil(), nil + } + if e.canEncodeByMarshaler(v) { + node, err := e.encodeByMarshaler(ctx, v, column) + if err != nil { + return nil, errors.Wrapf(err, "failed to encode by marshaler") + } + return node, nil + } + switch v.Type().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return e.encodeInt(v.Int()), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return e.encodeUint(v.Uint()), nil + case reflect.Float32: + return e.encodeFloat(v.Float(), 32), nil + case reflect.Float64: + return e.encodeFloat(v.Float(), 64), nil + case reflect.Ptr: + anchorName := e.anchorPtrToNameMap[v.Pointer()] + if anchorName != "" { + aliasName := anchorName + alias := ast.Alias(token.New("*", "*", e.pos(column))) + alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column))) + return alias, nil + } + return e.encodeValue(ctx, v.Elem(), column) + case reflect.Interface: + return e.encodeValue(ctx, v.Elem(), column) + case reflect.String: + return e.encodeString(v.String(), column), nil + case reflect.Bool: + return e.encodeBool(v.Bool()), nil + case reflect.Slice: + if mapSlice, ok := v.Interface().(MapSlice); ok { + return e.encodeMapSlice(ctx, mapSlice, column) + } + return e.encodeSlice(ctx, v) + case reflect.Array: + return e.encodeArray(ctx, v) + case reflect.Struct: + if v.CanInterface() { + if mapItem, ok := v.Interface().(MapItem); ok { + return e.encodeMapItem(ctx, mapItem, column) + } + if t, ok := v.Interface().(time.Time); ok { + return e.encodeTime(t, column), nil + } + } + return e.encodeStruct(ctx, v, column) + case reflect.Map: + return e.encodeMap(ctx, v, column), nil + default: + return nil, xerrors.Errorf("unknown value type %s", v.Type().String()) + } +} + +func (e *Encoder) pos(column int) *token.Position { + return &token.Position{ + Line: e.line, + Column: column, + Offset: e.offset, + IndentNum: e.indentNum, + IndentLevel: e.indentLevel, + } +} + +func (e *Encoder) encodeNil() *ast.NullNode { + value := "null" + return ast.Null(token.New(value, value, e.pos(e.column))) +} + +func (e *Encoder) encodeInt(v int64) *ast.IntegerNode { + value := fmt.Sprint(v) + return ast.Integer(token.New(value, value, e.pos(e.column))) +} + +func (e *Encoder) encodeUint(v uint64) *ast.IntegerNode { + value := fmt.Sprint(v) + return ast.Integer(token.New(value, value, e.pos(e.column))) +} + +func (e *Encoder) encodeFloat(v float64, bitSize int) ast.Node { + if v == math.Inf(0) { + value := ".inf" + return ast.Infinity(token.New(value, value, e.pos(e.column))) + } else if v == math.Inf(-1) { + value := "-.inf" + return ast.Infinity(token.New(value, value, e.pos(e.column))) + } else if math.IsNaN(v) { + value := ".nan" + return ast.Nan(token.New(value, value, e.pos(e.column))) + } + value := strconv.FormatFloat(v, 'g', -1, bitSize) + if !strings.Contains(value, ".") && !strings.Contains(value, "e") { + // append x.0 suffix to keep float value context + value = fmt.Sprintf("%s.0", value) + } + return ast.Float(token.New(value, value, e.pos(e.column))) +} + +func (e *Encoder) isNeedQuoted(v string) bool { + if e.isJSONStyle { + return true + } + if e.useLiteralStyleIfMultiline && strings.ContainsAny(v, "\n\r") { + return false + } + if e.isFlowStyle && strings.ContainsAny(v, `]},'"`) { + return true + } + if token.IsNeedQuoted(v) { + return true + } + return false +} + +func (e *Encoder) encodeString(v string, column int) *ast.StringNode { + if e.isNeedQuoted(v) { + if e.singleQuote { + v = quoteWith(v, '\'') + } else { + v = strconv.Quote(v) + } + } + return ast.String(token.New(v, v, e.pos(column))) +} + +func (e *Encoder) encodeBool(v bool) *ast.BoolNode { + value := fmt.Sprint(v) + return ast.Bool(token.New(value, value, e.pos(e.column))) +} + +func (e *Encoder) encodeSlice(ctx context.Context, value reflect.Value) (*ast.SequenceNode, error) { + if e.indentSequence { + e.column += e.indent + } + column := e.column + sequence := ast.Sequence(token.New("-", "-", e.pos(column)), e.isFlowStyle) + for i := 0; i < value.Len(); i++ { + node, err := e.encodeValue(ctx, value.Index(i), column) + if err != nil { + return nil, errors.Wrapf(err, "failed to encode value for slice") + } + sequence.Values = append(sequence.Values, node) + } + if e.indentSequence { + e.column -= e.indent + } + return sequence, nil +} + +func (e *Encoder) encodeArray(ctx context.Context, value reflect.Value) (*ast.SequenceNode, error) { + if e.indentSequence { + e.column += e.indent + } + column := e.column + sequence := ast.Sequence(token.New("-", "-", e.pos(column)), e.isFlowStyle) + for i := 0; i < value.Len(); i++ { + node, err := e.encodeValue(ctx, value.Index(i), column) + if err != nil { + return nil, errors.Wrapf(err, "failed to encode value for array") + } + sequence.Values = append(sequence.Values, node) + } + if e.indentSequence { + e.column -= e.indent + } + return sequence, nil +} + +func (e *Encoder) encodeMapItem(ctx context.Context, item MapItem, column int) (*ast.MappingValueNode, error) { + k := reflect.ValueOf(item.Key) + v := reflect.ValueOf(item.Value) + value, err := e.encodeValue(ctx, v, column) + if err != nil { + return nil, errors.Wrapf(err, "failed to encode MapItem") + } + if e.isMapNode(value) { + value.AddColumn(e.indent) + } + return ast.MappingValue( + token.New("", "", e.pos(column)), + e.encodeString(k.Interface().(string), column), + value, + ), nil +} + +func (e *Encoder) encodeMapSlice(ctx context.Context, value MapSlice, column int) (*ast.MappingNode, error) { + node := ast.Mapping(token.New("", "", e.pos(column)), e.isFlowStyle) + for _, item := range value { + value, err := e.encodeMapItem(ctx, item, column) + if err != nil { + return nil, errors.Wrapf(err, "failed to encode MapItem for MapSlice") + } + node.Values = append(node.Values, value) + } + return node, nil +} + +func (e *Encoder) isMapNode(node ast.Node) bool { + _, ok := node.(ast.MapNode) + return ok +} + +func (e *Encoder) encodeMap(ctx context.Context, value reflect.Value, column int) ast.Node { + node := ast.Mapping(token.New("", "", e.pos(column)), e.isFlowStyle) + keys := make([]interface{}, len(value.MapKeys())) + for i, k := range value.MapKeys() { + keys[i] = k.Interface() + } + sort.Slice(keys, func(i, j int) bool { + return fmt.Sprint(keys[i]) < fmt.Sprint(keys[j]) + }) + for _, key := range keys { + k := reflect.ValueOf(key) + v := value.MapIndex(k) + value, err := e.encodeValue(ctx, v, column) + if err != nil { + return nil + } + if e.isMapNode(value) { + value.AddColumn(e.indent) + } + node.Values = append(node.Values, ast.MappingValue( + nil, + e.encodeString(fmt.Sprint(key), column), + value, + )) + } + return node +} + +// IsZeroer is used to check whether an object is zero to determine +// whether it should be omitted when marshaling with the omitempty flag. +// One notable implementation is time.Time. +type IsZeroer interface { + IsZero() bool +} + +func (e *Encoder) isZeroValue(v reflect.Value) bool { + kind := v.Kind() + if z, ok := v.Interface().(IsZeroer); ok { + if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() { + return true + } + return z.IsZero() + } + switch kind { + case reflect.String: + return len(v.String()) == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Slice: + return v.Len() == 0 + case reflect.Map: + return v.Len() == 0 + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Struct: + vt := v.Type() + for i := v.NumField() - 1; i >= 0; i-- { + if vt.Field(i).PkgPath != "" { + continue // private field + } + if !e.isZeroValue(v.Field(i)) { + return false + } + } + return true + } + return false +} + +func (e *Encoder) encodeTime(v time.Time, column int) *ast.StringNode { + value := v.Format(time.RFC3339Nano) + if e.isJSONStyle { + value = strconv.Quote(value) + } + return ast.String(token.New(value, value, e.pos(column))) +} + +func (e *Encoder) encodeDuration(v time.Duration, column int) *ast.StringNode { + value := v.String() + if e.isJSONStyle { + value = strconv.Quote(value) + } + return ast.String(token.New(value, value, e.pos(column))) +} + +func (e *Encoder) encodeAnchor(anchorName string, value ast.Node, fieldValue reflect.Value, column int) (*ast.AnchorNode, error) { + anchorNode := ast.Anchor(token.New("&", "&", e.pos(column))) + anchorNode.Name = ast.String(token.New(anchorName, anchorName, e.pos(column))) + anchorNode.Value = value + if e.anchorCallback != nil { + if err := e.anchorCallback(anchorNode, fieldValue.Interface()); err != nil { + return nil, errors.Wrapf(err, "failed to marshal anchor") + } + if snode, ok := anchorNode.Name.(*ast.StringNode); ok { + anchorName = snode.Value + } + } + if fieldValue.Kind() == reflect.Ptr { + e.anchorPtrToNameMap[fieldValue.Pointer()] = anchorName + } + return anchorNode, nil +} + +func (e *Encoder) encodeStruct(ctx context.Context, value reflect.Value, column int) (ast.Node, error) { + node := ast.Mapping(token.New("", "", e.pos(column)), e.isFlowStyle) + structType := value.Type() + structFieldMap, err := structFieldMap(structType) + if err != nil { + return nil, errors.Wrapf(err, "failed to get struct field map") + } + hasInlineAnchorField := false + var inlineAnchorValue reflect.Value + for i := 0; i < value.NumField(); i++ { + field := structType.Field(i) + if isIgnoredStructField(field) { + continue + } + fieldValue := value.FieldByName(field.Name) + structField := structFieldMap[field.Name] + if structField.IsOmitEmpty && e.isZeroValue(fieldValue) { + // omit encoding + continue + } + ve := e + if !e.isFlowStyle && structField.IsFlow { + ve = &Encoder{} + *ve = *e + ve.isFlowStyle = true + } + value, err := ve.encodeValue(ctx, fieldValue, column) + if err != nil { + return nil, errors.Wrapf(err, "failed to encode value") + } + if e.isMapNode(value) { + value.AddColumn(e.indent) + } + var key ast.MapKeyNode = e.encodeString(structField.RenderName, column) + switch { + case structField.AnchorName != "": + anchorNode, err := e.encodeAnchor(structField.AnchorName, value, fieldValue, column) + if err != nil { + return nil, errors.Wrapf(err, "failed to encode anchor") + } + value = anchorNode + case structField.IsAutoAlias: + if fieldValue.Kind() != reflect.Ptr { + return nil, xerrors.Errorf( + "%s in struct is not pointer type. but required automatically alias detection", + structField.FieldName, + ) + } + anchorName := e.anchorPtrToNameMap[fieldValue.Pointer()] + if anchorName == "" { + return nil, xerrors.Errorf( + "cannot find anchor name from pointer address for automatically alias detection", + ) + } + aliasName := anchorName + alias := ast.Alias(token.New("*", "*", e.pos(column))) + alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column))) + value = alias + if structField.IsInline { + // if both used alias and inline, output `<<: *alias` + key = ast.MergeKey(token.New("<<", "<<", e.pos(column))) + } + case structField.AliasName != "": + aliasName := structField.AliasName + alias := ast.Alias(token.New("*", "*", e.pos(column))) + alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column))) + value = alias + if structField.IsInline { + // if both used alias and inline, output `<<: *alias` + key = ast.MergeKey(token.New("<<", "<<", e.pos(column))) + } + case structField.IsInline: + isAutoAnchor := structField.IsAutoAnchor + if !hasInlineAnchorField { + hasInlineAnchorField = isAutoAnchor + } + if isAutoAnchor { + inlineAnchorValue = fieldValue + } + mapNode, ok := value.(ast.MapNode) + if !ok { + return nil, xerrors.Errorf("inline value is must be map or struct type") + } + mapIter := mapNode.MapRange() + for mapIter.Next() { + key := mapIter.Key() + value := mapIter.Value() + keyName := key.GetToken().Value + if structFieldMap.isIncludedRenderName(keyName) { + // if declared same key name, skip encoding this field + continue + } + key.AddColumn(-e.indent) + value.AddColumn(-e.indent) + node.Values = append(node.Values, ast.MappingValue(nil, key, value)) + } + continue + case structField.IsAutoAnchor: + anchorNode, err := e.encodeAnchor(structField.RenderName, value, fieldValue, column) + if err != nil { + return nil, errors.Wrapf(err, "failed to encode anchor") + } + value = anchorNode + } + node.Values = append(node.Values, ast.MappingValue(nil, key, value)) + } + if hasInlineAnchorField { + node.AddColumn(e.indent) + anchorName := "anchor" + anchorNode := ast.Anchor(token.New("&", "&", e.pos(column))) + anchorNode.Name = ast.String(token.New(anchorName, anchorName, e.pos(column))) + anchorNode.Value = node + if e.anchorCallback != nil { + if err := e.anchorCallback(anchorNode, value.Addr().Interface()); err != nil { + return nil, errors.Wrapf(err, "failed to marshal anchor") + } + if snode, ok := anchorNode.Name.(*ast.StringNode); ok { + anchorName = snode.Value + } + } + if inlineAnchorValue.Kind() == reflect.Ptr { + e.anchorPtrToNameMap[inlineAnchorValue.Pointer()] = anchorName + } + return anchorNode, nil + } + return node, nil +} diff --git a/vendor/github.com/goccy/go-yaml/error.go b/vendor/github.com/goccy/go-yaml/error.go new file mode 100644 index 000000000..163dcc558 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/error.go @@ -0,0 +1,62 @@ +package yaml + +import ( + "github.com/goccy/go-yaml/ast" + "golang.org/x/xerrors" +) + +var ( + ErrInvalidQuery = xerrors.New("invalid query") + ErrInvalidPath = xerrors.New("invalid path instance") + ErrInvalidPathString = xerrors.New("invalid path string") + ErrNotFoundNode = xerrors.New("node not found") + ErrUnknownCommentPositionType = xerrors.New("unknown comment position type") + ErrInvalidCommentMapValue = xerrors.New("invalid comment map value. it must be not nil value") +) + +func ErrUnsupportedHeadPositionType(node ast.Node) error { + return xerrors.Errorf("unsupported comment head position for %s", node.Type()) +} + +func ErrUnsupportedLinePositionType(node ast.Node) error { + return xerrors.Errorf("unsupported comment line position for %s", node.Type()) +} + +func ErrUnsupportedFootPositionType(node ast.Node) error { + return xerrors.Errorf("unsupported comment foot position for %s", node.Type()) +} + +// IsInvalidQueryError whether err is ErrInvalidQuery or not. +func IsInvalidQueryError(err error) bool { + return xerrors.Is(err, ErrInvalidQuery) +} + +// IsInvalidPathError whether err is ErrInvalidPath or not. +func IsInvalidPathError(err error) bool { + return xerrors.Is(err, ErrInvalidPath) +} + +// IsInvalidPathStringError whether err is ErrInvalidPathString or not. +func IsInvalidPathStringError(err error) bool { + return xerrors.Is(err, ErrInvalidPathString) +} + +// IsNotFoundNodeError whether err is ErrNotFoundNode or not. +func IsNotFoundNodeError(err error) bool { + return xerrors.Is(err, ErrNotFoundNode) +} + +// IsInvalidTokenTypeError whether err is ast.ErrInvalidTokenType or not. +func IsInvalidTokenTypeError(err error) bool { + return xerrors.Is(err, ast.ErrInvalidTokenType) +} + +// IsInvalidAnchorNameError whether err is ast.ErrInvalidAnchorName or not. +func IsInvalidAnchorNameError(err error) bool { + return xerrors.Is(err, ast.ErrInvalidAnchorName) +} + +// IsInvalidAliasNameError whether err is ast.ErrInvalidAliasName or not. +func IsInvalidAliasNameError(err error) bool { + return xerrors.Is(err, ast.ErrInvalidAliasName) +} diff --git a/vendor/github.com/goccy/go-yaml/internal/errors/error.go b/vendor/github.com/goccy/go-yaml/internal/errors/error.go new file mode 100644 index 000000000..7f1ea9af7 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/internal/errors/error.go @@ -0,0 +1,260 @@ +package errors + +import ( + "bytes" + "fmt" + "reflect" + + "github.com/goccy/go-yaml/printer" + "github.com/goccy/go-yaml/token" + "golang.org/x/xerrors" +) + +const ( + defaultColorize = false + defaultIncludeSource = true +) + +var ( + ErrDecodeRequiredPointerType = xerrors.New("required pointer type value") +) + +// Wrapf wrap error for stack trace +func Wrapf(err error, msg string, args ...interface{}) error { + return &wrapError{ + baseError: &baseError{}, + err: xerrors.Errorf(msg, args...), + nextErr: err, + frame: xerrors.Caller(1), + } +} + +// ErrSyntax create syntax error instance with message and token +func ErrSyntax(msg string, tk *token.Token) *syntaxError { + return &syntaxError{ + baseError: &baseError{}, + msg: msg, + token: tk, + frame: xerrors.Caller(1), + } +} + +type baseError struct { + state fmt.State + verb rune +} + +func (e *baseError) Error() string { + return "" +} + +func (e *baseError) chainStateAndVerb(err error) { + wrapErr, ok := err.(*wrapError) + if ok { + wrapErr.state = e.state + wrapErr.verb = e.verb + } + syntaxErr, ok := err.(*syntaxError) + if ok { + syntaxErr.state = e.state + syntaxErr.verb = e.verb + } +} + +type wrapError struct { + *baseError + err error + nextErr error + frame xerrors.Frame +} + +type FormatErrorPrinter struct { + xerrors.Printer + Colored bool + InclSource bool +} + +func (e *wrapError) As(target interface{}) bool { + err := e.nextErr + for { + if wrapErr, ok := err.(*wrapError); ok { + err = wrapErr.nextErr + continue + } + break + } + return xerrors.As(err, target) +} + +func (e *wrapError) Unwrap() error { + return e.nextErr +} + +func (e *wrapError) PrettyPrint(p xerrors.Printer, colored, inclSource bool) error { + return e.FormatError(&FormatErrorPrinter{Printer: p, Colored: colored, InclSource: inclSource}) +} + +func (e *wrapError) FormatError(p xerrors.Printer) error { + if _, ok := p.(*FormatErrorPrinter); !ok { + p = &FormatErrorPrinter{ + Printer: p, + Colored: defaultColorize, + InclSource: defaultIncludeSource, + } + } + if e.verb == 'v' && e.state.Flag('+') { + // print stack trace for debugging + p.Print(e.err, "\n") + e.frame.Format(p) + e.chainStateAndVerb(e.nextErr) + return e.nextErr + } + err := e.nextErr + for { + if wrapErr, ok := err.(*wrapError); ok { + err = wrapErr.nextErr + continue + } + break + } + e.chainStateAndVerb(err) + if fmtErr, ok := err.(xerrors.Formatter); ok { + fmtErr.FormatError(p) + } else { + p.Print(err) + } + return nil +} + +type wrapState struct { + org fmt.State +} + +func (s *wrapState) Write(b []byte) (n int, err error) { + return s.org.Write(b) +} + +func (s *wrapState) Width() (wid int, ok bool) { + return s.org.Width() +} + +func (s *wrapState) Precision() (prec int, ok bool) { + return s.org.Precision() +} + +func (s *wrapState) Flag(c int) bool { + // set true to 'printDetail' forced because when p.Detail() is false, xerrors.Printer no output any text + if c == '#' { + // ignore '#' keyword because xerrors.FormatError doesn't set true to printDetail. + // ( see https://github.com/golang/xerrors/blob/master/adaptor.go#L39-L43 ) + return false + } + return true +} + +func (e *wrapError) Format(state fmt.State, verb rune) { + e.state = state + e.verb = verb + xerrors.FormatError(e, &wrapState{org: state}, verb) +} + +func (e *wrapError) Error() string { + var buf bytes.Buffer + e.PrettyPrint(&Sink{&buf}, defaultColorize, defaultIncludeSource) + return buf.String() +} + +type syntaxError struct { + *baseError + msg string + token *token.Token + frame xerrors.Frame +} + +func (e *syntaxError) PrettyPrint(p xerrors.Printer, colored, inclSource bool) error { + return e.FormatError(&FormatErrorPrinter{Printer: p, Colored: colored, InclSource: inclSource}) +} + +func (e *syntaxError) FormatError(p xerrors.Printer) error { + var pp printer.Printer + + var colored, inclSource bool + if fep, ok := p.(*FormatErrorPrinter); ok { + colored = fep.Colored + inclSource = fep.InclSource + } + + pos := fmt.Sprintf("[%d:%d] ", e.token.Position.Line, e.token.Position.Column) + msg := pp.PrintErrorMessage(fmt.Sprintf("%s%s", pos, e.msg), colored) + if inclSource { + msg += "\n" + pp.PrintErrorToken(e.token, colored) + } + p.Print(msg) + + if e.verb == 'v' && e.state.Flag('+') { + // %+v + // print stack trace for debugging + e.frame.Format(p) + } + return nil +} + +type PrettyPrinter interface { + PrettyPrint(xerrors.Printer, bool, bool) error +} +type Sink struct{ *bytes.Buffer } + +func (es *Sink) Print(args ...interface{}) { + fmt.Fprint(es.Buffer, args...) +} + +func (es *Sink) Printf(f string, args ...interface{}) { + fmt.Fprintf(es.Buffer, f, args...) +} + +func (es *Sink) Detail() bool { + return false +} + +func (e *syntaxError) Error() string { + var buf bytes.Buffer + e.PrettyPrint(&Sink{&buf}, defaultColorize, defaultIncludeSource) + return buf.String() +} + +type TypeError struct { + DstType reflect.Type + SrcType reflect.Type + StructFieldName *string + Token *token.Token +} + +func (e *TypeError) Error() string { + if e.StructFieldName != nil { + return fmt.Sprintf("cannot unmarshal %s into Go struct field %s of type %s", e.SrcType, *e.StructFieldName, e.DstType) + } + return fmt.Sprintf("cannot unmarshal %s into Go value of type %s", e.SrcType, e.DstType) +} + +func (e *TypeError) PrettyPrint(p xerrors.Printer, colored, inclSource bool) error { + return e.FormatError(&FormatErrorPrinter{Printer: p, Colored: colored, InclSource: inclSource}) +} + +func (e *TypeError) FormatError(p xerrors.Printer) error { + var pp printer.Printer + + var colored, inclSource bool + if fep, ok := p.(*FormatErrorPrinter); ok { + colored = fep.Colored + inclSource = fep.InclSource + } + + pos := fmt.Sprintf("[%d:%d] ", e.Token.Position.Line, e.Token.Position.Column) + msg := pp.PrintErrorMessage(fmt.Sprintf("%s%s", pos, e.Error()), colored) + if inclSource { + msg += "\n" + pp.PrintErrorToken(e.Token, colored) + } + p.Print(msg) + + return nil +} diff --git a/vendor/github.com/goccy/go-yaml/lexer/lexer.go b/vendor/github.com/goccy/go-yaml/lexer/lexer.go new file mode 100644 index 000000000..3207f4f22 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/lexer/lexer.go @@ -0,0 +1,23 @@ +package lexer + +import ( + "io" + + "github.com/goccy/go-yaml/scanner" + "github.com/goccy/go-yaml/token" +) + +// Tokenize split to token instances from string +func Tokenize(src string) token.Tokens { + var s scanner.Scanner + s.Init(src) + var tokens token.Tokens + for { + subTokens, err := s.Scan() + if err == io.EOF { + break + } + tokens.Add(subTokens...) + } + return tokens +} diff --git a/vendor/github.com/goccy/go-yaml/option.go b/vendor/github.com/goccy/go-yaml/option.go new file mode 100644 index 000000000..eab5d43a8 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/option.go @@ -0,0 +1,278 @@ +package yaml + +import ( + "io" + "reflect" + + "github.com/goccy/go-yaml/ast" +) + +// DecodeOption functional option type for Decoder +type DecodeOption func(d *Decoder) error + +// ReferenceReaders pass to Decoder that reference to anchor defined by passed readers +func ReferenceReaders(readers ...io.Reader) DecodeOption { + return func(d *Decoder) error { + d.referenceReaders = append(d.referenceReaders, readers...) + return nil + } +} + +// ReferenceFiles pass to Decoder that reference to anchor defined by passed files +func ReferenceFiles(files ...string) DecodeOption { + return func(d *Decoder) error { + d.referenceFiles = files + return nil + } +} + +// ReferenceDirs pass to Decoder that reference to anchor defined by files under the passed dirs +func ReferenceDirs(dirs ...string) DecodeOption { + return func(d *Decoder) error { + d.referenceDirs = dirs + return nil + } +} + +// RecursiveDir search yaml file recursively from passed dirs by ReferenceDirs option +func RecursiveDir(isRecursive bool) DecodeOption { + return func(d *Decoder) error { + d.isRecursiveDir = isRecursive + return nil + } +} + +// Validator set StructValidator instance to Decoder +func Validator(v StructValidator) DecodeOption { + return func(d *Decoder) error { + d.validator = v + return nil + } +} + +// Strict enable DisallowUnknownField and DisallowDuplicateKey +func Strict() DecodeOption { + return func(d *Decoder) error { + d.disallowUnknownField = true + d.disallowDuplicateKey = true + return nil + } +} + +// DisallowUnknownField causes the Decoder to return an error when the destination +// is a struct and the input contains object keys which do not match any +// non-ignored, exported fields in the destination. +func DisallowUnknownField() DecodeOption { + return func(d *Decoder) error { + d.disallowUnknownField = true + return nil + } +} + +// DisallowDuplicateKey causes an error when mapping keys that are duplicates +func DisallowDuplicateKey() DecodeOption { + return func(d *Decoder) error { + d.disallowDuplicateKey = true + return nil + } +} + +// UseOrderedMap can be interpreted as a map, +// and uses MapSlice ( ordered map ) aggressively if there is no type specification +func UseOrderedMap() DecodeOption { + return func(d *Decoder) error { + d.useOrderedMap = true + return nil + } +} + +// UseJSONUnmarshaler if neither `BytesUnmarshaler` nor `InterfaceUnmarshaler` is implemented +// and `UnmashalJSON([]byte)error` is implemented, convert the argument from `YAML` to `JSON` and then call it. +func UseJSONUnmarshaler() DecodeOption { + return func(d *Decoder) error { + d.useJSONUnmarshaler = true + return nil + } +} + +// CustomUnmarshaler overrides any decoding process for the type specified in generics. +// +// NOTE: If RegisterCustomUnmarshaler and CustomUnmarshaler of DecodeOption are specified for the same type, +// the CustomUnmarshaler specified in DecodeOption takes precedence. +func CustomUnmarshaler[T any](unmarshaler func(*T, []byte) error) DecodeOption { + return func(d *Decoder) error { + var typ *T + d.customUnmarshalerMap[reflect.TypeOf(typ)] = func(v interface{}, b []byte) error { + return unmarshaler(v.(*T), b) + } + return nil + } +} + +// EncodeOption functional option type for Encoder +type EncodeOption func(e *Encoder) error + +// Indent change indent number +func Indent(spaces int) EncodeOption { + return func(e *Encoder) error { + e.indent = spaces + return nil + } +} + +// IndentSequence causes sequence values to be indented the same value as Indent +func IndentSequence(indent bool) EncodeOption { + return func(e *Encoder) error { + e.indentSequence = indent + return nil + } +} + +// UseSingleQuote determines if single or double quotes should be preferred for strings. +func UseSingleQuote(sq bool) EncodeOption { + return func(e *Encoder) error { + e.singleQuote = sq + return nil + } +} + +// Flow encoding by flow style +func Flow(isFlowStyle bool) EncodeOption { + return func(e *Encoder) error { + e.isFlowStyle = isFlowStyle + return nil + } +} + +// UseLiteralStyleIfMultiline causes encoding multiline strings with a literal syntax, +// no matter what characters they include +func UseLiteralStyleIfMultiline(useLiteralStyleIfMultiline bool) EncodeOption { + return func(e *Encoder) error { + e.useLiteralStyleIfMultiline = useLiteralStyleIfMultiline + return nil + } +} + +// JSON encode in JSON format +func JSON() EncodeOption { + return func(e *Encoder) error { + e.isJSONStyle = true + e.isFlowStyle = true + return nil + } +} + +// MarshalAnchor call back if encoder find an anchor during encoding +func MarshalAnchor(callback func(*ast.AnchorNode, interface{}) error) EncodeOption { + return func(e *Encoder) error { + e.anchorCallback = callback + return nil + } +} + +// UseJSONMarshaler if neither `BytesMarshaler` nor `InterfaceMarshaler` +// nor `encoding.TextMarshaler` is implemented and `MarshalJSON()([]byte, error)` is implemented, +// call `MarshalJSON` to convert the returned `JSON` to `YAML` for processing. +func UseJSONMarshaler() EncodeOption { + return func(e *Encoder) error { + e.useJSONMarshaler = true + return nil + } +} + +// CustomMarshaler overrides any encoding process for the type specified in generics. +// +// NOTE: If type T implements MarshalYAML for pointer receiver, the type specified in CustomMarshaler must be *T. +// If RegisterCustomMarshaler and CustomMarshaler of EncodeOption are specified for the same type, +// the CustomMarshaler specified in EncodeOption takes precedence. +func CustomMarshaler[T any](marshaler func(T) ([]byte, error)) EncodeOption { + return func(e *Encoder) error { + var typ T + e.customMarshalerMap[reflect.TypeOf(typ)] = func(v interface{}) ([]byte, error) { + return marshaler(v.(T)) + } + return nil + } +} + +// CommentPosition type of the position for comment. +type CommentPosition int + +const ( + CommentHeadPosition CommentPosition = CommentPosition(iota) + CommentLinePosition + CommentFootPosition +) + +func (p CommentPosition) String() string { + switch p { + case CommentHeadPosition: + return "Head" + case CommentLinePosition: + return "Line" + case CommentFootPosition: + return "Foot" + default: + return "" + } +} + +// LineComment create a one-line comment for CommentMap. +func LineComment(text string) *Comment { + return &Comment{ + Texts: []string{text}, + Position: CommentLinePosition, + } +} + +// HeadComment create a multiline comment for CommentMap. +func HeadComment(texts ...string) *Comment { + return &Comment{ + Texts: texts, + Position: CommentHeadPosition, + } +} + +// FootComment create a multiline comment for CommentMap. +func FootComment(texts ...string) *Comment { + return &Comment{ + Texts: texts, + Position: CommentFootPosition, + } +} + +// Comment raw data for comment. +type Comment struct { + Texts []string + Position CommentPosition +} + +// CommentMap map of the position of the comment and the comment information. +type CommentMap map[string][]*Comment + +// WithComment add a comment using the location and text information given in the CommentMap. +func WithComment(cm CommentMap) EncodeOption { + return func(e *Encoder) error { + commentMap := map[*Path][]*Comment{} + for k, v := range cm { + path, err := PathString(k) + if err != nil { + return err + } + commentMap[path] = v + } + e.commentMap = commentMap + return nil + } +} + +// CommentToMap apply the position and content of comments in a YAML document to a CommentMap. +func CommentToMap(cm CommentMap) DecodeOption { + return func(d *Decoder) error { + if cm == nil { + return ErrInvalidCommentMapValue + } + d.toCommentMap = cm + return nil + } +} diff --git a/vendor/github.com/goccy/go-yaml/parser/context.go b/vendor/github.com/goccy/go-yaml/parser/context.go new file mode 100644 index 000000000..99f18b184 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/parser/context.go @@ -0,0 +1,199 @@ +package parser + +import ( + "fmt" + "strings" + + "github.com/goccy/go-yaml/token" +) + +// context context at parsing +type context struct { + parent *context + idx int + size int + tokens token.Tokens + mode Mode + path string +} + +var pathSpecialChars = []string{ + "$", "*", ".", "[", "]", +} + +func containsPathSpecialChar(path string) bool { + for _, char := range pathSpecialChars { + if strings.Contains(path, char) { + return true + } + } + return false +} + +func normalizePath(path string) string { + if containsPathSpecialChar(path) { + return fmt.Sprintf("'%s'", path) + } + return path +} + +func (c *context) withChild(path string) *context { + ctx := c.copy() + path = normalizePath(path) + ctx.path += fmt.Sprintf(".%s", path) + return ctx +} + +func (c *context) withIndex(idx uint) *context { + ctx := c.copy() + ctx.path += fmt.Sprintf("[%d]", idx) + return ctx +} + +func (c *context) copy() *context { + return &context{ + parent: c, + idx: c.idx, + size: c.size, + tokens: append(token.Tokens{}, c.tokens...), + mode: c.mode, + path: c.path, + } +} + +func (c *context) next() bool { + return c.idx < c.size +} + +func (c *context) previousToken() *token.Token { + if c.idx > 0 { + return c.tokens[c.idx-1] + } + return nil +} + +func (c *context) insertToken(idx int, tk *token.Token) { + if c.parent != nil { + c.parent.insertToken(idx, tk) + } + if c.size < idx { + return + } + if c.size == idx { + curToken := c.tokens[c.size-1] + tk.Next = curToken + curToken.Prev = tk + + c.tokens = append(c.tokens, tk) + c.size = len(c.tokens) + return + } + + curToken := c.tokens[idx] + tk.Next = curToken + curToken.Prev = tk + + c.tokens = append(c.tokens[:idx+1], c.tokens[idx:]...) + c.tokens[idx] = tk + c.size = len(c.tokens) +} + +func (c *context) currentToken() *token.Token { + if c.idx >= c.size { + return nil + } + return c.tokens[c.idx] +} + +func (c *context) nextToken() *token.Token { + if c.idx+1 >= c.size { + return nil + } + return c.tokens[c.idx+1] +} + +func (c *context) afterNextToken() *token.Token { + if c.idx+2 >= c.size { + return nil + } + return c.tokens[c.idx+2] +} + +func (c *context) nextNotCommentToken() *token.Token { + for i := c.idx + 1; i < c.size; i++ { + tk := c.tokens[i] + if tk.Type == token.CommentType { + continue + } + return tk + } + return nil +} + +func (c *context) afterNextNotCommentToken() *token.Token { + notCommentTokenCount := 0 + for i := c.idx + 1; i < c.size; i++ { + tk := c.tokens[i] + if tk.Type == token.CommentType { + continue + } + notCommentTokenCount++ + if notCommentTokenCount == 2 { + return tk + } + } + return nil +} + +func (c *context) enabledComment() bool { + return c.mode&ParseComments != 0 +} + +func (c *context) isCurrentCommentToken() bool { + tk := c.currentToken() + if tk == nil { + return false + } + return tk.Type == token.CommentType +} + +func (c *context) progressIgnoreComment(num int) { + if c.parent != nil { + c.parent.progressIgnoreComment(num) + } + if c.size <= c.idx+num { + c.idx = c.size + } else { + c.idx += num + } +} + +func (c *context) progress(num int) { + if c.isCurrentCommentToken() { + return + } + c.progressIgnoreComment(num) +} + +func newContext(tokens token.Tokens, mode Mode) *context { + filteredTokens := []*token.Token{} + if mode&ParseComments != 0 { + filteredTokens = tokens + } else { + for _, tk := range tokens { + if tk.Type == token.CommentType { + continue + } + // keep prev/next reference between tokens containing comments + // https://github.com/goccy/go-yaml/issues/254 + filteredTokens = append(filteredTokens, tk) + } + } + return &context{ + idx: 0, + size: len(filteredTokens), + tokens: token.Tokens(filteredTokens), + mode: mode, + path: "$", + } +} diff --git a/vendor/github.com/goccy/go-yaml/parser/parser.go b/vendor/github.com/goccy/go-yaml/parser/parser.go new file mode 100644 index 000000000..568e6ad42 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/parser/parser.go @@ -0,0 +1,714 @@ +package parser + +import ( + "fmt" + "io/ioutil" + "strings" + + "github.com/goccy/go-yaml/ast" + "github.com/goccy/go-yaml/internal/errors" + "github.com/goccy/go-yaml/lexer" + "github.com/goccy/go-yaml/token" + "golang.org/x/xerrors" +) + +type parser struct{} + +func (p *parser) parseMapping(ctx *context) (*ast.MappingNode, error) { + mapTk := ctx.currentToken() + node := ast.Mapping(mapTk, true) + node.SetPath(ctx.path) + ctx.progress(1) // skip MappingStart token + for ctx.next() { + tk := ctx.currentToken() + if tk.Type == token.MappingEndType { + node.End = tk + return node, nil + } else if tk.Type == token.CollectEntryType { + ctx.progress(1) + continue + } + + value, err := p.parseMappingValue(ctx) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse mapping value in mapping node") + } + mvnode, ok := value.(*ast.MappingValueNode) + if !ok { + return nil, errors.ErrSyntax("failed to parse flow mapping node", value.GetToken()) + } + node.Values = append(node.Values, mvnode) + ctx.progress(1) + } + return nil, errors.ErrSyntax("unterminated flow mapping", node.GetToken()) +} + +func (p *parser) parseSequence(ctx *context) (*ast.SequenceNode, error) { + node := ast.Sequence(ctx.currentToken(), true) + node.SetPath(ctx.path) + ctx.progress(1) // skip SequenceStart token + for ctx.next() { + tk := ctx.currentToken() + if tk.Type == token.SequenceEndType { + node.End = tk + break + } else if tk.Type == token.CollectEntryType { + ctx.progress(1) + continue + } + + value, err := p.parseToken(ctx.withIndex(uint(len(node.Values))), tk) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse sequence value in flow sequence node") + } + node.Values = append(node.Values, value) + ctx.progress(1) + } + return node, nil +} + +func (p *parser) parseTag(ctx *context) (*ast.TagNode, error) { + tagToken := ctx.currentToken() + node := ast.Tag(tagToken) + node.SetPath(ctx.path) + ctx.progress(1) // skip tag token + var ( + value ast.Node + err error + ) + switch token.ReservedTagKeyword(tagToken.Value) { + case token.MappingTag, + token.OrderedMapTag: + value, err = p.parseMapping(ctx) + case token.IntegerTag, + token.FloatTag, + token.StringTag, + token.BinaryTag, + token.TimestampTag, + token.NullTag: + typ := ctx.currentToken().Type + if typ == token.LiteralType || typ == token.FoldedType { + value, err = p.parseLiteral(ctx) + } else { + value = p.parseScalarValue(ctx.currentToken()) + } + case token.SequenceTag, + token.SetTag: + err = errors.ErrSyntax(fmt.Sprintf("sorry, currently not supported %s tag", tagToken.Value), tagToken) + default: + // custom tag + value, err = p.parseToken(ctx, ctx.currentToken()) + } + if err != nil { + return nil, errors.Wrapf(err, "failed to parse tag value") + } + node.Value = value + return node, nil +} + +func (p *parser) removeLeftSideNewLineCharacter(src string) string { + // CR or LF or CRLF + return strings.TrimLeft(strings.TrimLeft(strings.TrimLeft(src, "\r"), "\n"), "\r\n") +} + +func (p *parser) existsNewLineCharacter(src string) bool { + if strings.Index(src, "\n") > 0 { + return true + } + if strings.Index(src, "\r") > 0 { + return true + } + return false +} + +func (p *parser) validateMapKey(tk *token.Token) error { + if tk.Type != token.StringType { + return nil + } + origin := p.removeLeftSideNewLineCharacter(tk.Origin) + if p.existsNewLineCharacter(origin) { + return errors.ErrSyntax("unexpected key name", tk) + } + return nil +} + +func (p *parser) createNullToken(base *token.Token) *token.Token { + pos := *(base.Position) + pos.Column++ + return token.New("null", "null", &pos) +} + +func (p *parser) parseMapValue(ctx *context, key ast.MapKeyNode, colonToken *token.Token) (ast.Node, error) { + node, err := p.createMapValueNode(ctx, key, colonToken) + if err != nil { + return nil, errors.Wrapf(err, "failed to create map value node") + } + if node != nil && node.GetPath() == "" { + node.SetPath(ctx.path) + } + return node, nil +} + +func (p *parser) createMapValueNode(ctx *context, key ast.MapKeyNode, colonToken *token.Token) (ast.Node, error) { + tk := ctx.currentToken() + if tk == nil { + nullToken := p.createNullToken(colonToken) + ctx.insertToken(ctx.idx, nullToken) + return ast.Null(nullToken), nil + } + + if tk.Position.Column == key.GetToken().Position.Column && tk.Type == token.StringType { + // in this case, + // ---- + // key: + // next + nullToken := p.createNullToken(colonToken) + ctx.insertToken(ctx.idx, nullToken) + return ast.Null(nullToken), nil + } + + if tk.Position.Column < key.GetToken().Position.Column { + // in this case, + // ---- + // key: + // next + nullToken := p.createNullToken(colonToken) + ctx.insertToken(ctx.idx, nullToken) + return ast.Null(nullToken), nil + } + + value, err := p.parseToken(ctx, ctx.currentToken()) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse mapping 'value' node") + } + return value, nil +} + +func (p *parser) validateMapValue(ctx *context, key, value ast.Node) error { + keyColumn := key.GetToken().Position.Column + valueColumn := value.GetToken().Position.Column + if keyColumn != valueColumn { + return nil + } + if value.Type() != ast.StringType { + return nil + } + ntk := ctx.nextToken() + if ntk == nil || (ntk.Type != token.MappingValueType && ntk.Type != token.SequenceEntryType) { + return errors.ErrSyntax("could not found expected ':' token", value.GetToken()) + } + return nil +} + +func (p *parser) parseMappingValue(ctx *context) (ast.Node, error) { + key, err := p.parseMapKey(ctx) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse map key") + } + keyText := key.GetToken().Value + key.SetPath(ctx.withChild(keyText).path) + if err := p.validateMapKey(key.GetToken()); err != nil { + return nil, errors.Wrapf(err, "validate mapping key error") + } + ctx.progress(1) // progress to mapping value token + tk := ctx.currentToken() // get mapping value token + if tk == nil { + return nil, errors.ErrSyntax("unexpected map", key.GetToken()) + } + ctx.progress(1) // progress to value token + if err := p.setSameLineCommentIfExists(ctx.withChild(keyText), key); err != nil { + return nil, errors.Wrapf(err, "failed to set same line comment to node") + } + if key.GetComment() != nil { + // if current token is comment, GetComment() is not nil. + // then progress to value token + ctx.progressIgnoreComment(1) + } + + value, err := p.parseMapValue(ctx.withChild(keyText), key, tk) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse map value") + } + if err := p.validateMapValue(ctx, key, value); err != nil { + return nil, errors.Wrapf(err, "failed to validate map value") + } + + mvnode := ast.MappingValue(tk, key, value) + mvnode.SetPath(ctx.withChild(keyText).path) + node := ast.Mapping(tk, false, mvnode) + node.SetPath(ctx.withChild(keyText).path) + + ntk := ctx.nextNotCommentToken() + antk := ctx.afterNextNotCommentToken() + for antk != nil && antk.Type == token.MappingValueType && + ntk.Position.Column == key.GetToken().Position.Column { + ctx.progressIgnoreComment(1) + value, err := p.parseToken(ctx, ctx.currentToken()) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse mapping node") + } + switch value.Type() { + case ast.MappingType: + c := value.(*ast.MappingNode) + comment := c.GetComment() + for idx, v := range c.Values { + if idx == 0 && comment != nil { + if err := v.SetComment(comment); err != nil { + return nil, errors.Wrapf(err, "failed to set comment token to node") + } + } + node.Values = append(node.Values, v) + } + case ast.MappingValueType: + node.Values = append(node.Values, value.(*ast.MappingValueNode)) + default: + return nil, xerrors.Errorf("failed to parse mapping value node node is %s", value.Type()) + } + ntk = ctx.nextNotCommentToken() + antk = ctx.afterNextNotCommentToken() + } + if len(node.Values) == 1 { + mapKeyCol := mvnode.Key.GetToken().Position.Column + commentTk := ctx.nextToken() + if commentTk != nil && commentTk.Type == token.CommentType && mapKeyCol <= commentTk.Position.Column { + // If the comment is in the same or deeper column as the last element column in map value, + // treat it as a footer comment for the last element. + comment := p.parseFootComment(ctx, mapKeyCol) + mvnode.FootComment = comment + } + return mvnode, nil + } + mapCol := node.GetToken().Position.Column + commentTk := ctx.nextToken() + if commentTk != nil && commentTk.Type == token.CommentType && mapCol <= commentTk.Position.Column { + // If the comment is in the same or deeper column as the last element column in map value, + // treat it as a footer comment for the last element. + comment := p.parseFootComment(ctx, mapCol) + node.FootComment = comment + } + return node, nil +} + +func (p *parser) parseSequenceEntry(ctx *context) (*ast.SequenceNode, error) { + tk := ctx.currentToken() + sequenceNode := ast.Sequence(tk, false) + sequenceNode.SetPath(ctx.path) + curColumn := tk.Position.Column + for tk.Type == token.SequenceEntryType { + ctx.progress(1) // skip sequence token + tk = ctx.currentToken() + if tk == nil { + return nil, errors.ErrSyntax("empty sequence entry", ctx.previousToken()) + } + var comment *ast.CommentGroupNode + if tk.Type == token.CommentType { + comment = p.parseCommentOnly(ctx) + tk = ctx.currentToken() + if tk.Type != token.SequenceEntryType { + break + } + ctx.progress(1) // skip sequence token + } + value, err := p.parseToken(ctx.withIndex(uint(len(sequenceNode.Values))), ctx.currentToken()) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse sequence") + } + if comment != nil { + comment.SetPath(ctx.withIndex(uint(len(sequenceNode.Values))).path) + sequenceNode.ValueHeadComments = append(sequenceNode.ValueHeadComments, comment) + } else { + sequenceNode.ValueHeadComments = append(sequenceNode.ValueHeadComments, nil) + } + sequenceNode.Values = append(sequenceNode.Values, value) + tk = ctx.nextNotCommentToken() + if tk == nil { + break + } + if tk.Type != token.SequenceEntryType { + break + } + if tk.Position.Column != curColumn { + break + } + ctx.progressIgnoreComment(1) + } + commentTk := ctx.nextToken() + if commentTk != nil && commentTk.Type == token.CommentType && curColumn <= commentTk.Position.Column { + // If the comment is in the same or deeper column as the last element column in sequence value, + // treat it as a footer comment for the last element. + comment := p.parseFootComment(ctx, curColumn) + sequenceNode.FootComment = comment + } + return sequenceNode, nil +} + +func (p *parser) parseAnchor(ctx *context) (*ast.AnchorNode, error) { + tk := ctx.currentToken() + anchor := ast.Anchor(tk) + anchor.SetPath(ctx.path) + ntk := ctx.nextToken() + if ntk == nil { + return nil, errors.ErrSyntax("unexpected anchor. anchor name is undefined", tk) + } + ctx.progress(1) // skip anchor token + name, err := p.parseToken(ctx, ctx.currentToken()) + if err != nil { + return nil, errors.Wrapf(err, "failed to parser anchor name node") + } + anchor.Name = name + ntk = ctx.nextToken() + if ntk == nil { + return nil, errors.ErrSyntax("unexpected anchor. anchor value is undefined", ctx.currentToken()) + } + ctx.progress(1) + value, err := p.parseToken(ctx, ctx.currentToken()) + if err != nil { + return nil, errors.Wrapf(err, "failed to parser anchor name node") + } + anchor.Value = value + return anchor, nil +} + +func (p *parser) parseAlias(ctx *context) (*ast.AliasNode, error) { + tk := ctx.currentToken() + alias := ast.Alias(tk) + alias.SetPath(ctx.path) + ntk := ctx.nextToken() + if ntk == nil { + return nil, errors.ErrSyntax("unexpected alias. alias name is undefined", tk) + } + ctx.progress(1) // skip alias token + name, err := p.parseToken(ctx, ctx.currentToken()) + if err != nil { + return nil, errors.Wrapf(err, "failed to parser alias name node") + } + alias.Value = name + return alias, nil +} + +func (p *parser) parseMapKey(ctx *context) (ast.MapKeyNode, error) { + tk := ctx.currentToken() + if value := p.parseScalarValue(tk); value != nil { + return value, nil + } + switch tk.Type { + case token.MergeKeyType: + return ast.MergeKey(tk), nil + case token.MappingKeyType: + return p.parseMappingKey(ctx) + } + return nil, errors.ErrSyntax("unexpected mapping key", tk) +} + +func (p *parser) parseStringValue(tk *token.Token) *ast.StringNode { + switch tk.Type { + case token.StringType, + token.SingleQuoteType, + token.DoubleQuoteType: + return ast.String(tk) + } + return nil +} + +func (p *parser) parseScalarValueWithComment(ctx *context, tk *token.Token) (ast.ScalarNode, error) { + node := p.parseScalarValue(tk) + if node == nil { + return nil, nil + } + node.SetPath(ctx.path) + if p.isSameLineComment(ctx.nextToken(), node) { + ctx.progress(1) + if err := p.setSameLineCommentIfExists(ctx, node); err != nil { + return nil, errors.Wrapf(err, "failed to set same line comment to node") + } + } + return node, nil +} + +func (p *parser) parseScalarValue(tk *token.Token) ast.ScalarNode { + if node := p.parseStringValue(tk); node != nil { + return node + } + switch tk.Type { + case token.NullType: + return ast.Null(tk) + case token.BoolType: + return ast.Bool(tk) + case token.IntegerType, + token.BinaryIntegerType, + token.OctetIntegerType, + token.HexIntegerType: + return ast.Integer(tk) + case token.FloatType: + return ast.Float(tk) + case token.InfinityType: + return ast.Infinity(tk) + case token.NanType: + return ast.Nan(tk) + } + return nil +} + +func (p *parser) parseDirective(ctx *context) (*ast.DirectiveNode, error) { + node := ast.Directive(ctx.currentToken()) + ctx.progress(1) // skip directive token + value, err := p.parseToken(ctx, ctx.currentToken()) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse directive value") + } + node.Value = value + ctx.progress(1) + tk := ctx.currentToken() + if tk == nil { + // Since current token is nil, use the previous token to specify + // the syntax error location. + return nil, errors.ErrSyntax("unexpected directive value. document not started", ctx.previousToken()) + } + if tk.Type != token.DocumentHeaderType { + return nil, errors.ErrSyntax("unexpected directive value. document not started", ctx.currentToken()) + } + return node, nil +} + +func (p *parser) parseLiteral(ctx *context) (*ast.LiteralNode, error) { + node := ast.Literal(ctx.currentToken()) + ctx.progress(1) // skip literal/folded token + + tk := ctx.currentToken() + var comment *ast.CommentGroupNode + if tk.Type == token.CommentType { + comment = p.parseCommentOnly(ctx) + comment.SetPath(ctx.path) + if err := node.SetComment(comment); err != nil { + return nil, errors.Wrapf(err, "failed to set comment to literal") + } + tk = ctx.currentToken() + } + value, err := p.parseToken(ctx, tk) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse literal/folded value") + } + snode, ok := value.(*ast.StringNode) + if !ok { + return nil, errors.ErrSyntax("unexpected token. required string token", value.GetToken()) + } + node.Value = snode + return node, nil +} + +func (p *parser) isSameLineComment(tk *token.Token, node ast.Node) bool { + if tk == nil { + return false + } + if tk.Type != token.CommentType { + return false + } + return tk.Position.Line == node.GetToken().Position.Line +} + +func (p *parser) setSameLineCommentIfExists(ctx *context, node ast.Node) error { + tk := ctx.currentToken() + if !p.isSameLineComment(tk, node) { + return nil + } + comment := ast.CommentGroup([]*token.Token{tk}) + comment.SetPath(ctx.path) + if err := node.SetComment(comment); err != nil { + return errors.Wrapf(err, "failed to set comment token to ast.Node") + } + return nil +} + +func (p *parser) parseDocument(ctx *context) (*ast.DocumentNode, error) { + startTk := ctx.currentToken() + ctx.progress(1) // skip document header token + body, err := p.parseToken(ctx, ctx.currentToken()) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse document body") + } + node := ast.Document(startTk, body) + if ntk := ctx.nextToken(); ntk != nil && ntk.Type == token.DocumentEndType { + node.End = ntk + ctx.progress(1) + } + return node, nil +} + +func (p *parser) parseCommentOnly(ctx *context) *ast.CommentGroupNode { + commentTokens := []*token.Token{} + for { + tk := ctx.currentToken() + if tk == nil { + break + } + if tk.Type != token.CommentType { + break + } + commentTokens = append(commentTokens, tk) + ctx.progressIgnoreComment(1) // skip comment token + } + return ast.CommentGroup(commentTokens) +} + +func (p *parser) parseFootComment(ctx *context, col int) *ast.CommentGroupNode { + commentTokens := []*token.Token{} + for { + ctx.progressIgnoreComment(1) + commentTokens = append(commentTokens, ctx.currentToken()) + + nextTk := ctx.nextToken() + if nextTk == nil { + break + } + if nextTk.Type != token.CommentType { + break + } + if col > nextTk.Position.Column { + break + } + } + return ast.CommentGroup(commentTokens) +} + +func (p *parser) parseComment(ctx *context) (ast.Node, error) { + group := p.parseCommentOnly(ctx) + node, err := p.parseToken(ctx, ctx.currentToken()) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse node after comment") + } + if node == nil { + return group, nil + } + group.SetPath(node.GetPath()) + if err := node.SetComment(group); err != nil { + return nil, errors.Wrapf(err, "failed to set comment token to node") + } + return node, nil +} + +func (p *parser) parseMappingKey(ctx *context) (*ast.MappingKeyNode, error) { + keyTk := ctx.currentToken() + node := ast.MappingKey(keyTk) + node.SetPath(ctx.path) + ctx.progress(1) // skip mapping key token + value, err := p.parseToken(ctx.withChild(keyTk.Value), ctx.currentToken()) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse map key") + } + node.Value = value + return node, nil +} + +func (p *parser) parseToken(ctx *context, tk *token.Token) (ast.Node, error) { + node, err := p.createNodeFromToken(ctx, tk) + if err != nil { + return nil, errors.Wrapf(err, "failed to create node from token") + } + if node != nil && node.GetPath() == "" { + node.SetPath(ctx.path) + } + return node, nil +} + +func (p *parser) createNodeFromToken(ctx *context, tk *token.Token) (ast.Node, error) { + if tk == nil { + return nil, nil + } + if tk.NextType() == token.MappingValueType { + node, err := p.parseMappingValue(ctx) + return node, err + } + node, err := p.parseScalarValueWithComment(ctx, tk) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse scalar value") + } + if node != nil { + return node, nil + } + switch tk.Type { + case token.CommentType: + return p.parseComment(ctx) + case token.MappingKeyType: + return p.parseMappingKey(ctx) + case token.DocumentHeaderType: + return p.parseDocument(ctx) + case token.MappingStartType: + return p.parseMapping(ctx) + case token.SequenceStartType: + return p.parseSequence(ctx) + case token.SequenceEntryType: + return p.parseSequenceEntry(ctx) + case token.AnchorType: + return p.parseAnchor(ctx) + case token.AliasType: + return p.parseAlias(ctx) + case token.DirectiveType: + return p.parseDirective(ctx) + case token.TagType: + return p.parseTag(ctx) + case token.LiteralType, token.FoldedType: + return p.parseLiteral(ctx) + } + return nil, nil +} + +func (p *parser) parse(tokens token.Tokens, mode Mode) (*ast.File, error) { + ctx := newContext(tokens, mode) + file := &ast.File{Docs: []*ast.DocumentNode{}} + for ctx.next() { + node, err := p.parseToken(ctx, ctx.currentToken()) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse") + } + ctx.progressIgnoreComment(1) + if node == nil { + continue + } + if doc, ok := node.(*ast.DocumentNode); ok { + file.Docs = append(file.Docs, doc) + } else { + file.Docs = append(file.Docs, ast.Document(nil, node)) + } + } + return file, nil +} + +type Mode uint + +const ( + ParseComments Mode = 1 << iota // parse comments and add them to AST +) + +// ParseBytes parse from byte slice, and returns ast.File +func ParseBytes(bytes []byte, mode Mode) (*ast.File, error) { + tokens := lexer.Tokenize(string(bytes)) + f, err := Parse(tokens, mode) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse") + } + return f, nil +} + +// Parse parse from token instances, and returns ast.File +func Parse(tokens token.Tokens, mode Mode) (*ast.File, error) { + var p parser + f, err := p.parse(tokens, mode) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse") + } + return f, nil +} + +// Parse parse from filename, and returns ast.File +func ParseFile(filename string, mode Mode) (*ast.File, error) { + file, err := ioutil.ReadFile(filename) + if err != nil { + return nil, errors.Wrapf(err, "failed to read file: %s", filename) + } + f, err := ParseBytes(file, mode) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse") + } + f.Name = filename + return f, nil +} diff --git a/vendor/github.com/goccy/go-yaml/path.go b/vendor/github.com/goccy/go-yaml/path.go new file mode 100644 index 000000000..7a0c3b115 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/path.go @@ -0,0 +1,794 @@ +package yaml + +import ( + "bytes" + "fmt" + "io" + "strconv" + "strings" + + "github.com/goccy/go-yaml/ast" + "github.com/goccy/go-yaml/internal/errors" + "github.com/goccy/go-yaml/parser" + "github.com/goccy/go-yaml/printer" +) + +// PathString create Path from string +// +// YAMLPath rule +// $ : the root object/element +// . : child operator +// .. : recursive descent +// [num] : object/element of array by number +// [*] : all objects/elements for array. +// +// If you want to use reserved characters such as `.` and `*` as a key name, +// enclose them in single quotation as follows ( $.foo.'bar.baz-*'.hoge ). +// If you want to use a single quote with reserved characters, escape it with `\` ( $.foo.'bar.baz\'s value'.hoge ). +func PathString(s string) (*Path, error) { + buf := []rune(s) + length := len(buf) + cursor := 0 + builder := &PathBuilder{} + for cursor < length { + c := buf[cursor] + switch c { + case '$': + builder = builder.Root() + cursor++ + case '.': + b, buf, c, err := parsePathDot(builder, buf, cursor) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse path of dot") + } + length = len(buf) + builder = b + cursor = c + case '[': + b, buf, c, err := parsePathIndex(builder, buf, cursor) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse path of index") + } + length = len(buf) + builder = b + cursor = c + default: + return nil, errors.Wrapf(ErrInvalidPathString, "invalid path at %d", cursor) + } + } + return builder.Build(), nil +} + +func parsePathRecursive(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) { + length := len(buf) + cursor += 2 // skip .. characters + start := cursor + for ; cursor < length; cursor++ { + c := buf[cursor] + switch c { + case '$': + return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '$' after '..' character") + case '*': + return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '*' after '..' character") + case '.', '[': + goto end + case ']': + return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified ']' after '..' character") + } + } +end: + if start == cursor { + return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "not found recursive selector") + } + return b.Recursive(string(buf[start:cursor])), buf, cursor, nil +} + +func parsePathDot(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) { + length := len(buf) + if cursor+1 < length && buf[cursor+1] == '.' { + b, buf, c, err := parsePathRecursive(b, buf, cursor) + if err != nil { + return nil, nil, 0, errors.Wrapf(err, "failed to parse path of recursive") + } + return b, buf, c, nil + } + cursor++ // skip . character + start := cursor + + // if started single quote, looking for end single quote char + if cursor < length && buf[cursor] == '\'' { + return parseQuotedKey(b, buf, cursor) + } + for ; cursor < length; cursor++ { + c := buf[cursor] + switch c { + case '$': + return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '$' after '.' character") + case '*': + return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '*' after '.' character") + case '.', '[': + goto end + case ']': + return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified ']' after '.' character") + } + } +end: + if start == cursor { + return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "cloud not find by empty key") + } + return b.child(string(buf[start:cursor])), buf, cursor, nil +} + +func parseQuotedKey(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) { + cursor++ // skip single quote + start := cursor + length := len(buf) + var foundEndDelim bool + for ; cursor < length; cursor++ { + switch buf[cursor] { + case '\\': + buf = append(append([]rune{}, buf[:cursor]...), buf[cursor+1:]...) + length = len(buf) + case '\'': + foundEndDelim = true + goto end + } + } +end: + if !foundEndDelim { + return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "could not find end delimiter for key") + } + if start == cursor { + return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "could not find by empty key") + } + selector := buf[start:cursor] + cursor++ + if cursor < length { + switch buf[cursor] { + case '$': + return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '$' after '.' character") + case '*': + return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '*' after '.' character") + case ']': + return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified ']' after '.' character") + } + } + return b.child(string(selector)), buf, cursor, nil +} + +func parsePathIndex(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) { + length := len(buf) + cursor++ // skip '[' character + if length <= cursor { + return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "unexpected end of YAML Path") + } + c := buf[cursor] + switch c { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*': + start := cursor + cursor++ + for ; cursor < length; cursor++ { + c := buf[cursor] + switch c { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + continue + } + break + } + if buf[cursor] != ']' { + return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "invalid character %s at %d", string(buf[cursor]), cursor) + } + numOrAll := string(buf[start:cursor]) + if numOrAll == "*" { + return b.IndexAll(), buf, cursor + 1, nil + } + num, err := strconv.ParseInt(numOrAll, 10, 64) + if err != nil { + return nil, nil, 0, errors.Wrapf(err, "failed to parse number") + } + return b.Index(uint(num)), buf, cursor + 1, nil + } + return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "invalid character %s at %d", c, cursor) +} + +// Path represent YAMLPath ( like a JSONPath ). +type Path struct { + node pathNode +} + +// String path to text. +func (p *Path) String() string { + return p.node.String() +} + +// Read decode from r and set extracted value by YAMLPath to v. +func (p *Path) Read(r io.Reader, v interface{}) error { + node, err := p.ReadNode(r) + if err != nil { + return errors.Wrapf(err, "failed to read node") + } + if err := Unmarshal([]byte(node.String()), v); err != nil { + return errors.Wrapf(err, "failed to unmarshal") + } + return nil +} + +// ReadNode create AST from r and extract node by YAMLPath. +func (p *Path) ReadNode(r io.Reader) (ast.Node, error) { + if p.node == nil { + return nil, ErrInvalidPath + } + var buf bytes.Buffer + if _, err := io.Copy(&buf, r); err != nil { + return nil, errors.Wrapf(err, "failed to copy from reader") + } + f, err := parser.ParseBytes(buf.Bytes(), 0) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse yaml") + } + node, err := p.FilterFile(f) + if err != nil { + return nil, errors.Wrapf(err, "failed to filter from ast.File") + } + return node, nil +} + +// Filter filter from target by YAMLPath and set it to v. +func (p *Path) Filter(target, v interface{}) error { + b, err := Marshal(target) + if err != nil { + return errors.Wrapf(err, "failed to marshal target value") + } + if err := p.Read(bytes.NewBuffer(b), v); err != nil { + return errors.Wrapf(err, "failed to read") + } + return nil +} + +// FilterFile filter from ast.File by YAMLPath. +func (p *Path) FilterFile(f *ast.File) (ast.Node, error) { + for _, doc := range f.Docs { + node, err := p.FilterNode(doc.Body) + if err != nil { + return nil, errors.Wrapf(err, "failed to filter node by path ( %s )", p.node) + } + if node != nil { + return node, nil + } + } + return nil, errors.Wrapf(ErrNotFoundNode, "failed to find path ( %s )", p.node) +} + +// FilterNode filter from node by YAMLPath. +func (p *Path) FilterNode(node ast.Node) (ast.Node, error) { + n, err := p.node.filter(node) + if err != nil { + return nil, errors.Wrapf(err, "failed to filter node by path ( %s )", p.node) + } + return n, nil +} + +// MergeFromReader merge YAML text into ast.File. +func (p *Path) MergeFromReader(dst *ast.File, src io.Reader) error { + var buf bytes.Buffer + if _, err := io.Copy(&buf, src); err != nil { + return errors.Wrapf(err, "failed to copy from reader") + } + file, err := parser.ParseBytes(buf.Bytes(), 0) + if err != nil { + return errors.Wrapf(err, "failed to parse") + } + if err := p.MergeFromFile(dst, file); err != nil { + return errors.Wrapf(err, "failed to merge file") + } + return nil +} + +// MergeFromFile merge ast.File into ast.File. +func (p *Path) MergeFromFile(dst *ast.File, src *ast.File) error { + base, err := p.FilterFile(dst) + if err != nil { + return errors.Wrapf(err, "failed to filter file") + } + for _, doc := range src.Docs { + if err := ast.Merge(base, doc); err != nil { + return errors.Wrapf(err, "failed to merge") + } + } + return nil +} + +// MergeFromNode merge ast.Node into ast.File. +func (p *Path) MergeFromNode(dst *ast.File, src ast.Node) error { + base, err := p.FilterFile(dst) + if err != nil { + return errors.Wrapf(err, "failed to filter file") + } + if err := ast.Merge(base, src); err != nil { + return errors.Wrapf(err, "failed to merge") + } + return nil +} + +// ReplaceWithReader replace ast.File with io.Reader. +func (p *Path) ReplaceWithReader(dst *ast.File, src io.Reader) error { + var buf bytes.Buffer + if _, err := io.Copy(&buf, src); err != nil { + return errors.Wrapf(err, "failed to copy from reader") + } + file, err := parser.ParseBytes(buf.Bytes(), 0) + if err != nil { + return errors.Wrapf(err, "failed to parse") + } + if err := p.ReplaceWithFile(dst, file); err != nil { + return errors.Wrapf(err, "failed to replace file") + } + return nil +} + +// ReplaceWithFile replace ast.File with ast.File. +func (p *Path) ReplaceWithFile(dst *ast.File, src *ast.File) error { + for _, doc := range src.Docs { + if err := p.ReplaceWithNode(dst, doc); err != nil { + return errors.Wrapf(err, "failed to replace file by path ( %s )", p.node) + } + } + return nil +} + +// ReplaceNode replace ast.File with ast.Node. +func (p *Path) ReplaceWithNode(dst *ast.File, node ast.Node) error { + for _, doc := range dst.Docs { + if node.Type() == ast.DocumentType { + node = node.(*ast.DocumentNode).Body + } + if err := p.node.replace(doc.Body, node); err != nil { + return errors.Wrapf(err, "failed to replace node by path ( %s )", p.node) + } + } + return nil +} + +// AnnotateSource add annotation to passed source ( see section 5.1 in README.md ). +func (p *Path) AnnotateSource(source []byte, colored bool) ([]byte, error) { + file, err := parser.ParseBytes([]byte(source), 0) + if err != nil { + return nil, err + } + node, err := p.FilterFile(file) + if err != nil { + return nil, err + } + var pp printer.Printer + return []byte(pp.PrintErrorToken(node.GetToken(), colored)), nil +} + +// PathBuilder represent builder for YAMLPath. +type PathBuilder struct { + root *rootNode + node pathNode +} + +// Root add '$' to current path. +func (b *PathBuilder) Root() *PathBuilder { + root := newRootNode() + return &PathBuilder{root: root, node: root} +} + +// IndexAll add '[*]' to current path. +func (b *PathBuilder) IndexAll() *PathBuilder { + b.node = b.node.chain(newIndexAllNode()) + return b +} + +// Recursive add '..selector' to current path. +func (b *PathBuilder) Recursive(selector string) *PathBuilder { + b.node = b.node.chain(newRecursiveNode(selector)) + return b +} + +func (b *PathBuilder) containsReservedPathCharacters(path string) bool { + if strings.Contains(path, ".") { + return true + } + if strings.Contains(path, "*") { + return true + } + return false +} + +func (b *PathBuilder) enclosedSingleQuote(name string) bool { + return strings.HasPrefix(name, "'") && strings.HasSuffix(name, "'") +} + +func (b *PathBuilder) normalizeSelectorName(name string) string { + if b.enclosedSingleQuote(name) { + // already escaped name + return name + } + if b.containsReservedPathCharacters(name) { + escapedName := strings.ReplaceAll(name, `'`, `\'`) + return "'" + escapedName + "'" + } + return name +} + +func (b *PathBuilder) child(name string) *PathBuilder { + b.node = b.node.chain(newSelectorNode(name)) + return b +} + +// Child add '.name' to current path. +func (b *PathBuilder) Child(name string) *PathBuilder { + return b.child(b.normalizeSelectorName(name)) +} + +// Index add '[idx]' to current path. +func (b *PathBuilder) Index(idx uint) *PathBuilder { + b.node = b.node.chain(newIndexNode(idx)) + return b +} + +// Build build YAMLPath. +func (b *PathBuilder) Build() *Path { + return &Path{node: b.root} +} + +type pathNode interface { + fmt.Stringer + chain(pathNode) pathNode + filter(ast.Node) (ast.Node, error) + replace(ast.Node, ast.Node) error +} + +type basePathNode struct { + child pathNode +} + +func (n *basePathNode) chain(node pathNode) pathNode { + n.child = node + return node +} + +type rootNode struct { + *basePathNode +} + +func newRootNode() *rootNode { + return &rootNode{basePathNode: &basePathNode{}} +} + +func (n *rootNode) String() string { + s := "$" + if n.child != nil { + s += n.child.String() + } + return s +} + +func (n *rootNode) filter(node ast.Node) (ast.Node, error) { + if n.child == nil { + return nil, nil + } + filtered, err := n.child.filter(node) + if err != nil { + return nil, errors.Wrapf(err, "failed to filter") + } + return filtered, nil +} + +func (n *rootNode) replace(node ast.Node, target ast.Node) error { + if n.child == nil { + return nil + } + if err := n.child.replace(node, target); err != nil { + return errors.Wrapf(err, "failed to replace") + } + return nil +} + +type selectorNode struct { + *basePathNode + selector string +} + +func newSelectorNode(selector string) *selectorNode { + return &selectorNode{ + basePathNode: &basePathNode{}, + selector: selector, + } +} + +func (n *selectorNode) filter(node ast.Node) (ast.Node, error) { + switch node.Type() { + case ast.MappingType: + for _, value := range node.(*ast.MappingNode).Values { + key := value.Key.GetToken().Value + if key == n.selector { + if n.child == nil { + return value.Value, nil + } + filtered, err := n.child.filter(value.Value) + if err != nil { + return nil, errors.Wrapf(err, "failed to filter") + } + return filtered, nil + } + } + case ast.MappingValueType: + value := node.(*ast.MappingValueNode) + key := value.Key.GetToken().Value + if key == n.selector { + if n.child == nil { + return value.Value, nil + } + filtered, err := n.child.filter(value.Value) + if err != nil { + return nil, errors.Wrapf(err, "failed to filter") + } + return filtered, nil + } + default: + return nil, errors.Wrapf(ErrInvalidQuery, "expected node type is map or map value. but got %s", node.Type()) + } + return nil, nil +} + +func (n *selectorNode) replaceMapValue(value *ast.MappingValueNode, target ast.Node) error { + key := value.Key.GetToken().Value + if key != n.selector { + return nil + } + if n.child == nil { + if err := value.Replace(target); err != nil { + return errors.Wrapf(err, "failed to replace") + } + } else { + if err := n.child.replace(value.Value, target); err != nil { + return errors.Wrapf(err, "failed to replace") + } + } + return nil +} + +func (n *selectorNode) replace(node ast.Node, target ast.Node) error { + switch node.Type() { + case ast.MappingType: + for _, value := range node.(*ast.MappingNode).Values { + if err := n.replaceMapValue(value, target); err != nil { + return errors.Wrapf(err, "failed to replace map value") + } + } + case ast.MappingValueType: + value := node.(*ast.MappingValueNode) + if err := n.replaceMapValue(value, target); err != nil { + return errors.Wrapf(err, "failed to replace map value") + } + default: + return errors.Wrapf(ErrInvalidQuery, "expected node type is map or map value. but got %s", node.Type()) + } + return nil +} + +func (n *selectorNode) String() string { + s := fmt.Sprintf(".%s", n.selector) + if n.child != nil { + s += n.child.String() + } + return s +} + +type indexNode struct { + *basePathNode + selector uint +} + +func newIndexNode(selector uint) *indexNode { + return &indexNode{ + basePathNode: &basePathNode{}, + selector: selector, + } +} + +func (n *indexNode) filter(node ast.Node) (ast.Node, error) { + if node.Type() != ast.SequenceType { + return nil, errors.Wrapf(ErrInvalidQuery, "expected sequence type node. but got %s", node.Type()) + } + sequence := node.(*ast.SequenceNode) + if n.selector >= uint(len(sequence.Values)) { + return nil, errors.Wrapf(ErrInvalidQuery, "expected index is %d. but got sequences has %d items", n.selector, sequence.Values) + } + value := sequence.Values[n.selector] + if n.child == nil { + return value, nil + } + filtered, err := n.child.filter(value) + if err != nil { + return nil, errors.Wrapf(err, "failed to filter") + } + return filtered, nil +} + +func (n *indexNode) replace(node ast.Node, target ast.Node) error { + if node.Type() != ast.SequenceType { + return errors.Wrapf(ErrInvalidQuery, "expected sequence type node. but got %s", node.Type()) + } + sequence := node.(*ast.SequenceNode) + if n.selector >= uint(len(sequence.Values)) { + return errors.Wrapf(ErrInvalidQuery, "expected index is %d. but got sequences has %d items", n.selector, sequence.Values) + } + if n.child == nil { + if err := sequence.Replace(int(n.selector), target); err != nil { + return errors.Wrapf(err, "failed to replace") + } + return nil + } + if err := n.child.replace(sequence.Values[n.selector], target); err != nil { + return errors.Wrapf(err, "failed to replace") + } + return nil +} + +func (n *indexNode) String() string { + s := fmt.Sprintf("[%d]", n.selector) + if n.child != nil { + s += n.child.String() + } + return s +} + +type indexAllNode struct { + *basePathNode +} + +func newIndexAllNode() *indexAllNode { + return &indexAllNode{ + basePathNode: &basePathNode{}, + } +} + +func (n *indexAllNode) String() string { + s := "[*]" + if n.child != nil { + s += n.child.String() + } + return s +} + +func (n *indexAllNode) filter(node ast.Node) (ast.Node, error) { + if node.Type() != ast.SequenceType { + return nil, errors.Wrapf(ErrInvalidQuery, "expected sequence type node. but got %s", node.Type()) + } + sequence := node.(*ast.SequenceNode) + if n.child == nil { + return sequence, nil + } + out := *sequence + out.Values = []ast.Node{} + for _, value := range sequence.Values { + filtered, err := n.child.filter(value) + if err != nil { + return nil, errors.Wrapf(err, "failed to filter") + } + out.Values = append(out.Values, filtered) + } + return &out, nil +} + +func (n *indexAllNode) replace(node ast.Node, target ast.Node) error { + if node.Type() != ast.SequenceType { + return errors.Wrapf(ErrInvalidQuery, "expected sequence type node. but got %s", node.Type()) + } + sequence := node.(*ast.SequenceNode) + if n.child == nil { + for idx := range sequence.Values { + if err := sequence.Replace(idx, target); err != nil { + return errors.Wrapf(err, "failed to replace") + } + } + return nil + } + for _, value := range sequence.Values { + if err := n.child.replace(value, target); err != nil { + return errors.Wrapf(err, "failed to replace") + } + } + return nil +} + +type recursiveNode struct { + *basePathNode + selector string +} + +func newRecursiveNode(selector string) *recursiveNode { + return &recursiveNode{ + basePathNode: &basePathNode{}, + selector: selector, + } +} + +func (n *recursiveNode) String() string { + s := fmt.Sprintf("..%s", n.selector) + if n.child != nil { + s += n.child.String() + } + return s +} + +func (n *recursiveNode) filterNode(node ast.Node) (*ast.SequenceNode, error) { + sequence := &ast.SequenceNode{BaseNode: &ast.BaseNode{}} + switch typedNode := node.(type) { + case *ast.MappingNode: + for _, value := range typedNode.Values { + seq, err := n.filterNode(value) + if err != nil { + return nil, errors.Wrapf(err, "failed to filter") + } + sequence.Values = append(sequence.Values, seq.Values...) + } + case *ast.MappingValueNode: + key := typedNode.Key.GetToken().Value + if n.selector == key { + sequence.Values = append(sequence.Values, typedNode.Value) + } + seq, err := n.filterNode(typedNode.Value) + if err != nil { + return nil, errors.Wrapf(err, "failed to filter") + } + sequence.Values = append(sequence.Values, seq.Values...) + case *ast.SequenceNode: + for _, value := range typedNode.Values { + seq, err := n.filterNode(value) + if err != nil { + return nil, errors.Wrapf(err, "failed to filter") + } + sequence.Values = append(sequence.Values, seq.Values...) + } + } + return sequence, nil +} + +func (n *recursiveNode) filter(node ast.Node) (ast.Node, error) { + sequence, err := n.filterNode(node) + if err != nil { + return nil, errors.Wrapf(err, "failed to filter") + } + sequence.Start = node.GetToken() + return sequence, nil +} + +func (n *recursiveNode) replaceNode(node ast.Node, target ast.Node) error { + switch typedNode := node.(type) { + case *ast.MappingNode: + for _, value := range typedNode.Values { + if err := n.replaceNode(value, target); err != nil { + return errors.Wrapf(err, "failed to replace") + } + } + case *ast.MappingValueNode: + key := typedNode.Key.GetToken().Value + if n.selector == key { + if err := typedNode.Replace(target); err != nil { + return errors.Wrapf(err, "failed to replace") + } + } + if err := n.replaceNode(typedNode.Value, target); err != nil { + return errors.Wrapf(err, "failed to replace") + } + case *ast.SequenceNode: + for _, value := range typedNode.Values { + if err := n.replaceNode(value, target); err != nil { + return errors.Wrapf(err, "failed to replace") + } + } + } + return nil +} + +func (n *recursiveNode) replace(node ast.Node, target ast.Node) error { + if err := n.replaceNode(node, target); err != nil { + return errors.Wrapf(err, "failed to replace") + } + return nil +} diff --git a/vendor/github.com/goccy/go-yaml/printer/printer.go b/vendor/github.com/goccy/go-yaml/printer/printer.go new file mode 100644 index 000000000..d5e25dc91 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/printer/printer.go @@ -0,0 +1,352 @@ +package printer + +import ( + "fmt" + "math" + "strings" + + "github.com/fatih/color" + "github.com/goccy/go-yaml/ast" + "github.com/goccy/go-yaml/token" +) + +// Property additional property set for each the token +type Property struct { + Prefix string + Suffix string +} + +// PrintFunc returns property instance +type PrintFunc func() *Property + +// Printer create text from token collection or ast +type Printer struct { + LineNumber bool + LineNumberFormat func(num int) string + MapKey PrintFunc + Anchor PrintFunc + Alias PrintFunc + Bool PrintFunc + String PrintFunc + Number PrintFunc +} + +func defaultLineNumberFormat(num int) string { + return fmt.Sprintf("%2d | ", num) +} + +func (p *Printer) property(tk *token.Token) *Property { + prop := &Property{} + switch tk.PreviousType() { + case token.AnchorType: + if p.Anchor != nil { + return p.Anchor() + } + return prop + case token.AliasType: + if p.Alias != nil { + return p.Alias() + } + return prop + } + switch tk.NextType() { + case token.MappingValueType: + if p.MapKey != nil { + return p.MapKey() + } + return prop + } + switch tk.Type { + case token.BoolType: + if p.Bool != nil { + return p.Bool() + } + return prop + case token.AnchorType: + if p.Anchor != nil { + return p.Anchor() + } + return prop + case token.AliasType: + if p.Anchor != nil { + return p.Alias() + } + return prop + case token.StringType, token.SingleQuoteType, token.DoubleQuoteType: + if p.String != nil { + return p.String() + } + return prop + case token.IntegerType, token.FloatType: + if p.Number != nil { + return p.Number() + } + return prop + default: + } + return prop +} + +// PrintTokens create text from token collection +func (p *Printer) PrintTokens(tokens token.Tokens) string { + if len(tokens) == 0 { + return "" + } + if p.LineNumber { + if p.LineNumberFormat == nil { + p.LineNumberFormat = defaultLineNumberFormat + } + } + texts := []string{} + lineNumber := tokens[0].Position.Line + for _, tk := range tokens { + lines := strings.Split(tk.Origin, "\n") + prop := p.property(tk) + header := "" + if p.LineNumber { + header = p.LineNumberFormat(lineNumber) + } + if len(lines) == 1 { + line := prop.Prefix + lines[0] + prop.Suffix + if len(texts) == 0 { + texts = append(texts, header+line) + lineNumber++ + } else { + text := texts[len(texts)-1] + texts[len(texts)-1] = text + line + } + } else { + for idx, src := range lines { + if p.LineNumber { + header = p.LineNumberFormat(lineNumber) + } + line := prop.Prefix + src + prop.Suffix + if idx == 0 { + if len(texts) == 0 { + texts = append(texts, header+line) + lineNumber++ + } else { + text := texts[len(texts)-1] + texts[len(texts)-1] = text + line + } + } else { + texts = append(texts, fmt.Sprintf("%s%s", header, line)) + lineNumber++ + } + } + } + } + return strings.Join(texts, "\n") +} + +// PrintNode create text from ast.Node +func (p *Printer) PrintNode(node ast.Node) []byte { + return []byte(fmt.Sprintf("%+v\n", node)) +} + +const escape = "\x1b" + +func format(attr color.Attribute) string { + return fmt.Sprintf("%s[%dm", escape, attr) +} + +func (p *Printer) setDefaultColorSet() { + p.Bool = func() *Property { + return &Property{ + Prefix: format(color.FgHiMagenta), + Suffix: format(color.Reset), + } + } + p.Number = func() *Property { + return &Property{ + Prefix: format(color.FgHiMagenta), + Suffix: format(color.Reset), + } + } + p.MapKey = func() *Property { + return &Property{ + Prefix: format(color.FgHiCyan), + Suffix: format(color.Reset), + } + } + p.Anchor = func() *Property { + return &Property{ + Prefix: format(color.FgHiYellow), + Suffix: format(color.Reset), + } + } + p.Alias = func() *Property { + return &Property{ + Prefix: format(color.FgHiYellow), + Suffix: format(color.Reset), + } + } + p.String = func() *Property { + return &Property{ + Prefix: format(color.FgHiGreen), + Suffix: format(color.Reset), + } + } +} + +func (p *Printer) PrintErrorMessage(msg string, isColored bool) string { + if isColored { + return fmt.Sprintf("%s%s%s", + format(color.FgHiRed), + msg, + format(color.Reset), + ) + } + return msg +} + +func (p *Printer) removeLeftSideNewLineChar(src string) string { + return strings.TrimLeft(strings.TrimLeft(strings.TrimLeft(src, "\r"), "\n"), "\r\n") +} + +func (p *Printer) removeRightSideNewLineChar(src string) string { + return strings.TrimRight(strings.TrimRight(strings.TrimRight(src, "\r"), "\n"), "\r\n") +} + +func (p *Printer) removeRightSideWhiteSpaceChar(src string) string { + return p.removeRightSideNewLineChar(strings.TrimRight(src, " ")) +} + +func (p *Printer) newLineCount(s string) int { + src := []rune(s) + size := len(src) + cnt := 0 + for i := 0; i < size; i++ { + c := src[i] + switch c { + case '\r': + if i+1 < size && src[i+1] == '\n' { + i++ + } + cnt++ + case '\n': + cnt++ + } + } + return cnt +} + +func (p *Printer) isNewLineLastChar(s string) bool { + for i := len(s) - 1; i > 0; i-- { + c := s[i] + switch c { + case ' ': + continue + case '\n', '\r': + return true + } + break + } + return false +} + +func (p *Printer) printBeforeTokens(tk *token.Token, minLine, extLine int) token.Tokens { + for { + if tk.Prev == nil { + break + } + if tk.Prev.Position.Line < minLine { + break + } + tk = tk.Prev + } + minTk := tk.Clone() + if minTk.Prev != nil { + // add white spaces to minTk by prev token + prev := minTk.Prev + whiteSpaceLen := len(prev.Origin) - len(strings.TrimRight(prev.Origin, " ")) + minTk.Origin = strings.Repeat(" ", whiteSpaceLen) + minTk.Origin + } + minTk.Origin = p.removeLeftSideNewLineChar(minTk.Origin) + tokens := token.Tokens{minTk} + tk = minTk.Next + for tk != nil && tk.Position.Line <= extLine { + clonedTk := tk.Clone() + tokens.Add(clonedTk) + tk = clonedTk.Next + } + lastTk := tokens[len(tokens)-1] + trimmedOrigin := p.removeRightSideWhiteSpaceChar(lastTk.Origin) + suffix := lastTk.Origin[len(trimmedOrigin):] + lastTk.Origin = trimmedOrigin + + if lastTk.Next != nil && len(suffix) > 1 { + next := lastTk.Next.Clone() + // add suffix to header of next token + if suffix[0] == '\n' || suffix[0] == '\r' { + suffix = suffix[1:] + } + next.Origin = suffix + next.Origin + lastTk.Next = next + } + return tokens +} + +func (p *Printer) printAfterTokens(tk *token.Token, maxLine int) token.Tokens { + tokens := token.Tokens{} + if tk == nil { + return tokens + } + if tk.Position.Line > maxLine { + return tokens + } + minTk := tk.Clone() + minTk.Origin = p.removeLeftSideNewLineChar(minTk.Origin) + tokens.Add(minTk) + tk = minTk.Next + for tk != nil && tk.Position.Line <= maxLine { + clonedTk := tk.Clone() + tokens.Add(clonedTk) + tk = clonedTk.Next + } + return tokens +} + +func (p *Printer) setupErrorTokenFormat(annotateLine int, isColored bool) { + prefix := func(annotateLine, num int) string { + if annotateLine == num { + return fmt.Sprintf("> %2d | ", num) + } + return fmt.Sprintf(" %2d | ", num) + } + p.LineNumber = true + p.LineNumberFormat = func(num int) string { + if isColored { + fn := color.New(color.Bold, color.FgHiWhite).SprintFunc() + return fn(prefix(annotateLine, num)) + } + return prefix(annotateLine, num) + } + if isColored { + p.setDefaultColorSet() + } +} + +func (p *Printer) PrintErrorToken(tk *token.Token, isColored bool) string { + errToken := tk + curLine := tk.Position.Line + curExtLine := curLine + p.newLineCount(p.removeLeftSideNewLineChar(tk.Origin)) + if p.isNewLineLastChar(tk.Origin) { + // if last character ( exclude white space ) is new line character, ignore it. + curExtLine-- + } + + minLine := int(math.Max(float64(curLine-3), 1)) + maxLine := curExtLine + 3 + p.setupErrorTokenFormat(curLine, isColored) + + beforeTokens := p.printBeforeTokens(tk, minLine, curExtLine) + lastTk := beforeTokens[len(beforeTokens)-1] + afterTokens := p.printAfterTokens(lastTk.Next, maxLine) + + beforeSource := p.PrintTokens(beforeTokens) + prefixSpaceNum := len(fmt.Sprintf(" %2d | ", curLine)) + annotateLine := strings.Repeat(" ", prefixSpaceNum+errToken.Position.Column-1) + "^" + afterSource := p.PrintTokens(afterTokens) + return fmt.Sprintf("%s\n%s\n%s", beforeSource, annotateLine, afterSource) +} diff --git a/vendor/github.com/goccy/go-yaml/scanner/context.go b/vendor/github.com/goccy/go-yaml/scanner/context.go new file mode 100644 index 000000000..09d0a2daa --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/scanner/context.go @@ -0,0 +1,229 @@ +package scanner + +import ( + "sync" + + "github.com/goccy/go-yaml/token" +) + +const whitespace = ' ' + +// Context context at scanning +type Context struct { + idx int + size int + notSpaceCharPos int + notSpaceOrgCharPos int + src []rune + buf []rune + obuf []rune + tokens token.Tokens + isRawFolded bool + isLiteral bool + isFolded bool + isSingleLine bool + literalOpt string +} + +var ( + ctxPool = sync.Pool{ + New: func() interface{} { + return createContext() + }, + } +) + +func createContext() *Context { + return &Context{ + idx: 0, + tokens: token.Tokens{}, + isSingleLine: true, + } +} + +func newContext(src []rune) *Context { + ctx := ctxPool.Get().(*Context) + ctx.reset(src) + return ctx +} + +func (c *Context) release() { + ctxPool.Put(c) +} + +func (c *Context) reset(src []rune) { + c.idx = 0 + c.size = len(src) + c.src = src + c.tokens = c.tokens[:0] + c.resetBuffer() + c.isRawFolded = false + c.isSingleLine = true + c.isLiteral = false + c.isFolded = false + c.literalOpt = "" +} + +func (c *Context) resetBuffer() { + c.buf = c.buf[:0] + c.obuf = c.obuf[:0] + c.notSpaceCharPos = 0 + c.notSpaceOrgCharPos = 0 +} + +func (c *Context) isSaveIndentMode() bool { + return c.isLiteral || c.isFolded || c.isRawFolded +} + +func (c *Context) breakLiteral() { + c.isLiteral = false + c.isRawFolded = false + c.isFolded = false + c.literalOpt = "" +} + +func (c *Context) addToken(tk *token.Token) { + if tk == nil { + return + } + c.tokens = append(c.tokens, tk) +} + +func (c *Context) addBuf(r rune) { + if len(c.buf) == 0 && r == ' ' { + return + } + c.buf = append(c.buf, r) + if r != ' ' && r != '\t' { + c.notSpaceCharPos = len(c.buf) + } +} + +func (c *Context) addOriginBuf(r rune) { + c.obuf = append(c.obuf, r) + if r != ' ' && r != '\t' { + c.notSpaceOrgCharPos = len(c.obuf) + } +} + +func (c *Context) removeRightSpaceFromBuf() int { + trimmedBuf := c.obuf[:c.notSpaceOrgCharPos] + buflen := len(trimmedBuf) + diff := len(c.obuf) - buflen + if diff > 0 { + c.obuf = c.obuf[:buflen] + c.buf = c.bufferedSrc() + } + return diff +} + +func (c *Context) isDocument() bool { + return c.isLiteral || c.isFolded || c.isRawFolded +} + +func (c *Context) isEOS() bool { + return len(c.src)-1 <= c.idx +} + +func (c *Context) isNextEOS() bool { + return len(c.src)-1 <= c.idx+1 +} + +func (c *Context) next() bool { + return c.idx < c.size +} + +func (c *Context) source(s, e int) string { + return string(c.src[s:e]) +} + +func (c *Context) previousChar() rune { + if c.idx > 0 { + return c.src[c.idx-1] + } + return rune(0) +} + +func (c *Context) currentChar() rune { + if c.size > c.idx { + return c.src[c.idx] + } + return rune(0) +} + +func (c *Context) currentCharWithSkipWhitespace() rune { + idx := c.idx + for c.size > idx { + ch := c.src[idx] + if ch != whitespace { + return ch + } + idx++ + } + return rune(0) +} + +func (c *Context) nextChar() rune { + if c.size > c.idx+1 { + return c.src[c.idx+1] + } + return rune(0) +} + +func (c *Context) repeatNum(r rune) int { + cnt := 0 + for i := c.idx; i < c.size; i++ { + if c.src[i] == r { + cnt++ + } else { + break + } + } + return cnt +} + +func (c *Context) progress(num int) { + c.idx += num +} + +func (c *Context) nextPos() int { + return c.idx + 1 +} + +func (c *Context) existsBuffer() bool { + return len(c.bufferedSrc()) != 0 +} + +func (c *Context) bufferedSrc() []rune { + src := c.buf[:c.notSpaceCharPos] + if len(src) > 0 && src[len(src)-1] == '\n' && c.isDocument() && c.literalOpt == "-" { + // remove end '\n' character + src = src[:len(src)-1] + } + return src +} + +func (c *Context) bufferedToken(pos *token.Position) *token.Token { + if c.idx == 0 { + return nil + } + source := c.bufferedSrc() + if len(source) == 0 { + return nil + } + var tk *token.Token + if c.isDocument() { + tk = token.String(string(source), string(c.obuf), pos) + } else { + tk = token.New(string(source), string(c.obuf), pos) + } + c.resetBuffer() + return tk +} + +func (c *Context) lastToken() *token.Token { + if len(c.tokens) != 0 { + return c.tokens[len(c.tokens)-1] + } + return nil +} diff --git a/vendor/github.com/goccy/go-yaml/scanner/scanner.go b/vendor/github.com/goccy/go-yaml/scanner/scanner.go new file mode 100644 index 000000000..ce9c6654e --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/scanner/scanner.go @@ -0,0 +1,903 @@ +package scanner + +import ( + "io" + "strings" + + "github.com/goccy/go-yaml/token" + "golang.org/x/xerrors" +) + +// IndentState state for indent +type IndentState int + +const ( + // IndentStateEqual equals previous indent + IndentStateEqual IndentState = iota + // IndentStateUp more indent than previous + IndentStateUp + // IndentStateDown less indent than previous + IndentStateDown + // IndentStateKeep uses not indent token + IndentStateKeep +) + +// Scanner holds the scanner's internal state while processing a given text. +// It can be allocated as part of another data structure but must be initialized via Init before use. +type Scanner struct { + source []rune + sourcePos int + sourceSize int + line int + column int + offset int + prevIndentLevel int + prevIndentNum int + prevIndentColumn int + docStartColumn int + indentLevel int + indentNum int + isFirstCharAtLine bool + isAnchor bool + startedFlowSequenceNum int + startedFlowMapNum int + indentState IndentState + savedPos *token.Position +} + +func (s *Scanner) pos() *token.Position { + return &token.Position{ + Line: s.line, + Column: s.column, + Offset: s.offset, + IndentNum: s.indentNum, + IndentLevel: s.indentLevel, + } +} + +func (s *Scanner) bufferedToken(ctx *Context) *token.Token { + if s.savedPos != nil { + tk := ctx.bufferedToken(s.savedPos) + s.savedPos = nil + return tk + } + line := s.line + column := s.column - len(ctx.buf) + level := s.indentLevel + if ctx.isSaveIndentMode() { + line -= s.newLineCount(ctx.buf) + column = strings.Index(string(ctx.obuf), string(ctx.buf)) + 1 + // Since we are in a literal, folded or raw folded + // we can use the indent level from the last token. + last := ctx.lastToken() + if last != nil { // The last token should never be nil here. + level = last.Position.IndentLevel + 1 + } + } + return ctx.bufferedToken(&token.Position{ + Line: line, + Column: column, + Offset: s.offset - len(ctx.buf), + IndentNum: s.indentNum, + IndentLevel: level, + }) +} + +func (s *Scanner) progressColumn(ctx *Context, num int) { + s.column += num + s.offset += num + ctx.progress(num) +} + +func (s *Scanner) progressLine(ctx *Context) { + s.column = 1 + s.line++ + s.offset++ + s.indentNum = 0 + s.isFirstCharAtLine = true + s.isAnchor = false + ctx.progress(1) +} + +func (s *Scanner) isNeededKeepPreviousIndentNum(ctx *Context, c rune) bool { + if !s.isChangedToIndentStateUp() { + return false + } + if ctx.isDocument() { + return true + } + if c == '-' && ctx.existsBuffer() { + return true + } + return false +} + +func (s *Scanner) isNewLineChar(c rune) bool { + if c == '\n' { + return true + } + if c == '\r' { + return true + } + return false +} + +func (s *Scanner) newLineCount(src []rune) int { + size := len(src) + cnt := 0 + for i := 0; i < size; i++ { + c := src[i] + switch c { + case '\r': + if i+1 < size && src[i+1] == '\n' { + i++ + } + cnt++ + case '\n': + cnt++ + } + } + return cnt +} + +func (s *Scanner) updateIndentState(ctx *Context) { + indentNumBasedIndentState := s.indentState + if s.prevIndentNum < s.indentNum { + s.indentLevel = s.prevIndentLevel + 1 + indentNumBasedIndentState = IndentStateUp + } else if s.prevIndentNum == s.indentNum { + s.indentLevel = s.prevIndentLevel + indentNumBasedIndentState = IndentStateEqual + } else { + indentNumBasedIndentState = IndentStateDown + if s.prevIndentLevel > 0 { + s.indentLevel = s.prevIndentLevel - 1 + } + } + + if s.prevIndentColumn > 0 { + if s.prevIndentColumn < s.column { + s.indentState = IndentStateUp + } else if s.prevIndentColumn != s.column || indentNumBasedIndentState != IndentStateEqual { + // The following case ( current position is 'd' ), some variables becomes like here + // - prevIndentColumn: 1 of 'a' + // - indentNumBasedIndentState: IndentStateDown because d's indentNum(1) is less than c's indentNum(3). + // Therefore, s.prevIndentColumn(1) == s.column(1) is true, but we want to treat this as IndentStateDown. + // So, we look also current indentState value by the above prevIndentNum based logic, and determins finally indentState. + // --- + // a: + // b + // c + // d: e + // ^ + s.indentState = IndentStateDown + } else { + s.indentState = IndentStateEqual + } + } else { + s.indentState = indentNumBasedIndentState + } +} + +func (s *Scanner) updateIndent(ctx *Context, c rune) { + if s.isFirstCharAtLine && s.isNewLineChar(c) && ctx.isDocument() { + return + } + if s.isFirstCharAtLine && c == ' ' { + s.indentNum++ + return + } + if !s.isFirstCharAtLine { + s.indentState = IndentStateKeep + return + } + s.updateIndentState(ctx) + s.isFirstCharAtLine = false + if s.isNeededKeepPreviousIndentNum(ctx, c) { + return + } + if s.indentState != IndentStateUp { + s.prevIndentColumn = 0 + } + s.prevIndentNum = s.indentNum + s.prevIndentLevel = s.indentLevel +} + +func (s *Scanner) isChangedToIndentStateDown() bool { + return s.indentState == IndentStateDown +} + +func (s *Scanner) isChangedToIndentStateUp() bool { + return s.indentState == IndentStateUp +} + +func (s *Scanner) isChangedToIndentStateEqual() bool { + return s.indentState == IndentStateEqual +} + +func (s *Scanner) addBufferedTokenIfExists(ctx *Context) { + ctx.addToken(s.bufferedToken(ctx)) +} + +func (s *Scanner) breakLiteral(ctx *Context) { + s.docStartColumn = 0 + ctx.breakLiteral() +} + +func (s *Scanner) scanSingleQuote(ctx *Context) (tk *token.Token, pos int) { + ctx.addOriginBuf('\'') + srcpos := s.pos() + startIndex := ctx.idx + 1 + src := ctx.src + size := len(src) + value := []rune{} + isFirstLineChar := false + isNewLine := false + for idx := startIndex; idx < size; idx++ { + if !isNewLine { + s.progressColumn(ctx, 1) + } else { + isNewLine = false + } + c := src[idx] + pos = idx + 1 + ctx.addOriginBuf(c) + if s.isNewLineChar(c) { + value = append(value, ' ') + isFirstLineChar = true + isNewLine = true + s.progressLine(ctx) + continue + } else if c == ' ' && isFirstLineChar { + continue + } else if c != '\'' { + value = append(value, c) + isFirstLineChar = false + continue + } + if idx+1 < len(ctx.src) && ctx.src[idx+1] == '\'' { + // '' handle as ' character + value = append(value, c) + ctx.addOriginBuf(c) + idx++ + continue + } + s.progressColumn(ctx, 1) + tk = token.SingleQuote(string(value), string(ctx.obuf), srcpos) + pos = idx - startIndex + 1 + return + } + return +} + +func hexToInt(b rune) int { + if b >= 'A' && b <= 'F' { + return int(b) - 'A' + 10 + } + if b >= 'a' && b <= 'f' { + return int(b) - 'a' + 10 + } + return int(b) - '0' +} + +func hexRunesToInt(b []rune) int { + sum := 0 + for i := 0; i < len(b); i++ { + sum += hexToInt(b[i]) << (uint(len(b)-i-1) * 4) + } + return sum +} + +func (s *Scanner) scanDoubleQuote(ctx *Context) (tk *token.Token, pos int) { + ctx.addOriginBuf('"') + srcpos := s.pos() + startIndex := ctx.idx + 1 + src := ctx.src + size := len(src) + value := []rune{} + isFirstLineChar := false + isNewLine := false + for idx := startIndex; idx < size; idx++ { + if !isNewLine { + s.progressColumn(ctx, 1) + } else { + isNewLine = false + } + c := src[idx] + pos = idx + 1 + ctx.addOriginBuf(c) + if s.isNewLineChar(c) { + value = append(value, ' ') + isFirstLineChar = true + isNewLine = true + s.progressLine(ctx) + continue + } else if c == ' ' && isFirstLineChar { + continue + } else if c == '\\' { + isFirstLineChar = false + if idx+1 < size { + nextChar := src[idx+1] + switch nextChar { + case 'b': + ctx.addOriginBuf(nextChar) + value = append(value, '\b') + idx++ + continue + case 'e': + ctx.addOriginBuf(nextChar) + value = append(value, '\x1B') + idx++ + continue + case 'f': + ctx.addOriginBuf(nextChar) + value = append(value, '\f') + idx++ + continue + case 'n': + ctx.addOriginBuf(nextChar) + value = append(value, '\n') + idx++ + continue + case 'v': + ctx.addOriginBuf(nextChar) + value = append(value, '\v') + idx++ + continue + case 'L': // LS (#x2028) + ctx.addOriginBuf(nextChar) + value = append(value, []rune{'\xE2', '\x80', '\xA8'}...) + idx++ + continue + case 'N': // NEL (#x85) + ctx.addOriginBuf(nextChar) + value = append(value, []rune{'\xC2', '\x85'}...) + idx++ + continue + case 'P': // PS (#x2029) + ctx.addOriginBuf(nextChar) + value = append(value, []rune{'\xE2', '\x80', '\xA9'}...) + idx++ + continue + case '_': // #xA0 + ctx.addOriginBuf(nextChar) + value = append(value, []rune{'\xC2', '\xA0'}...) + idx++ + continue + case '"': + ctx.addOriginBuf(nextChar) + value = append(value, nextChar) + idx++ + continue + case 'x': + if idx+3 >= size { + // TODO: need to return error + //err = xerrors.New("invalid escape character \\x") + return + } + codeNum := hexRunesToInt(src[idx+2 : idx+4]) + value = append(value, rune(codeNum)) + idx += 3 + continue + case 'u': + if idx+5 >= size { + // TODO: need to return error + //err = xerrors.New("invalid escape character \\u") + return + } + codeNum := hexRunesToInt(src[idx+2 : idx+6]) + value = append(value, rune(codeNum)) + idx += 5 + continue + case 'U': + if idx+9 >= size { + // TODO: need to return error + //err = xerrors.New("invalid escape character \\U") + return + } + codeNum := hexRunesToInt(src[idx+2 : idx+10]) + value = append(value, rune(codeNum)) + idx += 9 + continue + case '\\': + ctx.addOriginBuf(nextChar) + idx++ + } + } + value = append(value, c) + continue + } else if c != '"' { + value = append(value, c) + isFirstLineChar = false + continue + } + s.progressColumn(ctx, 1) + tk = token.DoubleQuote(string(value), string(ctx.obuf), srcpos) + pos = idx - startIndex + 1 + return + } + return +} + +func (s *Scanner) scanQuote(ctx *Context, ch rune) (tk *token.Token, pos int) { + if ch == '\'' { + return s.scanSingleQuote(ctx) + } + return s.scanDoubleQuote(ctx) +} + +func (s *Scanner) isMergeKey(ctx *Context) bool { + if ctx.repeatNum('<') != 2 { + return false + } + src := ctx.src + size := len(src) + for idx := ctx.idx + 2; idx < size; idx++ { + c := src[idx] + if c == ' ' { + continue + } + if c != ':' { + return false + } + if idx+1 < size { + nc := src[idx+1] + if nc == ' ' || s.isNewLineChar(nc) { + return true + } + } + } + return false +} + +func (s *Scanner) scanTag(ctx *Context) (tk *token.Token, pos int) { + ctx.addOriginBuf('!') + ctx.progress(1) // skip '!' character + for idx, c := range ctx.src[ctx.idx:] { + pos = idx + 1 + ctx.addOriginBuf(c) + switch c { + case ' ', '\n', '\r': + value := ctx.source(ctx.idx-1, ctx.idx+idx) + tk = token.Tag(value, string(ctx.obuf), s.pos()) + pos = len([]rune(value)) + return + } + } + return +} + +func (s *Scanner) scanComment(ctx *Context) (tk *token.Token, pos int) { + ctx.addOriginBuf('#') + ctx.progress(1) // skip '#' character + for idx, c := range ctx.src[ctx.idx:] { + pos = idx + 1 + ctx.addOriginBuf(c) + switch c { + case '\n', '\r': + if ctx.previousChar() == '\\' { + continue + } + value := ctx.source(ctx.idx, ctx.idx+idx) + tk = token.Comment(value, string(ctx.obuf), s.pos()) + pos = len([]rune(value)) + 1 + return + } + } + // document ends with comment. + value := string(ctx.src[ctx.idx:]) + tk = token.Comment(value, string(ctx.obuf), s.pos()) + pos = len([]rune(value)) + 1 + return +} + +func trimCommentFromLiteralOpt(text string) (string, error) { + idx := strings.Index(text, "#") + if idx < 0 { + return text, nil + } + if idx == 0 { + return "", xerrors.New("invalid literal header") + } + return text[:idx-1], nil +} + +func (s *Scanner) scanLiteral(ctx *Context, c rune) { + ctx.addOriginBuf(c) + if ctx.isEOS() { + if ctx.isLiteral { + ctx.addBuf(c) + } + value := ctx.bufferedSrc() + ctx.addToken(token.String(string(value), string(ctx.obuf), s.pos())) + ctx.resetBuffer() + s.progressColumn(ctx, 1) + } else if s.isNewLineChar(c) { + if ctx.isLiteral { + ctx.addBuf(c) + } else { + ctx.addBuf(' ') + } + s.progressLine(ctx) + } else if s.isFirstCharAtLine && c == ' ' { + if 0 < s.docStartColumn && s.docStartColumn <= s.column { + ctx.addBuf(c) + } + s.progressColumn(ctx, 1) + } else { + if s.docStartColumn == 0 { + s.docStartColumn = s.column + } + ctx.addBuf(c) + s.progressColumn(ctx, 1) + } +} + +func (s *Scanner) scanLiteralHeader(ctx *Context) (pos int, err error) { + header := ctx.currentChar() + ctx.addOriginBuf(header) + ctx.progress(1) // skip '|' or '>' character + for idx, c := range ctx.src[ctx.idx:] { + pos = idx + ctx.addOriginBuf(c) + switch c { + case '\n', '\r': + value := ctx.source(ctx.idx, ctx.idx+idx) + opt := strings.TrimRight(value, " ") + orgOptLen := len(opt) + opt, err = trimCommentFromLiteralOpt(opt) + if err != nil { + return + } + switch opt { + case "", "+", "-", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9": + hasComment := len(opt) < orgOptLen + if header == '|' { + if hasComment { + commentLen := orgOptLen - len(opt) + headerPos := strings.Index(string(ctx.obuf), "|") + litBuf := ctx.obuf[:len(ctx.obuf)-commentLen-headerPos] + commentBuf := ctx.obuf[len(litBuf):] + ctx.addToken(token.Literal("|"+opt, string(litBuf), s.pos())) + s.column += len(litBuf) + s.offset += len(litBuf) + commentHeader := strings.Index(value, "#") + ctx.addToken(token.Comment(string(value[commentHeader+1:]), string(commentBuf), s.pos())) + } else { + ctx.addToken(token.Literal("|"+opt, string(ctx.obuf), s.pos())) + } + ctx.isLiteral = true + } else if header == '>' { + if hasComment { + commentLen := orgOptLen - len(opt) + headerPos := strings.Index(string(ctx.obuf), ">") + foldedBuf := ctx.obuf[:len(ctx.obuf)-commentLen-headerPos] + commentBuf := ctx.obuf[len(foldedBuf):] + ctx.addToken(token.Folded(">"+opt, string(foldedBuf), s.pos())) + s.column += len(foldedBuf) + s.offset += len(foldedBuf) + commentHeader := strings.Index(value, "#") + ctx.addToken(token.Comment(string(value[commentHeader+1:]), string(commentBuf), s.pos())) + } else { + ctx.addToken(token.Folded(">"+opt, string(ctx.obuf), s.pos())) + } + ctx.isFolded = true + } + s.indentState = IndentStateKeep + ctx.resetBuffer() + ctx.literalOpt = opt + return + } + break + } + } + err = xerrors.New("invalid literal header") + return +} + +func (s *Scanner) scanNewLine(ctx *Context, c rune) { + if len(ctx.buf) > 0 && s.savedPos == nil { + s.savedPos = s.pos() + s.savedPos.Column -= len(ctx.bufferedSrc()) + } + + // if the following case, origin buffer has unnecessary two spaces. + // So, `removeRightSpaceFromOriginBuf` remove them, also fix column number too. + // --- + // a:[space][space] + // b: c + removedNum := ctx.removeRightSpaceFromBuf() + if removedNum > 0 { + s.column -= removedNum + s.offset -= removedNum + if s.savedPos != nil { + s.savedPos.Column -= removedNum + } + } + + if ctx.isEOS() { + s.addBufferedTokenIfExists(ctx) + } else if s.isAnchor { + s.addBufferedTokenIfExists(ctx) + } + ctx.addBuf(' ') + ctx.addOriginBuf(c) + ctx.isSingleLine = false + s.progressLine(ctx) +} + +func (s *Scanner) scan(ctx *Context) (pos int) { + for ctx.next() { + pos = ctx.nextPos() + c := ctx.currentChar() + s.updateIndent(ctx, c) + if ctx.isDocument() { + if s.isChangedToIndentStateEqual() || + s.isChangedToIndentStateDown() { + s.addBufferedTokenIfExists(ctx) + s.breakLiteral(ctx) + } else { + s.scanLiteral(ctx, c) + continue + } + } else if s.isChangedToIndentStateDown() { + s.addBufferedTokenIfExists(ctx) + } else if s.isChangedToIndentStateEqual() { + // if first character is new line character, buffer expect to raw folded literal + if len(ctx.obuf) > 0 && s.newLineCount(ctx.obuf) <= 1 { + // doesn't raw folded literal + s.addBufferedTokenIfExists(ctx) + } + } + switch c { + case '{': + if !ctx.existsBuffer() { + ctx.addOriginBuf(c) + ctx.addToken(token.MappingStart(string(ctx.obuf), s.pos())) + s.startedFlowMapNum++ + s.progressColumn(ctx, 1) + return + } + case '}': + if !ctx.existsBuffer() || s.startedFlowMapNum > 0 { + ctx.addToken(s.bufferedToken(ctx)) + ctx.addOriginBuf(c) + ctx.addToken(token.MappingEnd(string(ctx.obuf), s.pos())) + s.startedFlowMapNum-- + s.progressColumn(ctx, 1) + return + } + case '.': + if s.indentNum == 0 && s.column == 1 && ctx.repeatNum('.') == 3 { + ctx.addToken(token.DocumentEnd(string(ctx.obuf)+"...", s.pos())) + s.progressColumn(ctx, 3) + pos += 2 + return + } + case '<': + if s.isMergeKey(ctx) { + s.prevIndentColumn = s.column + ctx.addToken(token.MergeKey(string(ctx.obuf)+"<<", s.pos())) + s.progressColumn(ctx, 1) + pos++ + return + } + case '-': + if s.indentNum == 0 && s.column == 1 && ctx.repeatNum('-') == 3 { + s.addBufferedTokenIfExists(ctx) + ctx.addToken(token.DocumentHeader(string(ctx.obuf)+"---", s.pos())) + s.progressColumn(ctx, 3) + pos += 2 + return + } + if ctx.existsBuffer() && s.isChangedToIndentStateUp() { + // raw folded + ctx.isRawFolded = true + ctx.addBuf(c) + ctx.addOriginBuf(c) + s.progressColumn(ctx, 1) + continue + } + if ctx.existsBuffer() { + // '-' is literal + ctx.addBuf(c) + ctx.addOriginBuf(c) + s.progressColumn(ctx, 1) + continue + } + nc := ctx.nextChar() + if nc == ' ' || s.isNewLineChar(nc) { + s.addBufferedTokenIfExists(ctx) + ctx.addOriginBuf(c) + tk := token.SequenceEntry(string(ctx.obuf), s.pos()) + s.prevIndentColumn = tk.Position.Column + ctx.addToken(tk) + s.progressColumn(ctx, 1) + return + } + case '[': + if !ctx.existsBuffer() { + ctx.addOriginBuf(c) + ctx.addToken(token.SequenceStart(string(ctx.obuf), s.pos())) + s.startedFlowSequenceNum++ + s.progressColumn(ctx, 1) + return + } + case ']': + if !ctx.existsBuffer() || s.startedFlowSequenceNum > 0 { + s.addBufferedTokenIfExists(ctx) + ctx.addOriginBuf(c) + ctx.addToken(token.SequenceEnd(string(ctx.obuf), s.pos())) + s.startedFlowSequenceNum-- + s.progressColumn(ctx, 1) + return + } + case ',': + if s.startedFlowSequenceNum > 0 || s.startedFlowMapNum > 0 { + s.addBufferedTokenIfExists(ctx) + ctx.addOriginBuf(c) + ctx.addToken(token.CollectEntry(string(ctx.obuf), s.pos())) + s.progressColumn(ctx, 1) + return + } + case ':': + nc := ctx.nextChar() + if s.startedFlowMapNum > 0 || nc == ' ' || s.isNewLineChar(nc) || ctx.isNextEOS() { + // mapping value + tk := s.bufferedToken(ctx) + if tk != nil { + s.prevIndentColumn = tk.Position.Column + ctx.addToken(tk) + } else if tk := ctx.lastToken(); tk != nil { + // If the map key is quote, the buffer does not exist because it has already been cut into tokens. + // Therefore, we need to check the last token. + if tk.Indicator == token.QuotedScalarIndicator { + s.prevIndentColumn = tk.Position.Column + } + } + ctx.addToken(token.MappingValue(s.pos())) + s.progressColumn(ctx, 1) + return + } + case '|', '>': + if !ctx.existsBuffer() { + progress, err := s.scanLiteralHeader(ctx) + if err != nil { + // TODO: returns syntax error object + return + } + s.progressColumn(ctx, progress) + s.progressLine(ctx) + continue + } + case '!': + if !ctx.existsBuffer() { + token, progress := s.scanTag(ctx) + ctx.addToken(token) + s.progressColumn(ctx, progress) + if c := ctx.previousChar(); s.isNewLineChar(c) { + s.progressLine(ctx) + } + pos += progress + return + } + case '%': + if !ctx.existsBuffer() && s.indentNum == 0 { + ctx.addToken(token.Directive(string(ctx.obuf)+"%", s.pos())) + s.progressColumn(ctx, 1) + return + } + case '?': + nc := ctx.nextChar() + if !ctx.existsBuffer() && nc == ' ' { + ctx.addToken(token.MappingKey(s.pos())) + s.progressColumn(ctx, 1) + return + } + case '&': + if !ctx.existsBuffer() { + s.addBufferedTokenIfExists(ctx) + ctx.addOriginBuf(c) + ctx.addToken(token.Anchor(string(ctx.obuf), s.pos())) + s.progressColumn(ctx, 1) + s.isAnchor = true + return + } + case '*': + if !ctx.existsBuffer() { + s.addBufferedTokenIfExists(ctx) + ctx.addOriginBuf(c) + ctx.addToken(token.Alias(string(ctx.obuf), s.pos())) + s.progressColumn(ctx, 1) + return + } + case '#': + if !ctx.existsBuffer() || ctx.previousChar() == ' ' { + s.addBufferedTokenIfExists(ctx) + token, progress := s.scanComment(ctx) + ctx.addToken(token) + s.progressColumn(ctx, progress) + s.progressLine(ctx) + pos += progress + return + } + case '\'', '"': + if !ctx.existsBuffer() { + token, progress := s.scanQuote(ctx, c) + ctx.addToken(token) + pos += progress + // If the non-whitespace character immediately following the quote is ':', the quote should be treated as a map key. + // Therefore, do not return and continue processing as a normal map key. + if ctx.currentCharWithSkipWhitespace() == ':' { + continue + } + return + } + case '\r', '\n': + // There is no problem that we ignore CR which followed by LF and normalize it to LF, because of following YAML1.2 spec. + // > Line breaks inside scalar content must be normalized by the YAML processor. Each such line break must be parsed into a single line feed character. + // > Outside scalar content, YAML allows any line break to be used to terminate lines. + // > -- https://yaml.org/spec/1.2/spec.html + if c == '\r' && ctx.nextChar() == '\n' { + ctx.addOriginBuf('\r') + ctx.progress(1) + c = '\n' + } + s.scanNewLine(ctx, c) + continue + case ' ': + if ctx.isSaveIndentMode() || (!s.isAnchor && !s.isFirstCharAtLine) { + ctx.addBuf(c) + ctx.addOriginBuf(c) + s.progressColumn(ctx, 1) + continue + } + if s.isFirstCharAtLine { + s.progressColumn(ctx, 1) + ctx.addOriginBuf(c) + continue + } + s.addBufferedTokenIfExists(ctx) + pos-- // to rescan white space at next scanning for adding white space to next buffer. + s.isAnchor = false + return + } + ctx.addBuf(c) + ctx.addOriginBuf(c) + s.progressColumn(ctx, 1) + } + s.addBufferedTokenIfExists(ctx) + return +} + +// Init prepares the scanner s to tokenize the text src by setting the scanner at the beginning of src. +func (s *Scanner) Init(text string) { + src := []rune(text) + s.source = src + s.sourcePos = 0 + s.sourceSize = len(src) + s.line = 1 + s.column = 1 + s.offset = 1 + s.prevIndentLevel = 0 + s.prevIndentNum = 0 + s.prevIndentColumn = 0 + s.indentLevel = 0 + s.indentNum = 0 + s.isFirstCharAtLine = true +} + +// Scan scans the next token and returns the token collection. The source end is indicated by io.EOF. +func (s *Scanner) Scan() (token.Tokens, error) { + if s.sourcePos >= s.sourceSize { + return nil, io.EOF + } + ctx := newContext(s.source[s.sourcePos:]) + defer ctx.release() + progress := s.scan(ctx) + s.sourcePos += progress + var tokens token.Tokens + tokens = append(tokens, ctx.tokens...) + return tokens, nil +} diff --git a/vendor/github.com/goccy/go-yaml/stdlib_quote.go b/vendor/github.com/goccy/go-yaml/stdlib_quote.go new file mode 100644 index 000000000..be50ae612 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/stdlib_quote.go @@ -0,0 +1,103 @@ +// Copied and trimmed down from https://github.com/golang/go/blob/e3769299cd3484e018e0e2a6e1b95c2b18ce4f41/src/strconv/quote.go +// We want to use the standard library's private "quoteWith" function rather than write our own so that we get robust unicode support. +// Every private function called by quoteWith was copied. +// There are 2 modifications to simplify the code: +// 1. The unicode.IsPrint function was substituted for the custom implementation of IsPrint +// 2. All code paths reachable only when ASCIIonly or grphicOnly are set to true were removed. + +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package yaml + +import ( + "unicode" + "unicode/utf8" +) + +const ( + lowerhex = "0123456789abcdef" +) + +func quoteWith(s string, quote byte) string { + return string(appendQuotedWith(make([]byte, 0, 3*len(s)/2), s, quote)) +} + +func appendQuotedWith(buf []byte, s string, quote byte) []byte { + // Often called with big strings, so preallocate. If there's quoting, + // this is conservative but still helps a lot. + if cap(buf)-len(buf) < len(s) { + nBuf := make([]byte, len(buf), len(buf)+1+len(s)+1) + copy(nBuf, buf) + buf = nBuf + } + buf = append(buf, quote) + for width := 0; len(s) > 0; s = s[width:] { + r := rune(s[0]) + width = 1 + if r >= utf8.RuneSelf { + r, width = utf8.DecodeRuneInString(s) + } + if width == 1 && r == utf8.RuneError { + buf = append(buf, `\x`...) + buf = append(buf, lowerhex[s[0]>>4]) + buf = append(buf, lowerhex[s[0]&0xF]) + continue + } + buf = appendEscapedRune(buf, r, quote) + } + buf = append(buf, quote) + return buf +} + +func appendEscapedRune(buf []byte, r rune, quote byte) []byte { + var runeTmp [utf8.UTFMax]byte + if r == rune(quote) || r == '\\' { // always backslashed + buf = append(buf, '\\') + buf = append(buf, byte(r)) + return buf + } + if unicode.IsPrint(r) { + n := utf8.EncodeRune(runeTmp[:], r) + buf = append(buf, runeTmp[:n]...) + return buf + } + switch r { + case '\a': + buf = append(buf, `\a`...) + case '\b': + buf = append(buf, `\b`...) + case '\f': + buf = append(buf, `\f`...) + case '\n': + buf = append(buf, `\n`...) + case '\r': + buf = append(buf, `\r`...) + case '\t': + buf = append(buf, `\t`...) + case '\v': + buf = append(buf, `\v`...) + default: + switch { + case r < ' ': + buf = append(buf, `\x`...) + buf = append(buf, lowerhex[byte(r)>>4]) + buf = append(buf, lowerhex[byte(r)&0xF]) + case r > utf8.MaxRune: + r = 0xFFFD + fallthrough + case r < 0x10000: + buf = append(buf, `\u`...) + for s := 12; s >= 0; s -= 4 { + buf = append(buf, lowerhex[r>>uint(s)&0xF]) + } + default: + buf = append(buf, `\U`...) + for s := 28; s >= 0; s -= 4 { + buf = append(buf, lowerhex[r>>uint(s)&0xF]) + } + } + } + return buf +} diff --git a/vendor/github.com/goccy/go-yaml/struct.go b/vendor/github.com/goccy/go-yaml/struct.go new file mode 100644 index 000000000..a3da8ddd3 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/struct.go @@ -0,0 +1,130 @@ +package yaml + +import ( + "reflect" + "strings" + + "golang.org/x/xerrors" +) + +const ( + // StructTagName tag keyword for Marshal/Unmarshal + StructTagName = "yaml" +) + +// StructField information for each the field in structure +type StructField struct { + FieldName string + RenderName string + AnchorName string + AliasName string + IsAutoAnchor bool + IsAutoAlias bool + IsOmitEmpty bool + IsFlow bool + IsInline bool +} + +func getTag(field reflect.StructField) string { + // If struct tag `yaml` exist, use that. If no `yaml` + // exists, but `json` does, use that and try the best to + // adhere to its rules + tag := field.Tag.Get(StructTagName) + if tag == "" { + tag = field.Tag.Get(`json`) + } + return tag +} + +func structField(field reflect.StructField) *StructField { + tag := getTag(field) + fieldName := strings.ToLower(field.Name) + options := strings.Split(tag, ",") + if len(options) > 0 { + if options[0] != "" { + fieldName = options[0] + } + } + structField := &StructField{ + FieldName: field.Name, + RenderName: fieldName, + } + if len(options) > 1 { + for _, opt := range options[1:] { + switch { + case opt == "omitempty": + structField.IsOmitEmpty = true + case opt == "flow": + structField.IsFlow = true + case opt == "inline": + structField.IsInline = true + case strings.HasPrefix(opt, "anchor"): + anchor := strings.Split(opt, "=") + if len(anchor) > 1 { + structField.AnchorName = anchor[1] + } else { + structField.IsAutoAnchor = true + } + case strings.HasPrefix(opt, "alias"): + alias := strings.Split(opt, "=") + if len(alias) > 1 { + structField.AliasName = alias[1] + } else { + structField.IsAutoAlias = true + } + default: + } + } + } + return structField +} + +func isIgnoredStructField(field reflect.StructField) bool { + if field.PkgPath != "" && !field.Anonymous { + // private field + return true + } + tag := getTag(field) + if tag == "-" { + return true + } + return false +} + +type StructFieldMap map[string]*StructField + +func (m StructFieldMap) isIncludedRenderName(name string) bool { + for _, v := range m { + if !v.IsInline && v.RenderName == name { + return true + } + } + return false +} + +func (m StructFieldMap) hasMergeProperty() bool { + for _, v := range m { + if v.IsOmitEmpty && v.IsInline && v.IsAutoAlias { + return true + } + } + return false +} + +func structFieldMap(structType reflect.Type) (StructFieldMap, error) { + structFieldMap := StructFieldMap{} + renderNameMap := map[string]struct{}{} + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if isIgnoredStructField(field) { + continue + } + structField := structField(field) + if _, exists := renderNameMap[structField.RenderName]; exists { + return nil, xerrors.Errorf("duplicated struct field name %s", structField.RenderName) + } + structFieldMap[structField.FieldName] = structField + renderNameMap[structField.RenderName] = struct{}{} + } + return structFieldMap, nil +} diff --git a/vendor/github.com/goccy/go-yaml/token/token.go b/vendor/github.com/goccy/go-yaml/token/token.go new file mode 100644 index 000000000..182f4bea9 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/token/token.go @@ -0,0 +1,1070 @@ +package token + +import ( + "fmt" + "strings" +) + +// Character type for character +type Character byte + +const ( + // SequenceEntryCharacter character for sequence entry + SequenceEntryCharacter Character = '-' + // MappingKeyCharacter character for mapping key + MappingKeyCharacter Character = '?' + // MappingValueCharacter character for mapping value + MappingValueCharacter Character = ':' + // CollectEntryCharacter character for collect entry + CollectEntryCharacter Character = ',' + // SequenceStartCharacter character for sequence start + SequenceStartCharacter Character = '[' + // SequenceEndCharacter character for sequence end + SequenceEndCharacter Character = ']' + // MappingStartCharacter character for mapping start + MappingStartCharacter Character = '{' + // MappingEndCharacter character for mapping end + MappingEndCharacter Character = '}' + // CommentCharacter character for comment + CommentCharacter Character = '#' + // AnchorCharacter character for anchor + AnchorCharacter Character = '&' + // AliasCharacter character for alias + AliasCharacter Character = '*' + // TagCharacter character for tag + TagCharacter Character = '!' + // LiteralCharacter character for literal + LiteralCharacter Character = '|' + // FoldedCharacter character for folded + FoldedCharacter Character = '>' + // SingleQuoteCharacter character for single quote + SingleQuoteCharacter Character = '\'' + // DoubleQuoteCharacter character for double quote + DoubleQuoteCharacter Character = '"' + // DirectiveCharacter character for directive + DirectiveCharacter Character = '%' + // SpaceCharacter character for space + SpaceCharacter Character = ' ' + // LineBreakCharacter character for line break + LineBreakCharacter Character = '\n' +) + +// Type type identifier for token +type Type int + +const ( + // UnknownType reserve for invalid type + UnknownType Type = iota + // DocumentHeaderType type for DocumentHeader token + DocumentHeaderType + // DocumentEndType type for DocumentEnd token + DocumentEndType + // SequenceEntryType type for SequenceEntry token + SequenceEntryType + // MappingKeyType type for MappingKey token + MappingKeyType + // MappingValueType type for MappingValue token + MappingValueType + // MergeKeyType type for MergeKey token + MergeKeyType + // CollectEntryType type for CollectEntry token + CollectEntryType + // SequenceStartType type for SequenceStart token + SequenceStartType + // SequenceEndType type for SequenceEnd token + SequenceEndType + // MappingStartType type for MappingStart token + MappingStartType + // MappingEndType type for MappingEnd token + MappingEndType + // CommentType type for Comment token + CommentType + // AnchorType type for Anchor token + AnchorType + // AliasType type for Alias token + AliasType + // TagType type for Tag token + TagType + // LiteralType type for Literal token + LiteralType + // FoldedType type for Folded token + FoldedType + // SingleQuoteType type for SingleQuote token + SingleQuoteType + // DoubleQuoteType type for DoubleQuote token + DoubleQuoteType + // DirectiveType type for Directive token + DirectiveType + // SpaceType type for Space token + SpaceType + // NullType type for Null token + NullType + // InfinityType type for Infinity token + InfinityType + // NanType type for Nan token + NanType + // IntegerType type for Integer token + IntegerType + // BinaryIntegerType type for BinaryInteger token + BinaryIntegerType + // OctetIntegerType type for OctetInteger token + OctetIntegerType + // HexIntegerType type for HexInteger token + HexIntegerType + // FloatType type for Float token + FloatType + // StringType type for String token + StringType + // BoolType type for Bool token + BoolType +) + +// String type identifier to text +func (t Type) String() string { + switch t { + case UnknownType: + return "Unknown" + case DocumentHeaderType: + return "DocumentHeader" + case DocumentEndType: + return "DocumentEnd" + case SequenceEntryType: + return "SequenceEntry" + case MappingKeyType: + return "MappingKey" + case MappingValueType: + return "MappingValue" + case MergeKeyType: + return "MergeKey" + case CollectEntryType: + return "CollectEntry" + case SequenceStartType: + return "SequenceStart" + case SequenceEndType: + return "SequenceEnd" + case MappingStartType: + return "MappingStart" + case MappingEndType: + return "MappingEnd" + case CommentType: + return "Comment" + case AnchorType: + return "Anchor" + case AliasType: + return "Alias" + case TagType: + return "Tag" + case LiteralType: + return "Literal" + case FoldedType: + return "Folded" + case SingleQuoteType: + return "SingleQuote" + case DoubleQuoteType: + return "DoubleQuote" + case DirectiveType: + return "Directive" + case SpaceType: + return "Space" + case StringType: + return "String" + case BoolType: + return "Bool" + case IntegerType: + return "Integer" + case BinaryIntegerType: + return "BinaryInteger" + case OctetIntegerType: + return "OctetInteger" + case HexIntegerType: + return "HexInteger" + case FloatType: + return "Float" + case NullType: + return "Null" + case InfinityType: + return "Infinity" + case NanType: + return "Nan" + } + return "" +} + +// CharacterType type for character category +type CharacterType int + +const ( + // CharacterTypeIndicator type of indicator character + CharacterTypeIndicator CharacterType = iota + // CharacterTypeWhiteSpace type of white space character + CharacterTypeWhiteSpace + // CharacterTypeMiscellaneous type of miscellaneous character + CharacterTypeMiscellaneous + // CharacterTypeEscaped type of escaped character + CharacterTypeEscaped +) + +// String character type identifier to text +func (c CharacterType) String() string { + switch c { + case CharacterTypeIndicator: + return "Indicator" + case CharacterTypeWhiteSpace: + return "WhiteSpcae" + case CharacterTypeMiscellaneous: + return "Miscellaneous" + case CharacterTypeEscaped: + return "Escaped" + } + return "" +} + +// Indicator type for indicator +type Indicator int + +const ( + // NotIndicator not indicator + NotIndicator Indicator = iota + // BlockStructureIndicator indicator for block structure ( '-', '?', ':' ) + BlockStructureIndicator + // FlowCollectionIndicator indicator for flow collection ( '[', ']', '{', '}', ',' ) + FlowCollectionIndicator + // CommentIndicator indicator for comment ( '#' ) + CommentIndicator + // NodePropertyIndicator indicator for node property ( '!', '&', '*' ) + NodePropertyIndicator + // BlockScalarIndicator indicator for block scalar ( '|', '>' ) + BlockScalarIndicator + // QuotedScalarIndicator indicator for quoted scalar ( ''', '"' ) + QuotedScalarIndicator + // DirectiveIndicator indicator for directive ( '%' ) + DirectiveIndicator + // InvalidUseOfReservedIndicator indicator for invalid use of reserved keyword ( '@', '`' ) + InvalidUseOfReservedIndicator +) + +// String indicator to text +func (i Indicator) String() string { + switch i { + case NotIndicator: + return "NotIndicator" + case BlockStructureIndicator: + return "BlockStructure" + case FlowCollectionIndicator: + return "FlowCollection" + case CommentIndicator: + return "Comment" + case NodePropertyIndicator: + return "NodeProperty" + case BlockScalarIndicator: + return "BlockScalar" + case QuotedScalarIndicator: + return "QuotedScalar" + case DirectiveIndicator: + return "Directive" + case InvalidUseOfReservedIndicator: + return "InvalidUseOfReserved" + } + return "" +} + +var ( + reservedNullKeywords = []string{ + "null", + "Null", + "NULL", + "~", + } + reservedBoolKeywords = []string{ + "true", + "True", + "TRUE", + "false", + "False", + "FALSE", + } + // For compatibility with other YAML 1.1 parsers + // Note that we use these solely for encoding the bool value with quotes. + // go-yaml should not treat these as reserved keywords at parsing time. + // as go-yaml is supposed to be compliant only with YAML 1.2. + reservedLegacyBoolKeywords = []string{ + "y", + "Y", + "yes", + "Yes", + "YES", + "n", + "N", + "no", + "No", + "NO", + "on", + "On", + "ON", + "off", + "Off", + "OFF", + } + reservedInfKeywords = []string{ + ".inf", + ".Inf", + ".INF", + "-.inf", + "-.Inf", + "-.INF", + } + reservedNanKeywords = []string{ + ".nan", + ".NaN", + ".NAN", + } + reservedKeywordMap = map[string]func(string, string, *Position) *Token{} + // reservedEncKeywordMap contains is the keyword map used at encoding time. + // This is supposed to be a superset of reservedKeywordMap, + // and used to quote legacy keywords present in YAML 1.1 or lesser for compatibility reasons, + // even though this library is supposed to be YAML 1.2-compliant. + reservedEncKeywordMap = map[string]func(string, string, *Position) *Token{} +) + +func reservedKeywordToken(typ Type, value, org string, pos *Position) *Token { + return &Token{ + Type: typ, + CharacterType: CharacterTypeMiscellaneous, + Indicator: NotIndicator, + Value: value, + Origin: org, + Position: pos, + } +} + +func init() { + for _, keyword := range reservedNullKeywords { + reservedKeywordMap[keyword] = func(value, org string, pos *Position) *Token { + return reservedKeywordToken(NullType, value, org, pos) + } + } + for _, keyword := range reservedBoolKeywords { + f := func(value, org string, pos *Position) *Token { + return reservedKeywordToken(BoolType, value, org, pos) + } + reservedKeywordMap[keyword] = f + reservedEncKeywordMap[keyword] = f + } + for _, keyword := range reservedLegacyBoolKeywords { + reservedEncKeywordMap[keyword] = func(value, org string, pos *Position) *Token { + return reservedKeywordToken(BoolType, value, org, pos) + } + } + for _, keyword := range reservedInfKeywords { + reservedKeywordMap[keyword] = func(value, org string, pos *Position) *Token { + return reservedKeywordToken(InfinityType, value, org, pos) + } + } + for _, keyword := range reservedNanKeywords { + reservedKeywordMap[keyword] = func(value, org string, pos *Position) *Token { + return reservedKeywordToken(NanType, value, org, pos) + } + } +} + +// ReservedTagKeyword type of reserved tag keyword +type ReservedTagKeyword string + +const ( + // IntegerTag `!!int` tag + IntegerTag ReservedTagKeyword = "!!int" + // FloatTag `!!float` tag + FloatTag ReservedTagKeyword = "!!float" + // NullTag `!!null` tag + NullTag ReservedTagKeyword = "!!null" + // SequenceTag `!!seq` tag + SequenceTag ReservedTagKeyword = "!!seq" + // MappingTag `!!map` tag + MappingTag ReservedTagKeyword = "!!map" + // StringTag `!!str` tag + StringTag ReservedTagKeyword = "!!str" + // BinaryTag `!!binary` tag + BinaryTag ReservedTagKeyword = "!!binary" + // OrderedMapTag `!!omap` tag + OrderedMapTag ReservedTagKeyword = "!!omap" + // SetTag `!!set` tag + SetTag ReservedTagKeyword = "!!set" + // TimestampTag `!!timestamp` tag + TimestampTag ReservedTagKeyword = "!!timestamp" +) + +var ( + // ReservedTagKeywordMap map for reserved tag keywords + ReservedTagKeywordMap = map[ReservedTagKeyword]func(string, string, *Position) *Token{ + IntegerTag: func(value, org string, pos *Position) *Token { + return &Token{ + Type: TagType, + CharacterType: CharacterTypeIndicator, + Indicator: NodePropertyIndicator, + Value: value, + Origin: org, + Position: pos, + } + }, + FloatTag: func(value, org string, pos *Position) *Token { + return &Token{ + Type: TagType, + CharacterType: CharacterTypeIndicator, + Indicator: NodePropertyIndicator, + Value: value, + Origin: org, + Position: pos, + } + }, + NullTag: func(value, org string, pos *Position) *Token { + return &Token{ + Type: TagType, + CharacterType: CharacterTypeIndicator, + Indicator: NodePropertyIndicator, + Value: value, + Origin: org, + Position: pos, + } + }, + SequenceTag: func(value, org string, pos *Position) *Token { + return &Token{ + Type: TagType, + CharacterType: CharacterTypeIndicator, + Indicator: NodePropertyIndicator, + Value: value, + Origin: org, + Position: pos, + } + }, + MappingTag: func(value, org string, pos *Position) *Token { + return &Token{ + Type: TagType, + CharacterType: CharacterTypeIndicator, + Indicator: NodePropertyIndicator, + Value: value, + Origin: org, + Position: pos, + } + }, + StringTag: func(value, org string, pos *Position) *Token { + return &Token{ + Type: TagType, + CharacterType: CharacterTypeIndicator, + Indicator: NodePropertyIndicator, + Value: value, + Origin: org, + Position: pos, + } + }, + BinaryTag: func(value, org string, pos *Position) *Token { + return &Token{ + Type: TagType, + CharacterType: CharacterTypeIndicator, + Indicator: NodePropertyIndicator, + Value: value, + Origin: org, + Position: pos, + } + }, + OrderedMapTag: func(value, org string, pos *Position) *Token { + return &Token{ + Type: TagType, + CharacterType: CharacterTypeIndicator, + Indicator: NodePropertyIndicator, + Value: value, + Origin: org, + Position: pos, + } + }, + SetTag: func(value, org string, pos *Position) *Token { + return &Token{ + Type: TagType, + CharacterType: CharacterTypeIndicator, + Indicator: NodePropertyIndicator, + Value: value, + Origin: org, + Position: pos, + } + }, + TimestampTag: func(value, org string, pos *Position) *Token { + return &Token{ + Type: TagType, + CharacterType: CharacterTypeIndicator, + Indicator: NodePropertyIndicator, + Value: value, + Origin: org, + Position: pos, + } + }, + } +) + +type numType int + +const ( + numTypeNone numType = iota + numTypeBinary + numTypeOctet + numTypeHex + numTypeFloat +) + +type numStat struct { + isNum bool + typ numType +} + +func getNumberStat(str string) *numStat { + stat := &numStat{} + if str == "" { + return stat + } + if str == "-" || str == "." || str == "+" || str == "_" { + return stat + } + if str[0] == '_' { + return stat + } + dotFound := false + isNegative := false + isExponent := false + if str[0] == '-' { + isNegative = true + } + for idx, c := range str { + switch c { + case 'x': + if (isNegative && idx == 2) || (!isNegative && idx == 1) { + continue + } + case 'o': + if (isNegative && idx == 2) || (!isNegative && idx == 1) { + continue + } + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + continue + case 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F': + if (len(str) > 2 && str[0] == '0' && str[1] == 'x') || + (len(str) > 3 && isNegative && str[1] == '0' && str[2] == 'x') { + // hex number + continue + } + if c == 'b' && ((isNegative && idx == 2) || (!isNegative && idx == 1)) { + // binary number + continue + } + if (c == 'e' || c == 'E') && dotFound { + // exponent + isExponent = true + continue + } + case '.': + if dotFound { + // multiple dot + return stat + } + dotFound = true + continue + case '-': + if idx == 0 || isExponent { + continue + } + case '+': + if idx == 0 || isExponent { + continue + } + case '_': + continue + } + return stat + } + stat.isNum = true + switch { + case dotFound: + stat.typ = numTypeFloat + case strings.HasPrefix(str, "0b") || strings.HasPrefix(str, "-0b"): + stat.typ = numTypeBinary + case strings.HasPrefix(str, "0x") || strings.HasPrefix(str, "-0x"): + stat.typ = numTypeHex + case strings.HasPrefix(str, "0o") || strings.HasPrefix(str, "-0o"): + stat.typ = numTypeOctet + case (len(str) > 1 && str[0] == '0') || (len(str) > 1 && str[0] == '-' && str[1] == '0'): + stat.typ = numTypeOctet + } + return stat +} + +func looksLikeTimeValue(value string) bool { + for i, c := range value { + switch c { + case ':', '1', '2', '3', '4', '5', '6', '7', '8', '9': + continue + case '0': + if i == 0 { + return false + } + continue + } + return false + } + return true +} + +// IsNeedQuoted whether need quote for passed string or not +func IsNeedQuoted(value string) bool { + if value == "" { + return true + } + if _, exists := reservedEncKeywordMap[value]; exists { + return true + } + if stat := getNumberStat(value); stat.isNum { + return true + } + first := value[0] + switch first { + case '*', '&', '[', '{', '}', ']', ',', '!', '|', '>', '%', '\'', '"', '@': + return true + } + last := value[len(value)-1] + switch last { + case ':': + return true + } + if looksLikeTimeValue(value) { + return true + } + for i, c := range value { + switch c { + case '#', '\\': + return true + case ':': + if i+1 < len(value) && value[i+1] == ' ' { + return true + } + } + } + return false +} + +// LiteralBlockHeader detect literal block scalar header +func LiteralBlockHeader(value string) string { + lbc := DetectLineBreakCharacter(value) + + switch { + case !strings.Contains(value, lbc): + return "" + case strings.HasSuffix(value, fmt.Sprintf("%s%s", lbc, lbc)): + return "|+" + case strings.HasSuffix(value, lbc): + return "|" + default: + return "|-" + } +} + +// New create reserved keyword token or number token and other string token +func New(value string, org string, pos *Position) *Token { + fn := reservedKeywordMap[value] + if fn != nil { + return fn(value, org, pos) + } + if stat := getNumberStat(value); stat.isNum { + tk := &Token{ + Type: IntegerType, + CharacterType: CharacterTypeMiscellaneous, + Indicator: NotIndicator, + Value: value, + Origin: org, + Position: pos, + } + switch stat.typ { + case numTypeFloat: + tk.Type = FloatType + case numTypeBinary: + tk.Type = BinaryIntegerType + case numTypeOctet: + tk.Type = OctetIntegerType + case numTypeHex: + tk.Type = HexIntegerType + } + return tk + } + return String(value, org, pos) +} + +// Position type for position in YAML document +type Position struct { + Line int + Column int + Offset int + IndentNum int + IndentLevel int +} + +// String position to text +func (p *Position) String() string { + return fmt.Sprintf("[level:%d,line:%d,column:%d,offset:%d]", p.IndentLevel, p.Line, p.Column, p.Offset) +} + +// Token type for token +type Token struct { + Type Type + CharacterType CharacterType + Indicator Indicator + Value string + Origin string + Position *Position + Next *Token + Prev *Token +} + +// PreviousType previous token type +func (t *Token) PreviousType() Type { + if t.Prev != nil { + return t.Prev.Type + } + return UnknownType +} + +// NextType next token type +func (t *Token) NextType() Type { + if t.Next != nil { + return t.Next.Type + } + return UnknownType +} + +// AddColumn append column number to current position of column +func (t *Token) AddColumn(col int) { + if t == nil { + return + } + t.Position.Column += col +} + +// Clone copy token ( preserve Prev/Next reference ) +func (t *Token) Clone() *Token { + if t == nil { + return nil + } + copied := *t + if t.Position != nil { + pos := *(t.Position) + copied.Position = &pos + } + return &copied +} + +// Tokens type of token collection +type Tokens []*Token + +func (t *Tokens) add(tk *Token) { + tokens := *t + if len(tokens) == 0 { + tokens = append(tokens, tk) + } else { + last := tokens[len(tokens)-1] + last.Next = tk + tk.Prev = last + tokens = append(tokens, tk) + } + *t = tokens +} + +// Add append new some tokens +func (t *Tokens) Add(tks ...*Token) { + for _, tk := range tks { + t.add(tk) + } +} + +// Dump dump all token structures for debugging +func (t Tokens) Dump() { + for _, tk := range t { + fmt.Printf("- %+v\n", tk) + } +} + +// String create token for String +func String(value string, org string, pos *Position) *Token { + return &Token{ + Type: StringType, + CharacterType: CharacterTypeMiscellaneous, + Indicator: NotIndicator, + Value: value, + Origin: org, + Position: pos, + } +} + +// SequenceEntry create token for SequenceEntry +func SequenceEntry(org string, pos *Position) *Token { + return &Token{ + Type: SequenceEntryType, + CharacterType: CharacterTypeIndicator, + Indicator: BlockStructureIndicator, + Value: string(SequenceEntryCharacter), + Origin: org, + Position: pos, + } +} + +// MappingKey create token for MappingKey +func MappingKey(pos *Position) *Token { + return &Token{ + Type: MappingKeyType, + CharacterType: CharacterTypeIndicator, + Indicator: BlockStructureIndicator, + Value: string(MappingKeyCharacter), + Origin: string(MappingKeyCharacter), + Position: pos, + } +} + +// MappingValue create token for MappingValue +func MappingValue(pos *Position) *Token { + return &Token{ + Type: MappingValueType, + CharacterType: CharacterTypeIndicator, + Indicator: BlockStructureIndicator, + Value: string(MappingValueCharacter), + Origin: string(MappingValueCharacter), + Position: pos, + } +} + +// CollectEntry create token for CollectEntry +func CollectEntry(org string, pos *Position) *Token { + return &Token{ + Type: CollectEntryType, + CharacterType: CharacterTypeIndicator, + Indicator: FlowCollectionIndicator, + Value: string(CollectEntryCharacter), + Origin: org, + Position: pos, + } +} + +// SequenceStart create token for SequenceStart +func SequenceStart(org string, pos *Position) *Token { + return &Token{ + Type: SequenceStartType, + CharacterType: CharacterTypeIndicator, + Indicator: FlowCollectionIndicator, + Value: string(SequenceStartCharacter), + Origin: org, + Position: pos, + } +} + +// SequenceEnd create token for SequenceEnd +func SequenceEnd(org string, pos *Position) *Token { + return &Token{ + Type: SequenceEndType, + CharacterType: CharacterTypeIndicator, + Indicator: FlowCollectionIndicator, + Value: string(SequenceEndCharacter), + Origin: org, + Position: pos, + } +} + +// MappingStart create token for MappingStart +func MappingStart(org string, pos *Position) *Token { + return &Token{ + Type: MappingStartType, + CharacterType: CharacterTypeIndicator, + Indicator: FlowCollectionIndicator, + Value: string(MappingStartCharacter), + Origin: org, + Position: pos, + } +} + +// MappingEnd create token for MappingEnd +func MappingEnd(org string, pos *Position) *Token { + return &Token{ + Type: MappingEndType, + CharacterType: CharacterTypeIndicator, + Indicator: FlowCollectionIndicator, + Value: string(MappingEndCharacter), + Origin: org, + Position: pos, + } +} + +// Comment create token for Comment +func Comment(value string, org string, pos *Position) *Token { + return &Token{ + Type: CommentType, + CharacterType: CharacterTypeIndicator, + Indicator: CommentIndicator, + Value: value, + Origin: org, + Position: pos, + } +} + +// Anchor create token for Anchor +func Anchor(org string, pos *Position) *Token { + return &Token{ + Type: AnchorType, + CharacterType: CharacterTypeIndicator, + Indicator: NodePropertyIndicator, + Value: string(AnchorCharacter), + Origin: org, + Position: pos, + } +} + +// Alias create token for Alias +func Alias(org string, pos *Position) *Token { + return &Token{ + Type: AliasType, + CharacterType: CharacterTypeIndicator, + Indicator: NodePropertyIndicator, + Value: string(AliasCharacter), + Origin: org, + Position: pos, + } +} + +// Tag create token for Tag +func Tag(value string, org string, pos *Position) *Token { + fn := ReservedTagKeywordMap[ReservedTagKeyword(value)] + if fn != nil { + return fn(value, org, pos) + } + return &Token{ + Type: TagType, + CharacterType: CharacterTypeIndicator, + Indicator: NodePropertyIndicator, + Value: value, + Origin: org, + Position: pos, + } +} + +// Literal create token for Literal +func Literal(value string, org string, pos *Position) *Token { + return &Token{ + Type: LiteralType, + CharacterType: CharacterTypeIndicator, + Indicator: BlockScalarIndicator, + Value: value, + Origin: org, + Position: pos, + } +} + +// Folded create token for Folded +func Folded(value string, org string, pos *Position) *Token { + return &Token{ + Type: FoldedType, + CharacterType: CharacterTypeIndicator, + Indicator: BlockScalarIndicator, + Value: value, + Origin: org, + Position: pos, + } +} + +// SingleQuote create token for SingleQuote +func SingleQuote(value string, org string, pos *Position) *Token { + return &Token{ + Type: SingleQuoteType, + CharacterType: CharacterTypeIndicator, + Indicator: QuotedScalarIndicator, + Value: value, + Origin: org, + Position: pos, + } +} + +// DoubleQuote create token for DoubleQuote +func DoubleQuote(value string, org string, pos *Position) *Token { + return &Token{ + Type: DoubleQuoteType, + CharacterType: CharacterTypeIndicator, + Indicator: QuotedScalarIndicator, + Value: value, + Origin: org, + Position: pos, + } +} + +// Directive create token for Directive +func Directive(org string, pos *Position) *Token { + return &Token{ + Type: DirectiveType, + CharacterType: CharacterTypeIndicator, + Indicator: DirectiveIndicator, + Value: string(DirectiveCharacter), + Origin: org, + Position: pos, + } +} + +// Space create token for Space +func Space(pos *Position) *Token { + return &Token{ + Type: SpaceType, + CharacterType: CharacterTypeWhiteSpace, + Indicator: NotIndicator, + Value: string(SpaceCharacter), + Origin: string(SpaceCharacter), + Position: pos, + } +} + +// MergeKey create token for MergeKey +func MergeKey(org string, pos *Position) *Token { + return &Token{ + Type: MergeKeyType, + CharacterType: CharacterTypeMiscellaneous, + Indicator: NotIndicator, + Value: "<<", + Origin: org, + Position: pos, + } +} + +// DocumentHeader create token for DocumentHeader +func DocumentHeader(org string, pos *Position) *Token { + return &Token{ + Type: DocumentHeaderType, + CharacterType: CharacterTypeMiscellaneous, + Indicator: NotIndicator, + Value: "---", + Origin: org, + Position: pos, + } +} + +// DocumentEnd create token for DocumentEnd +func DocumentEnd(org string, pos *Position) *Token { + return &Token{ + Type: DocumentEndType, + CharacterType: CharacterTypeMiscellaneous, + Indicator: NotIndicator, + Value: "...", + Origin: org, + Position: pos, + } +} + +// DetectLineBreakCharacter detect line break character in only one inside scalar content scope. +func DetectLineBreakCharacter(src string) string { + nc := strings.Count(src, "\n") + rc := strings.Count(src, "\r") + rnc := strings.Count(src, "\r\n") + switch { + case nc == rnc && rc == rnc: + return "\r\n" + case rc > nc: + return "\r" + default: + return "\n" + } +} diff --git a/vendor/github.com/goccy/go-yaml/validate.go b/vendor/github.com/goccy/go-yaml/validate.go new file mode 100644 index 000000000..20a2d6d91 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/validate.go @@ -0,0 +1,13 @@ +package yaml + +// StructValidator need to implement Struct method only +// ( see https://pkg.go.dev/github.com/go-playground/validator/v10#Validate.Struct ) +type StructValidator interface { + Struct(interface{}) error +} + +// FieldError need to implement StructField method only +// ( see https://pkg.go.dev/github.com/go-playground/validator/v10#FieldError ) +type FieldError interface { + StructField() string +} diff --git a/vendor/github.com/goccy/go-yaml/yaml.go b/vendor/github.com/goccy/go-yaml/yaml.go new file mode 100644 index 000000000..2e541d855 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/yaml.go @@ -0,0 +1,290 @@ +package yaml + +import ( + "bytes" + "context" + "io" + "reflect" + "sync" + + "github.com/goccy/go-yaml/ast" + "github.com/goccy/go-yaml/internal/errors" + "golang.org/x/xerrors" +) + +// BytesMarshaler interface may be implemented by types to customize their +// behavior when being marshaled into a YAML document. The returned value +// is marshaled in place of the original value implementing Marshaler. +// +// If an error is returned by MarshalYAML, the marshaling procedure stops +// and returns with the provided error. +type BytesMarshaler interface { + MarshalYAML() ([]byte, error) +} + +// BytesMarshalerContext interface use BytesMarshaler with context.Context. +type BytesMarshalerContext interface { + MarshalYAML(context.Context) ([]byte, error) +} + +// InterfaceMarshaler interface has MarshalYAML compatible with github.com/go-yaml/yaml package. +type InterfaceMarshaler interface { + MarshalYAML() (interface{}, error) +} + +// InterfaceMarshalerContext interface use InterfaceMarshaler with context.Context. +type InterfaceMarshalerContext interface { + MarshalYAML(context.Context) (interface{}, error) +} + +// BytesUnmarshaler interface may be implemented by types to customize their +// behavior when being unmarshaled from a YAML document. +type BytesUnmarshaler interface { + UnmarshalYAML([]byte) error +} + +// BytesUnmarshalerContext interface use BytesUnmarshaler with context.Context. +type BytesUnmarshalerContext interface { + UnmarshalYAML(context.Context, []byte) error +} + +// InterfaceUnmarshaler interface has UnmarshalYAML compatible with github.com/go-yaml/yaml package. +type InterfaceUnmarshaler interface { + UnmarshalYAML(func(interface{}) error) error +} + +// InterfaceUnmarshalerContext interface use InterfaceUnmarshaler with context.Context. +type InterfaceUnmarshalerContext interface { + UnmarshalYAML(context.Context, func(interface{}) error) error +} + +// MapItem is an item in a MapSlice. +type MapItem struct { + Key, Value interface{} +} + +// MapSlice encodes and decodes as a YAML map. +// The order of keys is preserved when encoding and decoding. +type MapSlice []MapItem + +// ToMap convert to map[interface{}]interface{}. +func (s MapSlice) ToMap() map[interface{}]interface{} { + v := map[interface{}]interface{}{} + for _, item := range s { + v[item.Key] = item.Value + } + return v +} + +// Marshal serializes the value provided into a YAML document. The structure +// of the generated document will reflect the structure of the value itself. +// Maps and pointers (to struct, string, int, etc) are accepted as the in value. +// +// Struct fields are only marshalled if they are exported (have an upper case +// first letter), and are marshalled using the field name lowercased as the +// default key. Custom keys may be defined via the "yaml" name in the field +// tag: the content preceding the first comma is used as the key, and the +// following comma-separated options are used to tweak the marshalling process. +// Conflicting names result in a runtime error. +// +// The field tag format accepted is: +// +// `(...) yaml:"[][,[,]]" (...)` +// +// The following flags are currently supported: +// +// omitempty Only include the field if it's not set to the zero +// value for the type or to empty slices or maps. +// Zero valued structs will be omitted if all their public +// fields are zero, unless they implement an IsZero +// method (see the IsZeroer interface type), in which +// case the field will be included if that method returns true. +// +// flow Marshal using a flow style (useful for structs, +// sequences and maps). +// +// inline Inline the field, which must be a struct or a map, +// causing all of its fields or keys to be processed as if +// they were part of the outer struct. For maps, keys must +// not conflict with the yaml keys of other struct fields. +// +// anchor Marshal with anchor. If want to define anchor name explicitly, use anchor=name style. +// Otherwise, if used 'anchor' name only, used the field name lowercased as the anchor name +// +// alias Marshal with alias. If want to define alias name explicitly, use alias=name style. +// Otherwise, If omitted alias name and the field type is pointer type, +// assigned anchor name automatically from same pointer address. +// +// In addition, if the key is "-", the field is ignored. +// +// For example: +// +// type T struct { +// F int `yaml:"a,omitempty"` +// B int +// } +// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n" +// yaml.Marshal(&T{F: 1}) // Returns "a: 1\nb: 0\n" +// +func Marshal(v interface{}) ([]byte, error) { + return MarshalWithOptions(v) +} + +// MarshalWithOptions serializes the value provided into a YAML document with EncodeOptions. +func MarshalWithOptions(v interface{}, opts ...EncodeOption) ([]byte, error) { + return MarshalContext(context.Background(), v, opts...) +} + +// MarshalContext serializes the value provided into a YAML document with context.Context and EncodeOptions. +func MarshalContext(ctx context.Context, v interface{}, opts ...EncodeOption) ([]byte, error) { + var buf bytes.Buffer + if err := NewEncoder(&buf, opts...).EncodeContext(ctx, v); err != nil { + return nil, errors.Wrapf(err, "failed to marshal") + } + return buf.Bytes(), nil +} + +// ValueToNode convert from value to ast.Node. +func ValueToNode(v interface{}, opts ...EncodeOption) (ast.Node, error) { + var buf bytes.Buffer + node, err := NewEncoder(&buf, opts...).EncodeToNode(v) + if err != nil { + return nil, errors.Wrapf(err, "failed to convert value to node") + } + return node, nil +} + +// Unmarshal decodes the first document found within the in byte slice +// and assigns decoded values into the out value. +// +// Struct fields are only unmarshalled if they are exported (have an +// upper case first letter), and are unmarshalled using the field name +// lowercased as the default key. Custom keys may be defined via the +// "yaml" name in the field tag: the content preceding the first comma +// is used as the key, and the following comma-separated options are +// used to tweak the marshalling process (see Marshal). +// Conflicting names result in a runtime error. +// +// For example: +// +// type T struct { +// F int `yaml:"a,omitempty"` +// B int +// } +// var t T +// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t) +// +// See the documentation of Marshal for the format of tags and a list of +// supported tag options. +// +func Unmarshal(data []byte, v interface{}) error { + return UnmarshalWithOptions(data, v) +} + +// UnmarshalWithOptions decodes with DecodeOptions the first document found within the in byte slice +// and assigns decoded values into the out value. +func UnmarshalWithOptions(data []byte, v interface{}, opts ...DecodeOption) error { + return UnmarshalContext(context.Background(), data, v, opts...) +} + +// UnmarshalContext decodes with context.Context and DecodeOptions. +func UnmarshalContext(ctx context.Context, data []byte, v interface{}, opts ...DecodeOption) error { + dec := NewDecoder(bytes.NewBuffer(data), opts...) + if err := dec.DecodeContext(ctx, v); err != nil { + if err == io.EOF { + return nil + } + return errors.Wrapf(err, "failed to unmarshal") + } + return nil +} + +// NodeToValue converts node to the value pointed to by v. +func NodeToValue(node ast.Node, v interface{}, opts ...DecodeOption) error { + var buf bytes.Buffer + if err := NewDecoder(&buf, opts...).DecodeFromNode(node, v); err != nil { + return errors.Wrapf(err, "failed to convert node to value") + } + return nil +} + +// FormatError is a utility function that takes advantage of the metadata +// stored in the errors returned by this package's parser. +// +// If the second argument `colored` is true, the error message is colorized. +// If the third argument `inclSource` is true, the error message will +// contain snippets of the YAML source that was used. +func FormatError(e error, colored, inclSource bool) string { + var pp errors.PrettyPrinter + if xerrors.As(e, &pp) { + var buf bytes.Buffer + pp.PrettyPrint(&errors.Sink{&buf}, colored, inclSource) + return buf.String() + } + + return e.Error() +} + +// YAMLToJSON convert YAML bytes to JSON. +func YAMLToJSON(bytes []byte) ([]byte, error) { + var v interface{} + if err := UnmarshalWithOptions(bytes, &v, UseOrderedMap()); err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal") + } + out, err := MarshalWithOptions(v, JSON()) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal with json option") + } + return out, nil +} + +// JSONToYAML convert JSON bytes to YAML. +func JSONToYAML(bytes []byte) ([]byte, error) { + var v interface{} + if err := UnmarshalWithOptions(bytes, &v, UseOrderedMap()); err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal from json bytes") + } + out, err := Marshal(v) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal") + } + return out, nil +} + +var ( + globalCustomMarshalerMu sync.Mutex + globalCustomUnmarshalerMu sync.Mutex + globalCustomMarshalerMap = map[reflect.Type]func(interface{}) ([]byte, error){} + globalCustomUnmarshalerMap = map[reflect.Type]func(interface{}, []byte) error{} +) + +// RegisterCustomMarshaler overrides any encoding process for the type specified in generics. +// If you want to switch the behavior for each encoder, use `CustomMarshaler` defined as EncodeOption. +// +// NOTE: If type T implements MarshalYAML for pointer receiver, the type specified in RegisterCustomMarshaler must be *T. +// If RegisterCustomMarshaler and CustomMarshaler of EncodeOption are specified for the same type, +// the CustomMarshaler specified in EncodeOption takes precedence. +func RegisterCustomMarshaler[T any](marshaler func(T) ([]byte, error)) { + globalCustomMarshalerMu.Lock() + defer globalCustomMarshalerMu.Unlock() + + var typ T + globalCustomMarshalerMap[reflect.TypeOf(typ)] = func(v interface{}) ([]byte, error) { + return marshaler(v.(T)) + } +} + +// RegisterCustomUnmarshaler overrides any decoding process for the type specified in generics. +// If you want to switch the behavior for each decoder, use `CustomUnmarshaler` defined as DecodeOption. +// +// NOTE: If RegisterCustomUnmarshaler and CustomUnmarshaler of DecodeOption are specified for the same type, +// the CustomUnmarshaler specified in DecodeOption takes precedence. +func RegisterCustomUnmarshaler[T any](unmarshaler func(*T, []byte) error) { + globalCustomUnmarshalerMu.Lock() + defer globalCustomUnmarshalerMu.Unlock() + + var typ *T + globalCustomUnmarshalerMap[reflect.TypeOf(typ)] = func(v interface{}, b []byte) error { + return unmarshaler(v.(*T), b) + } +} diff --git a/vendor/github.com/golang/glog/glog.go b/vendor/github.com/golang/glog/glog.go index 718c34f88..e108ae8b4 100644 --- a/vendor/github.com/golang/glog/glog.go +++ b/vendor/github.com/golang/glog/glog.go @@ -1,6 +1,6 @@ -// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ +// Go support for leveled logs, analogous to https://github.com/google/glog. // -// Copyright 2013 Google Inc. All Rights Reserved. +// Copyright 2023 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -50,15 +50,15 @@ // Log files will be written to this directory instead of the // default temporary directory. // -// Other flags provide aids to debugging. +// Other flags provide aids to debugging. // // -log_backtrace_at="" -// When set to a file and line number holding a logging statement, -// such as +// A comma-separated list of file and line numbers holding a logging +// statement, such as // -log_backtrace_at=gopherflakes.go:234 -// a stack trace will be written to the Info log whenever execution -// hits that statement. (Unlike with -vmodule, the ".go" must be -// present.) +// A stack trace will be written to the Info log whenever execution +// hits one of these statements. (Unlike with -vmodule, the ".go" +// must bepresent.) // -v=0 // Enable V-leveled logging at the specified level. // -vmodule="" @@ -66,100 +66,47 @@ // where pattern is a literal file name (minus the ".go" suffix) or // "glob" pattern and N is a V level. For instance, // -vmodule=gopher*=3 -// sets the V level to 3 in all Go files whose names begin "gopher". -// +// sets the V level to 3 in all Go files whose names begin with "gopher", +// and +// -vmodule=/path/to/glog/glog_test=1 +// sets the V level to 1 in the Go file /path/to/glog/glog_test.go. +// If a glob pattern contains a slash, it is matched against the full path, +// and the file name. Otherwise, the pattern is +// matched only against the file's basename. When both -vmodule and -v +// are specified, the -vmodule values take precedence for the specified +// modules. package glog +// This file contains the parts of the log package that are shared among all +// implementations (file, envelope, and appengine). + import ( - "bufio" "bytes" "errors" - "flag" "fmt" - "io" stdLog "log" "os" - "path/filepath" + "reflect" "runtime" + "runtime/pprof" "strconv" - "strings" "sync" "sync/atomic" + "syscall" "time" + + "github.com/golang/glog/internal/logsink" + "github.com/golang/glog/internal/stackdump" ) -// severity identifies the sort of log: info, warning etc. It also implements -// the flag.Value interface. The -stderrthreshold flag is of type severity and -// should be modified only through the flag.Value interface. The values match -// the corresponding constants in C++. -type severity int32 // sync/atomic int32 +var timeNow = time.Now // Stubbed out for testing. -// These constants identify the log levels in order of increasing severity. -// A message written to a high-severity log file is also written to each -// lower-severity log file. -const ( - infoLog severity = iota - warningLog - errorLog - fatalLog - numSeverity = 4 -) +// MaxSize is the maximum size of a log file in bytes. +var MaxSize uint64 = 1024 * 1024 * 1800 -const severityChar = "IWEF" - -var severityName = []string{ - infoLog: "INFO", - warningLog: "WARNING", - errorLog: "ERROR", - fatalLog: "FATAL", -} - -// get returns the value of the severity. -func (s *severity) get() severity { - return severity(atomic.LoadInt32((*int32)(s))) -} - -// set sets the value of the severity. -func (s *severity) set(val severity) { - atomic.StoreInt32((*int32)(s), int32(val)) -} - -// String is part of the flag.Value interface. -func (s *severity) String() string { - return strconv.FormatInt(int64(*s), 10) -} - -// Get is part of the flag.Value interface. -func (s *severity) Get() interface{} { - return *s -} - -// Set is part of the flag.Value interface. -func (s *severity) Set(value string) error { - var threshold severity - // Is it a known name? - if v, ok := severityByName(value); ok { - threshold = v - } else { - v, err := strconv.Atoi(value) - if err != nil { - return err - } - threshold = severity(v) - } - logging.stderrThreshold.set(threshold) - return nil -} - -func severityByName(s string) (severity, bool) { - s = strings.ToUpper(s) - for i, name := range severityName { - if name == s { - return severity(i), true - } - } - return 0, false -} +// ErrNoLog is the error we return if no log file has yet been created +// for the specified log type. +var ErrNoLog = errors.New("log file not yet created") // OutputStats tracks the number of output lines and bytes written. type OutputStats struct { @@ -183,724 +130,99 @@ var Stats struct { Info, Warning, Error OutputStats } -var severityStats = [numSeverity]*OutputStats{ - infoLog: &Stats.Info, - warningLog: &Stats.Warning, - errorLog: &Stats.Error, +var severityStats = [...]*OutputStats{ + logsink.Info: &Stats.Info, + logsink.Warning: &Stats.Warning, + logsink.Error: &Stats.Error, + logsink.Fatal: nil, } -// Level is exported because it appears in the arguments to V and is -// the type of the v flag, which can be set programmatically. -// It's a distinct type because we want to discriminate it from logType. -// Variables of type level are only changed under logging.mu. -// The -v flag is read only with atomic ops, so the state of the logging -// module is consistent. - -// Level is treated as a sync/atomic int32. - -// Level specifies a level of verbosity for V logs. *Level implements -// flag.Value; the -v flag is of type Level and should be modified -// only through the flag.Value interface. +// Level specifies a level of verbosity for V logs. The -v flag is of type +// Level and should be modified only through the flag.Value interface. type Level int32 -// get returns the value of the Level. -func (l *Level) get() Level { - return Level(atomic.LoadInt32((*int32)(l))) -} +var metaPool sync.Pool // Pool of *logsink.Meta. -// set sets the value of the Level. -func (l *Level) set(val Level) { - atomic.StoreInt32((*int32)(l), int32(val)) -} - -// String is part of the flag.Value interface. -func (l *Level) String() string { - return strconv.FormatInt(int64(*l), 10) -} - -// Get is part of the flag.Value interface. -func (l *Level) Get() interface{} { - return *l -} - -// Set is part of the flag.Value interface. -func (l *Level) Set(value string) error { - v, err := strconv.Atoi(value) - if err != nil { - return err +// metaPoolGet returns a *logsink.Meta from metaPool as both an interface and a +// pointer, allocating a new one if necessary. (Returning the interface value +// directly avoids an allocation if there was an existing pointer in the pool.) +func metaPoolGet() (any, *logsink.Meta) { + if metai := metaPool.Get(); metai != nil { + return metai, metai.(*logsink.Meta) } - logging.mu.Lock() - defer logging.mu.Unlock() - logging.setVState(Level(v), logging.vmodule.filter, false) - return nil + meta := new(logsink.Meta) + return meta, meta } -// moduleSpec represents the setting of the -vmodule flag. -type moduleSpec struct { - filter []modulePat +type stack bool + +const ( + noStack = stack(false) + withStack = stack(true) +) + +func appendBacktrace(depth int, format string, args []any) (string, []any) { + // Capture a backtrace as a stackdump.Stack (both text and PC slice). + // Structured log sinks can extract the backtrace in whichever format they + // prefer (PCs or text), and Text sinks will include it as just another part + // of the log message. + // + // Use depth instead of depth+1 so that the backtrace always includes the + // log function itself - otherwise the reason for the trace appearing in the + // log may not be obvious to the reader. + dump := stackdump.Caller(depth) + + // Add an arg and an entry in the format string for the stack dump. + // + // Copy the "args" slice to avoid a rare but serious aliasing bug + // (corrupting the caller's slice if they passed it to a non-Fatal call + // using "..."). + format = format + "\n\n%v\n" + args = append(append([]any(nil), args...), dump) + + return format, args } -// modulePat contains a filter for the -vmodule flag. -// It holds a verbosity level and a file pattern to match. -type modulePat struct { - pattern string - literal bool // The pattern is a literal string - level Level -} - -// match reports whether the file matches the pattern. It uses a string -// comparison if the pattern contains no metacharacters. -func (m *modulePat) match(file string) bool { - if m.literal { - return file == m.pattern - } - match, _ := filepath.Match(m.pattern, file) - return match -} - -func (m *moduleSpec) String() string { - // Lock because the type is not atomic. TODO: clean this up. - logging.mu.Lock() - defer logging.mu.Unlock() - var b bytes.Buffer - for i, f := range m.filter { - if i > 0 { - b.WriteRune(',') - } - fmt.Fprintf(&b, "%s=%d", f.pattern, f.level) - } - return b.String() -} - -// Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the -// struct is not exported. -func (m *moduleSpec) Get() interface{} { - return nil -} - -var errVmoduleSyntax = errors.New("syntax error: expect comma-separated list of filename=N") - -// Syntax: -vmodule=recordio=2,file=1,gfs*=3 -func (m *moduleSpec) Set(value string) error { - var filter []modulePat - for _, pat := range strings.Split(value, ",") { - if len(pat) == 0 { - // Empty strings such as from a trailing comma can be ignored. - continue - } - patLev := strings.Split(pat, "=") - if len(patLev) != 2 || len(patLev[0]) == 0 || len(patLev[1]) == 0 { - return errVmoduleSyntax - } - pattern := patLev[0] - v, err := strconv.Atoi(patLev[1]) - if err != nil { - return errors.New("syntax error: expect comma-separated list of filename=N") - } - if v < 0 { - return errors.New("negative value for vmodule level") - } - if v == 0 { - continue // Ignore. It's harmless but no point in paying the overhead. - } - // TODO: check syntax of filter? - filter = append(filter, modulePat{pattern, isLiteral(pattern), Level(v)}) - } - logging.mu.Lock() - defer logging.mu.Unlock() - logging.setVState(logging.verbosity, filter, true) - return nil -} - -// isLiteral reports whether the pattern is a literal string, that is, has no metacharacters -// that require filepath.Match to be called to match the pattern. -func isLiteral(pattern string) bool { - return !strings.ContainsAny(pattern, `\*?[]`) -} - -// traceLocation represents the setting of the -log_backtrace_at flag. -type traceLocation struct { - file string - line int -} - -// isSet reports whether the trace location has been specified. -// logging.mu is held. -func (t *traceLocation) isSet() bool { - return t.line > 0 -} - -// match reports whether the specified file and line matches the trace location. -// The argument file name is the full path, not the basename specified in the flag. -// logging.mu is held. -func (t *traceLocation) match(file string, line int) bool { - if t.line != line { - return false - } - if i := strings.LastIndex(file, "/"); i >= 0 { - file = file[i+1:] - } - return t.file == file -} - -func (t *traceLocation) String() string { - // Lock because the type is not atomic. TODO: clean this up. - logging.mu.Lock() - defer logging.mu.Unlock() - return fmt.Sprintf("%s:%d", t.file, t.line) -} - -// Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the -// struct is not exported -func (t *traceLocation) Get() interface{} { - return nil -} - -var errTraceSyntax = errors.New("syntax error: expect file.go:234") - -// Syntax: -log_backtrace_at=gopherflakes.go:234 -// Note that unlike vmodule the file extension is included here. -func (t *traceLocation) Set(value string) error { - if value == "" { - // Unset. - t.line = 0 - t.file = "" - } - fields := strings.Split(value, ":") - if len(fields) != 2 { - return errTraceSyntax - } - file, line := fields[0], fields[1] - if !strings.Contains(file, ".") { - return errTraceSyntax - } - v, err := strconv.Atoi(line) - if err != nil { - return errTraceSyntax - } - if v <= 0 { - return errors.New("negative or zero value for level") - } - logging.mu.Lock() - defer logging.mu.Unlock() - t.line = v - t.file = file - return nil -} - -// flushSyncWriter is the interface satisfied by logging destinations. -type flushSyncWriter interface { - Flush() error - Sync() error - io.Writer -} - -func init() { - flag.BoolVar(&logging.toStderr, "logtostderr", false, "log to standard error instead of files") - flag.BoolVar(&logging.alsoToStderr, "alsologtostderr", false, "log to standard error as well as files") - flag.Var(&logging.verbosity, "v", "log level for V logs") - flag.Var(&logging.stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr") - flag.Var(&logging.vmodule, "vmodule", "comma-separated list of pattern=N settings for file-filtered logging") - flag.Var(&logging.traceLocation, "log_backtrace_at", "when logging hits line file:N, emit a stack trace") - - // Default stderrThreshold is ERROR. - logging.stderrThreshold = errorLog - - logging.setVState(0, nil, false) - go logging.flushDaemon() -} - -// Flush flushes all pending log I/O. -func Flush() { - logging.lockAndFlushAll() -} - -// loggingT collects all the global state of the logging setup. -type loggingT struct { - // Boolean flags. Not handled atomically because the flag.Value interface - // does not let us avoid the =true, and that shorthand is necessary for - // compatibility. TODO: does this matter enough to fix? Seems unlikely. - toStderr bool // The -logtostderr flag. - alsoToStderr bool // The -alsologtostderr flag. - - // Level flag. Handled atomically. - stderrThreshold severity // The -stderrthreshold flag. - - // freeList is a list of byte buffers, maintained under freeListMu. - freeList *buffer - // freeListMu maintains the free list. It is separate from the main mutex - // so buffers can be grabbed and printed to without holding the main lock, - // for better parallelization. - freeListMu sync.Mutex - - // mu protects the remaining elements of this structure and is - // used to synchronize logging. - mu sync.Mutex - // file holds writer for each of the log types. - file [numSeverity]flushSyncWriter - // pcs is used in V to avoid an allocation when computing the caller's PC. - pcs [1]uintptr - // vmap is a cache of the V Level for each V() call site, identified by PC. - // It is wiped whenever the vmodule flag changes state. - vmap map[uintptr]Level - // filterLength stores the length of the vmodule filter chain. If greater - // than zero, it means vmodule is enabled. It may be read safely - // using sync.LoadInt32, but is only modified under mu. - filterLength int32 - // traceLocation is the state of the -log_backtrace_at flag. - traceLocation traceLocation - // These flags are modified only under lock, although verbosity may be fetched - // safely using atomic.LoadInt32. - vmodule moduleSpec // The state of the -vmodule flag. - verbosity Level // V logging level, the value of the -v flag/ -} - -// buffer holds a byte Buffer for reuse. The zero value is ready for use. -type buffer struct { - bytes.Buffer - tmp [64]byte // temporary byte array for creating headers. - next *buffer -} - -var logging loggingT - -// setVState sets a consistent state for V logging. -// l.mu is held. -func (l *loggingT) setVState(verbosity Level, filter []modulePat, setFilter bool) { - // Turn verbosity off so V will not fire while we are in transition. - logging.verbosity.set(0) - // Ditto for filter length. - atomic.StoreInt32(&logging.filterLength, 0) - - // Set the new filters and wipe the pc->Level map if the filter has changed. - if setFilter { - logging.vmodule.filter = filter - logging.vmap = make(map[uintptr]Level) - } - - // Things are consistent now, so enable filtering and verbosity. - // They are enabled in order opposite to that in V. - atomic.StoreInt32(&logging.filterLength, int32(len(filter))) - logging.verbosity.set(verbosity) -} - -// getBuffer returns a new, ready-to-use buffer. -func (l *loggingT) getBuffer() *buffer { - l.freeListMu.Lock() - b := l.freeList - if b != nil { - l.freeList = b.next - } - l.freeListMu.Unlock() - if b == nil { - b = new(buffer) - } else { - b.next = nil - b.Reset() - } - return b -} - -// putBuffer returns a buffer to the free list. -func (l *loggingT) putBuffer(b *buffer) { - if b.Len() >= 256 { - // Let big buffers die a natural death. - return - } - l.freeListMu.Lock() - b.next = l.freeList - l.freeList = b - l.freeListMu.Unlock() -} - -var timeNow = time.Now // Stubbed out for testing. - -/* -header formats a log header as defined by the C++ implementation. -It returns a buffer containing the formatted header and the user's file and line number. -The depth specifies how many stack frames above lives the source line to be identified in the log message. - -Log lines have this form: - Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg... -where the fields are defined as follows: - L A single character, representing the log level (eg 'I' for INFO) - mm The month (zero padded; ie May is '05') - dd The day (zero padded) - hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds - threadid The space-padded thread ID as returned by GetTID() - file The file name - line The line number - msg The user-supplied message -*/ -func (l *loggingT) header(s severity, depth int) (*buffer, string, int) { - _, file, line, ok := runtime.Caller(3 + depth) +// logf writes a log message for a log function call (or log function wrapper) +// at the given depth in the current goroutine's stack. +func logf(depth int, severity logsink.Severity, verbose bool, stack stack, format string, args ...any) { + now := timeNow() + _, file, line, ok := runtime.Caller(depth + 1) if !ok { file = "???" line = 1 - } else { - slash := strings.LastIndex(file, "/") - if slash >= 0 { - file = file[slash+1:] - } } - return l.formatHeader(s, file, line), file, line + + if stack == withStack || backtraceAt(file, line) { + format, args = appendBacktrace(depth+1, format, args) + } + + metai, meta := metaPoolGet() + *meta = logsink.Meta{ + Time: now, + File: file, + Line: line, + Depth: depth + 1, + Severity: severity, + Verbose: verbose, + Thread: int64(pid), + } + sinkf(meta, format, args...) + metaPool.Put(metai) } -// formatHeader formats a log header using the provided file name and line number. -func (l *loggingT) formatHeader(s severity, file string, line int) *buffer { - now := timeNow() - if line < 0 { - line = 0 // not a real line number, but acceptable to someDigits - } - if s > fatalLog { - s = infoLog // for safety. - } - buf := l.getBuffer() - - // Avoid Fprintf, for speed. The format is so simple that we can do it quickly by hand. - // It's worth about 3X. Fprintf is hard. - _, month, day := now.Date() - hour, minute, second := now.Clock() - // Lmmdd hh:mm:ss.uuuuuu threadid file:line] - buf.tmp[0] = severityChar[s] - buf.twoDigits(1, int(month)) - buf.twoDigits(3, day) - buf.tmp[5] = ' ' - buf.twoDigits(6, hour) - buf.tmp[8] = ':' - buf.twoDigits(9, minute) - buf.tmp[11] = ':' - buf.twoDigits(12, second) - buf.tmp[14] = '.' - buf.nDigits(6, 15, now.Nanosecond()/1000, '0') - buf.tmp[21] = ' ' - buf.nDigits(7, 22, pid, ' ') // TODO: should be TID - buf.tmp[29] = ' ' - buf.Write(buf.tmp[:30]) - buf.WriteString(file) - buf.tmp[0] = ':' - n := buf.someDigits(1, line) - buf.tmp[n+1] = ']' - buf.tmp[n+2] = ' ' - buf.Write(buf.tmp[:n+3]) - return buf -} - -// Some custom tiny helper functions to print the log header efficiently. - -const digits = "0123456789" - -// twoDigits formats a zero-prefixed two-digit integer at buf.tmp[i]. -func (buf *buffer) twoDigits(i, d int) { - buf.tmp[i+1] = digits[d%10] - d /= 10 - buf.tmp[i] = digits[d%10] -} - -// nDigits formats an n-digit integer at buf.tmp[i], -// padding with pad on the left. -// It assumes d >= 0. -func (buf *buffer) nDigits(n, i, d int, pad byte) { - j := n - 1 - for ; j >= 0 && d > 0; j-- { - buf.tmp[i+j] = digits[d%10] - d /= 10 - } - for ; j >= 0; j-- { - buf.tmp[i+j] = pad - } -} - -// someDigits formats a zero-prefixed variable-width integer at buf.tmp[i]. -func (buf *buffer) someDigits(i, d int) int { - // Print into the top, then copy down. We know there's space for at least - // a 10-digit number. - j := len(buf.tmp) - for { - j-- - buf.tmp[j] = digits[d%10] - d /= 10 - if d == 0 { - break - } - } - return copy(buf.tmp[i:], buf.tmp[j:]) -} - -func (l *loggingT) println(s severity, args ...interface{}) { - buf, file, line := l.header(s, 0) - fmt.Fprintln(buf, args...) - l.output(s, buf, file, line, false) -} - -func (l *loggingT) print(s severity, args ...interface{}) { - l.printDepth(s, 1, args...) -} - -func (l *loggingT) printDepth(s severity, depth int, args ...interface{}) { - buf, file, line := l.header(s, depth) - fmt.Fprint(buf, args...) - if buf.Bytes()[buf.Len()-1] != '\n' { - buf.WriteByte('\n') - } - l.output(s, buf, file, line, false) -} - -func (l *loggingT) printf(s severity, format string, args ...interface{}) { - buf, file, line := l.header(s, 0) - fmt.Fprintf(buf, format, args...) - if buf.Bytes()[buf.Len()-1] != '\n' { - buf.WriteByte('\n') - } - l.output(s, buf, file, line, false) -} - -// printWithFileLine behaves like print but uses the provided file and line number. If -// alsoLogToStderr is true, the log message always appears on standard error; it -// will also appear in the log file unless --logtostderr is set. -func (l *loggingT) printWithFileLine(s severity, file string, line int, alsoToStderr bool, args ...interface{}) { - buf := l.formatHeader(s, file, line) - fmt.Fprint(buf, args...) - if buf.Bytes()[buf.Len()-1] != '\n' { - buf.WriteByte('\n') - } - l.output(s, buf, file, line, alsoToStderr) -} - -// output writes the data to the log files and releases the buffer. -func (l *loggingT) output(s severity, buf *buffer, file string, line int, alsoToStderr bool) { - l.mu.Lock() - if l.traceLocation.isSet() { - if l.traceLocation.match(file, line) { - buf.Write(stacks(false)) - } - } - data := buf.Bytes() - if !flag.Parsed() { - os.Stderr.Write([]byte("ERROR: logging before flag.Parse: ")) - os.Stderr.Write(data) - } else if l.toStderr { - os.Stderr.Write(data) - } else { - if alsoToStderr || l.alsoToStderr || s >= l.stderrThreshold.get() { - os.Stderr.Write(data) - } - if l.file[s] == nil { - if err := l.createFiles(s); err != nil { - os.Stderr.Write(data) // Make sure the message appears somewhere. - l.exit(err) - } - } - switch s { - case fatalLog: - l.file[fatalLog].Write(data) - fallthrough - case errorLog: - l.file[errorLog].Write(data) - fallthrough - case warningLog: - l.file[warningLog].Write(data) - fallthrough - case infoLog: - l.file[infoLog].Write(data) - } - } - if s == fatalLog { - // If we got here via Exit rather than Fatal, print no stacks. - if atomic.LoadUint32(&fatalNoStacks) > 0 { - l.mu.Unlock() - timeoutFlush(10 * time.Second) - os.Exit(1) - } - // Dump all goroutine stacks before exiting. - // First, make sure we see the trace for the current goroutine on standard error. - // If -logtostderr has been specified, the loop below will do that anyway - // as the first stack in the full dump. - if !l.toStderr { - os.Stderr.Write(stacks(false)) - } - // Write the stack trace for all goroutines to the files. - trace := stacks(true) - logExitFunc = func(error) {} // If we get a write error, we'll still exit below. - for log := fatalLog; log >= infoLog; log-- { - if f := l.file[log]; f != nil { // Can be nil if -logtostderr is set. - f.Write(trace) - } - } - l.mu.Unlock() - timeoutFlush(10 * time.Second) - os.Exit(255) // C++ uses -1, which is silly because it's anded with 255 anyway. - } - l.putBuffer(buf) - l.mu.Unlock() - if stats := severityStats[s]; stats != nil { +func sinkf(meta *logsink.Meta, format string, args ...any) { + meta.Depth++ + n, err := logsink.Printf(meta, format, args...) + if stats := severityStats[meta.Severity]; stats != nil { atomic.AddInt64(&stats.lines, 1) - atomic.AddInt64(&stats.bytes, int64(len(data))) + atomic.AddInt64(&stats.bytes, int64(n)) } -} -// timeoutFlush calls Flush and returns when it completes or after timeout -// elapses, whichever happens first. This is needed because the hooks invoked -// by Flush may deadlock when glog.Fatal is called from a hook that holds -// a lock. -func timeoutFlush(timeout time.Duration) { - done := make(chan bool, 1) - go func() { - Flush() // calls logging.lockAndFlushAll() - done <- true - }() - select { - case <-done: - case <-time.After(timeout): - fmt.Fprintln(os.Stderr, "glog: Flush took longer than", timeout) - } -} - -// stacks is a wrapper for runtime.Stack that attempts to recover the data for all goroutines. -func stacks(all bool) []byte { - // We don't know how big the traces are, so grow a few times if they don't fit. Start large, though. - n := 10000 - if all { - n = 100000 - } - var trace []byte - for i := 0; i < 5; i++ { - trace = make([]byte, n) - nbytes := runtime.Stack(trace, all) - if nbytes < len(trace) { - return trace[:nbytes] - } - n *= 2 - } - return trace -} - -// logExitFunc provides a simple mechanism to override the default behavior -// of exiting on error. Used in testing and to guarantee we reach a required exit -// for fatal logs. Instead, exit could be a function rather than a method but that -// would make its use clumsier. -var logExitFunc func(error) - -// exit is called if there is trouble creating or writing log files. -// It flushes the logs and exits the program; there's no point in hanging around. -// l.mu is held. -func (l *loggingT) exit(err error) { - fmt.Fprintf(os.Stderr, "log: exiting because of error: %s\n", err) - // If logExitFunc is set, we do that instead of exiting. - if logExitFunc != nil { - logExitFunc(err) - return - } - l.flushAll() - os.Exit(2) -} - -// syncBuffer joins a bufio.Writer to its underlying file, providing access to the -// file's Sync method and providing a wrapper for the Write method that provides log -// file rotation. There are conflicting methods, so the file cannot be embedded. -// l.mu is held for all its methods. -type syncBuffer struct { - logger *loggingT - *bufio.Writer - file *os.File - sev severity - nbytes uint64 // The number of bytes written to this file -} - -func (sb *syncBuffer) Sync() error { - return sb.file.Sync() -} - -func (sb *syncBuffer) Write(p []byte) (n int, err error) { - if sb.nbytes+uint64(len(p)) >= MaxSize { - if err := sb.rotateFile(time.Now()); err != nil { - sb.logger.exit(err) - } - } - n, err = sb.Writer.Write(p) - sb.nbytes += uint64(n) if err != nil { - sb.logger.exit(err) - } - return -} - -// rotateFile closes the syncBuffer's file and starts a new one. -func (sb *syncBuffer) rotateFile(now time.Time) error { - if sb.file != nil { - sb.Flush() - sb.file.Close() - } - var err error - sb.file, _, err = create(severityName[sb.sev], now) - sb.nbytes = 0 - if err != nil { - return err - } - - sb.Writer = bufio.NewWriterSize(sb.file, bufferSize) - - // Write header. - var buf bytes.Buffer - fmt.Fprintf(&buf, "Log file created at: %s\n", now.Format("2006/01/02 15:04:05")) - fmt.Fprintf(&buf, "Running on machine: %s\n", host) - fmt.Fprintf(&buf, "Binary: Built with %s %s for %s/%s\n", runtime.Compiler, runtime.Version(), runtime.GOOS, runtime.GOARCH) - fmt.Fprintf(&buf, "Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg\n") - n, err := sb.file.Write(buf.Bytes()) - sb.nbytes += uint64(n) - return err -} - -// bufferSize sizes the buffer associated with each log file. It's large -// so that log records can accumulate without the logging thread blocking -// on disk I/O. The flushDaemon will block instead. -const bufferSize = 256 * 1024 - -// createFiles creates all the log files for severity from sev down to infoLog. -// l.mu is held. -func (l *loggingT) createFiles(sev severity) error { - now := time.Now() - // Files are created in decreasing severity order, so as soon as we find one - // has already been created, we can stop. - for s := sev; s >= infoLog && l.file[s] == nil; s-- { - sb := &syncBuffer{ - logger: l, - sev: s, - } - if err := sb.rotateFile(now); err != nil { - return err - } - l.file[s] = sb - } - return nil -} - -const flushInterval = 30 * time.Second - -// flushDaemon periodically flushes the log file buffers. -func (l *loggingT) flushDaemon() { - for range time.NewTicker(flushInterval).C { - l.lockAndFlushAll() - } -} - -// lockAndFlushAll is like flushAll but locks l.mu first. -func (l *loggingT) lockAndFlushAll() { - l.mu.Lock() - l.flushAll() - l.mu.Unlock() -} - -// flushAll flushes all the logs and attempts to "sync" their data to disk. -// l.mu is held. -func (l *loggingT) flushAll() { - // Flush from fatal down, in case there's trouble flushing. - for s := fatalLog; s >= infoLog; s-- { - file := l.file[s] - if file != nil { - file.Flush() // ignore error - file.Sync() // ignore error - } + logsink.Printf(meta, "glog: exiting because of error: %s", err) + sinks.file.Flush() + os.Exit(2) } } @@ -912,9 +234,9 @@ func (l *loggingT) flushAll() { // Valid names are "INFO", "WARNING", "ERROR", and "FATAL". If the name is not // recognized, CopyStandardLogTo panics. func CopyStandardLogTo(name string) { - sev, ok := severityByName(name) - if !ok { - panic(fmt.Sprintf("log.CopyStandardLogTo(%q): unrecognized severity name", name)) + sev, err := logsink.ParseSeverity(name) + if err != nil { + panic(fmt.Sprintf("log.CopyStandardLogTo(%q): %v", name, err)) } // Set a log format that captures the user's file and line: // d.go:23: message @@ -922,9 +244,22 @@ func CopyStandardLogTo(name string) { stdLog.SetOutput(logBridge(sev)) } +// NewStandardLogger returns a Logger that writes to the Google logs for the +// named and lower severities. +// +// Valid names are "INFO", "WARNING", "ERROR", and "FATAL". If the name is not +// recognized, NewStandardLogger panics. +func NewStandardLogger(name string) *stdLog.Logger { + sev, err := logsink.ParseSeverity(name) + if err != nil { + panic(fmt.Sprintf("log.NewStandardLogger(%q): %v", name, err)) + } + return stdLog.New(logBridge(sev), "", stdLog.Lshortfile) +} + // logBridge provides the Write method that enables CopyStandardLogTo to connect // Go's standard logs to the logs provided by this package. -type logBridge severity +type logBridge logsink.Severity // Write parses the standard logging line and passes its components to the // logger for severity(lb). @@ -946,36 +281,72 @@ func (lb logBridge) Write(b []byte) (n int, err error) { line = 1 } } - // printWithFileLine with alsoToStderr=true, so standard log messages - // always appear on standard error. - logging.printWithFileLine(severity(lb), file, line, true, text) + + // The depth below hard-codes details of how stdlog gets here. The alternative would be to walk + // up the stack looking for src/log/log.go but that seems like it would be + // unfortunately slow. + const stdLogDepth = 4 + + metai, meta := metaPoolGet() + *meta = logsink.Meta{ + Time: timeNow(), + File: file, + Line: line, + Depth: stdLogDepth, + Severity: logsink.Severity(lb), + Thread: int64(pid), + } + + format := "%s" + args := []any{text} + if backtraceAt(file, line) { + format, args = appendBacktrace(meta.Depth, format, args) + } + + sinkf(meta, format, args...) + metaPool.Put(metai) + return len(b), nil } -// setV computes and remembers the V level for a given PC -// when vmodule is enabled. -// File pattern matching takes the basename of the file, stripped -// of its .go suffix, and uses filepath.Match, which is a little more -// general than the *? matching used in C++. -// l.mu is held. -func (l *loggingT) setV(pc uintptr) Level { - fn := runtime.FuncForPC(pc) - file, _ := fn.FileLine(pc) - // The file is something like /a/b/c/d.go. We want just the d. - if strings.HasSuffix(file, ".go") { - file = file[:len(file)-3] +// defaultFormat returns a fmt.Printf format specifier that formats its +// arguments as if they were passed to fmt.Print. +func defaultFormat(args []any) string { + n := len(args) + switch n { + case 0: + return "" + case 1: + return "%v" } - if slash := strings.LastIndex(file, "/"); slash >= 0 { - file = file[slash+1:] - } - for _, filter := range l.vmodule.filter { - if filter.match(file) { - l.vmap[pc] = filter.level - return filter.level + + b := make([]byte, 0, n*3-1) + wasString := true // Suppress leading space. + for _, arg := range args { + isString := arg != nil && reflect.TypeOf(arg).Kind() == reflect.String + if wasString || isString { + b = append(b, "%v"...) + } else { + b = append(b, " %v"...) } + wasString = isString } - l.vmap[pc] = 0 - return 0 + return string(b) +} + +// lnFormat returns a fmt.Printf format specifier that formats its arguments +// as if they were passed to fmt.Println. +func lnFormat(args []any) string { + if len(args) == 0 { + return "\n" + } + + b := make([]byte, 0, len(args)*3) + for range args { + b = append(b, "%v "...) + } + b[len(b)-1] = '\n' // Replace the last space with a newline. + return string(b) } // Verbose is a boolean type that implements Infof (like Printf) etc. @@ -986,9 +357,13 @@ type Verbose bool // The returned value is a boolean of type Verbose, which implements Info, Infoln // and Infof. These methods will write to the Info log if called. // Thus, one may write either +// // if glog.V(2) { glog.Info("log this") } +// // or +// // glog.V(2).Info("log this") +// // The second form is shorter but the first is cheaper if logging is off because it does // not evaluate its arguments. // @@ -997,184 +372,250 @@ type Verbose bool // V is at most the value of -v, or of -vmodule for the source file containing the // call, the V call will log. func V(level Level) Verbose { - // This function tries hard to be cheap unless there's work to do. - // The fast path is two atomic loads and compares. + return VDepth(1, level) +} - // Here is a cheap but safe test to see if V logging is enabled globally. - if logging.verbosity.get() >= level { - return Verbose(true) - } - - // It's off globally but it vmodule may still be set. - // Here is another cheap but safe test to see if vmodule is enabled. - if atomic.LoadInt32(&logging.filterLength) > 0 { - // Now we need a proper lock to use the logging structure. The pcs field - // is shared so we must lock before accessing it. This is fairly expensive, - // but if V logging is enabled we're slow anyway. - logging.mu.Lock() - defer logging.mu.Unlock() - if runtime.Callers(2, logging.pcs[:]) == 0 { - return Verbose(false) - } - v, ok := logging.vmap[logging.pcs[0]] - if !ok { - v = logging.setV(logging.pcs[0]) - } - return Verbose(v >= level) - } - return Verbose(false) +// VDepth acts as V but uses depth to determine which call frame to check vmodule for. +// VDepth(0, level) is the same as V(level). +func VDepth(depth int, level Level) Verbose { + return Verbose(verboseEnabled(depth+1, level)) } // Info is equivalent to the global Info function, guarded by the value of v. // See the documentation of V for usage. -func (v Verbose) Info(args ...interface{}) { +func (v Verbose) Info(args ...any) { + v.InfoDepth(1, args...) +} + +// InfoDepth is equivalent to the global InfoDepth function, guarded by the value of v. +// See the documentation of V for usage. +func (v Verbose) InfoDepth(depth int, args ...any) { if v { - logging.print(infoLog, args...) + logf(depth+1, logsink.Info, true, noStack, defaultFormat(args), args...) + } +} + +// InfoDepthf is equivalent to the global InfoDepthf function, guarded by the value of v. +// See the documentation of V for usage. +func (v Verbose) InfoDepthf(depth int, format string, args ...any) { + if v { + logf(depth+1, logsink.Info, true, noStack, format, args...) } } // Infoln is equivalent to the global Infoln function, guarded by the value of v. // See the documentation of V for usage. -func (v Verbose) Infoln(args ...interface{}) { +func (v Verbose) Infoln(args ...any) { if v { - logging.println(infoLog, args...) + logf(1, logsink.Info, true, noStack, lnFormat(args), args...) } } // Infof is equivalent to the global Infof function, guarded by the value of v. // See the documentation of V for usage. -func (v Verbose) Infof(format string, args ...interface{}) { +func (v Verbose) Infof(format string, args ...any) { if v { - logging.printf(infoLog, format, args...) + logf(1, logsink.Info, true, noStack, format, args...) } } // Info logs to the INFO log. // Arguments are handled in the manner of fmt.Print; a newline is appended if missing. -func Info(args ...interface{}) { - logging.print(infoLog, args...) +func Info(args ...any) { + InfoDepth(1, args...) } -// InfoDepth acts as Info but uses depth to determine which call frame to log. -// InfoDepth(0, "msg") is the same as Info("msg"). -func InfoDepth(depth int, args ...interface{}) { - logging.printDepth(infoLog, depth, args...) +// InfoDepth calls Info from a different depth in the call stack. +// This enables a callee to emit logs that use the callsite information of its caller +// or any other callers in the stack. When depth == 0, the original callee's line +// information is emitted. When depth > 0, depth frames are skipped in the call stack +// and the final frame is treated like the original callee to Info. +func InfoDepth(depth int, args ...any) { + logf(depth+1, logsink.Info, false, noStack, defaultFormat(args), args...) +} + +// InfoDepthf acts as InfoDepth but with format string. +func InfoDepthf(depth int, format string, args ...any) { + logf(depth+1, logsink.Info, false, noStack, format, args...) } // Infoln logs to the INFO log. // Arguments are handled in the manner of fmt.Println; a newline is appended if missing. -func Infoln(args ...interface{}) { - logging.println(infoLog, args...) +func Infoln(args ...any) { + logf(1, logsink.Info, false, noStack, lnFormat(args), args...) } // Infof logs to the INFO log. // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. -func Infof(format string, args ...interface{}) { - logging.printf(infoLog, format, args...) +func Infof(format string, args ...any) { + logf(1, logsink.Info, false, noStack, format, args...) } // Warning logs to the WARNING and INFO logs. // Arguments are handled in the manner of fmt.Print; a newline is appended if missing. -func Warning(args ...interface{}) { - logging.print(warningLog, args...) +func Warning(args ...any) { + WarningDepth(1, args...) } // WarningDepth acts as Warning but uses depth to determine which call frame to log. // WarningDepth(0, "msg") is the same as Warning("msg"). -func WarningDepth(depth int, args ...interface{}) { - logging.printDepth(warningLog, depth, args...) +func WarningDepth(depth int, args ...any) { + logf(depth+1, logsink.Warning, false, noStack, defaultFormat(args), args...) +} + +// WarningDepthf acts as Warningf but uses depth to determine which call frame to log. +// WarningDepthf(0, "msg") is the same as Warningf("msg"). +func WarningDepthf(depth int, format string, args ...any) { + logf(depth+1, logsink.Warning, false, noStack, format, args...) } // Warningln logs to the WARNING and INFO logs. // Arguments are handled in the manner of fmt.Println; a newline is appended if missing. -func Warningln(args ...interface{}) { - logging.println(warningLog, args...) +func Warningln(args ...any) { + logf(1, logsink.Warning, false, noStack, lnFormat(args), args...) } // Warningf logs to the WARNING and INFO logs. // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. -func Warningf(format string, args ...interface{}) { - logging.printf(warningLog, format, args...) +func Warningf(format string, args ...any) { + logf(1, logsink.Warning, false, noStack, format, args...) } // Error logs to the ERROR, WARNING, and INFO logs. // Arguments are handled in the manner of fmt.Print; a newline is appended if missing. -func Error(args ...interface{}) { - logging.print(errorLog, args...) +func Error(args ...any) { + ErrorDepth(1, args...) } // ErrorDepth acts as Error but uses depth to determine which call frame to log. // ErrorDepth(0, "msg") is the same as Error("msg"). -func ErrorDepth(depth int, args ...interface{}) { - logging.printDepth(errorLog, depth, args...) +func ErrorDepth(depth int, args ...any) { + logf(depth+1, logsink.Error, false, noStack, defaultFormat(args), args...) +} + +// ErrorDepthf acts as Errorf but uses depth to determine which call frame to log. +// ErrorDepthf(0, "msg") is the same as Errorf("msg"). +func ErrorDepthf(depth int, format string, args ...any) { + logf(depth+1, logsink.Error, false, noStack, format, args...) } // Errorln logs to the ERROR, WARNING, and INFO logs. // Arguments are handled in the manner of fmt.Println; a newline is appended if missing. -func Errorln(args ...interface{}) { - logging.println(errorLog, args...) +func Errorln(args ...any) { + logf(1, logsink.Error, false, noStack, lnFormat(args), args...) } // Errorf logs to the ERROR, WARNING, and INFO logs. // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. -func Errorf(format string, args ...interface{}) { - logging.printf(errorLog, format, args...) +func Errorf(format string, args ...any) { + logf(1, logsink.Error, false, noStack, format, args...) +} + +func fatalf(depth int, format string, args ...any) { + logf(depth+1, logsink.Fatal, false, withStack, format, args...) + sinks.file.Flush() + + err := abortProcess() // Should not return. + + // Failed to abort the process using signals. Dump a stack trace and exit. + Errorf("abortProcess returned unexpectedly: %v", err) + sinks.file.Flush() + pprof.Lookup("goroutine").WriteTo(os.Stderr, 1) + os.Exit(2) // Exit with the same code as the default SIGABRT handler. +} + +// abortProcess attempts to kill the current process in a way that will dump the +// currently-running goroutines someplace useful (Coroner or stderr). +// +// It does this by sending SIGABRT to the current process. Unfortunately, the +// signal may or may not be delivered to the current thread; in order to do that +// portably, we would need to add a cgo dependency and call pthread_kill. +// +// If successful, abortProcess does not return. +func abortProcess() error { + p, err := os.FindProcess(os.Getpid()) + if err != nil { + return err + } + if err := p.Signal(syscall.SIGABRT); err != nil { + return err + } + + // Sent the signal. Now we wait for it to arrive and any SIGABRT handlers to + // run (and eventually terminate the process themselves). + // + // We could just "select{}" here, but there's an outside chance that would + // trigger the runtime's deadlock detector if there happen not to be any + // background goroutines running. So we'll sleep a while first to give + // the signal some time. + time.Sleep(10 * time.Second) + select {} } // Fatal logs to the FATAL, ERROR, WARNING, and INFO logs, -// including a stack trace of all running goroutines, then calls os.Exit(255). +// including a stack trace of all running goroutines, then calls os.Exit(2). // Arguments are handled in the manner of fmt.Print; a newline is appended if missing. -func Fatal(args ...interface{}) { - logging.print(fatalLog, args...) +func Fatal(args ...any) { + FatalDepth(1, args...) } // FatalDepth acts as Fatal but uses depth to determine which call frame to log. // FatalDepth(0, "msg") is the same as Fatal("msg"). -func FatalDepth(depth int, args ...interface{}) { - logging.printDepth(fatalLog, depth, args...) +func FatalDepth(depth int, args ...any) { + fatalf(depth+1, defaultFormat(args), args...) +} + +// FatalDepthf acts as Fatalf but uses depth to determine which call frame to log. +// FatalDepthf(0, "msg") is the same as Fatalf("msg"). +func FatalDepthf(depth int, format string, args ...any) { + fatalf(depth+1, format, args...) } // Fatalln logs to the FATAL, ERROR, WARNING, and INFO logs, -// including a stack trace of all running goroutines, then calls os.Exit(255). +// including a stack trace of all running goroutines, then calls os.Exit(2). // Arguments are handled in the manner of fmt.Println; a newline is appended if missing. -func Fatalln(args ...interface{}) { - logging.println(fatalLog, args...) +func Fatalln(args ...any) { + fatalf(1, lnFormat(args), args...) } // Fatalf logs to the FATAL, ERROR, WARNING, and INFO logs, -// including a stack trace of all running goroutines, then calls os.Exit(255). +// including a stack trace of all running goroutines, then calls os.Exit(2). // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. -func Fatalf(format string, args ...interface{}) { - logging.printf(fatalLog, format, args...) +func Fatalf(format string, args ...any) { + fatalf(1, format, args...) } -// fatalNoStacks is non-zero if we are to exit without dumping goroutine stacks. -// It allows Exit and relatives to use the Fatal logs. -var fatalNoStacks uint32 +func exitf(depth int, format string, args ...any) { + logf(depth+1, logsink.Fatal, false, noStack, format, args...) + sinks.file.Flush() + os.Exit(1) +} // Exit logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). // Arguments are handled in the manner of fmt.Print; a newline is appended if missing. -func Exit(args ...interface{}) { - atomic.StoreUint32(&fatalNoStacks, 1) - logging.print(fatalLog, args...) +func Exit(args ...any) { + ExitDepth(1, args...) } // ExitDepth acts as Exit but uses depth to determine which call frame to log. // ExitDepth(0, "msg") is the same as Exit("msg"). -func ExitDepth(depth int, args ...interface{}) { - atomic.StoreUint32(&fatalNoStacks, 1) - logging.printDepth(fatalLog, depth, args...) +func ExitDepth(depth int, args ...any) { + exitf(depth+1, defaultFormat(args), args...) +} + +// ExitDepthf acts as Exitf but uses depth to determine which call frame to log. +// ExitDepthf(0, "msg") is the same as Exitf("msg"). +func ExitDepthf(depth int, format string, args ...any) { + exitf(depth+1, format, args...) } // Exitln logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). -func Exitln(args ...interface{}) { - atomic.StoreUint32(&fatalNoStacks, 1) - logging.println(fatalLog, args...) +func Exitln(args ...any) { + exitf(1, lnFormat(args), args...) } // Exitf logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. -func Exitf(format string, args ...interface{}) { - atomic.StoreUint32(&fatalNoStacks, 1) - logging.printf(fatalLog, format, args...) +func Exitf(format string, args ...any) { + exitf(1, format, args...) } diff --git a/vendor/github.com/golang/glog/glog_file.go b/vendor/github.com/golang/glog/glog_file.go index 65075d281..af1c934b8 100644 --- a/vendor/github.com/golang/glog/glog_file.go +++ b/vendor/github.com/golang/glog/glog_file.go @@ -1,6 +1,6 @@ -// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ +// Go support for leveled logs, analogous to https://github.com/google/glog. // -// Copyright 2013 Google Inc. All Rights Reserved. +// Copyright 2023 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,26 +19,34 @@ package glog import ( + "bufio" + "bytes" "errors" "flag" "fmt" + "io" "os" "os/user" "path/filepath" + "runtime" "strings" "sync" "time" -) -// MaxSize is the maximum size of a log file in bytes. -var MaxSize uint64 = 1024 * 1024 * 1800 + "github.com/golang/glog/internal/logsink" +) // logDirs lists the candidate directories for new log files. var logDirs []string -// If non-empty, overrides the choice of directory in which to write logs. -// See createLogDirs for the full list of possible destinations. -var logDir = flag.String("log_dir", "", "If non-empty, write log files in this directory") +var ( + // If non-empty, overrides the choice of directory in which to write logs. + // See createLogDirs for the full list of possible destinations. + logDir = flag.String("log_dir", "", "If non-empty, write log files in this directory") + logLink = flag.String("log_link", "", "If non-empty, add symbolic links in this directory to the log files") + logBufLevel = flag.Int("logbuflevel", int(logsink.Info), "Buffer log messages logged at this level or lower"+ + " (-1 means don't buffer; 0 means buffer INFO only; ...). Has limited applicability on non-prod platforms.") +) func createLogDirs() { if *logDir != "" { @@ -64,9 +72,17 @@ func init() { if err == nil { userName = current.Username } - - // Sanitize userName since it may contain filepath separators on Windows. - userName = strings.Replace(userName, `\`, "_", -1) + // Sanitize userName since it is used to construct file paths. + userName = strings.Map(func(r rune) rune { + switch { + case r >= 'a' && r <= 'z': + case r >= 'A' && r <= 'Z': + case r >= '0' && r <= '9': + default: + return '_' + } + return r + }, userName) } // shortHostname returns its argument, truncating at the first period. @@ -122,3 +138,270 @@ func create(tag string, t time.Time) (f *os.File, filename string, err error) { } return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr) } + +// flushSyncWriter is the interface satisfied by logging destinations. +type flushSyncWriter interface { + Flush() error + Sync() error + io.Writer + filenames() []string +} + +var sinks struct { + stderr stderrSink + file fileSink +} + +func init() { + sinks.stderr.w = os.Stderr + + // Register stderr first: that way if we crash during file-writing at least + // the log will have gone somewhere. + logsink.TextSinks = append(logsink.TextSinks, &sinks.stderr, &sinks.file) + + sinks.file.flushChan = make(chan logsink.Severity, 1) + go sinks.file.flushDaemon() +} + +// stderrSink is a logsink.Text that writes log entries to stderr +// if they meet certain conditions. +type stderrSink struct { + mu sync.Mutex + w io.Writer +} + +// Enabled implements logsink.Text.Enabled. It returns true if any of the +// various stderr flags are enabled for logs of the given severity, if the log +// message is from the standard "log" package, or if google.Init has not yet run +// (and hence file logging is not yet initialized). +func (s *stderrSink) Enabled(m *logsink.Meta) bool { + return toStderr || alsoToStderr || m.Severity >= stderrThreshold.get() +} + +// Emit implements logsink.Text.Emit. +func (s *stderrSink) Emit(m *logsink.Meta, data []byte) (n int, err error) { + s.mu.Lock() + defer s.mu.Unlock() + + dn, err := s.w.Write(data) + n += dn + return n, err +} + +// severityWriters is an array of flushSyncWriter with a value for each +// logsink.Severity. +type severityWriters [4]flushSyncWriter + +// fileSink is a logsink.Text that prints to a set of Google log files. +type fileSink struct { + mu sync.Mutex + // file holds writer for each of the log types. + file severityWriters + flushChan chan logsink.Severity +} + +// Enabled implements logsink.Text.Enabled. It returns true if google.Init +// has run and both --disable_log_to_disk and --logtostderr are false. +func (s *fileSink) Enabled(m *logsink.Meta) bool { + return !toStderr +} + +// Emit implements logsink.Text.Emit +func (s *fileSink) Emit(m *logsink.Meta, data []byte) (n int, err error) { + s.mu.Lock() + defer s.mu.Unlock() + + if err = s.createMissingFiles(m.Severity); err != nil { + return 0, err + } + for sev := m.Severity; sev >= logsink.Info; sev-- { + if _, fErr := s.file[sev].Write(data); fErr != nil && err == nil { + err = fErr // Take the first error. + } + } + n = len(data) + if int(m.Severity) > *logBufLevel { + select { + case s.flushChan <- m.Severity: + default: + } + } + + return n, err +} + +// syncBuffer joins a bufio.Writer to its underlying file, providing access to the +// file's Sync method and providing a wrapper for the Write method that provides log +// file rotation. There are conflicting methods, so the file cannot be embedded. +// s.mu is held for all its methods. +type syncBuffer struct { + sink *fileSink + *bufio.Writer + file *os.File + names []string + sev logsink.Severity + nbytes uint64 // The number of bytes written to this file +} + +func (sb *syncBuffer) Sync() error { + return sb.file.Sync() +} + +func (sb *syncBuffer) Write(p []byte) (n int, err error) { + if sb.nbytes+uint64(len(p)) >= MaxSize { + if err := sb.rotateFile(time.Now()); err != nil { + return 0, err + } + } + n, err = sb.Writer.Write(p) + sb.nbytes += uint64(n) + return n, err +} + +func (sb *syncBuffer) filenames() []string { + return sb.names +} + +const footer = "\nCONTINUED IN NEXT FILE\n" + +// rotateFile closes the syncBuffer's file and starts a new one. +func (sb *syncBuffer) rotateFile(now time.Time) error { + var err error + pn := "" + file, name, err := create(sb.sev.String(), now) + + if sb.file != nil { + // The current log file becomes the previous log at the end of + // this block, so save its name for use in the header of the next + // file. + pn = sb.file.Name() + sb.Flush() + // If there's an existing file, write a footer with the name of + // the next file in the chain, followed by the constant string + // \nCONTINUED IN NEXT FILE\n to make continuation detection simple. + sb.file.Write([]byte("Next log: ")) + sb.file.Write([]byte(name)) + sb.file.Write([]byte(footer)) + sb.file.Close() + } + + sb.file = file + sb.names = append(sb.names, name) + sb.nbytes = 0 + if err != nil { + return err + } + + sb.Writer = bufio.NewWriterSize(sb.file, bufferSize) + + // Write header. + var buf bytes.Buffer + fmt.Fprintf(&buf, "Log file created at: %s\n", now.Format("2006/01/02 15:04:05")) + fmt.Fprintf(&buf, "Running on machine: %s\n", host) + fmt.Fprintf(&buf, "Binary: Built with %s %s for %s/%s\n", runtime.Compiler, runtime.Version(), runtime.GOOS, runtime.GOARCH) + fmt.Fprintf(&buf, "Previous log: %s\n", pn) + fmt.Fprintf(&buf, "Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg\n") + n, err := sb.file.Write(buf.Bytes()) + sb.nbytes += uint64(n) + return err +} + +// bufferSize sizes the buffer associated with each log file. It's large +// so that log records can accumulate without the logging thread blocking +// on disk I/O. The flushDaemon will block instead. +const bufferSize = 256 * 1024 + +// createMissingFiles creates all the log files for severity from infoLog up to +// upTo that have not already been created. +// s.mu is held. +func (s *fileSink) createMissingFiles(upTo logsink.Severity) error { + if s.file[upTo] != nil { + return nil + } + now := time.Now() + // Files are created in increasing severity order, so we can be assured that + // if a high severity logfile exists, then so do all of lower severity. + for sev := logsink.Info; sev <= upTo; sev++ { + if s.file[sev] != nil { + continue + } + sb := &syncBuffer{ + sink: s, + sev: sev, + } + if err := sb.rotateFile(now); err != nil { + return err + } + s.file[sev] = sb + } + return nil +} + +// flushDaemon periodically flushes the log file buffers. +func (s *fileSink) flushDaemon() { + tick := time.NewTicker(30 * time.Second) + defer tick.Stop() + for { + select { + case <-tick.C: + s.Flush() + case sev := <-s.flushChan: + s.flush(sev) + } + } +} + +// Flush flushes all pending log I/O. +func Flush() { + sinks.file.Flush() +} + +// Flush flushes all the logs and attempts to "sync" their data to disk. +func (s *fileSink) Flush() error { + return s.flush(logsink.Info) +} + +// flush flushes all logs of severity threshold or greater. +func (s *fileSink) flush(threshold logsink.Severity) error { + s.mu.Lock() + defer s.mu.Unlock() + + var firstErr error + updateErr := func(err error) { + if err != nil && firstErr == nil { + firstErr = err + } + } + + // Flush from fatal down, in case there's trouble flushing. + for sev := logsink.Fatal; sev >= threshold; sev-- { + file := s.file[sev] + if file != nil { + updateErr(file.Flush()) + updateErr(file.Sync()) + } + } + + return firstErr +} + +// Names returns the names of the log files holding the FATAL, ERROR, +// WARNING, or INFO logs. Returns ErrNoLog if the log for the given +// level doesn't exist (e.g. because no messages of that level have been +// written). This may return multiple names if the log type requested +// has rolled over. +func Names(s string) ([]string, error) { + severity, err := logsink.ParseSeverity(s) + if err != nil { + return nil, err + } + + sinks.file.mu.Lock() + defer sinks.file.mu.Unlock() + f := sinks.file.file[severity] + if f == nil { + return nil, ErrNoLog + } + + return f.filenames(), nil +} diff --git a/vendor/github.com/golang/glog/glog_flags.go b/vendor/github.com/golang/glog/glog_flags.go new file mode 100644 index 000000000..3060e54d9 --- /dev/null +++ b/vendor/github.com/golang/glog/glog_flags.go @@ -0,0 +1,395 @@ +// Go support for leveled logs, analogous to https://github.com/google/glog. +// +// Copyright 2023 Google Inc. All Rights Reserved. +// +// 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 glog + +import ( + "bytes" + "errors" + "flag" + "fmt" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" + "sync/atomic" + + "github.com/golang/glog/internal/logsink" +) + +// modulePat contains a filter for the -vmodule flag. +// It holds a verbosity level and a file pattern to match. +type modulePat struct { + pattern string + literal bool // The pattern is a literal string + full bool // The pattern wants to match the full path + level Level +} + +// match reports whether the file matches the pattern. It uses a string +// comparison if the pattern contains no metacharacters. +func (m *modulePat) match(full, file string) bool { + if m.literal { + if m.full { + return full == m.pattern + } + return file == m.pattern + } + if m.full { + match, _ := filepath.Match(m.pattern, full) + return match + } + match, _ := filepath.Match(m.pattern, file) + return match +} + +// isLiteral reports whether the pattern is a literal string, that is, has no metacharacters +// that require filepath.Match to be called to match the pattern. +func isLiteral(pattern string) bool { + return !strings.ContainsAny(pattern, `\*?[]`) +} + +// isFull reports whether the pattern matches the full file path, that is, +// whether it contains /. +func isFull(pattern string) bool { + return strings.ContainsRune(pattern, '/') +} + +// verboseFlags represents the setting of the -v and -vmodule flags. +type verboseFlags struct { + // moduleLevelCache is a sync.Map storing the -vmodule Level for each V() + // call site, identified by PC. If there is no matching -vmodule filter, + // the cached value is exactly v. moduleLevelCache is replaced with a new + // Map whenever the -vmodule or -v flag changes state. + moduleLevelCache atomic.Value + + // mu guards all fields below. + mu sync.Mutex + + // v stores the value of the -v flag. It may be read safely using + // sync.LoadInt32, but is only modified under mu. + v Level + + // module stores the parsed -vmodule flag. + module []modulePat + + // moduleLength caches len(module). If greater than zero, it + // means vmodule is enabled. It may be read safely using sync.LoadInt32, but + // is only modified under mu. + moduleLength int32 +} + +// NOTE: For compatibility with the open-sourced v1 version of this +// package (github.com/golang/glog) we need to retain that flag.Level +// implements the flag.Value interface. See also go/log-vs-glog. + +// String is part of the flag.Value interface. +func (l *Level) String() string { + return strconv.FormatInt(int64(l.Get().(Level)), 10) +} + +// Get is part of the flag.Value interface. +func (l *Level) Get() any { + if l == &vflags.v { + // l is the value registered for the -v flag. + return Level(atomic.LoadInt32((*int32)(l))) + } + return *l +} + +// Set is part of the flag.Value interface. +func (l *Level) Set(value string) error { + v, err := strconv.Atoi(value) + if err != nil { + return err + } + if l == &vflags.v { + // l is the value registered for the -v flag. + vflags.mu.Lock() + defer vflags.mu.Unlock() + vflags.moduleLevelCache.Store(&sync.Map{}) + atomic.StoreInt32((*int32)(l), int32(v)) + return nil + } + *l = Level(v) + return nil +} + +// vModuleFlag is the flag.Value for the --vmodule flag. +type vModuleFlag struct{ *verboseFlags } + +func (f vModuleFlag) String() string { + f.mu.Lock() + defer f.mu.Unlock() + + var b bytes.Buffer + for i, f := range f.module { + if i > 0 { + b.WriteRune(',') + } + fmt.Fprintf(&b, "%s=%d", f.pattern, f.level) + } + return b.String() +} + +// Get returns nil for this flag type since the struct is not exported. +func (f vModuleFlag) Get() any { return nil } + +var errVmoduleSyntax = errors.New("syntax error: expect comma-separated list of filename=N") + +// Syntax: -vmodule=recordio=2,foo/bar/baz=1,gfs*=3 +func (f vModuleFlag) Set(value string) error { + var filter []modulePat + for _, pat := range strings.Split(value, ",") { + if len(pat) == 0 { + // Empty strings such as from a trailing comma can be ignored. + continue + } + patLev := strings.Split(pat, "=") + if len(patLev) != 2 || len(patLev[0]) == 0 || len(patLev[1]) == 0 { + return errVmoduleSyntax + } + pattern := patLev[0] + v, err := strconv.Atoi(patLev[1]) + if err != nil { + return errors.New("syntax error: expect comma-separated list of filename=N") + } + // TODO: check syntax of filter? + filter = append(filter, modulePat{pattern, isLiteral(pattern), isFull(pattern), Level(v)}) + } + + f.mu.Lock() + defer f.mu.Unlock() + f.module = filter + atomic.StoreInt32((*int32)(&f.moduleLength), int32(len(f.module))) + f.moduleLevelCache.Store(&sync.Map{}) + return nil +} + +func (f *verboseFlags) levelForPC(pc uintptr) Level { + if level, ok := f.moduleLevelCache.Load().(*sync.Map).Load(pc); ok { + return level.(Level) + } + + f.mu.Lock() + defer f.mu.Unlock() + level := Level(f.v) + fn := runtime.FuncForPC(pc) + file, _ := fn.FileLine(pc) + // The file is something like /a/b/c/d.go. We want just the d for + // regular matches, /a/b/c/d for full matches. + if strings.HasSuffix(file, ".go") { + file = file[:len(file)-3] + } + full := file + if slash := strings.LastIndex(file, "/"); slash >= 0 { + file = file[slash+1:] + } + for _, filter := range f.module { + if filter.match(full, file) { + level = filter.level + break // Use the first matching level. + } + } + f.moduleLevelCache.Load().(*sync.Map).Store(pc, level) + return level +} + +func (f *verboseFlags) enabled(callerDepth int, level Level) bool { + if atomic.LoadInt32(&f.moduleLength) == 0 { + // No vmodule values specified, so compare against v level. + return Level(atomic.LoadInt32((*int32)(&f.v))) >= level + } + + pcs := [1]uintptr{} + if runtime.Callers(callerDepth+2, pcs[:]) < 1 { + return false + } + frame, _ := runtime.CallersFrames(pcs[:]).Next() + return f.levelForPC(frame.Entry) >= level +} + +// traceLocation represents an entry in the -log_backtrace_at flag. +type traceLocation struct { + file string + line int +} + +var errTraceSyntax = errors.New("syntax error: expect file.go:234") + +func parseTraceLocation(value string) (traceLocation, error) { + fields := strings.Split(value, ":") + if len(fields) != 2 { + return traceLocation{}, errTraceSyntax + } + file, lineStr := fields[0], fields[1] + if !strings.Contains(file, ".") { + return traceLocation{}, errTraceSyntax + } + line, err := strconv.Atoi(lineStr) + if err != nil { + return traceLocation{}, errTraceSyntax + } + if line < 0 { + return traceLocation{}, errors.New("negative value for line") + } + return traceLocation{file, line}, nil +} + +// match reports whether the specified file and line matches the trace location. +// The argument file name is the full path, not the basename specified in the flag. +func (t traceLocation) match(file string, line int) bool { + if t.line != line { + return false + } + if i := strings.LastIndex(file, "/"); i >= 0 { + file = file[i+1:] + } + return t.file == file +} + +func (t traceLocation) String() string { + return fmt.Sprintf("%s:%d", t.file, t.line) +} + +// traceLocations represents the -log_backtrace_at flag. +// Syntax: -log_backtrace_at=recordio.go:234,sstable.go:456 +// Note that unlike vmodule the file extension is included here. +type traceLocations struct { + mu sync.Mutex + locsLen int32 // Safe for atomic read without mu. + locs []traceLocation +} + +func (t *traceLocations) String() string { + t.mu.Lock() + defer t.mu.Unlock() + + var buf bytes.Buffer + for i, tl := range t.locs { + if i > 0 { + buf.WriteString(",") + } + buf.WriteString(tl.String()) + } + return buf.String() +} + +// Get always returns nil for this flag type since the struct is not exported +func (t *traceLocations) Get() any { return nil } + +func (t *traceLocations) Set(value string) error { + var locs []traceLocation + for _, s := range strings.Split(value, ",") { + if s == "" { + continue + } + loc, err := parseTraceLocation(s) + if err != nil { + return err + } + locs = append(locs, loc) + } + + t.mu.Lock() + defer t.mu.Unlock() + atomic.StoreInt32(&t.locsLen, int32(len(locs))) + t.locs = locs + return nil +} + +func (t *traceLocations) match(file string, line int) bool { + if atomic.LoadInt32(&t.locsLen) == 0 { + return false + } + + t.mu.Lock() + defer t.mu.Unlock() + for _, tl := range t.locs { + if tl.match(file, line) { + return true + } + } + return false +} + +// severityFlag is an atomic flag.Value implementation for logsink.Severity. +type severityFlag int32 + +func (s *severityFlag) get() logsink.Severity { + return logsink.Severity(atomic.LoadInt32((*int32)(s))) +} +func (s *severityFlag) String() string { return strconv.FormatInt(int64(*s), 10) } +func (s *severityFlag) Get() any { return s.get() } +func (s *severityFlag) Set(value string) error { + threshold, err := logsink.ParseSeverity(value) + if err != nil { + // Not a severity name. Try a raw number. + v, err := strconv.Atoi(value) + if err != nil { + return err + } + threshold = logsink.Severity(v) + if threshold < logsink.Info || threshold > logsink.Fatal { + return fmt.Errorf("Severity %d out of range (min %d, max %d).", v, logsink.Info, logsink.Fatal) + } + } + atomic.StoreInt32((*int32)(s), int32(threshold)) + return nil +} + +var ( + vflags verboseFlags // The -v and -vmodule flags. + + logBacktraceAt traceLocations // The -log_backtrace_at flag. + + // Boolean flags. Not handled atomically because the flag.Value interface + // does not let us avoid the =true, and that shorthand is necessary for + // compatibility. TODO: does this matter enough to fix? Seems unlikely. + toStderr bool // The -logtostderr flag. + alsoToStderr bool // The -alsologtostderr flag. + + stderrThreshold severityFlag // The -stderrthreshold flag. +) + +// verboseEnabled returns whether the caller at the given depth should emit +// verbose logs at the given level, with depth 0 identifying the caller of +// verboseEnabled. +func verboseEnabled(callerDepth int, level Level) bool { + return vflags.enabled(callerDepth+1, level) +} + +// backtraceAt returns whether the logging call at the given function and line +// should also emit a backtrace of the current call stack. +func backtraceAt(file string, line int) bool { + return logBacktraceAt.match(file, line) +} + +func init() { + vflags.moduleLevelCache.Store(&sync.Map{}) + + flag.Var(&vflags.v, "v", "log level for V logs") + flag.Var(vModuleFlag{&vflags}, "vmodule", "comma-separated list of pattern=N settings for file-filtered logging") + + flag.Var(&logBacktraceAt, "log_backtrace_at", "when logging hits line file:N, emit a stack trace") + + stderrThreshold = severityFlag(logsink.Error) + + flag.BoolVar(&toStderr, "logtostderr", false, "log to standard error instead of files") + flag.BoolVar(&alsoToStderr, "alsologtostderr", false, "log to standard error as well as files") + flag.Var(&stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr") +} diff --git a/vendor/github.com/golang/glog/internal/logsink/logsink.go b/vendor/github.com/golang/glog/internal/logsink/logsink.go new file mode 100644 index 000000000..53758e1c9 --- /dev/null +++ b/vendor/github.com/golang/glog/internal/logsink/logsink.go @@ -0,0 +1,387 @@ +// Copyright 2023 Google Inc. All Rights Reserved. +// +// 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 logsink + +import ( + "bytes" + "fmt" + "strconv" + "strings" + "sync" + "time" + + "github.com/golang/glog/internal/stackdump" +) + +// MaxLogMessageLen is the limit on length of a formatted log message, including +// the standard line prefix and trailing newline. +// +// Chosen to match C++ glog. +const MaxLogMessageLen = 15000 + +// A Severity is a severity at which a message can be logged. +type Severity int8 + +// These constants identify the log levels in order of increasing severity. +// A message written to a high-severity log file is also written to each +// lower-severity log file. +const ( + Info Severity = iota + Warning + Error + + // Fatal contains logs written immediately before the process terminates. + // + // Sink implementations should not terminate the process themselves: the log + // package will perform any necessary cleanup and terminate the process as + // appropriate. + Fatal +) + +func (s Severity) String() string { + switch s { + case Info: + return "INFO" + case Warning: + return "WARNING" + case Error: + return "ERROR" + case Fatal: + return "FATAL" + } + return fmt.Sprintf("%T(%d)", s, s) +} + +// ParseSeverity returns the case-insensitive Severity value for the given string. +func ParseSeverity(name string) (Severity, error) { + name = strings.ToUpper(name) + for s := Info; s <= Fatal; s++ { + if s.String() == name { + return s, nil + } + } + return -1, fmt.Errorf("logsink: invalid severity %q", name) +} + +// Meta is metadata about a logging call. +type Meta struct { + // Time is the time at which the log call was made. + Time time.Time + + // File is the source file from which the log entry originates. + File string + // Line is the line offset within the source file. + Line int + // Depth is the number of stack frames between the logsink and the log call. + Depth int + + Severity Severity + + // Verbose indicates whether the call was made via "log.V". Log entries below + // the current verbosity threshold are not sent to the sink. + Verbose bool + + // Thread ID. This can be populated with a thread ID from another source, + // such as a system we are importing logs from. In the normal case, this + // will be set to the process ID (PID), since Go doesn't have threads. + Thread int64 + + // Stack trace starting in the logging function. May be nil. + // A logsink should implement the StackWanter interface to request this. + // + // Even if WantStack returns false, this field may be set (e.g. if another + // sink wants a stack trace). + Stack *stackdump.Stack +} + +// Structured is a logging destination that accepts structured data as input. +type Structured interface { + // Printf formats according to a fmt.Printf format specifier and writes a log + // entry. The precise result of formatting depends on the sink, but should + // aim for consistency with fmt.Printf. + // + // Printf returns the number of bytes occupied by the log entry, which + // may not be equal to the total number of bytes written. + // + // Printf returns any error encountered *if* it is severe enough that the log + // package should terminate the process. + // + // The sink must not modify the *Meta parameter, nor reference it after + // Printf has returned: it may be reused in subsequent calls. + Printf(meta *Meta, format string, a ...any) (n int, err error) +} + +// StackWanter can be implemented by a logsink.Structured to indicate that it +// wants a stack trace to accompany at least some of the log messages it receives. +type StackWanter interface { + // WantStack returns true if the sink requires a stack trace for a log message + // with this metadata. + // + // NOTE: Returning true implies that meta.Stack will be non-nil. Returning + // false does NOT imply that meta.Stack will be nil. + WantStack(meta *Meta) bool +} + +// Text is a logging destination that accepts pre-formatted log lines (instead of +// structured data). +type Text interface { + // Enabled returns whether this sink should output messages for the given + // Meta. If the sink returns false for a given Meta, the Printf function will + // not call Emit on it for the corresponding log message. + Enabled(*Meta) bool + + // Emit writes a pre-formatted text log entry (including any applicable + // header) to the log. It returns the number of bytes occupied by the entry + // (which may differ from the length of the passed-in slice). + // + // Emit returns any error encountered *if* it is severe enough that the log + // package should terminate the process. + // + // The sink must not modify the *Meta parameter, nor reference it after + // Printf has returned: it may be reused in subsequent calls. + // + // NOTE: When developing a text sink, keep in mind the surface in which the + // logs will be displayed, and whether it's important that the sink be + // resistent to tampering in the style of b/211428300. Standard text sinks + // (like `stderrSink`) do not protect against this (e.g. by escaping + // characters) because the cases where they would show user-influenced bytes + // are vanishingly small. + Emit(*Meta, []byte) (n int, err error) +} + +// bufs is a pool of *bytes.Buffer used in formatting log entries. +var bufs sync.Pool // Pool of *bytes.Buffer. + +// textPrintf formats a text log entry and emits it to all specified Text sinks. +// +// The returned n is the maximum across all Emit calls. +// The returned err is the first non-nil error encountered. +// Sinks that are disabled by configuration should return (0, nil). +func textPrintf(m *Meta, textSinks []Text, format string, args ...any) (n int, err error) { + // We expect at most file, stderr, and perhaps syslog. If there are more, + // we'll end up allocating - no big deal. + const maxExpectedTextSinks = 3 + var noAllocSinks [maxExpectedTextSinks]Text + + sinks := noAllocSinks[:0] + for _, s := range textSinks { + if s.Enabled(m) { + sinks = append(sinks, s) + } + } + if len(sinks) == 0 && m.Severity != Fatal { + return 0, nil // No TextSinks specified; don't bother formatting. + } + + bufi := bufs.Get() + var buf *bytes.Buffer + if bufi == nil { + buf = bytes.NewBuffer(nil) + bufi = buf + } else { + buf = bufi.(*bytes.Buffer) + buf.Reset() + } + + // Lmmdd hh:mm:ss.uuuuuu PID/GID file:line] + // + // The "PID" entry arguably ought to be TID for consistency with other + // environments, but TID is not meaningful in a Go program due to the + // multiplexing of goroutines across threads. + // + // Avoid Fprintf, for speed. The format is so simple that we can do it quickly by hand. + // It's worth about 3X. Fprintf is hard. + const severityChar = "IWEF" + buf.WriteByte(severityChar[m.Severity]) + + _, month, day := m.Time.Date() + hour, minute, second := m.Time.Clock() + twoDigits(buf, int(month)) + twoDigits(buf, day) + buf.WriteByte(' ') + twoDigits(buf, hour) + buf.WriteByte(':') + twoDigits(buf, minute) + buf.WriteByte(':') + twoDigits(buf, second) + buf.WriteByte('.') + nDigits(buf, 6, uint64(m.Time.Nanosecond()/1000), '0') + buf.WriteByte(' ') + + nDigits(buf, 7, uint64(m.Thread), ' ') + buf.WriteByte(' ') + + { + file := m.File + if i := strings.LastIndex(file, "/"); i >= 0 { + file = file[i+1:] + } + buf.WriteString(file) + } + + buf.WriteByte(':') + { + var tmp [19]byte + buf.Write(strconv.AppendInt(tmp[:0], int64(m.Line), 10)) + } + buf.WriteString("] ") + + msgStart := buf.Len() + fmt.Fprintf(buf, format, args...) + if buf.Len() > MaxLogMessageLen-1 { + buf.Truncate(MaxLogMessageLen - 1) + } + msgEnd := buf.Len() + if b := buf.Bytes(); b[len(b)-1] != '\n' { + buf.WriteByte('\n') + } + + for _, s := range sinks { + sn, sErr := s.Emit(m, buf.Bytes()) + if sn > n { + n = sn + } + if sErr != nil && err == nil { + err = sErr + } + } + + if m.Severity == Fatal { + savedM := *m + fatalMessageStore(savedEntry{ + meta: &savedM, + msg: buf.Bytes()[msgStart:msgEnd], + }) + } else { + bufs.Put(bufi) + } + return n, err +} + +const digits = "0123456789" + +// twoDigits formats a zero-prefixed two-digit integer to buf. +func twoDigits(buf *bytes.Buffer, d int) { + buf.WriteByte(digits[(d/10)%10]) + buf.WriteByte(digits[d%10]) +} + +// nDigits formats an n-digit integer to buf, padding with pad on the left. It +// assumes d != 0. +func nDigits(buf *bytes.Buffer, n int, d uint64, pad byte) { + var tmp [20]byte + + cutoff := len(tmp) - n + j := len(tmp) - 1 + for ; d > 0; j-- { + tmp[j] = digits[d%10] + d /= 10 + } + for ; j >= cutoff; j-- { + tmp[j] = pad + } + j++ + buf.Write(tmp[j:]) +} + +// Printf writes a log entry to all registered TextSinks in this package, then +// to all registered StructuredSinks. +// +// The returned n is the maximum across all Emit and Printf calls. +// The returned err is the first non-nil error encountered. +// Sinks that are disabled by configuration should return (0, nil). +func Printf(m *Meta, format string, args ...any) (n int, err error) { + m.Depth++ + n, err = textPrintf(m, TextSinks, format, args...) + + for _, sink := range StructuredSinks { + // TODO: Support TextSinks that implement StackWanter? + if sw, ok := sink.(StackWanter); ok && sw.WantStack(m) { + if m.Stack == nil { + // First, try to find a stacktrace in args, otherwise generate one. + for _, arg := range args { + if stack, ok := arg.(stackdump.Stack); ok { + m.Stack = &stack + break + } + } + if m.Stack == nil { + stack := stackdump.Caller( /* skipDepth = */ m.Depth) + m.Stack = &stack + } + } + } + sn, sErr := sink.Printf(m, format, args...) + if sn > n { + n = sn + } + if sErr != nil && err == nil { + err = sErr + } + } + return n, err +} + +// The sets of sinks to which logs should be written. +// +// These must only be modified during package init, and are read-only thereafter. +var ( + // StructuredSinks is the set of Structured sink instances to which logs + // should be written. + StructuredSinks []Structured + + // TextSinks is the set of Text sink instances to which logs should be + // written. + // + // These are registered separately from Structured sink implementations to + // avoid the need to repeat the work of formatting a message for each Text + // sink that writes it. The package-level Printf function writes to both sets + // independenty, so a given log destination should only register a Structured + // *or* a Text sink (not both). + TextSinks []Text +) + +type savedEntry struct { + meta *Meta + msg []byte +} + +// StructuredTextWrapper is a Structured sink which forwards logs to a set of Text sinks. +// +// The purpose of this sink is to allow applications to intercept logging calls before they are +// serialized and sent to Text sinks. For example, if one needs to redact PII from logging +// arguments before they reach STDERR, one solution would be to do the redacting in a Structured +// sink that forwards logs to a StructuredTextWrapper instance, and make STDERR a child of that +// StructuredTextWrapper instance. This is how one could set this up in their application: +// +// func init() { +// +// wrapper := logsink.StructuredTextWrapper{TextSinks: logsink.TextSinks} +// // sanitizersink will intercept logs and remove PII +// sanitizer := sanitizersink{Sink: &wrapper} +// logsink.StructuredSinks = append(logsink.StructuredSinks, &sanitizer) +// logsink.TextSinks = nil +// +// } +type StructuredTextWrapper struct { + // TextSinks is the set of Text sinks that should receive logs from this + // StructuredTextWrapper instance. + TextSinks []Text +} + +// Printf forwards logs to all Text sinks registered in the StructuredTextWrapper. +func (w *StructuredTextWrapper) Printf(meta *Meta, format string, args ...any) (n int, err error) { + return textPrintf(meta, w.TextSinks, format, args...) +} diff --git a/vendor/github.com/golang/glog/internal/logsink/logsink_fatal.go b/vendor/github.com/golang/glog/internal/logsink/logsink_fatal.go new file mode 100644 index 000000000..3dc269abc --- /dev/null +++ b/vendor/github.com/golang/glog/internal/logsink/logsink_fatal.go @@ -0,0 +1,35 @@ +package logsink + +import ( + "sync/atomic" + "unsafe" +) + +func fatalMessageStore(e savedEntry) { + // Only put a new one in if we haven't assigned before. + atomic.CompareAndSwapPointer(&fatalMessage, nil, unsafe.Pointer(&e)) +} + +var fatalMessage unsafe.Pointer // savedEntry stored with CompareAndSwapPointer + +// FatalMessage returns the Meta and message contents of the first message +// logged with Fatal severity, or false if none has occurred. +func FatalMessage() (*Meta, []byte, bool) { + e := (*savedEntry)(atomic.LoadPointer(&fatalMessage)) + if e == nil { + return nil, nil, false + } + return e.meta, e.msg, true +} + +// DoNotUseRacyFatalMessage is FatalMessage, but worse. +// +//go:norace +//go:nosplit +func DoNotUseRacyFatalMessage() (*Meta, []byte, bool) { + e := (*savedEntry)(fatalMessage) + if e == nil { + return nil, nil, false + } + return e.meta, e.msg, true +} diff --git a/vendor/github.com/golang/glog/internal/stackdump/stackdump.go b/vendor/github.com/golang/glog/internal/stackdump/stackdump.go new file mode 100644 index 000000000..3427c9d6b --- /dev/null +++ b/vendor/github.com/golang/glog/internal/stackdump/stackdump.go @@ -0,0 +1,127 @@ +// Copyright 2023 Google Inc. All Rights Reserved. +// +// 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 stackdump provides wrappers for runtime.Stack and runtime.Callers +// with uniform support for skipping caller frames. +// +// ⚠ Unlike the functions in the runtime package, these may allocate a +// non-trivial quantity of memory: use them with care. ⚠ +package stackdump + +import ( + "bytes" + "runtime" +) + +// runtimeStackSelfFrames is 1 if runtime.Stack includes the call to +// runtime.Stack itself or 0 if it does not. +// +// As of 2016-04-27, the gccgo compiler includes runtime.Stack but the gc +// compiler does not. +var runtimeStackSelfFrames = func() int { + for n := 1 << 10; n < 1<<20; n *= 2 { + buf := make([]byte, n) + n := runtime.Stack(buf, false) + if bytes.Contains(buf[:n], []byte("runtime.Stack")) { + return 1 + } else if n < len(buf) || bytes.Count(buf, []byte("\n")) >= 3 { + return 0 + } + } + return 0 +}() + +// Stack is a stack dump for a single goroutine. +type Stack struct { + // Text is a representation of the stack dump in a human-readable format. + Text []byte + + // PC is a representation of the stack dump using raw program counter values. + PC []uintptr +} + +func (s Stack) String() string { return string(s.Text) } + +// Caller returns the Stack dump for the calling goroutine, starting skipDepth +// frames before the caller of Caller. (Caller(0) provides a dump starting at +// the caller of this function.) +func Caller(skipDepth int) Stack { + return Stack{ + Text: CallerText(skipDepth + 1), + PC: CallerPC(skipDepth + 1), + } +} + +// CallerText returns a textual dump of the stack starting skipDepth frames before +// the caller. (CallerText(0) provides a dump starting at the caller of this +// function.) +func CallerText(skipDepth int) []byte { + for n := 1 << 10; ; n *= 2 { + buf := make([]byte, n) + n := runtime.Stack(buf, false) + if n < len(buf) { + return pruneFrames(skipDepth+1+runtimeStackSelfFrames, buf[:n]) + } + } +} + +// CallerPC returns a dump of the program counters of the stack starting +// skipDepth frames before the caller. (CallerPC(0) provides a dump starting at +// the caller of this function.) +func CallerPC(skipDepth int) []uintptr { + for n := 1 << 8; ; n *= 2 { + buf := make([]uintptr, n) + n := runtime.Callers(skipDepth+2, buf) + if n < len(buf) { + return buf[:n] + } + } +} + +// pruneFrames removes the topmost skipDepth frames of the first goroutine in a +// textual stack dump. It overwrites the passed-in slice. +// +// If there are fewer than skipDepth frames in the first goroutine's stack, +// pruneFrames prunes it to an empty stack and leaves the remaining contents +// intact. +func pruneFrames(skipDepth int, stack []byte) []byte { + headerLen := 0 + for i, c := range stack { + if c == '\n' { + headerLen = i + 1 + break + } + } + if headerLen == 0 { + return stack // No header line - not a well-formed stack trace. + } + + skipLen := headerLen + skipNewlines := skipDepth * 2 + for ; skipLen < len(stack) && skipNewlines > 0; skipLen++ { + c := stack[skipLen] + if c != '\n' { + continue + } + skipNewlines-- + skipLen++ + if skipNewlines == 0 || skipLen == len(stack) || stack[skipLen] == '\n' { + break + } + } + + pruned := stack[skipLen-headerLen:] + copy(pruned, stack[:headerLen]) + return pruned +} diff --git a/vendor/github.com/gookit/color/README.md b/vendor/github.com/gookit/color/README.md index 718b11b58..77d50ca3b 100644 --- a/vendor/github.com/gookit/color/README.md +++ b/vendor/github.com/gookit/color/README.md @@ -570,6 +570,7 @@ Check out these projects, which use https://github.com/gookit/color : - [xo/terminfo](https://github.com/xo/terminfo) - [beego/bee](https://github.com/beego/bee) - [issue9/term](https://github.com/issue9/term) + - [muesli/termenv](https://github.com/muesli/termenv) - [ANSI escape code](https://en.wikipedia.org/wiki/ANSI_escape_code) - [Standard ANSI color map](https://conemu.github.io/en/AnsiEscapeCodes.html#Standard_ANSI_color_map) - [Terminal Colors](https://gist.github.com/XVilka/8346728) diff --git a/vendor/github.com/gookit/color/README.zh-CN.md b/vendor/github.com/gookit/color/README.zh-CN.md index 1b144058e..192a89c5a 100644 --- a/vendor/github.com/gookit/color/README.zh-CN.md +++ b/vendor/github.com/gookit/color/README.zh-CN.md @@ -578,6 +578,7 @@ const ( ## 参考项目 - [inhere/console](https://github.com/inhere/php-console) + - [muesli/termenv](https://github.com/muesli/termenv) - [xo/terminfo](https://github.com/xo/terminfo) - [beego/bee](https://github.com/beego/bee) - [issue9/term](https://github.com/issue9/term) diff --git a/vendor/github.com/gookit/color/any.go b/vendor/github.com/gookit/color/any.go new file mode 100644 index 000000000..8bf31c181 --- /dev/null +++ b/vendor/github.com/gookit/color/any.go @@ -0,0 +1,6 @@ +//go:build !go1.18 +// +build !go1.18 + +package color + +type any = interface{} diff --git a/vendor/github.com/gookit/color/color.go b/vendor/github.com/gookit/color/color.go index 59e0b0b67..22de1b045 100644 --- a/vendor/github.com/gookit/color/color.go +++ b/vendor/github.com/gookit/color/color.go @@ -183,7 +183,7 @@ func InnerErrs() []error { // Usage: // // msg := RenderCode("3;32;45", "some", "message") -func RenderCode(code string, args ...interface{}) string { +func RenderCode(code string, args ...any) string { var message string if ln := len(args); ln == 0 { return "" @@ -205,7 +205,7 @@ func RenderCode(code string, args ...interface{}) string { // RenderWithSpaces Render code with spaces. // If the number of args is > 1, a space will be added between the args -func RenderWithSpaces(code string, args ...interface{}) string { +func RenderWithSpaces(code string, args ...any) string { msg := formatArgsForPrintln(args) if len(code) == 0 { return msg diff --git a/vendor/github.com/gookit/color/color_16.go b/vendor/github.com/gookit/color/color_16.go index 3551521c0..0b70efe44 100644 --- a/vendor/github.com/gookit/color/color_16.go +++ b/vendor/github.com/gookit/color/color_16.go @@ -188,57 +188,65 @@ func (c Color) Text(message string) string { return RenderString(c.String(), mes // Render messages by color setting // // Usage: -// green := color.FgGreen.Render -// fmt.Println(green("message")) -func (c Color) Render(a ...interface{}) string { return RenderCode(c.String(), a...) } +// +// green := color.FgGreen.Render +// fmt.Println(green("message")) +func (c Color) Render(a ...any) string { return RenderCode(c.String(), a...) } // Renderln messages by color setting. // like Println, will add spaces for each argument // // Usage: -// green := color.FgGreen.Renderln -// fmt.Println(green("message")) -func (c Color) Renderln(a ...interface{}) string { return RenderWithSpaces(c.String(), a...) } +// +// green := color.FgGreen.Renderln +// fmt.Println(green("message")) +func (c Color) Renderln(a ...any) string { return RenderWithSpaces(c.String(), a...) } // Sprint render messages by color setting. is alias of the Render() -func (c Color) Sprint(a ...interface{}) string { return RenderCode(c.String(), a...) } +func (c Color) Sprint(a ...any) string { return RenderCode(c.String(), a...) } // Sprintf format and render message. // // Usage: -// green := color.Green.Sprintf -// colored := green("message") -func (c Color) Sprintf(format string, args ...interface{}) string { +// +// green := color.Green.Sprintf +// colored := green("message") +func (c Color) Sprintf(format string, args ...any) string { return RenderString(c.String(), fmt.Sprintf(format, args...)) } // Print messages. // // Usage: -// color.Green.Print("message") +// +// color.Green.Print("message") +// // OR: -// green := color.FgGreen.Print -// green("message") -func (c Color) Print(args ...interface{}) { +// +// green := color.FgGreen.Print +// green("message") +func (c Color) Print(args ...any) { doPrintV2(c.Code(), fmt.Sprint(args...)) } // Printf format and print messages. // // Usage: -// color.Cyan.Printf("string %s", "arg0") -func (c Color) Printf(format string, a ...interface{}) { +// +// color.Cyan.Printf("string %s", "arg0") +func (c Color) Printf(format string, a ...any) { doPrintV2(c.Code(), fmt.Sprintf(format, a...)) } // Println messages with new line -func (c Color) Println(a ...interface{}) { doPrintlnV2(c.String(), a) } +func (c Color) Println(a ...any) { doPrintlnV2(c.String(), a) } // Light current color. eg: 36(FgCyan) -> 96(FgLightCyan). // // Usage: -// lightCyan := Cyan.Light() -// lightCyan.Print("message") +// +// lightCyan := Cyan.Light() +// lightCyan.Print("message") func (c Color) Light() Color { val := int(c) if val >= 30 && val <= 47 { @@ -252,8 +260,9 @@ func (c Color) Light() Color { // Darken current color. eg. 96(FgLightCyan) -> 36(FgCyan) // // Usage: -// cyan := LightCyan.Darken() -// cyan.Print("message") +// +// cyan := LightCyan.Darken() +// cyan.Print("message") func (c Color) Darken() Color { val := int(c) if val >= 90 && val <= 107 { @@ -461,9 +470,7 @@ func Fg2Bg(val uint8) uint8 { } // Basic2nameMap data -func Basic2nameMap() map[uint8]string { - return basic2nameMap -} +func Basic2nameMap() map[uint8]string { return basic2nameMap } // func initName2basicMap() map[string]uint8 { // n2b := make(map[string]uint8, len(basic2nameMap)) diff --git a/vendor/github.com/gookit/color/color_256.go b/vendor/github.com/gookit/color/color_256.go index c95c0f7b4..991e604c0 100644 --- a/vendor/github.com/gookit/color/color_256.go +++ b/vendor/github.com/gookit/color/color_256.go @@ -19,16 +19,19 @@ from wikipedia, 256 color: // tpl for 8 bit 256 color(`2^8`) // // format: -// ESC[ … 38;5; … m // 选择前景色 -// ESC[ … 48;5; … m // 选择背景色 +// +// ESC[ … 38;5; … m // 选择前景色 +// ESC[ … 48;5; … m // 选择背景色 // // example: -// fg "\x1b[38;5;242m" -// bg "\x1b[48;5;208m" -// both "\x1b[38;5;242;48;5;208m" +// +// fg "\x1b[38;5;242m" +// bg "\x1b[48;5;208m" +// both "\x1b[38;5;242;48;5;208m" // // links: -// https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#8位 +// +// https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#8位 const ( TplFg256 = "38;5;%d" TplBg256 = "48;5;%d" @@ -45,12 +48,14 @@ const ( // 颜色值使用10进制和16进制都可 0x98 = 152 // // The color consists of two uint8: -// 0: color value -// 1: color type; Fg=0, Bg=1, >1: unset value +// +// 0: color value +// 1: color type; Fg=0, Bg=1, >1: unset value // // example: -// fg color: [152, 0] -// bg color: [152, 1] +// +// fg color: [152, 0] +// bg color: [152, 1] // // NOTICE: now support 256 color on windows CMD, PowerShell // lint warn - Name starts with package name @@ -87,27 +92,27 @@ func (c Color256) Reset() error { } // Print print message -func (c Color256) Print(a ...interface{}) { +func (c Color256) Print(a ...any) { doPrintV2(c.String(), fmt.Sprint(a...)) } // Printf format and print message -func (c Color256) Printf(format string, a ...interface{}) { +func (c Color256) Printf(format string, a ...any) { doPrintV2(c.String(), fmt.Sprintf(format, a...)) } // Println print message with newline -func (c Color256) Println(a ...interface{}) { +func (c Color256) Println(a ...any) { doPrintlnV2(c.String(), a) } // Sprint returns rendered message -func (c Color256) Sprint(a ...interface{}) string { +func (c Color256) Sprint(a ...any) string { return RenderCode(c.String(), a...) } // Sprintf returns format and rendered message -func (c Color256) Sprintf(format string, a ...interface{}) string { +func (c Color256) Sprintf(format string, a ...any) string { return RenderString(c.String(), fmt.Sprintf(format, a...)) } @@ -206,9 +211,10 @@ type Style256 struct { // S256 create a color256 style // // Usage: -// s := color.S256() -// s := color.S256(132) // fg -// s := color.S256(132, 203) // fg and bg +// +// s := color.S256() +// s := color.S256(132) // fg +// s := color.S256(132, 203) // fg and bg func S256(fgAndBg ...uint8) *Style256 { s := &Style256{} vl := len(fgAndBg) @@ -256,27 +262,27 @@ func (s *Style256) AddOpts(opts ...Color) *Style256 { } // Print message -func (s *Style256) Print(a ...interface{}) { +func (s *Style256) Print(a ...any) { doPrintV2(s.String(), fmt.Sprint(a...)) } // Printf format and print message -func (s *Style256) Printf(format string, a ...interface{}) { +func (s *Style256) Printf(format string, a ...any) { doPrintV2(s.String(), fmt.Sprintf(format, a...)) } // Println print message with newline -func (s *Style256) Println(a ...interface{}) { +func (s *Style256) Println(a ...any) { doPrintlnV2(s.String(), a) } // Sprint returns rendered message -func (s *Style256) Sprint(a ...interface{}) string { +func (s *Style256) Sprint(a ...any) string { return RenderCode(s.Code(), a...) } // Sprintf returns format and rendered message -func (s *Style256) Sprintf(format string, a ...interface{}) string { +func (s *Style256) Sprintf(format string, a ...any) string { return RenderString(s.Code(), fmt.Sprintf(format, a...)) } diff --git a/vendor/github.com/gookit/color/color_rgb.go b/vendor/github.com/gookit/color/color_rgb.go index ff3c1bb08..724cf6659 100644 --- a/vendor/github.com/gookit/color/color_rgb.go +++ b/vendor/github.com/gookit/color/color_rgb.go @@ -8,20 +8,24 @@ import ( // 24 bit RGB color // RGB: -// R 0-255 G 0-255 B 0-255 -// R 00-FF G 00-FF B 00-FF (16进制) +// +// R 0-255 G 0-255 B 0-255 +// R 00-FF G 00-FF B 00-FF (16进制) // // Format: -// ESC[ … 38;2;;; … m // Select RGB foreground color -// ESC[ … 48;2;;; … m // Choose RGB background color +// +// ESC[ … 38;2;;; … m // Select RGB foreground color +// ESC[ … 48;2;;; … m // Choose RGB background color // // links: -// https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#24位 +// +// https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#24位 // // example: -// fg: \x1b[38;2;30;144;255mMESSAGE\x1b[0m -// bg: \x1b[48;2;30;144;255mMESSAGE\x1b[0m -// both: \x1b[38;2;233;90;203;48;2;30;144;255mMESSAGE\x1b[0m +// +// fg: \x1b[38;2;30;144;255mMESSAGE\x1b[0m +// bg: \x1b[48;2;30;144;255mMESSAGE\x1b[0m +// both: \x1b[38;2;233;90;203;48;2;30;144;255mMESSAGE\x1b[0m const ( TplFgRGB = "38;2;%d;%d;%d" TplBgRGB = "48;2;%d;%d;%d" @@ -45,10 +49,11 @@ const ( // The last digit represents the foreground(0), background(1), >1 is unset value // // Usage: -// // 0, 1, 2 is R,G,B. -// // 3rd: Fg=0, Bg=1, >1: unset value -// RGBColor{30,144,255, 0} -// RGBColor{30,144,255, 1} +// +// // 0, 1, 2 is R,G,B. +// // 3rd: Fg=0, Bg=1, >1: unset value +// RGBColor{30,144,255, 0} +// RGBColor{30,144,255, 1} // // NOTICE: now support RGB color on Windows CMD, PowerShell type RGBColor [4]uint8 @@ -59,9 +64,10 @@ var emptyRGBColor = RGBColor{3: 99} // RGB color create. // // Usage: -// c := RGB(30,144,255) -// c := RGB(30,144,255, true) -// c.Print("message") +// +// c := RGB(30,144,255) +// c := RGB(30,144,255, true) +// c.Print("message") func RGB(r, g, b uint8, isBg ...bool) RGBColor { rgb := RGBColor{r, g, b} if len(isBg) > 0 && isBg[0] { @@ -90,11 +96,12 @@ func RgbFromInts(rgb []int, isBg ...bool) RGBColor { // HEX create RGB color from a HEX color string. // // Usage: -// c := HEX("ccc") // rgb: [204 204 204] -// c := HEX("aabbcc") // rgb: [170 187 204] -// c := HEX("#aabbcc") -// c := HEX("0xaabbcc") -// c.Print("message") +// +// c := HEX("ccc") // rgb: [204 204 204] +// c := HEX("aabbcc") // rgb: [170 187 204] +// c := HEX("#aabbcc") +// c := HEX("0xaabbcc") +// c.Print("message") func HEX(hex string, isBg ...bool) RGBColor { if rgb := HexToRgb(hex); len(rgb) > 0 { return RGB(uint8(rgb[0]), uint8(rgb[1]), uint8(rgb[2]), isBg...) @@ -139,11 +146,12 @@ func RGBFromSlice(rgb []uint8, isBg ...bool) RGBColor { // Support use color name in the {namedRgbMap} // // Usage: -// c := RGBFromString("170,187,204") -// c.Print("message") // -// c := RGBFromString("brown") -// c.Print("message with color brown") +// c := RGBFromString("170,187,204") +// c.Print("message") +// +// c := RGBFromString("brown") +// c.Print("message with color brown") func RGBFromString(rgb string, isBg ...bool) RGBColor { // use color name in the {namedRgbMap} if rgbVal, ok := namedRgbMap[rgb]; ok { @@ -180,27 +188,27 @@ func (c RGBColor) Reset() error { } // Print print message -func (c RGBColor) Print(a ...interface{}) { +func (c RGBColor) Print(a ...any) { doPrintV2(c.String(), fmt.Sprint(a...)) } // Printf format and print message -func (c RGBColor) Printf(format string, a ...interface{}) { +func (c RGBColor) Printf(format string, a ...any) { doPrintV2(c.String(), fmt.Sprintf(format, a...)) } // Println print message with newline -func (c RGBColor) Println(a ...interface{}) { +func (c RGBColor) Println(a ...any) { doPrintlnV2(c.String(), a) } // Sprint returns rendered message -func (c RGBColor) Sprint(a ...interface{}) string { +func (c RGBColor) Sprint(a ...any) string { return RenderCode(c.String(), a...) } // Sprintf returns format and rendered message -func (c RGBColor) Sprintf(format string, a ...interface{}) string { +func (c RGBColor) Sprintf(format string, a ...any) string { return RenderString(c.String(), fmt.Sprintf(format, a...)) } @@ -279,8 +287,8 @@ func (c RGBColor) C16() Color { return c.Basic() } // All are composed of 4 digits uint8, the first three digits are the color value; // The last bit is different from RGBColor, here it indicates whether the value is set. // -// 1 Has been set -// ^1 Not set +// 1 Has been set +// ^1 Not set type RGBStyle struct { // Name of the style Name string @@ -303,8 +311,9 @@ func NewRGBStyle(fg RGBColor, bg ...RGBColor) *RGBStyle { // HEXStyle create a RGBStyle from HEX color string. // // Usage: -// s := HEXStyle("aabbcc", "eee") -// s.Print("message") +// +// s := HEXStyle("aabbcc", "eee") +// s.Print("message") func HEXStyle(fg string, bg ...string) *RGBStyle { s := &RGBStyle{} if len(bg) > 0 { @@ -320,8 +329,9 @@ func HEXStyle(fg string, bg ...string) *RGBStyle { // RGBStyleFromString create a RGBStyle from color value string. // // Usage: -// s := RGBStyleFromString("170,187,204", "70,87,4") -// s.Print("message") +// +// s := RGBStyleFromString("170,187,204", "70,87,4") +// s.Print("message") func RGBStyleFromString(fg string, bg ...string) *RGBStyle { s := &RGBStyle{} if len(bg) > 0 { @@ -363,27 +373,27 @@ func (s *RGBStyle) AddOpts(opts ...Color) *RGBStyle { } // Print print message -func (s *RGBStyle) Print(a ...interface{}) { +func (s *RGBStyle) Print(a ...any) { doPrintV2(s.String(), fmt.Sprint(a...)) } // Printf format and print message -func (s *RGBStyle) Printf(format string, a ...interface{}) { +func (s *RGBStyle) Printf(format string, a ...any) { doPrintV2(s.String(), fmt.Sprintf(format, a...)) } // Println print message with newline -func (s *RGBStyle) Println(a ...interface{}) { +func (s *RGBStyle) Println(a ...any) { doPrintlnV2(s.String(), a) } // Sprint returns rendered message -func (s *RGBStyle) Sprint(a ...interface{}) string { +func (s *RGBStyle) Sprint(a ...any) string { return RenderCode(s.String(), a...) } // Sprintf returns format and rendered message -func (s *RGBStyle) Sprintf(format string, a ...interface{}) string { +func (s *RGBStyle) Sprintf(format string, a ...any) string { return RenderString(s.String(), fmt.Sprintf(format, a...)) } diff --git a/vendor/github.com/gookit/color/color_tag.go b/vendor/github.com/gookit/color/color_tag.go index 4f6fed938..1d2b9d3fe 100644 --- a/vendor/github.com/gookit/color/color_tag.go +++ b/vendor/github.com/gookit/color/color_tag.go @@ -41,7 +41,8 @@ var ( // There are internal defined fg color tags // // Usage: -// content text +// +// content text // // @notice 加 0 在前面是为了防止之前的影响到现在的设置 var colorTags = map[string]string{ @@ -324,15 +325,17 @@ func (tp *TagParser) ParseByEnv(str string) string { return tp.Parse(str) } -// Parse parse given string, replace color tag and return rendered string +// Parse given string, replace color tag and return rendered string // // Use built in tags: -// CONTENT -// // e.g: `message` +// +// CONTENT +// // e.g: `message` // // Custom tag attributes: -// `CONTENT` -// // e.g: `wel` +// +// `CONTENT` +// // e.g: `wel` func (tp *TagParser) Parse(str string) string { // not contains color tag if !strings.Contains(str, "") { @@ -376,26 +379,30 @@ func ReplaceTag(str string) string { // ParseCodeFromAttr parse color attributes. // // attr format: -// // VALUE please see var: FgColors, BgColors, AllOptions -// "fg=VALUE;bg=VALUE;op=VALUE" +// +// // VALUE please see var: FgColors, BgColors, AllOptions +// "fg=VALUE;bg=VALUE;op=VALUE" // // 16 color: -// "fg=yellow" -// "bg=red" -// "op=bold,underscore" // option is allow multi value -// "fg=white;bg=blue;op=bold" -// "fg=white;op=bold,underscore" +// +// "fg=yellow" +// "bg=red" +// "op=bold,underscore" // option is allow multi value +// "fg=white;bg=blue;op=bold" +// "fg=white;op=bold,underscore" // // 256 color: +// // "fg=167" // "fg=167;bg=23" // "fg=167;bg=23;op=bold" // // True color: -// // hex +// +// // hex // "fg=fc1cac" // "fg=fc1cac;bg=c2c3c4" -// // r,g,b +// // r,g,b // "fg=23,45,214" // "fg=23,45,214;bg=109,99,88" func ParseCodeFromAttr(attr string) (code string) { @@ -476,12 +483,10 @@ func ClearTag(s string) string { *************************************************************/ // GetTagCode get color code by tag name -func GetTagCode(name string) string { - return colorTags[name] -} +func GetTagCode(name string) string { return colorTags[name] } // ApplyTag for messages -func ApplyTag(tag string, a ...interface{}) string { +func ApplyTag(tag string, a ...any) string { return RenderCode(GetTagCode(tag), a...) } @@ -510,11 +515,12 @@ func IsDefinedTag(name string) bool { // Tag value is a defined style name // Usage: -// Tag("info").Println("message") +// +// Tag("info").Println("message") type Tag string // Print messages -func (tg Tag) Print(a ...interface{}) { +func (tg Tag) Print(a ...any) { name := string(tg) str := fmt.Sprint(a...) @@ -526,7 +532,7 @@ func (tg Tag) Print(a ...interface{}) { } // Printf format and print messages -func (tg Tag) Printf(format string, a ...interface{}) { +func (tg Tag) Printf(format string, a ...any) { name := string(tg) str := fmt.Sprintf(format, a...) @@ -538,7 +544,7 @@ func (tg Tag) Printf(format string, a ...interface{}) { } // Println messages line -func (tg Tag) Println(a ...interface{}) { +func (tg Tag) Println(a ...any) { name := string(tg) if stl := GetStyle(name); !stl.IsEmpty() { stl.Println(a...) @@ -548,12 +554,12 @@ func (tg Tag) Println(a ...interface{}) { } // Sprint render messages -func (tg Tag) Sprint(a ...interface{}) string { +func (tg Tag) Sprint(a ...any) string { return RenderCode(GetTagCode(string(tg)), a...) } // Sprintf format and render messages -func (tg Tag) Sprintf(format string, a ...interface{}) string { +func (tg Tag) Sprintf(format string, a ...any) string { tag := string(tg) str := fmt.Sprintf(format, a...) diff --git a/vendor/github.com/gookit/color/printer.go b/vendor/github.com/gookit/color/printer.go index 326aabc0b..985a0b624 100644 --- a/vendor/github.com/gookit/color/printer.go +++ b/vendor/github.com/gookit/color/printer.go @@ -9,18 +9,19 @@ import "fmt" // PrinterFace interface type PrinterFace interface { fmt.Stringer - Sprint(a ...interface{}) string - Sprintf(format string, a ...interface{}) string - Print(a ...interface{}) - Printf(format string, a ...interface{}) - Println(a ...interface{}) + Sprint(a ...any) string + Sprintf(format string, a ...any) string + Print(a ...any) + Printf(format string, a ...any) + Println(a ...any) } // Printer a generic color message printer. // // Usage: -// p := &Printer{Code: "32;45;3"} -// p.Print("message") +// +// p := &Printer{Code: "32;45;3"} +// p.Print("message") type Printer struct { // NoColor disable color. NoColor bool @@ -40,27 +41,27 @@ func (p *Printer) String() string { } // Sprint returns rendering colored messages -func (p *Printer) Sprint(a ...interface{}) string { +func (p *Printer) Sprint(a ...any) string { return RenderCode(p.String(), a...) } // Sprintf returns format and rendering colored messages -func (p *Printer) Sprintf(format string, a ...interface{}) string { +func (p *Printer) Sprintf(format string, a ...any) string { return RenderString(p.String(), fmt.Sprintf(format, a...)) } // Print rendering colored messages -func (p *Printer) Print(a ...interface{}) { +func (p *Printer) Print(a ...any) { doPrintV2(p.String(), fmt.Sprint(a...)) } // Printf format and rendering colored messages -func (p *Printer) Printf(format string, a ...interface{}) { +func (p *Printer) Printf(format string, a ...any) { doPrintV2(p.String(), fmt.Sprintf(format, a...)) } // Println rendering colored messages with newline -func (p *Printer) Println(a ...interface{}) { +func (p *Printer) Println(a ...any) { doPrintlnV2(p.Code, a) } @@ -77,46 +78,56 @@ func (p *Printer) IsEmpty() bool { type SimplePrinter struct{} // Print message -func (s *SimplePrinter) Print(v ...interface{}) { +func (s *SimplePrinter) Print(v ...any) { Print(v...) } // Printf message -func (s *SimplePrinter) Printf(format string, v ...interface{}) { +func (s *SimplePrinter) Printf(format string, v ...any) { Printf(format, v...) } // Println message -func (s *SimplePrinter) Println(v ...interface{}) { +func (s *SimplePrinter) Println(v ...any) { Println(v...) } +// Successf message +func (s *SimplePrinter) Successf(format string, a ...any) { + Success.Printf(format, a...) +} + +// Successln message +func (s *SimplePrinter) Successln(a ...any) { + Success.Println(a...) +} + // Infof message -func (s *SimplePrinter) Infof(format string, a ...interface{}) { +func (s *SimplePrinter) Infof(format string, a ...any) { Info.Printf(format, a...) } // Infoln message -func (s *SimplePrinter) Infoln(a ...interface{}) { +func (s *SimplePrinter) Infoln(a ...any) { Info.Println(a...) } // Warnf message -func (s *SimplePrinter) Warnf(format string, a ...interface{}) { +func (s *SimplePrinter) Warnf(format string, a ...any) { Warn.Printf(format, a...) } // Warnln message -func (s *SimplePrinter) Warnln(a ...interface{}) { +func (s *SimplePrinter) Warnln(a ...any) { Warn.Println(a...) } // Errorf message -func (s *SimplePrinter) Errorf(format string, a ...interface{}) { +func (s *SimplePrinter) Errorf(format string, a ...any) { Error.Printf(format, a...) } // Errorln message -func (s *SimplePrinter) Errorln(a ...interface{}) { +func (s *SimplePrinter) Errorln(a ...any) { Error.Println(a...) } diff --git a/vendor/github.com/gookit/color/quickstart.go b/vendor/github.com/gookit/color/quickstart.go index 4dbd1a431..b368b8a14 100644 --- a/vendor/github.com/gookit/color/quickstart.go +++ b/vendor/github.com/gookit/color/quickstart.go @@ -5,104 +5,104 @@ package color *************************************************************/ // Redp print message with Red color -func Redp(a ...interface{}) { Red.Print(a...) } +func Redp(a ...any) { Red.Print(a...) } // Redf print message with Red color -func Redf(format string, a ...interface{}) { Red.Printf(format, a...) } +func Redf(format string, a ...any) { Red.Printf(format, a...) } // Redln print message line with Red color -func Redln(a ...interface{}) { Red.Println(a...) } +func Redln(a ...any) { Red.Println(a...) } // Bluep print message with Blue color -func Bluep(a ...interface{}) { Blue.Print(a...) } +func Bluep(a ...any) { Blue.Print(a...) } // Bluef print message with Blue color -func Bluef(format string, a ...interface{}) { Blue.Printf(format, a...) } +func Bluef(format string, a ...any) { Blue.Printf(format, a...) } // Blueln print message line with Blue color -func Blueln(a ...interface{}) { Blue.Println(a...) } +func Blueln(a ...any) { Blue.Println(a...) } // Cyanp print message with Cyan color -func Cyanp(a ...interface{}) { Cyan.Print(a...) } +func Cyanp(a ...any) { Cyan.Print(a...) } // Cyanf print message with Cyan color -func Cyanf(format string, a ...interface{}) { Cyan.Printf(format, a...) } +func Cyanf(format string, a ...any) { Cyan.Printf(format, a...) } // Cyanln print message line with Cyan color -func Cyanln(a ...interface{}) { Cyan.Println(a...) } +func Cyanln(a ...any) { Cyan.Println(a...) } // Grayp print message with Gray color -func Grayp(a ...interface{}) { Gray.Print(a...) } +func Grayp(a ...any) { Gray.Print(a...) } // Grayf print message with Gray color -func Grayf(format string, a ...interface{}) { Gray.Printf(format, a...) } +func Grayf(format string, a ...any) { Gray.Printf(format, a...) } // Grayln print message line with Gray color -func Grayln(a ...interface{}) { Gray.Println(a...) } +func Grayln(a ...any) { Gray.Println(a...) } // Greenp print message with Green color -func Greenp(a ...interface{}) { Green.Print(a...) } +func Greenp(a ...any) { Green.Print(a...) } // Greenf print message with Green color -func Greenf(format string, a ...interface{}) { Green.Printf(format, a...) } +func Greenf(format string, a ...any) { Green.Printf(format, a...) } // Greenln print message line with Green color -func Greenln(a ...interface{}) { Green.Println(a...) } +func Greenln(a ...any) { Green.Println(a...) } // Yellowp print message with Yellow color -func Yellowp(a ...interface{}) { Yellow.Print(a...) } +func Yellowp(a ...any) { Yellow.Print(a...) } // Yellowf print message with Yellow color -func Yellowf(format string, a ...interface{}) { Yellow.Printf(format, a...) } +func Yellowf(format string, a ...any) { Yellow.Printf(format, a...) } // Yellowln print message line with Yellow color -func Yellowln(a ...interface{}) { Yellow.Println(a...) } +func Yellowln(a ...any) { Yellow.Println(a...) } // Magentap print message with Magenta color -func Magentap(a ...interface{}) { Magenta.Print(a...) } +func Magentap(a ...any) { Magenta.Print(a...) } // Magentaf print message with Magenta color -func Magentaf(format string, a ...interface{}) { Magenta.Printf(format, a...) } +func Magentaf(format string, a ...any) { Magenta.Printf(format, a...) } // Magentaln print message line with Magenta color -func Magentaln(a ...interface{}) { Magenta.Println(a...) } +func Magentaln(a ...any) { Magenta.Println(a...) } /************************************************************* * quick use style print message *************************************************************/ // Infop print message with Info color -func Infop(a ...interface{}) { Info.Print(a...) } +func Infop(a ...any) { Info.Print(a...) } // Infof print message with Info style -func Infof(format string, a ...interface{}) { Info.Printf(format, a...) } +func Infof(format string, a ...any) { Info.Printf(format, a...) } // Infoln print message with Info style -func Infoln(a ...interface{}) { Info.Println(a...) } +func Infoln(a ...any) { Info.Println(a...) } // Successp print message with success color -func Successp(a ...interface{}) { Success.Print(a...) } +func Successp(a ...any) { Success.Print(a...) } // Successf print message with success style -func Successf(format string, a ...interface{}) { Success.Printf(format, a...) } +func Successf(format string, a ...any) { Success.Printf(format, a...) } // Successln print message with success style -func Successln(a ...interface{}) { Success.Println(a...) } +func Successln(a ...any) { Success.Println(a...) } // Errorp print message with Error color -func Errorp(a ...interface{}) { Error.Print(a...) } +func Errorp(a ...any) { Error.Print(a...) } // Errorf print message with Error style -func Errorf(format string, a ...interface{}) { Error.Printf(format, a...) } +func Errorf(format string, a ...any) { Error.Printf(format, a...) } // Errorln print message with Error style -func Errorln(a ...interface{}) { Error.Println(a...) } +func Errorln(a ...any) { Error.Println(a...) } // Warnp print message with Warn color -func Warnp(a ...interface{}) { Warn.Print(a...) } +func Warnp(a ...any) { Warn.Print(a...) } // Warnf print message with Warn style -func Warnf(format string, a ...interface{}) { Warn.Printf(format, a...) } +func Warnf(format string, a ...any) { Warn.Printf(format, a...) } // Warnln print message with Warn style -func Warnln(a ...interface{}) { Warn.Println(a...) } +func Warnln(a ...any) { Warn.Println(a...) } diff --git a/vendor/github.com/gookit/color/style.go b/vendor/github.com/gookit/color/style.go index fad76fb33..a009d1d6e 100644 --- a/vendor/github.com/gookit/color/style.go +++ b/vendor/github.com/gookit/color/style.go @@ -12,12 +12,14 @@ import ( // Style a 16 color style. can add: fg color, bg color, color options // // Example: -// color.Style{color.FgGreen}.Print("message") +// +// color.Style{color.FgGreen}.Print("message") type Style []Color // New create a custom style // // Usage: +// // color.New(color.FgGreen).Print("message") // equals to: // color.Style{color.FgGreen}.Print("message") @@ -37,43 +39,45 @@ func (s *Style) Add(cs ...Color) { // Render render text // Usage: -// color.New(color.FgGreen).Render("text") -// color.New(color.FgGreen, color.BgBlack, color.OpBold).Render("text") -func (s Style) Render(a ...interface{}) string { +// +// color.New(color.FgGreen).Render("text") +// color.New(color.FgGreen, color.BgBlack, color.OpBold).Render("text") +func (s Style) Render(a ...any) string { return RenderCode(s.String(), a...) } // Renderln render text line. // like Println, will add spaces for each argument // Usage: -// color.New(color.FgGreen).Renderln("text", "more") -// color.New(color.FgGreen, color.BgBlack, color.OpBold).Render("text", "more") -func (s Style) Renderln(a ...interface{}) string { +// +// color.New(color.FgGreen).Renderln("text", "more") +// color.New(color.FgGreen, color.BgBlack, color.OpBold).Render("text", "more") +func (s Style) Renderln(a ...any) string { return RenderWithSpaces(s.String(), a...) } // Sprint is alias of the 'Render' -func (s Style) Sprint(a ...interface{}) string { +func (s Style) Sprint(a ...any) string { return RenderCode(s.String(), a...) } // Sprintf format and render message. -func (s Style) Sprintf(format string, a ...interface{}) string { +func (s Style) Sprintf(format string, a ...any) string { return RenderString(s.String(), fmt.Sprintf(format, a...)) } // Print render and Print text -func (s Style) Print(a ...interface{}) { +func (s Style) Print(a ...any) { doPrintV2(s.String(), fmt.Sprint(a...)) } // Printf render and print text -func (s Style) Printf(format string, a ...interface{}) { +func (s Style) Printf(format string, a ...any) { doPrintV2(s.Code(), fmt.Sprintf(format, a...)) } // Println render and print text line -func (s Style) Println(a ...interface{}) { +func (s Style) Println(a ...any) { doPrintlnV2(s.String(), a) } @@ -115,20 +119,20 @@ func (t *Theme) Save() { } // Tips use name as title, only apply style for name -func (t *Theme) Tips(format string, a ...interface{}) { +func (t *Theme) Tips(format string, a ...any) { // only apply style for name t.Print(strings.ToUpper(t.Name) + ": ") Printf(format+"\n", a...) } // Prompt use name as title, and apply style for message -func (t *Theme) Prompt(format string, a ...interface{}) { +func (t *Theme) Prompt(format string, a ...any) { title := strings.ToUpper(t.Name) + ":" t.Println(title, fmt.Sprintf(format, a...)) } // Block like Prompt, but will wrap a empty line -func (t *Theme) Block(format string, a ...interface{}) { +func (t *Theme) Block(format string, a ...any) { title := strings.ToUpper(t.Name) + ":\n" t.Println(title, fmt.Sprintf(format, a...)) @@ -140,10 +144,11 @@ func (t *Theme) Block(format string, a ...interface{}) { // internal themes(like bootstrap style) // Usage: -// color.Info.Print("message") -// color.Info.Printf("a %s message", "test") -// color.Warn.Println("message") -// color.Error.Println("message") +// +// color.Info.Print("message") +// color.Info.Printf("a %s message", "test") +// color.Warn.Println("message") +// color.Error.Println("message") var ( // Info color style Info = &Theme{"info", Style{OpReset, FgGreen}} @@ -175,7 +180,8 @@ var ( // Themes internal defined themes. // Usage: -// color.Themes["info"].Println("message") +// +// color.Themes["info"].Println("message") var Themes = map[string]*Theme{ "info": Info, "note": Note, @@ -211,7 +217,8 @@ func GetTheme(name string) *Theme { // Styles internal defined styles, like bootstrap styles. // Usage: -// color.Styles["info"].Println("message") +// +// color.Styles["info"].Println("message") var Styles = map[string]Style{ "info": {OpReset, FgGreen}, "note": {OpBold, FgLightCyan}, @@ -285,31 +292,31 @@ func (s *Scheme) Style(name string) Style { } // Infof message print -func (s *Scheme) Infof(format string, a ...interface{}) { +func (s *Scheme) Infof(format string, a ...any) { s.Styles["info"].Printf(format, a...) } // Infoln message print -func (s *Scheme) Infoln(v ...interface{}) { +func (s *Scheme) Infoln(v ...any) { s.Styles["info"].Println(v...) } // Warnf message print -func (s *Scheme) Warnf(format string, a ...interface{}) { +func (s *Scheme) Warnf(format string, a ...any) { s.Styles["warn"].Printf(format, a...) } // Warnln message print -func (s *Scheme) Warnln(v ...interface{}) { +func (s *Scheme) Warnln(v ...any) { s.Styles["warn"].Println(v...) } // Errorf message print -func (s *Scheme) Errorf(format string, a ...interface{}) { +func (s *Scheme) Errorf(format string, a ...any) { s.Styles["error"].Printf(format, a...) } // Errorln message print -func (s *Scheme) Errorln(v ...interface{}) { +func (s *Scheme) Errorln(v ...any) { s.Styles["error"].Println(v...) } diff --git a/vendor/github.com/gookit/color/utils.go b/vendor/github.com/gookit/color/utils.go index 4554b27ee..b6920f6dc 100644 --- a/vendor/github.com/gookit/color/utils.go +++ b/vendor/github.com/gookit/color/utils.go @@ -32,39 +32,31 @@ func ResetTerminal() error { *************************************************************/ // Print render color tag and print messages -func Print(a ...interface{}) { +func Print(a ...any) { Fprint(output, a...) } // Printf format and print messages -func Printf(format string, a ...interface{}) { +func Printf(format string, a ...any) { Fprintf(output, format, a...) } // Println messages with new line -func Println(a ...interface{}) { +func Println(a ...any) { Fprintln(output, a...) } // Fprint print rendered messages to writer // // Notice: will ignore print error -func Fprint(w io.Writer, a ...interface{}) { +func Fprint(w io.Writer, a ...any) { _, err := fmt.Fprint(w, Render(a...)) saveInternalError(err) - - // if isLikeInCmd { - // renderColorCodeOnCmd(func() { - // _, _ = fmt.Fprint(w, Render(a...)) - // }) - // } else { - // _, _ = fmt.Fprint(w, Render(a...)) - // } } // Fprintf print format and rendered messages to writer. // Notice: will ignore print error -func Fprintf(w io.Writer, format string, a ...interface{}) { +func Fprintf(w io.Writer, format string, a ...any) { str := fmt.Sprintf(format, a...) _, err := fmt.Fprint(w, ReplaceTag(str)) saveInternalError(err) @@ -72,7 +64,7 @@ func Fprintf(w io.Writer, format string, a ...interface{}) { // Fprintln print rendered messages line to writer // Notice: will ignore print error -func Fprintln(w io.Writer, a ...interface{}) { +func Fprintln(w io.Writer, a ...any) { str := formatArgsForPrintln(a) _, err := fmt.Fprintln(w, ReplaceTag(str)) saveInternalError(err) @@ -80,7 +72,7 @@ func Fprintln(w io.Writer, a ...interface{}) { // Lprint passes colored messages to a log.Logger for printing. // Notice: should be goroutine safe -func Lprint(l *log.Logger, a ...interface{}) { +func Lprint(l *log.Logger, a ...any) { l.Print(Render(a...)) } @@ -90,7 +82,7 @@ func Lprint(l *log.Logger, a ...interface{}) { // // text := Render("hello world!") // fmt.Println(text) -func Render(a ...interface{}) string { +func Render(a ...any) string { if len(a) == 0 { return "" } @@ -98,28 +90,23 @@ func Render(a ...interface{}) string { } // Sprint parse color tags, return rendered string -func Sprint(a ...interface{}) string { +func Sprint(a ...any) string { if len(a) == 0 { return "" } - return ReplaceTag(fmt.Sprint(a...)) } // Sprintf format and return rendered string -func Sprintf(format string, a ...interface{}) string { +func Sprintf(format string, a ...any) string { return ReplaceTag(fmt.Sprintf(format, a...)) } // String alias of the ReplaceTag -func String(s string) string { - return ReplaceTag(s) -} +func String(s string) string { return ReplaceTag(s) } // Text alias of the ReplaceTag -func Text(s string) string { - return ReplaceTag(s) -} +func Text(s string) string { return ReplaceTag(s) } // Uint8sToInts convert []uint8 to []int // func Uint8sToInts(u8s []uint8 ) []int { @@ -138,25 +125,17 @@ func Text(s string) string { func doPrintV2(code, str string) { _, err := fmt.Fprint(output, RenderString(code, str)) saveInternalError(err) - - // if isLikeInCmd { - // renderColorCodeOnCmd(func() { - // _, _ = fmt.Fprint(output, RenderString(code, str)) - // }) - // } else { - // _, _ = fmt.Fprint(output, RenderString(code, str)) - // } } // new implementation, support render full color code on pwsh.exe, cmd.exe -func doPrintlnV2(code string, args []interface{}) { +func doPrintlnV2(code string, args []any) { str := formatArgsForPrintln(args) _, err := fmt.Fprintln(output, RenderString(code, str)) saveInternalError(err) } // use Println, will add spaces for each arg -func formatArgsForPrintln(args []interface{}) (message string) { +func formatArgsForPrintln(args []any) (message string) { if ln := len(args); ln == 0 { message = "" } else if ln == 1 { @@ -178,7 +157,7 @@ func formatArgsForPrintln(args []interface{}) (message string) { // return debugMode == "on" // } -func debugf(f string, v ...interface{}) { +func debugf(f string, v ...any) { if debugMode { fmt.Print("COLOR_DEBUG: ") fmt.Printf(f, v...) diff --git a/vendor/github.com/gookit/config/v2/README.md b/vendor/github.com/gookit/config/v2/README.md index e011b3356..908385d62 100644 --- a/vendor/github.com/gookit/config/v2/README.md +++ b/vendor/github.com/gookit/config/v2/README.md @@ -84,7 +84,7 @@ package main import ( "github.com/gookit/config/v2" - "github.com/gookit/config/v2/yamlv3" + "github.com/gookit/config/v2/yaml" ) // go run ./examples/yaml.go @@ -92,7 +92,7 @@ func main() { config.WithOptions(config.ParseEnv) // add driver for support yaml content - config.AddDriver(yamlv3.Driver) + config.AddDriver(yaml.Driver) err := config.LoadFiles("testdata/yml_base.yml") if err != nil { @@ -389,7 +389,7 @@ Support parse default value by struct tag `default` c := config.New("test").WithOptions(config.ParseDefault) // only set name -c.SetData(map[string]interface{}{ +c.SetData(map[string]any{ "name": "inhere", }) @@ -421,7 +421,7 @@ dump.Println(user) ### Load Config - `LoadOSEnvs(nameToKeyMap map[string]string)` Load data from os ENV -- `LoadData(dataSource ...interface{}) (err error)` Load from struts or maps +- `LoadData(dataSource ...any) (err error)` Load from struts or maps - `LoadFlags(keys []string) (err error)` Load from CLI flags - `LoadExists(sourceFiles ...string) (err error)` - `LoadFiles(sourceFiles ...string) (err error)` @@ -445,7 +445,7 @@ dump.Println(user) - `Strings(key string) (arr []string)` - `SubDataMap(key string) maputi.Data` - `StringMap(key string) (mp map[string]string)` -- `Get(key string, findByPath ...bool) (value interface{})` +- `Get(key string, findByPath ...bool) (value any)` **Mapping data to struct:** @@ -455,14 +455,14 @@ dump.Println(user) ### Setting Values -- `Set(key string, val interface{}, setByPath ...bool) (err error)` +- `Set(key string, val any, setByPath ...bool) (err error)` ### Useful Methods - `Getenv(name string, defVal ...string) (val string)` - `AddDriver(driver Driver)` -- `Data() map[string]interface{}` -- `SetData(data map[string]interface{})` set data to override the Config.Data +- `Data() map[string]any` +- `SetData(data map[string]any)` set data to override the Config.Data - `Exists(key string, findByPath ...bool) bool` - `DumpTo(out io.Writer, format string) (n int64, err error)` @@ -497,11 +497,18 @@ Check out these projects, which use https://github.com/gookit/config : ## See also -- Ini parse [gookit/ini/parser](https://github.com/gookit/ini/tree/master/parser) -- Properties parse [gookit/properties](https://github.com/gookit/properties) -- Json5 parse [json5](https://github.com/yosuke-furukawa/json5) -- Yaml parse [go-yaml](https://github.com/go-yaml/yaml) -- Toml parse [go toml](https://github.com/BurntSushi/toml) +- Ini parser [gookit/ini/parser](https://github.com/gookit/ini/tree/master/parser) +- Properties parser [gookit/properties](https://github.com/gookit/properties) +- Json5 parser + - [yosuke-furukawa/json5](https://github.com/yosuke-furukawa/json5) + - [titanous/json5](https://github.com/titanous/json5) +- Json parser + - [goccy/go-json](https://github.com/goccy/go-json) + - [json-iterator/go](https://github.com/json-iterator/go) +- Yaml parser + - [goccy/go-yaml](https://github.com/goccy/go-yaml) + - [go-yaml/yaml](https://github.com/go-yaml/yaml) +- Toml parser [go toml](https://github.com/BurntSushi/toml) - Data merge [mergo](https://github.com/imdario/mergo) - Map structure [mapstructure](https://github.com/mitchellh/mapstructure) diff --git a/vendor/github.com/gookit/config/v2/README.zh-CN.md b/vendor/github.com/gookit/config/v2/README.zh-CN.md index 17b8a6aac..077e8046d 100644 --- a/vendor/github.com/gookit/config/v2/README.zh-CN.md +++ b/vendor/github.com/gookit/config/v2/README.zh-CN.md @@ -83,7 +83,7 @@ package main import ( "github.com/gookit/config/v2" - "github.com/gookit/config/v2/yamlv3" + "github.com/gookit/config/v2/yaml" ) // go run ./examples/yaml.go @@ -92,7 +92,7 @@ func main() { config.WithOptions(config.ParseEnv) // 添加驱动程序以支持yaml内容解析(除了JSON是默认支持,其他的则是按需使用) - config.AddDriver(yamlv3.Driver) + config.AddDriver(yaml.Driver) // 加载配置,可以同时传入多个文件 err := config.LoadFiles("testdata/yml_base.yml") @@ -371,7 +371,7 @@ NEW: 支持通过结构标签 `default` 解析并设置默认值 c := config.New("test").WithOptions(config.ParseDefault) // only set name - c.SetData(map[string]interface{}{ + c.SetData(map[string]any{ "name": "inhere", }) @@ -402,7 +402,7 @@ NEW: 支持通过结构标签 `default` 解析并设置默认值 ### 载入配置 -- `LoadData(dataSource ...interface{}) (err error)` 从struct或map加载数据 +- `LoadData(dataSource ...any) (err error)` 从struct或map加载数据 - `LoadFlags(keys []string) (err error)` 从命令行参数载入数据 - `LoadOSEnvs(nameToKeyMap map[string]string)` 从ENV载入数据 - `LoadExists(sourceFiles ...string) (err error)` 从存在的配置文件里加载数据,会忽略不存在的文件 @@ -427,25 +427,25 @@ NEW: 支持通过结构标签 `default` 解析并设置默认值 - `Strings(key string) (arr []string)` - `StringMap(key string) (mp map[string]string)` - `SubDataMap(key string) maputi.Data` -- `Get(key string, findByPath ...bool) (value interface{})` +- `Get(key string, findByPath ...bool) (value any)` **将数据映射到结构体:** -- `BindStruct(key string, dst interface{}) error` -- `MapOnExists(key string, dst interface{}) error` +- `BindStruct(key string, dst any) error` +- `MapOnExists(key string, dst any) error` ### 设置值 -- `Set(key string, val interface{}, setByPath ...bool) (err error)` +- `Set(key string, val any, setByPath ...bool) (err error)` ### 有用的方法 - `Getenv(name string, defVal ...string) (val string)` - `AddDriver(driver Driver)` -- `Data() map[string]interface{}` +- `Data() map[string]any` - `Exists(key string, findByPath ...bool) bool` - `DumpTo(out io.Writer, format string) (n int64, err error)` -- `SetData(data map[string]interface{})` 设置数据以覆盖 `Config.Data` +- `SetData(data map[string]any)` 设置数据以覆盖 `Config.Data` ## 单元测试 @@ -484,6 +484,9 @@ go test -cover ./... - Toml 解析 [go toml](https://github.com/BurntSushi/toml) - 数据合并 [mergo](https://github.com/imdario/mergo) - 映射数据到结构体 [mapstructure](https://github.com/mitchellh/mapstructure) +- JSON5 解析 + - [yosuke-furukawa/json5](https://github.com/yosuke-furukawa/json5) + - [titanous/json5](https://github.com/titanous/json5) ## License diff --git a/vendor/github.com/gookit/config/v2/any.go b/vendor/github.com/gookit/config/v2/any.go deleted file mode 100644 index 37237165c..000000000 --- a/vendor/github.com/gookit/config/v2/any.go +++ /dev/null @@ -1,6 +0,0 @@ -package config - -// alias of interface{} -// -// TIP: cannot add `go:build !go1.18` in file head, that require the go.mod set `go 1.18` -type any = interface{} diff --git a/vendor/github.com/gookit/config/v2/config.go b/vendor/github.com/gookit/config/v2/config.go index ea7544857..4c708c93a 100644 --- a/vendor/github.com/gookit/config/v2/config.go +++ b/vendor/github.com/gookit/config/v2/config.go @@ -90,13 +90,15 @@ type Config struct { loadedUrls []string loadedFiles []string driverNames []string - reloading bool + // driver alias to name map. + aliasMap map[string]string + reloading bool // TODO Deprecated decoder and encoder, use driver instead // drivers map[string]Driver - // decoders["toml"] = func(blob []byte, v interface{}) (err error){} - // decoders["yaml"] = func(blob []byte, v interface{}) (err error){} + // decoders["toml"] = func(blob []byte, v any) (err error){} + // decoders["yaml"] = func(blob []byte, v any) (err error){} decoders map[string]Decoder encoders map[string]Encoder @@ -109,17 +111,9 @@ type Config struct { sMapCache map[string]strMap } -// New config instance -func New(name string) *Config { - return &Config{ - name: name, - opts: newDefaultOption(), - data: make(map[string]any), - - // default add JSON driver - encoders: map[string]Encoder{JSON: JSONEncoder}, - decoders: map[string]Decoder{JSON: JSONDecoder}, - } +// New config instance, default add JSON driver +func New(name string, opts ...OptionFn) *Config { + return NewEmpty(name).WithDriver(JSONDriver).WithOptions(opts...) } // NewEmpty config instance @@ -132,6 +126,7 @@ func NewEmpty(name string) *Config { // don't add any drivers encoders: map[string]Encoder{}, decoders: map[string]Decoder{}, + aliasMap: make(map[string]string), } } @@ -140,15 +135,13 @@ func NewWith(name string, fn func(c *Config)) *Config { return New(name).With(fn) } -// NewWithOptions config instance -func NewWithOptions(name string, opts ...func(opts *Options)) *Config { +// NewWithOptions config instance. alias of New() +func NewWithOptions(name string, opts ...OptionFn) *Config { return New(name).WithOptions(opts...) } // Default get the default instance -func Default() *Config { - return dc -} +func Default() *Config { return dc } /************************************************************* * config drivers @@ -171,6 +164,11 @@ func AddDriver(driver Driver) { dc.AddDriver(driver) } // AddDriver set a decoder and encoder driver for a format. func (c *Config) AddDriver(driver Driver) { format := driver.Name() + if len(driver.Aliases()) > 0 { + for _, alias := range driver.Aliases() { + c.aliasMap[alias] = format + } + } c.driverNames = append(c.driverNames, format) c.decoders[format] = driver.GetDecoder() @@ -179,21 +177,21 @@ func (c *Config) AddDriver(driver Driver) { // HasDecoder has decoder func (c *Config) HasDecoder(format string) bool { - format = fixFormat(format) + format = c.resolveFormat(format) _, ok := c.decoders[format] return ok } // HasEncoder has encoder func (c *Config) HasEncoder(format string) bool { - format = fixFormat(format) + format = c.resolveFormat(format) _, ok := c.encoders[format] return ok } // DelDriver delete driver of the format func (c *Config) DelDriver(format string) { - format = fixFormat(format) + format = c.resolveFormat(format) delete(c.decoders, format) delete(c.encoders, format) } @@ -205,6 +203,21 @@ func (c *Config) DelDriver(format string) { // Name get config name func (c *Config) Name() string { return c.name } +// AddAlias add alias for a format(driver name) +func AddAlias(format, alias string) { dc.AddAlias(format, alias) } + +// AddAlias add alias for a format(driver name) +// +// Example: +// +// config.AddAlias("ini", "conf") +func (c *Config) AddAlias(format, alias string) { + c.aliasMap[alias] = format +} + +// AliasMap get alias map +func (c *Config) AliasMap() map[string]string { return c.aliasMap } + // Error get last error, will clear after read. func (c *Config) Error() error { err := c.err @@ -237,8 +250,8 @@ func (c *Config) ClearAll() { c.ClearData() c.ClearCaches() - c.loadedUrls = []string{} - c.loadedFiles = []string{} + c.aliasMap = make(map[string]string) + // options c.opts.Readonly = false } @@ -246,7 +259,7 @@ func (c *Config) ClearAll() { func (c *Config) ClearData() { c.fireHook(OnCleanData) - c.data = make(map[string]interface{}) + c.data = make(map[string]any) c.loadedUrls = []string{} c.loadedFiles = []string{} } diff --git a/vendor/github.com/gookit/config/v2/driver.go b/vendor/github.com/gookit/config/v2/driver.go index 502b7520c..2776b569c 100644 --- a/vendor/github.com/gookit/config/v2/driver.go +++ b/vendor/github.com/gookit/config/v2/driver.go @@ -1,6 +1,5 @@ package config -// default json driver(encoder/decoder) import ( "encoding/json" @@ -11,10 +10,19 @@ import ( // TODO refactor: rename GetDecoder() to Decode(), rename GetEncoder() to Encode() type Driver interface { Name() string + Aliases() []string // alias format names, use for resolve format name GetDecoder() Decoder GetEncoder() Encoder } +// DriverV2 interface. +type DriverV2 interface { + Name() string // driver name, also is format name. + Aliases() []string // alias format names, use for resolve format name + Decode(blob []byte, v any) (err error) + Encode(v any) (out []byte, err error) +} + // Decoder for decode yml,json,toml format content type Decoder func(blob []byte, v any) (err error) @@ -24,6 +32,7 @@ type Encoder func(v any) (out []byte, err error) // StdDriver struct type StdDriver struct { name string + aliases []string decoder Decoder encoder Encoder } @@ -33,9 +42,24 @@ func NewDriver(name string, dec Decoder, enc Encoder) *StdDriver { return &StdDriver{name: name, decoder: dec, encoder: enc} } +// WithAliases set aliases for driver +func (d *StdDriver) WithAliases(aliases ...string) *StdDriver { + d.aliases = aliases + return d +} + +// WithAlias add alias for driver +func (d *StdDriver) WithAlias(alias string) *StdDriver { + d.aliases = append(d.aliases, alias) + return d +} + // Name of driver -func (d *StdDriver) Name() string { - return d.name +func (d *StdDriver) Name() string { return d.name } + +// Aliases format name of driver +func (d *StdDriver) Aliases() []string { + return d.aliases } // Decode of driver @@ -59,13 +83,11 @@ func (d *StdDriver) GetEncoder() Encoder { } /************************************************************* - * json driver + * JSON driver *************************************************************/ var ( // JSONAllowComments support write comments on json file. - // - // Deprecated: please use JSONDriver.ClearComments = true JSONAllowComments = true // JSONMarshalIndent if not empty, will use json.MarshalIndent for encode data. @@ -107,6 +129,11 @@ func (d *jsonDriver) Name() string { return d.driverName } +// Aliases of the driver +func (d *jsonDriver) Aliases() []string { + return nil +} + // Decode for the driver func (d *jsonDriver) Decode(data []byte, v any) error { if d.ClearComments { diff --git a/vendor/github.com/gookit/config/v2/export.go b/vendor/github.com/gookit/config/v2/export.go index 85187dcba..3371d127a 100644 --- a/vendor/github.com/gookit/config/v2/export.go +++ b/vendor/github.com/gookit/config/v2/export.go @@ -55,6 +55,9 @@ func MapOnExists(key string, dst any) error { } // MapOnExists mapping data to the dst structure only on key exists. +// +// - Support ParseEnv on mapping +// - Support ParseDefault on mapping func (c *Config) MapOnExists(key string, dst any) error { err := c.Structure(key, dst) if err != nil && err == ErrNotFound { @@ -66,12 +69,15 @@ func (c *Config) MapOnExists(key string, dst any) error { // Structure get config data and binding to the dst structure. // +// - Support ParseEnv on mapping +// - Support ParseDefault on mapping +// // Usage: // // dbInfo := Db{} // config.Structure("db", &dbInfo) func (c *Config) Structure(key string, dst any) error { - var data interface{} + var data any // binding all data if key == "" { data = c.data @@ -133,7 +139,7 @@ func (c *Config) DumpTo(out io.Writer, format string) (n int64, err error) { var ok bool var encoder Encoder - format = fixFormat(format) + format = c.resolveFormat(format) if encoder, ok = c.encoders[format]; !ok { err = errors.New("not exists/register encoder for the format: " + format) return diff --git a/vendor/github.com/gookit/config/v2/load.go b/vendor/github.com/gookit/config/v2/load.go index 36ef5adc5..38becdf8c 100644 --- a/vendor/github.com/gookit/config/v2/load.go +++ b/vendor/github.com/gookit/config/v2/load.go @@ -4,13 +4,15 @@ import ( "errors" "flag" "fmt" - "io/ioutil" + "io" + "io/fs" "net/http" "os" "path/filepath" "strings" "time" + "github.com/gookit/goutil/errorx" "github.com/gookit/goutil/fsutil" "github.com/imdario/mergo" ) @@ -42,6 +44,10 @@ func LoadExists(sourceFiles ...string) error { return dc.LoadExists(sourceFiles. // LoadExists load and parse config files, but will ignore not exists file. func (c *Config) LoadExists(sourceFiles ...string) (err error) { for _, file := range sourceFiles { + if file == "" { + continue + } + if err = c.loadFile(file, true, ""); err != nil { return } @@ -72,7 +78,7 @@ func (c *Config) LoadRemote(format, url string) (err error) { } // read response content - bts, err := ioutil.ReadAll(resp.Body) + bts, err := io.ReadAll(resp.Body) if err == nil { if err = c.parseSourceCode(format, bts); err != nil { return @@ -191,15 +197,21 @@ func LoadData(dataSource ...any) error { return dc.LoadData(dataSource...) } // // The dataSources can be: // - map[string]any +// - map[string]string func (c *Config) LoadData(dataSources ...any) (err error) { if c.opts.Delimiter == 0 { c.opts.Delimiter = defaultDelimiter } for _, ds := range dataSources { + if smp, ok := ds.(map[string]string); ok { + c.LoadSMap(smp) + continue + } + err = mergo.Merge(&c.data, ds, mergo.WithOverride) if err != nil { - return + return errorx.WithStack(err) } } @@ -207,6 +219,14 @@ func (c *Config) LoadData(dataSources ...any) (err error) { return } +// LoadSMap to config +func (c *Config) LoadSMap(smp map[string]string) { + for k, v := range smp { + c.data[k] = v + } + c.fireHook(OnLoadData) +} + // LoadSources load one or multi byte data func LoadSources(format string, src []byte, more ...[]byte) error { return dc.LoadSources(format, src, more...) @@ -314,9 +334,8 @@ func (c *Config) LoadFromDir(dirPath, format string) (err error) { extName := "." + format extLen := len(extName) - return fsutil.FindInDir(dirPath, func(fPath string, fi os.FileInfo) error { - baseName := fi.Name() - + return fsutil.FindInDir(dirPath, func(fPath string, ent fs.DirEntry) error { + baseName := ent.Name() if strings.HasSuffix(baseName, extName) { data, err := c.parseSourceToMap(format, fsutil.MustReadFile(fPath)) if err != nil { @@ -386,7 +405,7 @@ func (c *Config) loadFile(file string, loadExist bool, format string) (err error defer fd.Close() // read file content - bts, err := ioutil.ReadAll(fd) + bts, err := io.ReadAll(fd) if err == nil { // get format for file ext if format == "" { @@ -431,8 +450,8 @@ func (c *Config) loadDataMap(data map[string]any) (err error) { } // parse config source code to Config. -func (c *Config) parseSourceToMap(format string, blob []byte) (map[string]interface{}, error) { - format = fixFormat(format) +func (c *Config) parseSourceToMap(format string, blob []byte) (map[string]any, error) { + format = c.resolveFormat(format) decode := c.decoders[format] if decode == nil { return nil, errors.New("not register decoder for the format: " + format) diff --git a/vendor/github.com/gookit/config/v2/options.go b/vendor/github.com/gookit/config/v2/options.go index 49916625e..595468f85 100644 --- a/vendor/github.com/gookit/config/v2/options.go +++ b/vendor/github.com/gookit/config/v2/options.go @@ -50,6 +50,9 @@ type Options struct { // WatchChange bool } +// OptionFn option func +type OptionFn func(*Options) + func newDefaultOption() *Options { return &Options{ ParseKey: true, @@ -159,10 +162,10 @@ func WithHookFunc(fn HookFunc) func(*Options) { func EnableCache(opts *Options) { opts.EnableCache = true } // WithOptions with options -func WithOptions(opts ...func(*Options)) { dc.WithOptions(opts...) } +func WithOptions(opts ...OptionFn) { dc.WithOptions(opts...) } // WithOptions apply some options -func (c *Config) WithOptions(opts ...func(opts *Options)) *Config { +func (c *Config) WithOptions(opts ...OptionFn) *Config { if !c.IsEmpty() { panic("config: Cannot set options after data has been loaded") } diff --git a/vendor/github.com/gookit/config/v2/read.go b/vendor/github.com/gookit/config/v2/read.go index 6bb79da97..246a2943c 100644 --- a/vendor/github.com/gookit/config/v2/read.go +++ b/vendor/github.com/gookit/config/v2/read.go @@ -90,32 +90,46 @@ func (c *Config) Exists(key string, findByPath ...bool) (ok bool) { *************************************************************/ // Data return all config data -func Data() map[string]interface{} { return dc.Data() } +func Data() map[string]any { return dc.Data() } -// Data get all config data -func (c *Config) Data() map[string]interface{} { +// Data get all config data. +// +// Note: will don't apply any options, like ParseEnv +func (c *Config) Data() map[string]any { return c.data } +// Keys return all config data +func Keys() []string { return dc.Keys() } + +// Keys get all config data +func (c *Config) Keys() []string { + keys := make([]string, 0, len(c.data)) + for key := range c.data { + keys = append(keys, key) + } + return keys +} + // Get config value by key string, support get sub-value by key path(eg. 'map.key'), // // - ok is true, find value from config // - ok is false, not found or error -func Get(key string, findByPath ...bool) interface{} { return dc.Get(key, findByPath...) } +func Get(key string, findByPath ...bool) any { return dc.Get(key, findByPath...) } // Get config value by key -func (c *Config) Get(key string, findByPath ...bool) interface{} { +func (c *Config) Get(key string, findByPath ...bool) any { val, _ := c.GetValue(key, findByPath...) return val } // GetValue get value by given key string. -func GetValue(key string, findByPath ...bool) (interface{}, bool) { +func GetValue(key string, findByPath ...bool) (any, bool) { return dc.GetValue(key, findByPath...) } // GetValue get value by given key string. -func (c *Config) GetValue(key string, findByPath ...bool) (value interface{}, ok bool) { +func (c *Config) GetValue(key string, findByPath ...bool) (value any, ok bool) { sep := c.opts.Delimiter if key = formatKey(key, string(sep)); key == "" { c.addError(ErrKeyIsEmpty) @@ -235,6 +249,18 @@ func (c *Config) String(key string, defVal ...string) string { return value } +// MustString get a string by key, will panic on empty or not exists +func MustString(key string) string { return dc.MustString(key) } + +// MustString get a string by key, will panic on empty or not exists +func (c *Config) MustString(key string) string { + value, ok := c.getString(key) + if !ok { + panic("config: string value not found, key: " + key) + } + return value +} + func (c *Config) getString(key string) (value string, ok bool) { // find from cache if c.opts.EnableCache && len(c.strCache) > 0 { @@ -257,8 +283,11 @@ func (c *Config) getString(key string) (value string, ok bool) { value = envutil.ParseEnvValue(value) } default: - // value = fmt.Sprintf("%v", val) - value, _ = strutil.AnyToString(val, false) + var err error + value, err = strutil.AnyToString(val, false) + if err != nil { + return "", false + } } // add cache @@ -520,6 +549,17 @@ func (c *Config) Strings(key string) (arr []string) { return } +// StringsBySplit get []string by split a string value. +func StringsBySplit(key, sep string) []string { return dc.StringsBySplit(key, sep) } + +// StringsBySplit get []string by split a string value. +func (c *Config) StringsBySplit(key, sep string) (ss []string) { + if str, ok := c.getString(key); ok { + ss = strutil.Split(str, sep) + } + return +} + // StringMap get config data as a map[string]string func StringMap(key string) map[string]string { return dc.StringMap(key) } diff --git a/vendor/github.com/gookit/config/v2/util.go b/vendor/github.com/gookit/config/v2/util.go index f42789314..bfb597d39 100644 --- a/vendor/github.com/gookit/config/v2/util.go +++ b/vendor/github.com/gookit/config/v2/util.go @@ -13,12 +13,15 @@ import ( // ValDecodeHookFunc returns a mapstructure.DecodeHookFunc // that parse ENV var, and more custom parse func ValDecodeHookFunc(parseEnv, parseTime bool) mapstructure.DecodeHookFunc { - return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + return func(f reflect.Type, t reflect.Type, data any) (any, error) { if f.Kind() != reflect.String { return data, nil } str := data.(string) + if parseEnv { + str = envutil.ParseEnvValue(str) + } if len(str) < 2 { return str, nil } @@ -32,14 +35,19 @@ func ValDecodeHookFunc(parseEnv, parseTime bool) mapstructure.DecodeHookFunc { return dur, nil } } - } else if parseEnv { // parse ENV value - str = envutil.ParseEnvValue(str) } - return str, nil } } +// resolve format, check is alias +func (c *Config) resolveFormat(f string) string { + if name, ok := c.aliasMap[f]; ok { + return name + } + return f +} + /************************************************************* * Deprecated methods *************************************************************/ @@ -55,7 +63,7 @@ func SetDecoder(format string, decoder Decoder) { // // Deprecated: please use driver instead func (c *Config) SetDecoder(format string, decoder Decoder) { - format = fixFormat(format) + format = c.resolveFormat(format) c.decoders[format] = decoder } @@ -79,7 +87,7 @@ func SetEncoder(format string, encoder Encoder) { // // Deprecated: please use driver instead func (c *Config) SetEncoder(format string, encoder Encoder) { - format = fixFormat(format) + format = c.resolveFormat(format) c.encoders[format] = encoder } @@ -141,20 +149,3 @@ func parseVarNameAndType(key string) (string, string) { func formatKey(key, sep string) string { return strings.Trim(strings.TrimSpace(key), sep) } - -// resolve fix inc/conf/yaml format -func fixFormat(f string) string { - if f == Yml { - f = Yaml - } - - if f == "inc" { - f = Ini - } - - // eg nginx config file. - if f == "conf" { - f = Hcl - } - return f -} diff --git a/vendor/github.com/gookit/config/v2/yaml/yaml.go b/vendor/github.com/gookit/config/v2/yaml/yaml.go index 8d630a88c..f479d6acf 100644 --- a/vendor/github.com/gookit/config/v2/yaml/yaml.go +++ b/vendor/github.com/gookit/config/v2/yaml/yaml.go @@ -5,10 +5,9 @@ Usage please see example: */ package yaml -// see https://pkg.go.dev/gopkg.in/yaml.v2 import ( + "github.com/goccy/go-yaml" "github.com/gookit/config/v2" - "gopkg.in/yaml.v2" ) // Decoder the yaml content decoder @@ -18,5 +17,4 @@ var Decoder config.Decoder = yaml.Unmarshal var Encoder config.Encoder = yaml.Marshal // Driver for yaml -// TIP: recommended use the yamlv3.Driver -var Driver = config.NewDriver(config.Yaml, Decoder, Encoder) +var Driver = config.NewDriver(config.Yaml, Decoder, Encoder).WithAliases(config.Yml) diff --git a/vendor/github.com/gookit/goutil/Makefile b/vendor/github.com/gookit/goutil/Makefile index f902a5f18..19b591e34 100644 --- a/vendor/github.com/gookit/goutil/Makefile +++ b/vendor/github.com/gookit/goutil/Makefile @@ -29,7 +29,7 @@ readme: readme-c: ## Generate or update README file and commit change to git readme-c: readme git add README.* internal - git commit -m "doc: update and re-generate README docs" + git commit -m ":memo: doc: update and re-generate README docs" csfix: ## Fix code style for all files by go fmt csfix: diff --git a/vendor/github.com/gookit/goutil/README.md b/vendor/github.com/gookit/goutil/README.md index 565f799ef..3fcc50276 100644 --- a/vendor/github.com/gookit/goutil/README.md +++ b/vendor/github.com/gookit/goutil/README.md @@ -7,7 +7,7 @@ [![Coverage Status](https://coveralls.io/repos/github/gookit/goutil/badge.svg?branch=master)](https://coveralls.io/github/gookit/goutil?branch=master) [![Go Reference](https://pkg.go.dev/badge/github.com/gookit/goutil.svg)](https://pkg.go.dev/github.com/gookit/goutil) -💪 Useful utils package for the Go: int, string, array/slice, map, error, time, format, CLI, ENV, filesystem, system, testing and more. +💪 Useful utils(**600+**) package for the Go: int, string, array/slice, map, error, time, format, CLI, ENV, filesystem, system, testing and more. > **[中文说明](README.zh-CN.md)** @@ -27,7 +27,6 @@ - [`structs`](./structs) Provide some extends util functions for struct. eg: tag parse, struct data init - [`strutil`](./strutil) String util functions. eg: bytes, check, convert, encode, format and more - [`sysutil`](./sysutil) System util functions. eg: sysenv, exec, user, process - - [process](./sysutil/process) Provide some process handle util functions. **Advance packages:** @@ -43,6 +42,7 @@ - sys util: - [clipboard](./sysutil/clipboard) Provide a simple clipboard read and write operations. - [cmdr](./sysutil/cmdr) Provide for quick build and run a cmd, batch run multi cmd tasks + - [process](./sysutil/process) Provide some process handle util functions. - [`testutil`](./testutil) Test help util functions. eg: http test, mock ENV value - [assert](./testutil/assert) Asserts functions for help testing - [`timex`](./timex) Provides an enhanced time.Time implementation. Add more commonly used functional methods @@ -82,54 +82,75 @@ u64Val = goutil.Uint("2") // 2 ## Packages -### Array/Slice +### Array and Slice > Package `github.com/gookit/goutil/arrutil` ```go // source at arrutil/arrutil.go -func Reverse(ss []string) -func StringsRemove(ss []string, s string) []string -func StringsFilter(ss []string, filter ...func(s string) bool) []string -func StringsMap(ss []string, mapFn func(s string) string) []string -func TrimStrings(ss []string, cutSet ...string) []string +func Reverse(ss []string) +func StringsRemove(ss []string, s string) []string +func StringsFilter(ss []string, filter ...func(s string) bool) []string +func StringsMap(ss []string, mapFn func(s string) string) []string +func TrimStrings(ss []string, cutSet ...string) []string +func GetRandomOne[T any](arr []T) T +func RandomOne[T any](arr []T) T +func Unique[T ~string | comdef.XintOrFloat](list []T) []T +func IndexOf[T ~string | comdef.XintOrFloat](val T, list []T) int // source at arrutil/check.go -func IntsHas(ints []int, val int) bool -func Int64sHas(ints []int64, val int64) bool -func InStrings(elem string, ss []string) bool { return StringsHas(ss, elem) } -func StringsHas(ss []string, val string) bool -func HasValue(arr, val any) bool -func Contains(arr, val any) bool -func NotContains(arr, val any) bool +func IntsHas(ints []int, val int) bool +func Int64sHas(ints []int64, val int64) bool +func InStrings(elem string, ss []string) bool +func StringsHas(ss []string, val string) bool +func NotIn[T comdef.ScalarType](value T, list []T) bool +func In[T comdef.ScalarType](value T, list []T) bool +func ContainsAll[T comdef.ScalarType](list, values []T) bool +func IsSubList[T comdef.ScalarType](values, list []T) bool +func IsParent[T comdef.ScalarType](values, list []T) bool +func HasValue(arr, val any) bool +func Contains(arr, val any) bool +func NotContains(arr, val any) bool // source at arrutil/collection.go -func TwowaySearch(data any, item any, fn Comparer) (int, error) -func MakeEmptySlice(itemType reflect.Type) any -func CloneSlice(data any) any -func Excepts(first, second any, fn Comparer) any -func Intersects(first any, second any, fn Comparer) any -func Union(first, second any, fn Comparer) any -func Find(source any, fn Predicate) (any, error) -func FindOrDefault(source any, fn Predicate, defaultValue any) any -func TakeWhile(data any, fn Predicate) any -func ExceptWhile(data any, fn Predicate) any +func TwowaySearch(data any, item any, fn Comparer) (int, error) +func MakeEmptySlice(itemType reflect.Type) any +func CloneSlice(data any) any +func Differences[T any](first, second []T, fn Comparer) []T +func Excepts(first, second any, fn Comparer) any +func Intersects(first any, second any, fn Comparer) any +func Union(first, second any, fn Comparer) any +func Find(source any, fn Predicate) (any, error) +func FindOrDefault(source any, fn Predicate, defaultValue any) any +func TakeWhile(data any, fn Predicate) any +func ExceptWhile(data any, fn Predicate) any +// source at arrutil/collection_gte118.go +func Map[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V +func Column[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V // source at arrutil/convert.go -func JoinStrings(sep string, ss ...string) string -func StringsJoin(sep string, ss ...string) string -func StringsToInts(ss []string) (ints []int, err error) -func MustToStrings(arr any) []string -func StringsToSlice(ss []string) []any -func ToInt64s(arr any) (ret []int64, err error) -func MustToInt64s(arr any) []int64 -func SliceToInt64s(arr []any) []int64 -func ToStrings(arr any) (ret []string, err error) -func SliceToStrings(arr []any) []string -func AnyToString(arr any) string -func SliceToString(arr ...any) string { return ToString(arr) } -func ToString(arr []any) string -func JoinSlice(sep string, arr ...any) string +func JoinStrings(sep string, ss ...string) string +func StringsJoin(sep string, ss ...string) string +func JoinSlice(sep string, arr ...any) string +func ToInt64s(arr any) (ret []int64, err error) +func MustToInt64s(arr any) []int64 +func SliceToInt64s(arr []any) []int64 +func StringsAsInts(ss []string) []int +func StringsToInts(ss []string) (ints []int, err error) +func StringsTryInts(ss []string) (ints []int, err error) +func AnyToSlice(sl any) (ls []any, err error) +func AnyToStrings(arr any) []string +func MustToStrings(arr any) []string +func StringsToSlice(ss []string) []any +func ToStrings(arr any) (ret []string, err error) +func SliceToStrings(arr []any) []string +func QuietStrings(arr []any) []string +func ConvType[T any, R any](arr []T, newElemTyp R) ([]R, error) +func AnyToString(arr any) string +func SliceToString(arr ...any) string +func ToString(arr []any) string +func CombineToMap[K comdef.SortedType, V any](keys []K, values []V) map[K]V +func CombineToSMap(keys, values []string) map[string]string // source at arrutil/format.go -func NewFormatter(arr any) *ArrFormatter -func FormatIndent(arr any, indent string) string +func NewFormatter(arr any) *ArrFormatter +func FormatIndent(arr any, indent string) string ``` #### ArrUtil Usage @@ -153,32 +174,83 @@ ss, err := arrutil.ToStrings([]int{1, 2}) // ss: []string{"1", "2"} ``` +### Basefn + +> Package `github.com/gookit/goutil/basefn` + +```go +// source at basefn/basefunc.go +func Panicf(format string, v ...any) +func MustOK(err error) +func Must[T any](v T, err error) T +func ErrOnFail(cond bool, err error) error +func OrError(cond bool, err error) error +func FirstOr[T any](sl []T, elseVal T) T +func OrValue[T any](cond bool, okVal, elVal T) T +func OrReturn[T any](cond bool, okFn, elseFn func() T) T +func CallOn(cond bool, fn ErrFunc) error +func CallOrElse(cond bool, okFn, elseFn ErrFunc) error +// source at basefn/extfunc.go +func DataSize(size uint64) string +func HowLongAgo(sec int64) string +``` + +### Bytes Utils + +> Package `github.com/gookit/goutil/byteutil` + +```go +// source at byteutil/buffer.go +func NewBuffer() *Buffer +// source at byteutil/byteutil.go +func Random(length int) ([]byte, error) +func FirstLine(bs []byte) []byte +func StrOrErr(bs []byte, err error) (string, error) +func SafeString(bs []byte, err error) string +func String(b []byte) string +func ToString(b []byte) string +func AppendAny(dst []byte, v any) []byte +func Cut(bs []byte, sep byte) (before, after []byte, found bool) +// source at byteutil/bytex.go +func Md5(src any) []byte +// source at byteutil/check.go +func IsNumChar(c byte) bool +// source at byteutil/encoder.go +func NewStdEncoder(encFn func(src []byte) []byte, decFn func(src []byte) ([]byte, error)) *StdEncoder +// source at byteutil/pool.go +func NewChanPool(maxSize int, width int, capWidth int) *ChanPool +``` + ### Cflag > Package `github.com/gookit/goutil/cflag` ```go // source at cflag/app.go -func NewApp(fns ...func(app *App)) *App -func NewCmd(name, desc string) *Cmd +func NewApp(fns ...func(app *App)) *App +func NewCmd(name, desc string) *Cmd // source at cflag/cflag.go -func SetDebug(open bool) -func New(fns ...func(c *CFlags)) *CFlags -func NewEmpty(fns ...func(c *CFlags)) *CFlags -func WithDesc(desc string) func(c *CFlags) -func WithVersion(version string) func(c *CFlags) +func SetDebug(open bool) +func New(fns ...func(c *CFlags)) *CFlags +func NewEmpty(fns ...func(c *CFlags)) *CFlags +func WithDesc(desc string) func(c *CFlags) +func WithVersion(version string) func(c *CFlags) +// source at cflag/ext.go +func NewEnumString(enum ...string) EnumString +func NewKVString() KVString // source at cflag/optarg.go -func NewArg(name, desc string, required bool) *FlagArg +func NewArg(name, desc string, required bool) *FlagArg // source at cflag/util.go -func IsZeroValue(opt *flag.Flag, value string) (bool, bool) -func AddPrefix(name string) string -func AddPrefixes(name string, shorts []string) string -func AddPrefixes2(name string, shorts []string, nameAtEnd bool) string -func SplitShortcut(shortcut string) []string -func FilterNames(names []string) []string -func IsFlagHelpErr(err error) bool -func WrapColorForCode(s string) string -func ReplaceShorts(args []string, shortsMap map[string]string) []string +func IsGoodName(name string) bool +func IsZeroValue(opt *flag.Flag, value string) (bool, bool) +func AddPrefix(name string) string +func AddPrefixes(name string, shorts []string) string +func AddPrefixes2(name string, shorts []string, nameAtEnd bool) string +func SplitShortcut(shortcut string) []string +func FilterNames(names []string) []string +func IsFlagHelpErr(err error) bool +func WrapColorForCode(s string) string +func ReplaceShorts(args []string, shortsMap map[string]string) []string ``` #### `cflag` Usage @@ -191,71 +263,72 @@ func ReplaceShorts(args []string, shortsMap map[string]string) []string ```go // source at cliutil/cliutil.go -func LineBuild(binFile string, args []string) string -func BuildLine(binFile string, args []string) string -func String2OSArgs(line string) []string -func StringToOSArgs(line string) []string -func ParseLine(line string) []string -func QuickExec(cmdLine string, workDir ...string) (string, error) -func ExecLine(cmdLine string, workDir ...string) (string, error) -func ExecCmd(binName string, args []string, workDir ...string) (string, error) -func ExecCommand(binName string, args []string, workDir ...string) (string, error) -func ShellExec(cmdLine string, shells ...string) (string, error) -func CurrentShell(onlyName bool) (path string) -func HasShellEnv(shell string) bool -func BuildOptionHelpName(names []string) string -func ShellQuote(s string) string -func OutputLines(output string) []string -func FirstLine(output string) string +func LineBuild(binFile string, args []string) string +func BuildLine(binFile string, args []string) string +func String2OSArgs(line string) []string +func StringToOSArgs(line string) []string +func ParseLine(line string) []string +func QuickExec(cmdLine string, workDir ...string) (string, error) +func ExecLine(cmdLine string, workDir ...string) (string, error) +func ExecCmd(binName string, args []string, workDir ...string) (string, error) +func ExecCommand(binName string, args []string, workDir ...string) (string, error) +func ShellExec(cmdLine string, shells ...string) (string, error) +func CurrentShell(onlyName bool) (path string) +func HasShellEnv(shell string) bool +func BuildOptionHelpName(names []string) string +func ShellQuote(s string) string +func OutputLines(output string) []string // source at cliutil/color_print.go -func Redp(a ...any) { color.Red.Print(a...) } -func Redf(format string, a ...any) { color.Red.Printf(format, a...) } -func Redln(a ...any) { color.Red.Println(a...) } -func Bluep(a ...any) { color.Blue.Print(a...) } -func Bluef(format string, a ...any) { color.Blue.Printf(format, a...) } -func Blueln(a ...any) { color.Blue.Println(a...) } -func Cyanp(a ...any) { color.Cyan.Print(a...) } -func Cyanf(format string, a ...any) { color.Cyan.Printf(format, a...) } -func Cyanln(a ...any) { color.Cyan.Println(a...) } -func Grayp(a ...any) { color.Gray.Print(a...) } -func Grayf(format string, a ...any) { color.Gray.Printf(format, a...) } -func Grayln(a ...any) { color.Gray.Println(a...) } -func Greenp(a ...any) { color.Green.Print(a...) } -func Greenf(format string, a ...any) { color.Green.Printf(format, a...) } -func Greenln(a ...any) { color.Green.Println(a...) } -func Yellowp(a ...any) { color.Yellow.Print(a...) } -func Yellowf(format string, a ...any) { color.Yellow.Printf(format, a...) } -func Yellowln(a ...any) { color.Yellow.Println(a...) } -func Magentap(a ...any) { color.Magenta.Print(a...) } -func Magentaf(format string, a ...any) { color.Magenta.Printf(format, a...) } -func Magentaln(a ...any) { color.Magenta.Println(a...) } -func Infop(a ...any) { color.Info.Print(a...) } -func Infof(format string, a ...any) { color.Info.Printf(format, a...) } -func Infoln(a ...any) { color.Info.Println(a...) } -func Successp(a ...any) { color.Success.Print(a...) } -func Successf(format string, a ...any) { color.Success.Printf(format, a...) } -func Successln(a ...any) { color.Success.Println(a...) } -func Errorp(a ...any) { color.Error.Print(a...) } -func Errorf(format string, a ...any) { color.Error.Printf(format, a...) } -func Errorln(a ...any) { color.Error.Println(a...) } -func Warnp(a ...any) { color.Warn.Print(a...) } -func Warnf(format string, a ...any) { color.Warn.Printf(format, a...) } -func Warnln(a ...any) { color.Warn.Println(a...) } +func Redp(a ...any) +func Redf(format string, a ...any) +func Redln(a ...any) +func Bluep(a ...any) +func Bluef(format string, a ...any) +func Blueln(a ...any) +func Cyanp(a ...any) +func Cyanf(format string, a ...any) +func Cyanln(a ...any) +func Grayp(a ...any) +func Grayf(format string, a ...any) +func Grayln(a ...any) +func Greenp(a ...any) +func Greenf(format string, a ...any) +func Greenln(a ...any) +func Yellowp(a ...any) +func Yellowf(format string, a ...any) +func Yellowln(a ...any) +func Magentap(a ...any) +func Magentaf(format string, a ...any) +func Magentaln(a ...any) +func Infop(a ...any) +func Infof(format string, a ...any) +func Infoln(a ...any) +func Successp(a ...any) +func Successf(format string, a ...any) +func Successln(a ...any) +func Errorp(a ...any) +func Errorf(format string, a ...any) +func Errorln(a ...any) +func Warnp(a ...any) +func Warnf(format string, a ...any) +func Warnln(a ...any) // source at cliutil/info.go -func Workdir() string -func BinDir() string -func BinFile() string -func BinName() string -func GetTermSize(refresh ...bool) (w int, h int) +func Workdir() string +func BinDir() string +func BinFile() string +func BinName() string +func GetTermSize(refresh ...bool) (w int, h int) // source at cliutil/read.go -func ReadInput(question string) (string, error) -func ReadLine(question string) (string, error) -func ReadFirst(question string) (string, error) -func ReadFirstByte(question string) (byte, error) -func ReadFirstRune(question string) (rune, error) -func ReadPassword(question ...string) string -func InputIsYes(ans string) bool -func ByteIsYes(ans byte) bool +func ReadInput(question string) (string, error) +func ReadLine(question string) (string, error) +func ReadFirst(question string) (string, error) +func ReadFirstByte(question string) (byte, error) +func ReadFirstRune(question string) (rune, error) +func ReadAsBool(tip string, defVal bool) bool +func ReadPassword(question ...string) string +func Confirm(tip string, defVal ...bool) bool +func InputIsYes(ans string) bool +func ByteIsYes(ans byte) bool ``` #### CLI Util Usage @@ -319,19 +392,30 @@ Build line: ./myapp -a val0 -m "this is message" arg0 ```go // source at dump/dump.go -func Std() *Dumper -func Reset() -func Config(fn func(opts *Options)) -func Print(vs ...any) -func Println(vs ...any) -func Fprint(w io.Writer, vs ...any) -func Format(vs ...any) string -func NoLoc(vs ...any) -func Clear(vs ...any) +func Std() *Dumper +func Reset() +func Config(fns ...OptionFunc) +func Print(vs ...any) +func Println(vs ...any) +func Fprint(w io.Writer, vs ...any) +func Std2() *Dumper +func Reset2() +func Format(vs ...any) string +func NoLoc(vs ...any) +func Clear(vs ...any) // source at dump/dumper.go -func NewDumper(out io.Writer, skip int) *Dumper -func NewWithOptions(fn func(opts *Options)) *Dumper -func NewDefaultOptions(out io.Writer, skip int) *Options +func NewDumper(out io.Writer, skip int) *Dumper +func NewWithOptions(fns ...OptionFunc) *Dumper +// source at dump/options.go +func NewDefaultOptions(out io.Writer, skip int) *Options +func SkipNilField() OptionFunc +func SkipPrivate() OptionFunc +func BytesAsString() OptionFunc +func WithCallerSkip(skip int) OptionFunc +func WithoutPosition() OptionFunc +func WithoutOutput(out io.Writer) OptionFunc +func WithoutColor() OptionFunc +func WithoutType() OptionFunc ``` #### Examples @@ -383,31 +467,38 @@ Preview: ```go // source at envutil/envutil.go -func VarReplace(s string) string { return os.ExpandEnv(s) } -func VarParse(val string) string -func ParseEnvValue(val string) string -func ParseValue(val string) (newVal string) -func SetEnvs(mp map[string]string) +func VarReplace(s string) string +func VarParse(val string) string +func ParseEnvValue(val string) string +func ParseValue(val string) (newVal string) +func SetEnvMap(mp map[string]string) +func SetEnvs(kvPairs ...string) +func UnsetEnvs(keys ...string) // source at envutil/get.go -func Getenv(name string, def ...string) string -func GetInt(name string, def ...int) int -func GetBool(name string, def ...bool) bool -func Environ() map[string]string +func Getenv(name string, def ...string) string +func GetInt(name string, def ...int) int +func GetBool(name string, def ...bool) bool +func GetMulti(names ...string) map[string]string +func EnvPaths() []string +func EnvMap() map[string]string +func Environ() map[string]string +func SearchEnvKeys(keywords string) map[string]string +func SearchEnv(keywords string, matchValue bool) map[string]string // source at envutil/info.go -func IsWin() bool -func IsWindows() bool -func IsMac() bool -func IsLinux() bool -func IsMSys() bool -func IsWSL() bool -func IsTerminal(fd uintptr) bool -func StdIsTerminal() bool -func IsConsole(out io.Writer) bool -func HasShellEnv(shell string) bool -func IsSupportColor() bool -func IsSupport256Color() bool -func IsSupportTrueColor() bool -func IsGithubActions() bool +func IsWin() bool +func IsWindows() bool +func IsMac() bool +func IsLinux() bool +func IsMSys() bool +func IsWSL() bool +func IsTerminal(fd uintptr) bool +func StdIsTerminal() bool +func IsConsole(out io.Writer) bool +func HasShellEnv(shell string) bool +func IsSupportColor() bool +func IsSupport256Color() bool +func IsSupportTrueColor() bool +func IsGithubActions() bool ``` #### ENV Util Usage @@ -436,41 +527,50 @@ Package errorx provide a enhanced error implements, allow with call stack and wr ```go +// source at errorx/assert.go +func IsTrue(result bool, fmtAndArgs ...any) error +func IsFalse(result bool, fmtAndArgs ...any) error +func IsIn[T comdef.ScalarType](value T, list []T, fmtAndArgs ...any) error +func NotIn[T comdef.ScalarType](value T, list []T, fmtAndArgs ...any) error // source at errorx/errors.go -func NewR(code int, msg string) ErrorR -func Fail(code int, msg string) ErrorR -func Suc(msg string) ErrorR +func NewR(code int, msg string) ErrorR +func Fail(code int, msg string) ErrorR +func Failf(code int, tpl string, v ...any) ErrorR +func Suc(msg string) ErrorR // source at errorx/errorx.go -func New(msg string) error -func Newf(tpl string, vars ...any) error -func Errorf(tpl string, vars ...any) error -func With(err error, msg string) error -func Withf(err error, tpl string, vars ...any) error -func WithPrev(err error, msg string) error -func WithPrevf(err error, tpl string, vars ...any) error -func WithStack(err error) error -func Traced(err error) error -func Stacked(err error) error -func WithOptions(msg string, fns ...func(opt *ErrStackOpt)) error -func Wrap(err error, msg string) error -func Wrapf(err error, tpl string, vars ...any) error +func New(msg string) error +func Newf(tpl string, vars ...any) error +func Errorf(tpl string, vars ...any) error +func With(err error, msg string) error +func Withf(err error, tpl string, vars ...any) error +func WithPrev(err error, msg string) error +func WithPrevf(err error, tpl string, vars ...any) error +func WithStack(err error) error +func Traced(err error) error +func Stacked(err error) error +func WithOptions(msg string, fns ...func(opt *ErrStackOpt)) error +func Wrap(err error, msg string) error +func Wrapf(err error, tpl string, vars ...any) error // source at errorx/stack.go -func FuncForPC(pc uintptr) *Func -func ResetStdOpt() -func Config(fns ...func(opt *ErrStackOpt)) -func SkipDepth(skipDepth int) func(opt *ErrStackOpt) -func TraceDepth(traceDepth int) func(opt *ErrStackOpt) +func FuncForPC(pc uintptr) *Func +func ResetStdOpt() +func Config(fns ...func(opt *ErrStackOpt)) +func SkipDepth(skipDepth int) func(opt *ErrStackOpt) +func TraceDepth(traceDepth int) func(opt *ErrStackOpt) // source at errorx/util.go -func Raw(msg string) error -func Rawf(tpl string, vars ...any) error -func Cause(err error) error -func Unwrap(err error) error -func Previous(err error) error { return Unwrap(err) } -func ToErrorX(err error) (ex *ErrorX, ok bool) -func Has(err, target error) bool -func Is(err, target error) bool -func To(err error, target any) bool -func As(err error, target any) bool +func Err(msg string) error +func Raw(msg string) error +func Ef(tpl string, vars ...any) error +func Errf(tpl string, vars ...any) error +func Rawf(tpl string, vars ...any) error +func Cause(err error) error +func Unwrap(err error) error +func Previous(err error) error +func ToErrorX(err error) (ex *ErrorX, ok bool) +func Has(err, target error) bool +func Is(err, target error) bool +func To(err error, target any) bool +func As(err error, target any) bool ``` #### Errorx Usage @@ -549,21 +649,23 @@ runtime.goexit() ``` -### Formatting +### Format Utils > Package `github.com/gookit/goutil/fmtutil` ```go +// source at fmtutil/fmtutil.go +func StringOrJSON(v any) ([]byte, error) // source at fmtutil/format.go -func DataSize(size uint64) string -func SizeToString(size uint64) string { return DataSize(size) } -func StringToByte(sizeStr string) uint64 { return ParseByte(sizeStr) } -func ParseByte(sizeStr string) uint64 -func PrettyJSON(v any) (string, error) -func StringsToInts(ss []string) (ints []int, err error) -func ArgsWithSpaces(args []any) (message string) +func DataSize(size uint64) string +func SizeToString(size uint64) string +func StringToByte(sizeStr string) uint64 +func ParseByte(sizeStr string) uint64 +func PrettyJSON(v any) (string, error) +func StringsToInts(ss []string) (ints []int, err error) +func ArgsWithSpaces(vs []any) (message string) // source at fmtutil/time.go -func HowLongAgo(sec int64) string +func HowLongAgo(sec int64) string ``` ### File System @@ -572,57 +674,93 @@ func HowLongAgo(sec int64) string ```go // source at fsutil/check.go -func PathExists(path string) bool -func IsDir(path string) bool -func FileExists(path string) bool -func IsFile(path string) bool -func IsAbsPath(aPath string) bool -func IsImageFile(path string) bool -func IsZipFile(filepath string) bool +func PathExists(path string) bool +func IsDir(path string) bool +func FileExists(path string) bool +func IsFile(path string) bool +func IsAbsPath(aPath string) bool +func IsImageFile(path string) bool +func IsZipFile(filepath string) bool +func PathMatch(pattern, s string) bool +// source at fsutil/find.go +func SearchNameUp(dirPath, name string) string +func SearchNameUpx(dirPath, name string) (string, bool) +func WalkDir(dir string, fn fs.WalkDirFunc) error +func GlobWithFunc(pattern string, fn func(filePath string) error) (err error) +func OnlyFindDir(_ string, ent fs.DirEntry) bool +func OnlyFindFile(_ string, ent fs.DirEntry) bool +func IncludeSuffix(ss ...string) FilterFunc +func ExcludeDotFile(_ string, ent fs.DirEntry) bool +func ExcludeSuffix(ss ...string) FilterFunc +func ApplyFilters(fPath string, ent fs.DirEntry, filters []FilterFunc) bool +func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error) // source at fsutil/fsutil.go -func OSTempFile(pattern string) (*os.File, error) -func TempFile(dir, pattern string) (*os.File, error) -func OSTempDir(pattern string) (string, error) -func TempDir(dir, pattern string) (string, error) -func MimeType(path string) (mime string) -func ReaderMimeType(r io.Reader) (mime string) +func OSTempFile(pattern string) (*os.File, error) +func TempFile(dir, pattern string) (*os.File, error) +func OSTempDir(pattern string) (string, error) +func TempDir(dir, pattern string) (string, error) +func MimeType(path string) (mime string) +func ReaderMimeType(r io.Reader) (mime string) +func JoinPaths(elem ...string) string +func JoinSubPaths(basePath string, elem ...string) string +func SlashPath(path string) string +func UnixPath(path string) string +func ToAbsPath(p string) string // source at fsutil/info.go -func Dir(fpath string) string -func PathName(fpath string) string -func Name(fpath string) string -func FileExt(fpath string) string -func Suffix(fpath string) string -func Expand(pathStr string) string -func ExpandPath(pathStr string) string -func Realpath(pathStr string) string -func SplitPath(pathStr string) (dir, name string) -func GlobWithFunc(pattern string, fn func(filePath string) error) (err error) -func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error) +func Dir(fpath string) string +func PathName(fpath string) string +func Name(fpath string) string +func FileExt(fpath string) string +func Extname(fpath string) string +func Suffix(fpath string) string +func Expand(pathStr string) string +func ExpandPath(pathStr string) string +func ResolvePath(pathStr string) string +func SplitPath(pathStr string) (dir, name string) +// source at fsutil/info_nonwin.go +func Realpath(pathStr string) string // source at fsutil/operate.go -func Mkdir(dirPath string, perm os.FileMode) error -func MkParentDir(fpath string) error -func DiscardReader(src io.Reader) -func MustReadFile(filePath string) []byte -func MustReadReader(r io.Reader) []byte -func GetContents(in any) []byte -func ReadExistFile(filePath string) []byte -func OpenFile(filepath string, flag int, perm os.FileMode) (*os.File, error) -func QuickOpenFile(filepath string, fileFlag ...int) (*os.File, error) -func OpenReadFile(filepath string) (*os.File, error) -func CreateFile(fpath string, filePerm, dirPerm os.FileMode, fileFlag ...int) (*os.File, error) -func MustCreateFile(filePath string, filePerm, dirPerm os.FileMode) *os.File -func PutContents(filePath string, data any, fileFlag ...int) (int, error) -func WriteFile(filePath string, data any, perm os.FileMode, fileFlag ...int) error -func CopyFile(srcPath, dstPath string) error -func MustCopyFile(srcPath, dstPath string) -func Remove(fPath string) error -func MustRemove(fPath string) -func QuietRemove(fPath string) { _ = os.Remove(fPath) } -func RmIfExist(fPath string) error { return DeleteIfExist(fPath) } -func DeleteIfExist(fPath string) error -func RmFileIfExist(fPath string) error { return DeleteIfFileExist(fPath) } -func DeleteIfFileExist(fPath string) error -func Unzip(archive, targetDir string) (err error) +func Mkdir(dirPath string, perm os.FileMode) error +func MkDirs(perm os.FileMode, dirPaths ...string) error +func MkSubDirs(perm os.FileMode, parentDir string, subDirs ...string) error +func MkParentDir(fpath string) error +func OpenFile(filepath string, flag int, perm os.FileMode) (*os.File, error) +func MustOpenFile(filepath string, flag int, perm os.FileMode) *os.File +func QuickOpenFile(filepath string, fileFlag ...int) (*os.File, error) +func OpenAppendFile(filepath string, filePerm ...os.FileMode) (*os.File, error) +func OpenTruncFile(filepath string, filePerm ...os.FileMode) (*os.File, error) +func OpenReadFile(filepath string) (*os.File, error) +func CreateFile(fpath string, filePerm, dirPerm os.FileMode, fileFlag ...int) (*os.File, error) +func MustCreateFile(filePath string, filePerm, dirPerm os.FileMode) *os.File +func Remove(fPath string) error +func MustRemove(fPath string) +func QuietRemove(fPath string) +func RmIfExist(fPath string) error +func DeleteIfExist(fPath string) error +func RmFileIfExist(fPath string) error +func DeleteIfFileExist(fPath string) error +func Unzip(archive, targetDir string) (err error) +// source at fsutil/opread.go +func NewIOReader(in any) (r io.Reader, err error) +func DiscardReader(src io.Reader) +func ReadFile(filePath string) []byte +func MustReadFile(filePath string) []byte +func ReadReader(r io.Reader) []byte +func MustReadReader(r io.Reader) []byte +func ReadString(in any) string +func ReadStringOrErr(in any) (string, error) +func ReadAll(in any) []byte +func GetContents(in any) []byte +func ReadOrErr(in any) ([]byte, error) +func ReadExistFile(filePath string) []byte +func TextScanner(in any) *scanner.Scanner +func LineScanner(in any) *bufio.Scanner +// source at fsutil/opwrite.go +func PutContents(filePath string, data any, fileFlag ...int) (int, error) +func WriteFile(filePath string, data any, perm os.FileMode, fileFlag ...int) error +func WriteOSFile(f *os.File, data any) (n int, err error) +func CopyFile(srcPath, dstPath string) error +func MustCopyFile(srcPath, dstPath string) ``` #### FsUtil Usage @@ -634,30 +772,23 @@ package main import ( "fmt" - "os" + "io/fs" - "github.com/gookit/goutil/fsutil/finder" + "github.com/gookit/goutil/fsutil" ) func main() { - f := finder.EmptyFinder() + // find all files in dir + fsutil.FindInDir("./", func(filePath string, de fs.DirEntry) error { + fmt.Println(filePath) + return nil + }) - f. - AddDir("./testdata"). - AddFile("finder.go"). - NoDotFile(). - // NoDotDir(). - Find(). - Each(func(filePath string) { - fmt.Println(filePath) - }) - - finder.NewFinder([]string{"./testdata"}). - AddFile("finder.go"). - NoDotDir(). - EachStat(func(fi os.FileInfo, filePath string) { - fmt.Println(filePath, "=>", fi.ModTime()) - }) + // find files with filters + fsutil.FindInDir("./", func(filePath string, de fs.DirEntry) error { + fmt.Println(filePath) + return nil + }, fsutil.ExcludeDotFile) } ``` @@ -668,18 +799,21 @@ func main() { ```go // source at jsonutil/jsonutil.go -func WriteFile(filePath string, data any) error -func ReadFile(filePath string, v any) error -func Pretty(v any) (string, error) -func Encode(v any) ([]byte, error) -func EncodePretty(v any) ([]byte, error) -func EncodeToWriter(v any, w io.Writer) error -func EncodeUnescapeHTML(v any) ([]byte, error) -func Decode(bts []byte, ptr any) error -func DecodeString(str string, ptr any) error -func DecodeReader(r io.Reader, ptr any) error -func Mapping(src, dst any) error -func StripComments(src string) string +func WriteFile(filePath string, data any) error +func WritePretty(filePath string, data any) error +func ReadFile(filePath string, v any) error +func Pretty(v any) (string, error) +func Encode(v any) ([]byte, error) +func EncodePretty(v any) ([]byte, error) +func EncodeToWriter(v any, w io.Writer) error +func EncodeUnescapeHTML(v any) ([]byte, error) +func Decode(bts []byte, ptr any) error +func DecodeString(str string, ptr any) error +func DecodeReader(r io.Reader, ptr any) error +func Mapping(src, dst any) error +func IsJSON(s string) bool +func IsJSONFast(s string) bool +func StripComments(src string) string ``` ### Map @@ -688,33 +822,41 @@ func StripComments(src string) string ```go // source at maputil/check.go -func HasKey(mp, key any) (ok bool) -func HasAllKeys(mp any, keys ...any) (ok bool, noKey any) +func HasKey(mp, key any) (ok bool) +func HasOneKey(mp any, keys ...any) (ok bool, key any) +func HasAllKeys(mp any, keys ...any) (ok bool, noKey any) // source at maputil/convert.go -func KeyToLower(src map[string]string) map[string]string -func ToStringMap(src map[string]any) map[string]string -func HttpQueryString(data map[string]any) string -func ToString(mp map[string]any) string -func ToString2(mp any) string -func FormatIndent(mp any, indent string) string -func Flatten(mp map[string]any) map[string]any -func FlatWithFunc(mp map[string]any, fn reflects.FlatFunc) +func KeyToLower(src map[string]string) map[string]string +func ToStringMap(src map[string]any) map[string]string +func CombineToSMap(keys, values []string) SMap +func CombineToMap[K comdef.SortedType, V any](keys []K, values []V) map[K]V +func ToAnyMap(mp any) map[string]any +func TryAnyMap(mp any) (map[string]any, error) +func HTTPQueryString(data map[string]any) string +func ToString(mp map[string]any) string +func ToString2(mp any) string +func FormatIndent(mp any, indent string) string +func Flatten(mp map[string]any) map[string]any +func FlatWithFunc(mp map[string]any, fn reflects.FlatFunc) // source at maputil/format.go -func NewFormatter(mp any) *MapFormatter +func NewFormatter(mp any) *MapFormatter // source at maputil/get.go -func DeepGet(mp map[string]any, path string) (val any) -func QuietGet(mp map[string]any, path string) (val any) -func GetByPath(path string, mp map[string]any) (val any, ok bool) -func Keys(mp any) (keys []string) -func Values(mp any) (values []any) +func DeepGet(mp map[string]any, path string) (val any) +func QuietGet(mp map[string]any, path string) (val any) +func GetByPath(path string, mp map[string]any) (val any, ok bool) +func Keys(mp any) (keys []string) +func Values(mp any) (values []any) +func EachAnyMap(mp any, fn func(key string, val any)) // source at maputil/maputil.go -func MergeSMap(src, dst map[string]string, ignoreCase bool) map[string]string -func MergeStringMap(src, dst map[string]string, ignoreCase bool) map[string]string -func MakeByPath(path string, val any) (mp map[string]any) -func MakeByKeys(keys []string, val any) (mp map[string]any) +func SimpleMerge(src, dst map[string]any) map[string]any +func DeepMerge(src, dst map[string]any, deep int) map[string]any +func MergeSMap(src, dst map[string]string, ignoreCase bool) map[string]string +func MergeStringMap(src, dst map[string]string, ignoreCase bool) map[string]string +func MakeByPath(path string, val any) (mp map[string]any) +func MakeByKeys(keys []string, val any) (mp map[string]any) // source at maputil/setval.go -func SetByPath(mp *map[string]any, path string, val any) error -func SetByKeys(mp *map[string]any, keys []string, val any) (err error) +func SetByPath(mp *map[string]any, path string, val any) error +func SetByKeys(mp *map[string]any, keys []string, val any) (err error) ``` ### Math/Number @@ -723,57 +865,68 @@ func SetByKeys(mp *map[string]any, keys []string, val any) (err error) ```go // source at mathutil/check.go -func Compare(srcVal, dstVal any, op string) (ok bool) -func CompInt64(srcI64, dstI64 int64, op string) (ok bool) -func CompFloat(srcF64, dstF64 float64, op string) (ok bool) +func Compare(srcVal, dstVal any, op string) (ok bool) +func CompInt[T comdef.Xint](srcVal, dstVal T, op string) (ok bool) +func CompInt64(srcVal, dstVal int64, op string) bool +func CompFloat[T comdef.Float](srcVal, dstVal T, op string) (ok bool) +func CompValue[T comdef.XintOrFloat](srcVal, dstVal T, op string) (ok bool) +func InRange[T comdef.IntOrFloat](val, min, max T) bool +func OutRange[T comdef.IntOrFloat](val, min, max T) bool +func InUintRange[T comdef.Uint](val, min, max T) bool // source at mathutil/convert.go -func Int(in any) (int, error) -func QuietInt(in any) int -func MustInt(in any) int -func IntOrPanic(in any) int -func IntOrErr(in any) (iVal int, err error) -func ToInt(in any) (iVal int, err error) -func StrInt(s string) int -func Uint(in any) (uint64, error) -func QuietUint(in any) uint64 -func MustUint(in any) uint64 -func UintOrErr(in any) (uint64, error) -func ToUint(in any) (u64 uint64, err error) -func Int64(in any) (int64, error) -func QuietInt64(in any) int64 -func MustInt64(in any) int64 -func Int64OrErr(in any) (int64, error) -func ToInt64(in any) (i64 int64, err error) -func QuietFloat(in any) float64 -func FloatOrPanic(in any) float64 -func MustFloat(in any) float64 -func Float(in any) (float64, error) -func FloatOrErr(in any) (float64, error) -func ToFloat(in any) (f64 float64, err error) -func StringOrPanic(val any) string -func MustString(val any) string -func ToString(val any) (string, error) -func StringOrErr(val any) (string, error) -func QuietString(val any) string -func String(val any) string -func TryToString(val any, defaultAsErr bool) (str string, err error) +func Int(in any) (int, error) +func QuietInt(in any) int +func MustInt(in any) int +func IntOrPanic(in any) int +func IntOrErr(in any) (iVal int, err error) +func ToInt(in any) (iVal int, err error) +func StrInt(s string) int +func Uint(in any) (uint64, error) +func QuietUint(in any) uint64 +func MustUint(in any) uint64 +func UintOrErr(in any) (uint64, error) +func ToUint(in any) (u64 uint64, err error) +func Int64(in any) (int64, error) +func SafeInt64(in any) int64 +func QuietInt64(in any) int64 +func MustInt64(in any) int64 +func Int64OrErr(in any) (int64, error) +func ToInt64(in any) (i64 int64, err error) +func QuietFloat(in any) float64 +func FloatOrPanic(in any) float64 +func MustFloat(in any) float64 +func Float(in any) (float64, error) +func FloatOrErr(in any) (float64, error) +func ToFloat(in any) (f64 float64, err error) +func StringOrPanic(val any) string +func MustString(val any) string +func ToString(val any) (string, error) +func StringOrErr(val any) (string, error) +func QuietString(val any) string +func String(val any) string +func TryToString(val any, defaultAsErr bool) (str string, err error) // source at mathutil/mathutil.go -func MaxFloat(x, y float64) float64 -func MaxInt(x, y int) int -func SwapMaxInt(x, y int) (int, int) -func MaxI64(x, y int64) int64 -func SwapMaxI64(x, y int64) (int64, int64) +func Min[T comdef.XintOrFloat](x, y T) T +func Max[T comdef.XintOrFloat](x, y T) T +func SwapMin[T comdef.XintOrFloat](x, y T) (T, T) +func SwapMax[T comdef.XintOrFloat](x, y T) (T, T) +func MaxInt(x, y int) int +func SwapMaxInt(x, y int) (int, int) +func MaxI64(x, y int64) int64 +func SwapMaxI64(x, y int64) (int64, int64) +func MaxFloat(x, y float64) float64 +func OrElse[T comdef.XintOrFloat](in, nv T) T // source at mathutil/number.go -func IsNumeric(c byte) bool -func Percent(val, total int) float64 -func ElapsedTime(startTime time.Time) string -func DataSize(size uint64) string -func HowLongAgo(sec int64) string +func IsNumeric(c byte) bool +func Percent(val, total int) float64 +func ElapsedTime(startTime time.Time) string +func DataSize(size uint64) string +func HowLongAgo(sec int64) string // source at mathutil/random.go -func RandomInt(min, max int) int -func RandInt(min, max int) int { return RandomInt(min, max) } -func RandIntWithSeed(min, max int, seed int64) int -func RandomIntWithSeed(min, max int, seed int64) int +func RandomInt(min, max int) int +func RandInt(min, max int) int +func RandIntWithSeed(min, max int, seed int64) int +func RandomIntWithSeed(min, max int, seed int64) int ``` ### Reflects @@ -782,33 +935,45 @@ func RandomIntWithSeed(min, max int, seed int64) int ```go // source at reflects/check.go -func HasChild(v reflect.Value) bool -func IsNil(v reflect.Value) bool -func IsFunc(val any) bool -func IsEqual(src, dst any) bool -func IsEmpty(v reflect.Value) bool -func IsEmptyValue(v reflect.Value) bool +func HasChild(v reflect.Value) bool +func IsArrayOrSlice(k reflect.Kind) bool +func IsSimpleKind(k reflect.Kind) bool +func IsAnyInt(k reflect.Kind) bool +func IsIntx(k reflect.Kind) bool +func IsUintX(k reflect.Kind) bool +func IsNil(v reflect.Value) bool +func IsFunc(val any) bool +func IsEqual(src, dst any) bool +func IsEmpty(v reflect.Value) bool +func IsEmptyValue(v reflect.Value) bool // source at reflects/conv.go -func BaseTypeVal(v reflect.Value) (value any, err error) -func ValueByType(val any, typ reflect.Type) (rv reflect.Value, err error) -func ValueByKind(val any, kind reflect.Kind) (rv reflect.Value, err error) -func String(rv reflect.Value) string -func ToString(rv reflect.Value) (str string, err error) -func ValToString(rv reflect.Value, defaultAsErr bool) (str string, err error) +func BaseTypeVal(v reflect.Value) (value any, err error) +func ValueByType(val any, typ reflect.Type) (rv reflect.Value, err error) +func ValueByKind(val any, kind reflect.Kind) (rv reflect.Value, err error) +func ConvSlice(oldSlRv reflect.Value, newElemTyp reflect.Type) (rv reflect.Value, err error) +func String(rv reflect.Value) string +func ToString(rv reflect.Value) (str string, err error) +func ValToString(rv reflect.Value, defaultAsErr bool) (str string, err error) // source at reflects/type.go -func ToBaseKind(kind reflect.Kind) BKind -func ToBKind(kind reflect.Kind) BKind -func TypeOf(v any) Type +func ToBaseKind(kind reflect.Kind) BKind +func ToBKind(kind reflect.Kind) BKind +func TypeOf(v any) Type // source at reflects/util.go -func Elem(v reflect.Value) reflect.Value -func Indirect(v reflect.Value) reflect.Value -func Len(v reflect.Value) int -func SliceSubKind(typ reflect.Type) reflect.Kind -func SetValue(rv reflect.Value, val any) error -func FlatMap(rv reflect.Value, fn FlatFunc) +func Elem(v reflect.Value) reflect.Value +func Indirect(v reflect.Value) reflect.Value +func Len(v reflect.Value) int +func SliceSubKind(typ reflect.Type) reflect.Kind +func SliceElemKind(typ reflect.Type) reflect.Kind +func UnexportedValue(rv reflect.Value) any +func SetUnexportedValue(rv reflect.Value, value any) +func SetValue(rv reflect.Value, val any) error +func SetRValue(rv, val reflect.Value) +func EachMap(mp reflect.Value, fn func(key, val reflect.Value)) +func EachStrAnyMap(mp reflect.Value, fn func(key string, val any)) +func FlatMap(rv reflect.Value, fn FlatFunc) // source at reflects/value.go -func Wrap(rv reflect.Value) Value -func ValueOf(v any) Value +func Wrap(rv reflect.Value) Value +func ValueOf(v any) Value ``` ### Stdio @@ -817,14 +982,23 @@ func ValueOf(v any) Value ```go // source at stdio/ioutil.go -func QuietFprint(w io.Writer, ss ...string) -func QuietFprintf(w io.Writer, tpl string, vs ...any) -func QuietFprintln(w io.Writer, ss ...string) -func QuietWriteString(w io.Writer, ss ...string) -func DiscardReader(src io.Reader) -func MustReadReader(r io.Reader) []byte +func QuietFprint(w io.Writer, a ...any) +func QuietFprintf(w io.Writer, tpl string, vs ...any) +func QuietFprintln(w io.Writer, a ...any) +func QuietWriteString(w io.Writer, ss ...string) +// source at stdio/stdio.go +func DiscardReader(src io.Reader) +func ReadString(r io.Reader) string +func MustReadReader(r io.Reader) []byte +func NewIOReader(in any) io.Reader +func NewScanner(in any) *bufio.Scanner +func WriteByte(b byte) +func WriteBytes(bs []byte) +func WritelnBytes(bs []byte) +func WriteString(s string) +func Writeln(s string) // source at stdio/writer.go -func NewWriteWrapper(w io.Writer) *WriteWrapper +func NewWriteWrapper(w io.Writer) *WriteWrapper ``` ### Standard @@ -833,40 +1007,40 @@ func NewWriteWrapper(w io.Writer) *WriteWrapper ```go // source at stdutil/chan.go -func WaitCloseSignals(closer io.Closer) error -func Go(f func() error) error -func SignalHandler(ctx context.Context, signals ...os.Signal) (execute func() error, interrupt func(error)) +func WaitCloseSignals(closer io.Closer) error +func Go(f func() error) error +func SignalHandler(ctx context.Context, signals ...os.Signal) (execute func() error, interrupt func(error)) // source at stdutil/check.go -func IsNil(v any) bool -func IsEmpty(v any) bool -func IsFunc(val any) bool -func IsEqual(src, dst any) bool -func Contains(data, elem any) bool -func IsContains(data, elem any) bool -func CheckContains(data, elem any) (valid, found bool) -func ValueIsEmpty(v reflect.Value) bool -func ValueLen(v reflect.Value) int +func IsNil(v any) bool +func IsEmpty(v any) bool +func IsFunc(val any) bool +func IsEqual(src, dst any) bool +func Contains(data, elem any) bool +func IsContains(data, elem any) bool +func CheckContains(data, elem any) (valid, found bool) +func ValueIsEmpty(v reflect.Value) bool +func ValueLen(v reflect.Value) int // source at stdutil/conv.go -func ToString(v any) string -func MustString(v any) string -func TryString(v any) (string, error) -func BaseTypeVal(val any) (value any, err error) -func BaseTypeVal2(v reflect.Value) (value any, err error) +func ToString(v any) string +func MustString(v any) string +func TryString(v any) (string, error) +func BaseTypeVal(val any) (value any, err error) +func BaseTypeVal2(v reflect.Value) (value any, err error) // source at stdutil/gofunc.go -func FuncName(fn any) string -func CutFuncName(fullFcName string) (pkgPath, shortFnName string) -func PkgName(fullFcName string) string +func FuncName(fn any) string +func CutFuncName(fullFcName string) (pkgPath, shortFnName string) +func PkgName(fullFcName string) string // source at stdutil/stack.go -func GetCallStacks(all bool) []byte -func GetCallerInfo(skip int) string -func SimpleCallersInfo(skip, num int) []string -func GetCallersInfo(skip, max int) []string +func GetCallStacks(all bool) []byte +func GetCallerInfo(skip int) string +func SimpleCallersInfo(skip, num int) []string +func GetCallersInfo(skip, max int) []string // source at stdutil/stdutil.go -func DiscardE(_ error) {} -func PanicIfErr(err error) -func PanicIf(err error) -func Panicf(format string, v ...any) -func GoVersion() string +func DiscardE(_ error) +func PanicIfErr(err error) +func PanicIf(err error) +func Panicf(format string, v ...any) +func GoVersion() string ``` ### Structs @@ -875,28 +1049,49 @@ func GoVersion() string ```go // source at structs/alias.go -func NewAliases(checker func(alias string)) *Aliases +func NewAliases(checker func(alias string)) *Aliases // source at structs/convert.go -func ToMap(st any, optFns ...MapOptFunc) map[string]any -func MustToMap(st any, optFns ...MapOptFunc) map[string]any -func TryToMap(st any, optFns ...MapOptFunc) (map[string]any, error) -func StructToMap(st any, optFns ...MapOptFunc) (map[string]any, error) +func ToMap(st any, optFns ...MapOptFunc) map[string]any +func MustToMap(st any, optFns ...MapOptFunc) map[string]any +func TryToMap(st any, optFns ...MapOptFunc) (map[string]any, error) +func ToSMap(st any, optFns ...MapOptFunc) map[string]string +func TryToSMap(st any, optFns ...MapOptFunc) (map[string]string, error) +func MustToSMap(st any, optFns ...MapOptFunc) map[string]string +func ToString(st any, optFns ...MapOptFunc) string +func WithMapTagName(tagName string) MapOptFunc +func MergeAnonymous(opt *MapOptions) +func ExportPrivate(opt *MapOptions) +func StructToMap(st any, optFns ...MapOptFunc) (map[string]any, error) +// source at structs/copy.go +func MapStruct(srcSt, dstSt any) // source at structs/data.go -func NewData() *Data -// source at structs/setval.go -func InitDefaults(ptr any, optFns ...InitOptFunc) error -func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error +func NewData() *Data +func NewOrderedMap(len int) *OrderedMap +// source at structs/init.go +func InitStructSlice(opt *InitOptions) +func Init(ptr any, optFns ...InitOptFunc) error +func InitDefaults(ptr any, optFns ...InitOptFunc) error // source at structs/structs.go -func MapStruct(srcSt, dstSt any) +func IsExported(name string) bool +func IsUnexported(name string) bool // source at structs/tags.go -func ParseTags(st any, tagNames []string) (map[string]maputil.SMap, error) -func ParseReflectTags(rt reflect.Type, tagNames []string) (map[string]maputil.SMap, error) -func NewTagParser(tagNames ...string) *TagParser -func ParseTagValueDefault(field, tagVal string) (mp maputil.SMap, err error) -func ParseTagValueDefine(sep string, defines []string) TagValFunc -func ParseTagValueNamed(field, tagVal string, keys ...string) (mp maputil.SMap, err error) +func ParseTags(st any, tagNames []string) (map[string]maputil.SMap, error) +func ParseReflectTags(rt reflect.Type, tagNames []string) (map[string]maputil.SMap, error) +func NewTagParser(tagNames ...string) *TagParser +func ParseTagValueDefault(field, tagVal string) (mp maputil.SMap, err error) +func ParseTagValueQuick(tagVal string, defines []string) maputil.SMap +func ParseTagValueDefine(sep string, defines []string) TagValFunc +func ParseTagValueNamed(field, tagVal string, keys ...string) (mp maputil.SMap, err error) // source at structs/value.go -func NewValue(val any) *Value +func NewValue(val any) *Value +// source at structs/wrapper.go +func Wrap(src any) *Wrapper +func NewWrapper(src any) *Wrapper +func WrapValue(rv reflect.Value) *Wrapper +// source at structs/writer.go +func NewWriter(ptr any) *Wrapper +func WithParseDefault(opt *SetOptions) +func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error ``` ### Strings @@ -905,270 +1100,319 @@ func NewValue(val any) *Value ```go // source at strutil/bytes.go -func NewBuffer() *Buffer -func NewByteChanPool(maxSize int, width int, capWidth int) *ByteChanPool +func NewBuffer() *Buffer +func NewByteChanPool(maxSize, width, capWidth int) *ByteChanPool // source at strutil/check.go -func NoCaseEq(s, t string) bool -func IsNumChar(c byte) bool -func IsNumeric(s string) bool -func IsAlphabet(char uint8) bool -func IsAlphaNum(c uint8) bool -func StrPos(s, sub string) int -func BytePos(s string, bt byte) int -func HasOneSub(s string, subs []string) bool -func HasAllSubs(s string, subs []string) bool -func IsStartsOf(s string, prefixes []string) bool -func HasOnePrefix(s string, prefixes []string) bool -func HasPrefix(s string, prefix string) bool { return strings.HasPrefix(s, prefix) } -func IsStartOf(s, prefix string) bool { return strings.HasPrefix(s, prefix) } -func HasSuffix(s string, suffix string) bool { return strings.HasSuffix(s, suffix) } -func IsEndOf(s, suffix string) bool { return strings.HasSuffix(s, suffix) } -func IsValidUtf8(s string) bool { return utf8.ValidString(s) } -func IsSpace(c byte) bool -func IsEmpty(s string) bool { return len(s) == 0 } -func IsBlank(s string) bool -func IsNotBlank(s string) bool -func IsBlankBytes(bs []byte) bool -func IsSymbol(r rune) bool -func IsVersion(s string) bool -func Compare(s1, s2, op string) bool -func VersionCompare(v1, v2, op string) bool +func IsNumChar(c byte) bool +func IsNumeric(s string) bool +func IsAlphabet(char uint8) bool +func IsAlphaNum(c uint8) bool +func StrPos(s, sub string) int +func BytePos(s string, bt byte) int +func IEqual(s1, s2 string) bool +func NoCaseEq(s, t string) bool +func IContains(s, sub string) bool +func ContainsByte(s string, c byte) bool +func ContainsOne(s string, subs []string) bool +func HasOneSub(s string, subs []string) bool +func ContainsAll(s string, subs []string) bool +func HasAllSubs(s string, subs []string) bool +func IsStartsOf(s string, prefixes []string) bool +func HasOnePrefix(s string, prefixes []string) bool +func HasPrefix(s string, prefix string) bool +func IsStartOf(s, prefix string) bool +func HasSuffix(s string, suffix string) bool +func IsEndOf(s, suffix string) bool +func HasOneSuffix(s string, suffixes []string) bool +func IsValidUtf8(s string) bool +func IsSpace(c byte) bool +func IsEmpty(s string) bool +func IsBlank(s string) bool +func IsNotBlank(s string) bool +func IsBlankBytes(bs []byte) bool +func IsSymbol(r rune) bool +func HasEmpty(ss ...string) bool +func IsAllEmpty(ss ...string) bool +func IsVersion(s string) bool +func Compare(s1, s2, op string) bool +func VersionCompare(v1, v2, op string) bool +func SimpleMatch(s string, keywords []string) bool +func QuickMatch(pattern, s string) bool +func PathMatch(pattern, s string) bool +func GlobMatch(pattern, s string) bool +func LikeMatch(pattern, s string) bool +func MatchNodePath(pattern, s string, sep string) bool // source at strutil/convert.go -func Quote(s string) string { return strconv.Quote(s) } -func Unquote(s string) string -func Join(sep string, ss ...string) string { return strings.Join(ss, sep) } -func JoinList(sep string, ss []string) string { return strings.Join(ss, sep) } -func Implode(sep string, ss ...string) string { return strings.Join(ss, sep) } -func String(val any) (string, error) -func QuietString(in any) string -func MustString(in any) string -func StringOrErr(val any) (string, error) -func ToString(val any) (string, error) -func AnyToString(val any, defaultAsErr bool) (str string, err error) -func Byte2str(b []byte) string -func Byte2string(b []byte) string -func ToBytes(s string) (b []byte) -func ToBool(s string) (bool, error) -func QuietBool(s string) bool -func MustBool(s string) bool -func Bool(s string) (bool, error) -func Int(s string) (int, error) -func ToInt(s string) (int, error) -func QuietInt(s string) int -func MustInt(s string) int -func IntOrPanic(s string) int -func Int64(s string) int64 -func QuietInt64(s string) int64 -func ToInt64(s string) (int64, error) -func Int64OrErr(s string) (int64, error) -func MustInt64(s string) int64 -func Int64OrPanic(s string) int64 -func Ints(s string, sep ...string) []int -func ToInts(s string, sep ...string) ([]int, error) { return ToIntSlice(s, sep...) } -func ToIntSlice(s string, sep ...string) (ints []int, err error) -func ToArray(s string, sep ...string) []string { return ToSlice(s, sep...) } -func Strings(s string, sep ...string) []string { return ToSlice(s, sep...) } -func ToStrings(s string, sep ...string) []string { return ToSlice(s, sep...) } -func ToSlice(s string, sep ...string) []string -func ToOSArgs(s string) []string -func MustToTime(s string, layouts ...string) time.Time -func ToTime(s string, layouts ...string) (t time.Time, err error) -func ToDuration(s string) (time.Duration, error) +func Quote(s string) string +func Unquote(s string) string +func Join(sep string, ss ...string) string +func JoinList(sep string, ss []string) string +func JoinAny(sep string, parts ...any) string +func Implode(sep string, ss ...string) string +func String(val any) (string, error) +func ToString(val any) (string, error) +func QuietString(in any) string +func SafeString(in any) string +func MustString(in any) string +func StringOrErr(val any) (string, error) +func AnyToString(val any, defaultAsErr bool) (str string, err error) +func Byte2str(b []byte) string +func Byte2string(b []byte) string +func ToBytes(s string) (b []byte) +func ToBool(s string) (bool, error) +func QuietBool(s string) bool +func MustBool(s string) bool +func Bool(s string) (bool, error) +func Int(s string) (int, error) +func ToInt(s string) (int, error) +func Int2(s string) int +func QuietInt(s string) int +func MustInt(s string) int +func IntOrPanic(s string) int +func Int64(s string) int64 +func QuietInt64(s string) int64 +func ToInt64(s string) (int64, error) +func Int64OrErr(s string) (int64, error) +func MustInt64(s string) int64 +func Int64OrPanic(s string) int64 +func Ints(s string, sep ...string) []int +func ToInts(s string, sep ...string) ([]int, error) +func ToIntSlice(s string, sep ...string) (ints []int, err error) +func ToArray(s string, sep ...string) []string +func Strings(s string, sep ...string) []string +func ToStrings(s string, sep ...string) []string +func ToSlice(s string, sep ...string) []string +func ToOSArgs(s string) []string +func ToDuration(s string) (time.Duration, error) +// source at strutil/crypto.go +func Md5(src any) string +func MD5(src any) string +func GenMd5(src any) string +func Md5Bytes(src any) []byte +func HashPasswd(pwd, key string) string +func VerifyPasswd(pwdMAC, pwd, key string) bool // source at strutil/encode.go -func EscapeJS(s string) string -func EscapeHTML(s string) string -func AddSlashes(s string) string -func StripSlashes(s string) string -func Md5(src any) string -func MD5(src any) string { return Md5(src) } -func GenMd5(src any) string { return Md5(src) } -func Md5Bytes(src any) []byte -func URLEncode(s string) string -func URLDecode(s string) string -func B32Encode(str string) string -func B32Decode(str string) string -func B64Encode(str string) string -func B64EncodeBytes(src []byte) []byte -func B64Decode(str string) string -func B64DecodeBytes(str string) []byte -func Encoding(base int, typ BaseType) BaseEncoder +func EscapeJS(s string) string +func EscapeHTML(s string) string +func AddSlashes(s string) string +func StripSlashes(s string) string +func URLEncode(s string) string +func URLDecode(s string) string +func B32Encode(str string) string +func B32Decode(str string) string +func B64Encode(str string) string +func B64EncodeBytes(src []byte) []byte +func B64Decode(str string) string +func B64DecodeBytes(str string) []byte +func Encoding(base int, typ BaseType) BaseEncoder +// source at strutil/ext.go +func NewComparator(src, dst string) *SimilarComparator +func Similarity(s, t string, rate float32) (float32, bool) // source at strutil/filter.go -func Trim(s string, cutSet ...string) string -func Ltrim(s string, cutSet ...string) string { return TrimLeft(s, cutSet...) } -func LTrim(s string, cutSet ...string) string { return TrimLeft(s, cutSet...) } -func TrimLeft(s string, cutSet ...string) string -func Rtrim(s string, cutSet ...string) string { return TrimRight(s, cutSet...) } -func RTrim(s string, cutSet ...string) string { return TrimRight(s, cutSet...) } -func TrimRight(s string, cutSet ...string) string -func FilterEmail(s string) string +func Trim(s string, cutSet ...string) string +func Ltrim(s string, cutSet ...string) string +func LTrim(s string, cutSet ...string) string +func TrimLeft(s string, cutSet ...string) string +func Rtrim(s string, cutSet ...string) string +func RTrim(s string, cutSet ...string) string +func TrimRight(s string, cutSet ...string) string +func FilterEmail(s string) string // source at strutil/format.go -func Title(s string) string { return strings.ToTitle(s) } -func Lower(s string) string { return strings.ToLower(s) } -func Lowercase(s string) string { return strings.ToLower(s) } -func Upper(s string) string { return strings.ToUpper(s) } -func Uppercase(s string) string { return strings.ToUpper(s) } -func UpperWord(s string) string -func LowerFirst(s string) string -func UpperFirst(s string) string -func SnakeCase(s string, sep ...string) string -func Camel(s string, sep ...string) string { return CamelCase(s, sep...) } -func CamelCase(s string, sep ...string) string -func Indent(s, prefix string) string -func IndentBytes(b, prefix []byte) []byte +func Title(s string) string +func Lower(s string) string +func Lowercase(s string) string +func Upper(s string) string +func Uppercase(s string) string +func UpperWord(s string) string +func LowerFirst(s string) string +func UpperFirst(s string) string +func SnakeCase(s string, sep ...string) string +func Camel(s string, sep ...string) string +func CamelCase(s string, sep ...string) string +func Indent(s, prefix string) string +func IndentBytes(b, prefix []byte) []byte // source at strutil/id.go -func MicroTimeID() string -func MicroTimeHexID() string +func MicroTimeID() string +func MicroTimeHexID() string +func DatetimeNo(prefix string) string // source at strutil/padding.go -func Padding(s, pad string, length int, pos PosFlag) string -func PadLeft(s, pad string, length int) string -func PadRight(s, pad string, length int) string -func Resize(s string, length int, align PosFlag) string -func PadBytes(bs []byte, pad byte, length int, pos PosFlag) []byte -func PadBytesLeft(bs []byte, pad byte, length int) []byte -func PadBytesRight(bs []byte, pad byte, length int) []byte -func PadRunes(rs []rune, pad rune, length int, pos PosFlag) []rune -func PadRunesLeft(rs []rune, pad rune, length int) []rune -func PadRunesRight(rs []rune, pad rune, length int) []rune -func Repeat(s string, times int) string -func RepeatRune(char rune, times int) []rune -func RepeatBytes(char byte, times int) []byte +func Padding(s, pad string, length int, pos PosFlag) string +func PadLeft(s, pad string, length int) string +func PadRight(s, pad string, length int) string +func Resize(s string, length int, align PosFlag) string +func PadChars[T byte | rune](cs []T, pad T, length int, pos PosFlag) []T +func PadBytes(bs []byte, pad byte, length int, pos PosFlag) []byte +func PadBytesLeft(bs []byte, pad byte, length int) []byte +func PadBytesRight(bs []byte, pad byte, length int) []byte +func PadRunes(rs []rune, pad rune, length int, pos PosFlag) []rune +func PadRunesLeft(rs []rune, pad rune, length int) []rune +func PadRunesRight(rs []rune, pad rune, length int) []rune +func Repeat(s string, times int) string +func RepeatRune(char rune, times int) []rune +func RepeatBytes(char byte, times int) []byte +func RepeatChars[T byte | rune](char T, times int) []T +// source at strutil/parse.go +func MustToTime(s string, layouts ...string) time.Time +func ToTime(s string, layouts ...string) (t time.Time, err error) +func ParseSizeRange(expr string, opt *ParseSizeOpt) (min, max uint64, err error) +func SafeByteSize(sizeStr string) uint64 +func ToByteSize(sizeStr string) (uint64, error) // source at strutil/random.go -func RandomChars(ln int) string -func RandomCharsV2(ln int) string -func RandomCharsV3(ln int) string -func RandomBytes(length int) ([]byte, error) -func RandomString(length int) (string, error) +func RandomChars(ln int) string +func RandomCharsV2(ln int) string +func RandomCharsV3(ln int) string +func RandomBytes(length int) ([]byte, error) +func RandomString(length int) (string, error) +func RandWithTpl(n int, letters string) string // source at strutil/runes.go -func RuneIsWord(c rune) bool -func RuneIsLower(c rune) bool -func RuneIsUpper(c rune) bool -func RunePos(s string, ru rune) int { return strings.IndexRune(s, ru) } -func IsSpaceRune(r rune) bool -func Utf8Len(s string) int { return utf8.RuneCountInString(s) } -func Utf8len(s string) int { return utf8.RuneCountInString(s) } -func RuneCount(s string) int { return len([]rune(s)) } -func RuneWidth(r rune) int -func TextWidth(s string) int { return Utf8Width(s) } -func Utf8Width(s string) int { return RunesWidth([]rune(s)) } -func RunesWidth(rs []rune) (w int) -func TextTruncate(s string, w int, tail string) string { return Utf8Truncate(s, w, tail) } -func Utf8Truncate(s string, w int, tail string) string -func TextSplit(s string, w int) []string { return Utf8Split(s, w) } -func Utf8Split(s string, w int) []string -func TextWrap(s string, w int) string { return WidthWrap(s, w) } -func WidthWrap(s string, w int) string -func WordWrap(s string, w int) string -// source at strutil/similar_find.go -func NewComparator(src, dst string) *SimilarComparator -func Similarity(s, t string, rate float32) (float32, bool) +func RuneIsWord(c rune) bool +func RuneIsLower(c rune) bool +func RuneIsUpper(c rune) bool +func RunePos(s string, ru rune) int +func IsSpaceRune(r rune) bool +func Utf8Len(s string) int +func Utf8len(s string) int +func RuneCount(s string) int +func RuneWidth(r rune) int +func TextWidth(s string) int +func Utf8Width(s string) int +func RunesWidth(rs []rune) (w int) +func Truncate(s string, w int, tail string) string +func TextTruncate(s string, w int, tail string) string +func Utf8Truncate(s string, w int, tail string) string +func TextSplit(s string, w int) []string +func Utf8Split(s string, w int) []string +func TextWrap(s string, w int) string +func WidthWrap(s string, w int) string +func WordWrap(s string, w int) string // source at strutil/split.go -func Cut(s, sep string) (before string, after string, found bool) -func MustCut(s, sep string) (before string, after string) -func TrimCut(s, sep string) (string, string) -func SplitValid(s, sep string) (ss []string) { return Split(s, sep) } -func Split(s, sep string) (ss []string) -func SplitNValid(s, sep string, n int) (ss []string) { return SplitN(s, sep, n) } -func SplitN(s, sep string, n int) (ss []string) -func SplitTrimmed(s, sep string) (ss []string) -func SplitNTrimmed(s, sep string, n int) (ss []string) -func Substr(s string, pos, length int) string -func SplitInlineComment(val string) (string, string) +func Cut(s, sep string) (before string, after string, found bool) +func QuietCut(s, sep string) (before string, after string) +func MustCut(s, sep string) (before string, after string) +func TrimCut(s, sep string) (string, string) +func SplitKV(s, sep string) (string, string) +func SplitValid(s, sep string) (ss []string) +func Split(s, sep string) (ss []string) +func SplitNValid(s, sep string, n int) (ss []string) +func SplitN(s, sep string, n int) (ss []string) +func SplitTrimmed(s, sep string) (ss []string) +func SplitNTrimmed(s, sep string, n int) (ss []string) +func Substr(s string, pos, length int) string +func SplitInlineComment(val string, strict ...bool) (string, string) +func FirstLine(output string) string // source at strutil/strutil.go -func Replaces(str string, pairs map[string]string) string -func PrettyJSON(v any) (string, error) -func RenderTemplate(input string, data any, fns template.FuncMap, isFile ...bool) string -func RenderText(input string, data any, fns template.FuncMap, isFile ...bool) string -func WrapTag(s, tag string) string +func OrCond(cond bool, s1, s2 string) string +func OrElse(s, orVal string) string +func OrHandle(s string, fn func(s string) string) string +func Valid(ss ...string) string +func Replaces(str string, pairs map[string]string) string +func NewReplacer(pairs map[string]string) *strings.Replacer +func PrettyJSON(v any) (string, error) +func RenderTemplate(input string, data any, fns template.FuncMap, isFile ...bool) string +func RenderText(input string, data any, fns template.FuncMap, isFile ...bool) string +func WrapTag(s, tag string) string +func SubstrCount(s string, substr string, params ...uint64) (int, error) ``` -### System +### System Utils > Package `github.com/gookit/goutil/sysutil` ```go // source at sysutil/exec.go -func NewCmd(bin string, args ...string) *cmdr.Cmd -func FlushExec(bin string, args ...string) error -func QuickExec(cmdLine string, workDir ...string) (string, error) -func ExecLine(cmdLine string, workDir ...string) (string, error) -func ExecCmd(binName string, args []string, workDir ...string) (string, error) -func ShellExec(cmdLine string, shells ...string) (string, error) +func NewCmd(bin string, args ...string) *cmdr.Cmd +func FlushExec(bin string, args ...string) error +func QuickExec(cmdLine string, workDir ...string) (string, error) +func ExecLine(cmdLine string, workDir ...string) (string, error) +func ExecCmd(binName string, args []string, workDir ...string) (string, error) +func ShellExec(cmdLine string, shells ...string) (string, error) // source at sysutil/stack.go -func CallersInfos(skip, num int, filters ...func(file string, fc *runtime.Func) bool) []*CallerInfo +func CallersInfos(skip, num int, filters ...func(file string, fc *runtime.Func) bool) []*CallerInfo // source at sysutil/sysenv.go -func IsMSys() bool -func IsConsole(out io.Writer) bool -func IsTerminal(fd uintptr) bool -func StdIsTerminal() bool -func Hostname() string -func CurrentShell(onlyName bool) (path string) -func HasShellEnv(shell string) bool -func IsShellSpecialVar(c uint8) bool -func EnvPaths() []string -func FindExecutable(binName string) (string, error) -func Executable(binName string) (string, error) -func HasExecutable(binName string) bool -func SearchPath(keywords string) []string +func IsMSys() bool +func IsConsole(out io.Writer) bool +func IsTerminal(fd uintptr) bool +func StdIsTerminal() bool +func Hostname() string +func CurrentShell(onlyName bool) (path string) +func HasShellEnv(shell string) bool +func IsShellSpecialVar(c uint8) bool +func FindExecutable(binName string) (string, error) +func Executable(binName string) (string, error) +func HasExecutable(binName string) bool +func Getenv(name string, def ...string) string +func Environ() map[string]string +func EnvMapWith(newEnv map[string]string) map[string]string +func EnvPaths() []string +func SearchPath(keywords string, limit int) []string // source at sysutil/sysgo.go -func GoVersion() string -func ParseGoVersion(line string) (*GoInfo, error) -func OsGoInfo() (*GoInfo, error) +func GoVersion() string +func ParseGoVersion(line string) (*GoInfo, error) +func OsGoInfo() (*GoInfo, error) // source at sysutil/sysutil.go -func Workdir() string -func BinDir() string -func BinFile() string +func Workdir() string +func BinDir() string +func BinName() string +func BinFile() string +func Open(fileOrUrl string) error +func OpenBrowser(fileOrUrl string) error +func OpenFile(path string) error // source at sysutil/sysutil_nonwin.go -func IsWin() bool -func IsWindows() bool -func IsMac() bool -func IsDarwin() bool -func IsLinux() bool -func Kill(pid int, signal syscall.Signal) error -func ProcessExists(pid int) bool -func OpenBrowser(URL string) error +func Kill(pid int, signal syscall.Signal) error +func ProcessExists(pid int) bool +// source at sysutil/sysutil_unix.go +func IsWin() bool +func IsWindows() bool +func IsMac() bool +func IsDarwin() bool +func IsLinux() bool +func OpenURL(URL string) error // source at sysutil/user.go -func MustFindUser(uname string) *user.User -func LoginUser() *user.User -func CurrentUser() *user.User -func UHomeDir() string -func UserHomeDir() string -func HomeDir() string -func UserDir(subPath string) string -func UserCacheDir(subPath string) string -func UserConfigDir(subPath string) string -func ExpandPath(path string) string +func MustFindUser(uname string) *user.User +func LoginUser() *user.User +func CurrentUser() *user.User +func UHomeDir() string +func UserHomeDir() string +func HomeDir() string +func UserDir(subPath string) string +func UserCacheDir(subPath string) string +func UserConfigDir(subPath string) string +func ExpandPath(path string) string +func ExpandHome(path string) string // source at sysutil/user_nonwin.go -func ChangeUserByName(newUname string) (err error) -func ChangeUserUidGid(newUid int, newGid int) (err error) +func ChangeUserByName(newUname string) (err error) +func ChangeUserUidGid(newUid int, newGid int) (err error) ``` -### Testing +### Testing Utils > Package `github.com/gookit/goutil/testutil` ```go // source at testutil/buffer.go -func NewBuffer() *Buffer +func NewBuffer() *Buffer // source at testutil/envmock.go -func MockEnvValue(key, val string, fn func(nv string)) -func MockEnvValues(kvMap map[string]string, fn func()) -func MockOsEnvByText(envText string, fn func()) -func MockOsEnv(mp map[string]string, fn func()) -func ClearOSEnv() { os.Clearenv() } -func RevertOSEnv() -func MockCleanOsEnv(mp map[string]string, fn func()) +func MockEnvValue(key, val string, fn func(nv string)) +func MockEnvValues(kvMap map[string]string, fn func()) +func MockOsEnvByText(envText string, fn func()) +func MockOsEnv(mp map[string]string, fn func()) +func ClearOSEnv() +func RevertOSEnv() +func MockCleanOsEnv(mp map[string]string, fn func()) +// source at testutil/fsmock.go +func NewDirEnt(fpath string, isDir ...bool) *DirEnt // source at testutil/httpmock.go -func NewHttpRequest(method, path string, data *MD) *http.Request -func MockRequest(h http.Handler, method, path string, data *MD) *httptest.ResponseRecorder +func NewHttpRequest(method, path string, data *MD) *http.Request +func MockRequest(h http.Handler, method, path string, data *MD) *httptest.ResponseRecorder // source at testutil/testutil.go -func DiscardStdout() error -func ReadOutput() (s string) -func RewriteStdout() -func RestoreStdout(printData ...bool) (s string) -func RewriteStderr() -func RestoreStderr(printData ...bool) (s string) +func DiscardStdout() error +func ReadOutput() (s string) +func RewriteStdout() +func RestoreStdout(printData ...bool) (s string) +func RewriteStderr() +func RestoreStderr(printData ...bool) (s string) // source at testutil/writer.go -func NewTestWriter() *TestWriter +func NewTestWriter() *TestWriter ``` ### Timex @@ -1177,47 +1421,57 @@ func NewTestWriter() *TestWriter Provides an enhanced time.Time implementation, and add more commonly used functional methods. ```go +// source at timex/gotime.go +func SetLocalByName(tzName string) error +func NowAddDay(day int) time.Time +func NowAddHour(hour int) time.Time +func NowAddMinutes(minutes int) time.Time +func NowAddSec(seconds int) time.Time +func NowAddSeconds(seconds int) time.Time +func NowHourStart() time.Time +func NowHourEnd() time.Time +func AddDay(t time.Time, day int) time.Time +func AddHour(t time.Time, hour int) time.Time +func AddMinutes(t time.Time, minutes int) time.Time +func AddSeconds(t time.Time, seconds int) time.Time +func AddSec(t time.Time, seconds int) time.Time +func HourStart(t time.Time) time.Time +func HourEnd(t time.Time) time.Time +func DayStart(t time.Time) time.Time +func DayEnd(t time.Time) time.Time +func TodayStart() time.Time +func TodayEnd() time.Time // source at timex/template.go -func ToLayout(template string) string +func ToLayout(template string) string // source at timex/timex.go -func Now() *Time -func New(t time.Time) *Time -func Wrap(t time.Time) *Time -func FromTime(t time.Time) *Time -func Local() *Time -func FromUnix(sec int64) *Time -func FromDate(s string, template ...string) (*Time, error) -func FromString(s string, layouts ...string) (*Time, error) -func LocalByName(tzName string) *Time +func Now() *Time +func New(t time.Time) *Time +func Wrap(t time.Time) *Time +func FromTime(t time.Time) *Time +func Local() *Time +func FromUnix(sec int64) *Time +func FromDate(s string, template ...string) (*Time, error) +func FromString(s string, layouts ...string) (*Time, error) +func LocalByName(tzName string) *Time // source at timex/util.go -func NowUnix() int64 -func SetLocalByName(tzName string) error -func Format(t time.Time) string -func FormatBy(t time.Time, layout string) string -func Date(t time.Time, template string) string -func DateFormat(t time.Time, template string) string -func FormatByTpl(t time.Time, template string) string -func FormatUnix(sec int64) string -func FormatUnixBy(sec int64, layout string) string -func FormatUnixByTpl(sec int64, template string) string -func NowAddDay(day int) time.Time -func NowAddHour(hour int) time.Time -func NowAddMinutes(minutes int) time.Time -func NowAddSeconds(seconds int) time.Time -func NowHourStart() time.Time -func NowHourEnd() time.Time -func AddDay(t time.Time, day int) time.Time -func AddHour(t time.Time, hour int) time.Time -func AddMinutes(t time.Time, minutes int) time.Time -func AddSeconds(t time.Time, seconds int) time.Time -func HourStart(t time.Time) time.Time -func HourEnd(t time.Time) time.Time -func DayStart(t time.Time) time.Time -func DayEnd(t time.Time) time.Time -func TodayStart() time.Time -func TodayEnd() time.Time -func HowLongAgo(sec int64) string -func ToDuration(s string) (time.Duration, error) +func NowUnix() int64 +func Format(t time.Time) string +func FormatBy(t time.Time, layout string) string +func Date(t time.Time, template ...string) string +func Datetime(t time.Time, template ...string) string +func DateFormat(t time.Time, template string) string +func FormatByTpl(t time.Time, template string) string +func FormatUnix(sec int64, layout ...string) string +func FormatUnixBy(sec int64, layout string) string +func FormatUnixByTpl(sec int64, template ...string) string +func HowLongAgo(sec int64) string +func ToTime(s string, layouts ...string) (time.Time, error) +func ToDur(s string) (time.Duration, error) +func ToDuration(s string) (time.Duration, error) +func IsDuration(s string) bool +func TryToTime(s string, bt time.Time) (time.Time, error) +func InRange(dst, start, end time.Time) bool +func ParseRange(expr string, opt *ParseRangeOpt) (start, end time.Time, err error) ``` #### Timex Usage @@ -1383,4 +1637,3 @@ root@xx:/go/work# go test ./... ## License [MIT](LICENSE) - diff --git a/vendor/github.com/gookit/goutil/README.zh-CN.md b/vendor/github.com/gookit/goutil/README.zh-CN.md index 829ffe39c..68af10a4a 100644 --- a/vendor/github.com/gookit/goutil/README.zh-CN.md +++ b/vendor/github.com/gookit/goutil/README.zh-CN.md @@ -7,7 +7,7 @@ [![Coverage Status](https://coveralls.io/repos/github/gookit/goutil/badge.svg?branch=master)](https://coveralls.io/github/gookit/goutil?branch=master) [![Go Reference](https://pkg.go.dev/badge/github.com/gookit/goutil.svg)](https://pkg.go.dev/github.com/gookit/goutil) -Go一些常用的string、number、slice、map、struct、env、system等工具函数实现、增强、收集和整理。 +`goutil` Go 常用功能的扩展工具库(**600+**)。包含:数字,byte, 字符串,slice/数组,Map,结构体,反射,文本,文件,错误,时间日期,测试,特殊处理,格式化,常用信息获取等等。 > **[EN README](README.md)** @@ -27,7 +27,6 @@ Go一些常用的string、number、slice、map、struct、env、system等工具 - [`structs`](./structs) 为 struct 提供一些扩展 util 函数。 eg: tag parse, struct data - [`strutil`](./strutil) string 相关操作的函数工具包. eg: bytes, check, convert, encode, format and more - [`sysutil`](./sysutil) system 相关操作的函数工具包. eg: sysenv, exec, user, process - - [process](./sysutil/process) 提供一些进程操作相关的实用功能。 **扩展工具包** @@ -41,9 +40,11 @@ Go一些常用的string、number、slice、map、struct、env、system等工具 - sysutil: - [clipboard](sysutil/clipboard) 提供简单的剪贴板读写操作工具库 - [cmdr](sysutil/cmdr) 提供快速构建和运行一个cmd,批量运行多个cmd任务 + - [process](./sysutil/process) 提供一些进程操作相关的实用功能。 - [`testutil`](testutil) test help 相关操作的函数工具包. eg: http test, mock ENV value - [assert](testutil/assert) 用于帮助测试的断言函数工具包 - [`timex`](timex) 提供增强的 time.Time 实现。添加更多常用的功能方法 + - 提供类似 `Y-m-d H:i:s` 的日期时间格式解析处理 - 例如: DayStart(), DayAfter(), DayAgo(), DateFormat() 等等 ## GoDoc @@ -80,54 +81,75 @@ u64Val = goutil.Uint("2") // 2 ## Packages -### Array/Slice +### Array and Slice > Package `github.com/gookit/goutil/arrutil` ```go // source at arrutil/arrutil.go -func Reverse(ss []string) -func StringsRemove(ss []string, s string) []string -func StringsFilter(ss []string, filter ...func(s string) bool) []string -func StringsMap(ss []string, mapFn func(s string) string) []string -func TrimStrings(ss []string, cutSet ...string) []string +func Reverse(ss []string) +func StringsRemove(ss []string, s string) []string +func StringsFilter(ss []string, filter ...func(s string) bool) []string +func StringsMap(ss []string, mapFn func(s string) string) []string +func TrimStrings(ss []string, cutSet ...string) []string +func GetRandomOne[T any](arr []T) T +func RandomOne[T any](arr []T) T +func Unique[T ~string | comdef.XintOrFloat](list []T) []T +func IndexOf[T ~string | comdef.XintOrFloat](val T, list []T) int // source at arrutil/check.go -func IntsHas(ints []int, val int) bool -func Int64sHas(ints []int64, val int64) bool -func InStrings(elem string, ss []string) bool { return StringsHas(ss, elem) } -func StringsHas(ss []string, val string) bool -func HasValue(arr, val any) bool -func Contains(arr, val any) bool -func NotContains(arr, val any) bool +func IntsHas(ints []int, val int) bool +func Int64sHas(ints []int64, val int64) bool +func InStrings(elem string, ss []string) bool +func StringsHas(ss []string, val string) bool +func NotIn[T comdef.ScalarType](value T, list []T) bool +func In[T comdef.ScalarType](value T, list []T) bool +func ContainsAll[T comdef.ScalarType](list, values []T) bool +func IsSubList[T comdef.ScalarType](values, list []T) bool +func IsParent[T comdef.ScalarType](values, list []T) bool +func HasValue(arr, val any) bool +func Contains(arr, val any) bool +func NotContains(arr, val any) bool // source at arrutil/collection.go -func TwowaySearch(data any, item any, fn Comparer) (int, error) -func MakeEmptySlice(itemType reflect.Type) any -func CloneSlice(data any) any -func Excepts(first, second any, fn Comparer) any -func Intersects(first any, second any, fn Comparer) any -func Union(first, second any, fn Comparer) any -func Find(source any, fn Predicate) (any, error) -func FindOrDefault(source any, fn Predicate, defaultValue any) any -func TakeWhile(data any, fn Predicate) any -func ExceptWhile(data any, fn Predicate) any +func TwowaySearch(data any, item any, fn Comparer) (int, error) +func MakeEmptySlice(itemType reflect.Type) any +func CloneSlice(data any) any +func Differences[T any](first, second []T, fn Comparer) []T +func Excepts(first, second any, fn Comparer) any +func Intersects(first any, second any, fn Comparer) any +func Union(first, second any, fn Comparer) any +func Find(source any, fn Predicate) (any, error) +func FindOrDefault(source any, fn Predicate, defaultValue any) any +func TakeWhile(data any, fn Predicate) any +func ExceptWhile(data any, fn Predicate) any +// source at arrutil/collection_gte118.go +func Map[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V +func Column[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V // source at arrutil/convert.go -func JoinStrings(sep string, ss ...string) string -func StringsJoin(sep string, ss ...string) string -func StringsToInts(ss []string) (ints []int, err error) -func MustToStrings(arr any) []string -func StringsToSlice(ss []string) []any -func ToInt64s(arr any) (ret []int64, err error) -func MustToInt64s(arr any) []int64 -func SliceToInt64s(arr []any) []int64 -func ToStrings(arr any) (ret []string, err error) -func SliceToStrings(arr []any) []string -func AnyToString(arr any) string -func SliceToString(arr ...any) string { return ToString(arr) } -func ToString(arr []any) string -func JoinSlice(sep string, arr ...any) string +func JoinStrings(sep string, ss ...string) string +func StringsJoin(sep string, ss ...string) string +func JoinSlice(sep string, arr ...any) string +func ToInt64s(arr any) (ret []int64, err error) +func MustToInt64s(arr any) []int64 +func SliceToInt64s(arr []any) []int64 +func StringsAsInts(ss []string) []int +func StringsToInts(ss []string) (ints []int, err error) +func StringsTryInts(ss []string) (ints []int, err error) +func AnyToSlice(sl any) (ls []any, err error) +func AnyToStrings(arr any) []string +func MustToStrings(arr any) []string +func StringsToSlice(ss []string) []any +func ToStrings(arr any) (ret []string, err error) +func SliceToStrings(arr []any) []string +func QuietStrings(arr []any) []string +func ConvType[T any, R any](arr []T, newElemTyp R) ([]R, error) +func AnyToString(arr any) string +func SliceToString(arr ...any) string +func ToString(arr []any) string +func CombineToMap[K comdef.SortedType, V any](keys []K, values []V) map[K]V +func CombineToSMap(keys, values []string) map[string]string // source at arrutil/format.go -func NewFormatter(arr any) *ArrFormatter -func FormatIndent(arr any, indent string) string +func NewFormatter(arr any) *ArrFormatter +func FormatIndent(arr any, indent string) string ``` #### ArrUtil Usage @@ -151,32 +173,83 @@ ss, err := arrutil.ToStrings([]int{1, 2}) // ss: []string{"1", "2"} ``` +### Basefn + +> Package `github.com/gookit/goutil/basefn` + +```go +// source at basefn/basefunc.go +func Panicf(format string, v ...any) +func MustOK(err error) +func Must[T any](v T, err error) T +func ErrOnFail(cond bool, err error) error +func OrError(cond bool, err error) error +func FirstOr[T any](sl []T, elseVal T) T +func OrValue[T any](cond bool, okVal, elVal T) T +func OrReturn[T any](cond bool, okFn, elseFn func() T) T +func CallOn(cond bool, fn ErrFunc) error +func CallOrElse(cond bool, okFn, elseFn ErrFunc) error +// source at basefn/extfunc.go +func DataSize(size uint64) string +func HowLongAgo(sec int64) string +``` + +### Bytes Utils + +> Package `github.com/gookit/goutil/byteutil` + +```go +// source at byteutil/buffer.go +func NewBuffer() *Buffer +// source at byteutil/byteutil.go +func Random(length int) ([]byte, error) +func FirstLine(bs []byte) []byte +func StrOrErr(bs []byte, err error) (string, error) +func SafeString(bs []byte, err error) string +func String(b []byte) string +func ToString(b []byte) string +func AppendAny(dst []byte, v any) []byte +func Cut(bs []byte, sep byte) (before, after []byte, found bool) +// source at byteutil/bytex.go +func Md5(src any) []byte +// source at byteutil/check.go +func IsNumChar(c byte) bool +// source at byteutil/encoder.go +func NewStdEncoder(encFn func(src []byte) []byte, decFn func(src []byte) ([]byte, error)) *StdEncoder +// source at byteutil/pool.go +func NewChanPool(maxSize int, width int, capWidth int) *ChanPool +``` + ### Cflag > Package `github.com/gookit/goutil/cflag` ```go // source at cflag/app.go -func NewApp(fns ...func(app *App)) *App -func NewCmd(name, desc string) *Cmd +func NewApp(fns ...func(app *App)) *App +func NewCmd(name, desc string) *Cmd // source at cflag/cflag.go -func SetDebug(open bool) -func New(fns ...func(c *CFlags)) *CFlags -func NewEmpty(fns ...func(c *CFlags)) *CFlags -func WithDesc(desc string) func(c *CFlags) -func WithVersion(version string) func(c *CFlags) +func SetDebug(open bool) +func New(fns ...func(c *CFlags)) *CFlags +func NewEmpty(fns ...func(c *CFlags)) *CFlags +func WithDesc(desc string) func(c *CFlags) +func WithVersion(version string) func(c *CFlags) +// source at cflag/ext.go +func NewEnumString(enum ...string) EnumString +func NewKVString() KVString // source at cflag/optarg.go -func NewArg(name, desc string, required bool) *FlagArg +func NewArg(name, desc string, required bool) *FlagArg // source at cflag/util.go -func IsZeroValue(opt *flag.Flag, value string) (bool, bool) -func AddPrefix(name string) string -func AddPrefixes(name string, shorts []string) string -func AddPrefixes2(name string, shorts []string, nameAtEnd bool) string -func SplitShortcut(shortcut string) []string -func FilterNames(names []string) []string -func IsFlagHelpErr(err error) bool -func WrapColorForCode(s string) string -func ReplaceShorts(args []string, shortsMap map[string]string) []string +func IsGoodName(name string) bool +func IsZeroValue(opt *flag.Flag, value string) (bool, bool) +func AddPrefix(name string) string +func AddPrefixes(name string, shorts []string) string +func AddPrefixes2(name string, shorts []string, nameAtEnd bool) string +func SplitShortcut(shortcut string) []string +func FilterNames(names []string) []string +func IsFlagHelpErr(err error) bool +func WrapColorForCode(s string) string +func ReplaceShorts(args []string, shortsMap map[string]string) []string ``` #### `cflag` Usage @@ -189,71 +262,72 @@ func ReplaceShorts(args []string, shortsMap map[string]string) []string ```go // source at cliutil/cliutil.go -func LineBuild(binFile string, args []string) string -func BuildLine(binFile string, args []string) string -func String2OSArgs(line string) []string -func StringToOSArgs(line string) []string -func ParseLine(line string) []string -func QuickExec(cmdLine string, workDir ...string) (string, error) -func ExecLine(cmdLine string, workDir ...string) (string, error) -func ExecCmd(binName string, args []string, workDir ...string) (string, error) -func ExecCommand(binName string, args []string, workDir ...string) (string, error) -func ShellExec(cmdLine string, shells ...string) (string, error) -func CurrentShell(onlyName bool) (path string) -func HasShellEnv(shell string) bool -func BuildOptionHelpName(names []string) string -func ShellQuote(s string) string -func OutputLines(output string) []string -func FirstLine(output string) string +func LineBuild(binFile string, args []string) string +func BuildLine(binFile string, args []string) string +func String2OSArgs(line string) []string +func StringToOSArgs(line string) []string +func ParseLine(line string) []string +func QuickExec(cmdLine string, workDir ...string) (string, error) +func ExecLine(cmdLine string, workDir ...string) (string, error) +func ExecCmd(binName string, args []string, workDir ...string) (string, error) +func ExecCommand(binName string, args []string, workDir ...string) (string, error) +func ShellExec(cmdLine string, shells ...string) (string, error) +func CurrentShell(onlyName bool) (path string) +func HasShellEnv(shell string) bool +func BuildOptionHelpName(names []string) string +func ShellQuote(s string) string +func OutputLines(output string) []string // source at cliutil/color_print.go -func Redp(a ...any) { color.Red.Print(a...) } -func Redf(format string, a ...any) { color.Red.Printf(format, a...) } -func Redln(a ...any) { color.Red.Println(a...) } -func Bluep(a ...any) { color.Blue.Print(a...) } -func Bluef(format string, a ...any) { color.Blue.Printf(format, a...) } -func Blueln(a ...any) { color.Blue.Println(a...) } -func Cyanp(a ...any) { color.Cyan.Print(a...) } -func Cyanf(format string, a ...any) { color.Cyan.Printf(format, a...) } -func Cyanln(a ...any) { color.Cyan.Println(a...) } -func Grayp(a ...any) { color.Gray.Print(a...) } -func Grayf(format string, a ...any) { color.Gray.Printf(format, a...) } -func Grayln(a ...any) { color.Gray.Println(a...) } -func Greenp(a ...any) { color.Green.Print(a...) } -func Greenf(format string, a ...any) { color.Green.Printf(format, a...) } -func Greenln(a ...any) { color.Green.Println(a...) } -func Yellowp(a ...any) { color.Yellow.Print(a...) } -func Yellowf(format string, a ...any) { color.Yellow.Printf(format, a...) } -func Yellowln(a ...any) { color.Yellow.Println(a...) } -func Magentap(a ...any) { color.Magenta.Print(a...) } -func Magentaf(format string, a ...any) { color.Magenta.Printf(format, a...) } -func Magentaln(a ...any) { color.Magenta.Println(a...) } -func Infop(a ...any) { color.Info.Print(a...) } -func Infof(format string, a ...any) { color.Info.Printf(format, a...) } -func Infoln(a ...any) { color.Info.Println(a...) } -func Successp(a ...any) { color.Success.Print(a...) } -func Successf(format string, a ...any) { color.Success.Printf(format, a...) } -func Successln(a ...any) { color.Success.Println(a...) } -func Errorp(a ...any) { color.Error.Print(a...) } -func Errorf(format string, a ...any) { color.Error.Printf(format, a...) } -func Errorln(a ...any) { color.Error.Println(a...) } -func Warnp(a ...any) { color.Warn.Print(a...) } -func Warnf(format string, a ...any) { color.Warn.Printf(format, a...) } -func Warnln(a ...any) { color.Warn.Println(a...) } +func Redp(a ...any) +func Redf(format string, a ...any) +func Redln(a ...any) +func Bluep(a ...any) +func Bluef(format string, a ...any) +func Blueln(a ...any) +func Cyanp(a ...any) +func Cyanf(format string, a ...any) +func Cyanln(a ...any) +func Grayp(a ...any) +func Grayf(format string, a ...any) +func Grayln(a ...any) +func Greenp(a ...any) +func Greenf(format string, a ...any) +func Greenln(a ...any) +func Yellowp(a ...any) +func Yellowf(format string, a ...any) +func Yellowln(a ...any) +func Magentap(a ...any) +func Magentaf(format string, a ...any) +func Magentaln(a ...any) +func Infop(a ...any) +func Infof(format string, a ...any) +func Infoln(a ...any) +func Successp(a ...any) +func Successf(format string, a ...any) +func Successln(a ...any) +func Errorp(a ...any) +func Errorf(format string, a ...any) +func Errorln(a ...any) +func Warnp(a ...any) +func Warnf(format string, a ...any) +func Warnln(a ...any) // source at cliutil/info.go -func Workdir() string -func BinDir() string -func BinFile() string -func BinName() string -func GetTermSize(refresh ...bool) (w int, h int) +func Workdir() string +func BinDir() string +func BinFile() string +func BinName() string +func GetTermSize(refresh ...bool) (w int, h int) // source at cliutil/read.go -func ReadInput(question string) (string, error) -func ReadLine(question string) (string, error) -func ReadFirst(question string) (string, error) -func ReadFirstByte(question string) (byte, error) -func ReadFirstRune(question string) (rune, error) -func ReadPassword(question ...string) string -func InputIsYes(ans string) bool -func ByteIsYes(ans byte) bool +func ReadInput(question string) (string, error) +func ReadLine(question string) (string, error) +func ReadFirst(question string) (string, error) +func ReadFirstByte(question string) (byte, error) +func ReadFirstRune(question string) (rune, error) +func ReadAsBool(tip string, defVal bool) bool +func ReadPassword(question ...string) string +func Confirm(tip string, defVal ...bool) bool +func InputIsYes(ans string) bool +func ByteIsYes(ans byte) bool ``` #### CLI Util Usage @@ -317,19 +391,30 @@ Build line: ./myapp -a val0 -m "this is message" arg0 ```go // source at dump/dump.go -func Std() *Dumper -func Reset() -func Config(fn func(opts *Options)) -func Print(vs ...any) -func Println(vs ...any) -func Fprint(w io.Writer, vs ...any) -func Format(vs ...any) string -func NoLoc(vs ...any) -func Clear(vs ...any) +func Std() *Dumper +func Reset() +func Config(fns ...OptionFunc) +func Print(vs ...any) +func Println(vs ...any) +func Fprint(w io.Writer, vs ...any) +func Std2() *Dumper +func Reset2() +func Format(vs ...any) string +func NoLoc(vs ...any) +func Clear(vs ...any) // source at dump/dumper.go -func NewDumper(out io.Writer, skip int) *Dumper -func NewWithOptions(fn func(opts *Options)) *Dumper -func NewDefaultOptions(out io.Writer, skip int) *Options +func NewDumper(out io.Writer, skip int) *Dumper +func NewWithOptions(fns ...OptionFunc) *Dumper +// source at dump/options.go +func NewDefaultOptions(out io.Writer, skip int) *Options +func SkipNilField() OptionFunc +func SkipPrivate() OptionFunc +func BytesAsString() OptionFunc +func WithCallerSkip(skip int) OptionFunc +func WithoutPosition() OptionFunc +func WithoutOutput(out io.Writer) OptionFunc +func WithoutColor() OptionFunc +func WithoutType() OptionFunc ``` #### Examples @@ -381,31 +466,38 @@ Preview: ```go // source at envutil/envutil.go -func VarReplace(s string) string { return os.ExpandEnv(s) } -func VarParse(val string) string -func ParseEnvValue(val string) string -func ParseValue(val string) (newVal string) -func SetEnvs(mp map[string]string) +func VarReplace(s string) string +func VarParse(val string) string +func ParseEnvValue(val string) string +func ParseValue(val string) (newVal string) +func SetEnvMap(mp map[string]string) +func SetEnvs(kvPairs ...string) +func UnsetEnvs(keys ...string) // source at envutil/get.go -func Getenv(name string, def ...string) string -func GetInt(name string, def ...int) int -func GetBool(name string, def ...bool) bool -func Environ() map[string]string +func Getenv(name string, def ...string) string +func GetInt(name string, def ...int) int +func GetBool(name string, def ...bool) bool +func GetMulti(names ...string) map[string]string +func EnvPaths() []string +func EnvMap() map[string]string +func Environ() map[string]string +func SearchEnvKeys(keywords string) map[string]string +func SearchEnv(keywords string, matchValue bool) map[string]string // source at envutil/info.go -func IsWin() bool -func IsWindows() bool -func IsMac() bool -func IsLinux() bool -func IsMSys() bool -func IsWSL() bool -func IsTerminal(fd uintptr) bool -func StdIsTerminal() bool -func IsConsole(out io.Writer) bool -func HasShellEnv(shell string) bool -func IsSupportColor() bool -func IsSupport256Color() bool -func IsSupportTrueColor() bool -func IsGithubActions() bool +func IsWin() bool +func IsWindows() bool +func IsMac() bool +func IsLinux() bool +func IsMSys() bool +func IsWSL() bool +func IsTerminal(fd uintptr) bool +func StdIsTerminal() bool +func IsConsole(out io.Writer) bool +func HasShellEnv(shell string) bool +func IsSupportColor() bool +func IsSupport256Color() bool +func IsSupportTrueColor() bool +func IsGithubActions() bool ``` #### ENV Util Usage @@ -436,41 +528,50 @@ envutil.ParseValue("${ENV_NAME | defValue}") ```go +// source at errorx/assert.go +func IsTrue(result bool, fmtAndArgs ...any) error +func IsFalse(result bool, fmtAndArgs ...any) error +func IsIn[T comdef.ScalarType](value T, list []T, fmtAndArgs ...any) error +func NotIn[T comdef.ScalarType](value T, list []T, fmtAndArgs ...any) error // source at errorx/errors.go -func NewR(code int, msg string) ErrorR -func Fail(code int, msg string) ErrorR -func Suc(msg string) ErrorR +func NewR(code int, msg string) ErrorR +func Fail(code int, msg string) ErrorR +func Failf(code int, tpl string, v ...any) ErrorR +func Suc(msg string) ErrorR // source at errorx/errorx.go -func New(msg string) error -func Newf(tpl string, vars ...any) error -func Errorf(tpl string, vars ...any) error -func With(err error, msg string) error -func Withf(err error, tpl string, vars ...any) error -func WithPrev(err error, msg string) error -func WithPrevf(err error, tpl string, vars ...any) error -func WithStack(err error) error -func Traced(err error) error -func Stacked(err error) error -func WithOptions(msg string, fns ...func(opt *ErrStackOpt)) error -func Wrap(err error, msg string) error -func Wrapf(err error, tpl string, vars ...any) error +func New(msg string) error +func Newf(tpl string, vars ...any) error +func Errorf(tpl string, vars ...any) error +func With(err error, msg string) error +func Withf(err error, tpl string, vars ...any) error +func WithPrev(err error, msg string) error +func WithPrevf(err error, tpl string, vars ...any) error +func WithStack(err error) error +func Traced(err error) error +func Stacked(err error) error +func WithOptions(msg string, fns ...func(opt *ErrStackOpt)) error +func Wrap(err error, msg string) error +func Wrapf(err error, tpl string, vars ...any) error // source at errorx/stack.go -func FuncForPC(pc uintptr) *Func -func ResetStdOpt() -func Config(fns ...func(opt *ErrStackOpt)) -func SkipDepth(skipDepth int) func(opt *ErrStackOpt) -func TraceDepth(traceDepth int) func(opt *ErrStackOpt) +func FuncForPC(pc uintptr) *Func +func ResetStdOpt() +func Config(fns ...func(opt *ErrStackOpt)) +func SkipDepth(skipDepth int) func(opt *ErrStackOpt) +func TraceDepth(traceDepth int) func(opt *ErrStackOpt) // source at errorx/util.go -func Raw(msg string) error -func Rawf(tpl string, vars ...any) error -func Cause(err error) error -func Unwrap(err error) error -func Previous(err error) error { return Unwrap(err) } -func ToErrorX(err error) (ex *ErrorX, ok bool) -func Has(err, target error) bool -func Is(err, target error) bool -func To(err error, target any) bool -func As(err error, target any) bool +func Err(msg string) error +func Raw(msg string) error +func Ef(tpl string, vars ...any) error +func Errf(tpl string, vars ...any) error +func Rawf(tpl string, vars ...any) error +func Cause(err error) error +func Unwrap(err error) error +func Previous(err error) error +func ToErrorX(err error) (ex *ErrorX, ok bool) +func Has(err, target error) bool +func Is(err, target error) bool +func To(err error, target any) bool +func As(err error, target any) bool ``` #### Errorx 使用示例 @@ -549,21 +650,23 @@ runtime.goexit() ``` -### Formatting +### Format Utils > Package `github.com/gookit/goutil/fmtutil` ```go +// source at fmtutil/fmtutil.go +func StringOrJSON(v any) ([]byte, error) // source at fmtutil/format.go -func DataSize(size uint64) string -func SizeToString(size uint64) string { return DataSize(size) } -func StringToByte(sizeStr string) uint64 { return ParseByte(sizeStr) } -func ParseByte(sizeStr string) uint64 -func PrettyJSON(v any) (string, error) -func StringsToInts(ss []string) (ints []int, err error) -func ArgsWithSpaces(args []any) (message string) +func DataSize(size uint64) string +func SizeToString(size uint64) string +func StringToByte(sizeStr string) uint64 +func ParseByte(sizeStr string) uint64 +func PrettyJSON(v any) (string, error) +func StringsToInts(ss []string) (ints []int, err error) +func ArgsWithSpaces(vs []any) (message string) // source at fmtutil/time.go -func HowLongAgo(sec int64) string +func HowLongAgo(sec int64) string ``` ### File System @@ -572,57 +675,93 @@ func HowLongAgo(sec int64) string ```go // source at fsutil/check.go -func PathExists(path string) bool -func IsDir(path string) bool -func FileExists(path string) bool -func IsFile(path string) bool -func IsAbsPath(aPath string) bool -func IsImageFile(path string) bool -func IsZipFile(filepath string) bool +func PathExists(path string) bool +func IsDir(path string) bool +func FileExists(path string) bool +func IsFile(path string) bool +func IsAbsPath(aPath string) bool +func IsImageFile(path string) bool +func IsZipFile(filepath string) bool +func PathMatch(pattern, s string) bool +// source at fsutil/find.go +func SearchNameUp(dirPath, name string) string +func SearchNameUpx(dirPath, name string) (string, bool) +func WalkDir(dir string, fn fs.WalkDirFunc) error +func GlobWithFunc(pattern string, fn func(filePath string) error) (err error) +func OnlyFindDir(_ string, ent fs.DirEntry) bool +func OnlyFindFile(_ string, ent fs.DirEntry) bool +func IncludeSuffix(ss ...string) FilterFunc +func ExcludeDotFile(_ string, ent fs.DirEntry) bool +func ExcludeSuffix(ss ...string) FilterFunc +func ApplyFilters(fPath string, ent fs.DirEntry, filters []FilterFunc) bool +func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error) // source at fsutil/fsutil.go -func OSTempFile(pattern string) (*os.File, error) -func TempFile(dir, pattern string) (*os.File, error) -func OSTempDir(pattern string) (string, error) -func TempDir(dir, pattern string) (string, error) -func MimeType(path string) (mime string) -func ReaderMimeType(r io.Reader) (mime string) +func OSTempFile(pattern string) (*os.File, error) +func TempFile(dir, pattern string) (*os.File, error) +func OSTempDir(pattern string) (string, error) +func TempDir(dir, pattern string) (string, error) +func MimeType(path string) (mime string) +func ReaderMimeType(r io.Reader) (mime string) +func JoinPaths(elem ...string) string +func JoinSubPaths(basePath string, elem ...string) string +func SlashPath(path string) string +func UnixPath(path string) string +func ToAbsPath(p string) string // source at fsutil/info.go -func Dir(fpath string) string -func PathName(fpath string) string -func Name(fpath string) string -func FileExt(fpath string) string -func Suffix(fpath string) string -func Expand(pathStr string) string -func ExpandPath(pathStr string) string -func Realpath(pathStr string) string -func SplitPath(pathStr string) (dir, name string) -func GlobWithFunc(pattern string, fn func(filePath string) error) (err error) -func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error) +func Dir(fpath string) string +func PathName(fpath string) string +func Name(fpath string) string +func FileExt(fpath string) string +func Extname(fpath string) string +func Suffix(fpath string) string +func Expand(pathStr string) string +func ExpandPath(pathStr string) string +func ResolvePath(pathStr string) string +func SplitPath(pathStr string) (dir, name string) +// source at fsutil/info_nonwin.go +func Realpath(pathStr string) string // source at fsutil/operate.go -func Mkdir(dirPath string, perm os.FileMode) error -func MkParentDir(fpath string) error -func DiscardReader(src io.Reader) -func MustReadFile(filePath string) []byte -func MustReadReader(r io.Reader) []byte -func GetContents(in any) []byte -func ReadExistFile(filePath string) []byte -func OpenFile(filepath string, flag int, perm os.FileMode) (*os.File, error) -func QuickOpenFile(filepath string, fileFlag ...int) (*os.File, error) -func OpenReadFile(filepath string) (*os.File, error) -func CreateFile(fpath string, filePerm, dirPerm os.FileMode, fileFlag ...int) (*os.File, error) -func MustCreateFile(filePath string, filePerm, dirPerm os.FileMode) *os.File -func PutContents(filePath string, data any, fileFlag ...int) (int, error) -func WriteFile(filePath string, data any, perm os.FileMode, fileFlag ...int) error -func CopyFile(srcPath, dstPath string) error -func MustCopyFile(srcPath, dstPath string) -func Remove(fPath string) error -func MustRemove(fPath string) -func QuietRemove(fPath string) { _ = os.Remove(fPath) } -func RmIfExist(fPath string) error { return DeleteIfExist(fPath) } -func DeleteIfExist(fPath string) error -func RmFileIfExist(fPath string) error { return DeleteIfFileExist(fPath) } -func DeleteIfFileExist(fPath string) error -func Unzip(archive, targetDir string) (err error) +func Mkdir(dirPath string, perm os.FileMode) error +func MkDirs(perm os.FileMode, dirPaths ...string) error +func MkSubDirs(perm os.FileMode, parentDir string, subDirs ...string) error +func MkParentDir(fpath string) error +func OpenFile(filepath string, flag int, perm os.FileMode) (*os.File, error) +func MustOpenFile(filepath string, flag int, perm os.FileMode) *os.File +func QuickOpenFile(filepath string, fileFlag ...int) (*os.File, error) +func OpenAppendFile(filepath string, filePerm ...os.FileMode) (*os.File, error) +func OpenTruncFile(filepath string, filePerm ...os.FileMode) (*os.File, error) +func OpenReadFile(filepath string) (*os.File, error) +func CreateFile(fpath string, filePerm, dirPerm os.FileMode, fileFlag ...int) (*os.File, error) +func MustCreateFile(filePath string, filePerm, dirPerm os.FileMode) *os.File +func Remove(fPath string) error +func MustRemove(fPath string) +func QuietRemove(fPath string) +func RmIfExist(fPath string) error +func DeleteIfExist(fPath string) error +func RmFileIfExist(fPath string) error +func DeleteIfFileExist(fPath string) error +func Unzip(archive, targetDir string) (err error) +// source at fsutil/opread.go +func NewIOReader(in any) (r io.Reader, err error) +func DiscardReader(src io.Reader) +func ReadFile(filePath string) []byte +func MustReadFile(filePath string) []byte +func ReadReader(r io.Reader) []byte +func MustReadReader(r io.Reader) []byte +func ReadString(in any) string +func ReadStringOrErr(in any) (string, error) +func ReadAll(in any) []byte +func GetContents(in any) []byte +func ReadOrErr(in any) ([]byte, error) +func ReadExistFile(filePath string) []byte +func TextScanner(in any) *scanner.Scanner +func LineScanner(in any) *bufio.Scanner +// source at fsutil/opwrite.go +func PutContents(filePath string, data any, fileFlag ...int) (int, error) +func WriteFile(filePath string, data any, perm os.FileMode, fileFlag ...int) error +func WriteOSFile(f *os.File, data any) (n int, err error) +func CopyFile(srcPath, dstPath string) error +func MustCopyFile(srcPath, dstPath string) ``` #### FsUtil Usage @@ -634,30 +773,23 @@ package main import ( "fmt" - "os" + "io/fs" - "github.com/gookit/goutil/fsutil/finder" + "github.com/gookit/goutil/fsutil" ) func main() { - f := finder.EmptyFinder() + // find all files in dir + fsutil.FindInDir("./", func(filePath string, de fs.DirEntry) error { + fmt.Println(filePath) + return nil + }) - f. - AddDir("./testdata"). - AddFile("finder.go"). - NoDotFile(). - // NoDotDir(). - Find(). - Each(func(filePath string) { - fmt.Println(filePath) - }) - - finder.NewFinder([]string{"./testdata"}). - AddFile("finder.go"). - NoDotDir(). - EachStat(func(fi os.FileInfo, filePath string) { - fmt.Println(filePath, "=>", fi.ModTime()) - }) + // find files with filters + fsutil.FindInDir("./", func(filePath string, de fs.DirEntry) error { + fmt.Println(filePath) + return nil + }, fsutil.ExcludeDotFile) } ``` @@ -668,18 +800,21 @@ func main() { ```go // source at jsonutil/jsonutil.go -func WriteFile(filePath string, data any) error -func ReadFile(filePath string, v any) error -func Pretty(v any) (string, error) -func Encode(v any) ([]byte, error) -func EncodePretty(v any) ([]byte, error) -func EncodeToWriter(v any, w io.Writer) error -func EncodeUnescapeHTML(v any) ([]byte, error) -func Decode(bts []byte, ptr any) error -func DecodeString(str string, ptr any) error -func DecodeReader(r io.Reader, ptr any) error -func Mapping(src, dst any) error -func StripComments(src string) string +func WriteFile(filePath string, data any) error +func WritePretty(filePath string, data any) error +func ReadFile(filePath string, v any) error +func Pretty(v any) (string, error) +func Encode(v any) ([]byte, error) +func EncodePretty(v any) ([]byte, error) +func EncodeToWriter(v any, w io.Writer) error +func EncodeUnescapeHTML(v any) ([]byte, error) +func Decode(bts []byte, ptr any) error +func DecodeString(str string, ptr any) error +func DecodeReader(r io.Reader, ptr any) error +func Mapping(src, dst any) error +func IsJSON(s string) bool +func IsJSONFast(s string) bool +func StripComments(src string) string ``` ### Map @@ -688,33 +823,41 @@ func StripComments(src string) string ```go // source at maputil/check.go -func HasKey(mp, key any) (ok bool) -func HasAllKeys(mp any, keys ...any) (ok bool, noKey any) +func HasKey(mp, key any) (ok bool) +func HasOneKey(mp any, keys ...any) (ok bool, key any) +func HasAllKeys(mp any, keys ...any) (ok bool, noKey any) // source at maputil/convert.go -func KeyToLower(src map[string]string) map[string]string -func ToStringMap(src map[string]any) map[string]string -func HttpQueryString(data map[string]any) string -func ToString(mp map[string]any) string -func ToString2(mp any) string -func FormatIndent(mp any, indent string) string -func Flatten(mp map[string]any) map[string]any -func FlatWithFunc(mp map[string]any, fn reflects.FlatFunc) +func KeyToLower(src map[string]string) map[string]string +func ToStringMap(src map[string]any) map[string]string +func CombineToSMap(keys, values []string) SMap +func CombineToMap[K comdef.SortedType, V any](keys []K, values []V) map[K]V +func ToAnyMap(mp any) map[string]any +func TryAnyMap(mp any) (map[string]any, error) +func HTTPQueryString(data map[string]any) string +func ToString(mp map[string]any) string +func ToString2(mp any) string +func FormatIndent(mp any, indent string) string +func Flatten(mp map[string]any) map[string]any +func FlatWithFunc(mp map[string]any, fn reflects.FlatFunc) // source at maputil/format.go -func NewFormatter(mp any) *MapFormatter +func NewFormatter(mp any) *MapFormatter // source at maputil/get.go -func DeepGet(mp map[string]any, path string) (val any) -func QuietGet(mp map[string]any, path string) (val any) -func GetByPath(path string, mp map[string]any) (val any, ok bool) -func Keys(mp any) (keys []string) -func Values(mp any) (values []any) +func DeepGet(mp map[string]any, path string) (val any) +func QuietGet(mp map[string]any, path string) (val any) +func GetByPath(path string, mp map[string]any) (val any, ok bool) +func Keys(mp any) (keys []string) +func Values(mp any) (values []any) +func EachAnyMap(mp any, fn func(key string, val any)) // source at maputil/maputil.go -func MergeSMap(src, dst map[string]string, ignoreCase bool) map[string]string -func MergeStringMap(src, dst map[string]string, ignoreCase bool) map[string]string -func MakeByPath(path string, val any) (mp map[string]any) -func MakeByKeys(keys []string, val any) (mp map[string]any) +func SimpleMerge(src, dst map[string]any) map[string]any +func DeepMerge(src, dst map[string]any, deep int) map[string]any +func MergeSMap(src, dst map[string]string, ignoreCase bool) map[string]string +func MergeStringMap(src, dst map[string]string, ignoreCase bool) map[string]string +func MakeByPath(path string, val any) (mp map[string]any) +func MakeByKeys(keys []string, val any) (mp map[string]any) // source at maputil/setval.go -func SetByPath(mp *map[string]any, path string, val any) error -func SetByKeys(mp *map[string]any, keys []string, val any) (err error) +func SetByPath(mp *map[string]any, path string, val any) error +func SetByKeys(mp *map[string]any, keys []string, val any) (err error) ``` ### Math/Number @@ -723,57 +866,68 @@ func SetByKeys(mp *map[string]any, keys []string, val any) (err error) ```go // source at mathutil/check.go -func Compare(srcVal, dstVal any, op string) (ok bool) -func CompInt64(srcI64, dstI64 int64, op string) (ok bool) -func CompFloat(srcF64, dstF64 float64, op string) (ok bool) +func Compare(srcVal, dstVal any, op string) (ok bool) +func CompInt[T comdef.Xint](srcVal, dstVal T, op string) (ok bool) +func CompInt64(srcVal, dstVal int64, op string) bool +func CompFloat[T comdef.Float](srcVal, dstVal T, op string) (ok bool) +func CompValue[T comdef.XintOrFloat](srcVal, dstVal T, op string) (ok bool) +func InRange[T comdef.IntOrFloat](val, min, max T) bool +func OutRange[T comdef.IntOrFloat](val, min, max T) bool +func InUintRange[T comdef.Uint](val, min, max T) bool // source at mathutil/convert.go -func Int(in any) (int, error) -func QuietInt(in any) int -func MustInt(in any) int -func IntOrPanic(in any) int -func IntOrErr(in any) (iVal int, err error) -func ToInt(in any) (iVal int, err error) -func StrInt(s string) int -func Uint(in any) (uint64, error) -func QuietUint(in any) uint64 -func MustUint(in any) uint64 -func UintOrErr(in any) (uint64, error) -func ToUint(in any) (u64 uint64, err error) -func Int64(in any) (int64, error) -func QuietInt64(in any) int64 -func MustInt64(in any) int64 -func Int64OrErr(in any) (int64, error) -func ToInt64(in any) (i64 int64, err error) -func QuietFloat(in any) float64 -func FloatOrPanic(in any) float64 -func MustFloat(in any) float64 -func Float(in any) (float64, error) -func FloatOrErr(in any) (float64, error) -func ToFloat(in any) (f64 float64, err error) -func StringOrPanic(val any) string -func MustString(val any) string -func ToString(val any) (string, error) -func StringOrErr(val any) (string, error) -func QuietString(val any) string -func String(val any) string -func TryToString(val any, defaultAsErr bool) (str string, err error) +func Int(in any) (int, error) +func QuietInt(in any) int +func MustInt(in any) int +func IntOrPanic(in any) int +func IntOrErr(in any) (iVal int, err error) +func ToInt(in any) (iVal int, err error) +func StrInt(s string) int +func Uint(in any) (uint64, error) +func QuietUint(in any) uint64 +func MustUint(in any) uint64 +func UintOrErr(in any) (uint64, error) +func ToUint(in any) (u64 uint64, err error) +func Int64(in any) (int64, error) +func SafeInt64(in any) int64 +func QuietInt64(in any) int64 +func MustInt64(in any) int64 +func Int64OrErr(in any) (int64, error) +func ToInt64(in any) (i64 int64, err error) +func QuietFloat(in any) float64 +func FloatOrPanic(in any) float64 +func MustFloat(in any) float64 +func Float(in any) (float64, error) +func FloatOrErr(in any) (float64, error) +func ToFloat(in any) (f64 float64, err error) +func StringOrPanic(val any) string +func MustString(val any) string +func ToString(val any) (string, error) +func StringOrErr(val any) (string, error) +func QuietString(val any) string +func String(val any) string +func TryToString(val any, defaultAsErr bool) (str string, err error) // source at mathutil/mathutil.go -func MaxFloat(x, y float64) float64 -func MaxInt(x, y int) int -func SwapMaxInt(x, y int) (int, int) -func MaxI64(x, y int64) int64 -func SwapMaxI64(x, y int64) (int64, int64) +func Min[T comdef.XintOrFloat](x, y T) T +func Max[T comdef.XintOrFloat](x, y T) T +func SwapMin[T comdef.XintOrFloat](x, y T) (T, T) +func SwapMax[T comdef.XintOrFloat](x, y T) (T, T) +func MaxInt(x, y int) int +func SwapMaxInt(x, y int) (int, int) +func MaxI64(x, y int64) int64 +func SwapMaxI64(x, y int64) (int64, int64) +func MaxFloat(x, y float64) float64 +func OrElse[T comdef.XintOrFloat](in, nv T) T // source at mathutil/number.go -func IsNumeric(c byte) bool -func Percent(val, total int) float64 -func ElapsedTime(startTime time.Time) string -func DataSize(size uint64) string -func HowLongAgo(sec int64) string +func IsNumeric(c byte) bool +func Percent(val, total int) float64 +func ElapsedTime(startTime time.Time) string +func DataSize(size uint64) string +func HowLongAgo(sec int64) string // source at mathutil/random.go -func RandomInt(min, max int) int -func RandInt(min, max int) int { return RandomInt(min, max) } -func RandIntWithSeed(min, max int, seed int64) int -func RandomIntWithSeed(min, max int, seed int64) int +func RandomInt(min, max int) int +func RandInt(min, max int) int +func RandIntWithSeed(min, max int, seed int64) int +func RandomIntWithSeed(min, max int, seed int64) int ``` ### Reflects @@ -782,33 +936,45 @@ func RandomIntWithSeed(min, max int, seed int64) int ```go // source at reflects/check.go -func HasChild(v reflect.Value) bool -func IsNil(v reflect.Value) bool -func IsFunc(val any) bool -func IsEqual(src, dst any) bool -func IsEmpty(v reflect.Value) bool -func IsEmptyValue(v reflect.Value) bool +func HasChild(v reflect.Value) bool +func IsArrayOrSlice(k reflect.Kind) bool +func IsSimpleKind(k reflect.Kind) bool +func IsAnyInt(k reflect.Kind) bool +func IsIntx(k reflect.Kind) bool +func IsUintX(k reflect.Kind) bool +func IsNil(v reflect.Value) bool +func IsFunc(val any) bool +func IsEqual(src, dst any) bool +func IsEmpty(v reflect.Value) bool +func IsEmptyValue(v reflect.Value) bool // source at reflects/conv.go -func BaseTypeVal(v reflect.Value) (value any, err error) -func ValueByType(val any, typ reflect.Type) (rv reflect.Value, err error) -func ValueByKind(val any, kind reflect.Kind) (rv reflect.Value, err error) -func String(rv reflect.Value) string -func ToString(rv reflect.Value) (str string, err error) -func ValToString(rv reflect.Value, defaultAsErr bool) (str string, err error) +func BaseTypeVal(v reflect.Value) (value any, err error) +func ValueByType(val any, typ reflect.Type) (rv reflect.Value, err error) +func ValueByKind(val any, kind reflect.Kind) (rv reflect.Value, err error) +func ConvSlice(oldSlRv reflect.Value, newElemTyp reflect.Type) (rv reflect.Value, err error) +func String(rv reflect.Value) string +func ToString(rv reflect.Value) (str string, err error) +func ValToString(rv reflect.Value, defaultAsErr bool) (str string, err error) // source at reflects/type.go -func ToBaseKind(kind reflect.Kind) BKind -func ToBKind(kind reflect.Kind) BKind -func TypeOf(v any) Type +func ToBaseKind(kind reflect.Kind) BKind +func ToBKind(kind reflect.Kind) BKind +func TypeOf(v any) Type // source at reflects/util.go -func Elem(v reflect.Value) reflect.Value -func Indirect(v reflect.Value) reflect.Value -func Len(v reflect.Value) int -func SliceSubKind(typ reflect.Type) reflect.Kind -func SetValue(rv reflect.Value, val any) error -func FlatMap(rv reflect.Value, fn FlatFunc) +func Elem(v reflect.Value) reflect.Value +func Indirect(v reflect.Value) reflect.Value +func Len(v reflect.Value) int +func SliceSubKind(typ reflect.Type) reflect.Kind +func SliceElemKind(typ reflect.Type) reflect.Kind +func UnexportedValue(rv reflect.Value) any +func SetUnexportedValue(rv reflect.Value, value any) +func SetValue(rv reflect.Value, val any) error +func SetRValue(rv, val reflect.Value) +func EachMap(mp reflect.Value, fn func(key, val reflect.Value)) +func EachStrAnyMap(mp reflect.Value, fn func(key string, val any)) +func FlatMap(rv reflect.Value, fn FlatFunc) // source at reflects/value.go -func Wrap(rv reflect.Value) Value -func ValueOf(v any) Value +func Wrap(rv reflect.Value) Value +func ValueOf(v any) Value ``` ### Stdio @@ -817,14 +983,23 @@ func ValueOf(v any) Value ```go // source at stdio/ioutil.go -func QuietFprint(w io.Writer, ss ...string) -func QuietFprintf(w io.Writer, tpl string, vs ...any) -func QuietFprintln(w io.Writer, ss ...string) -func QuietWriteString(w io.Writer, ss ...string) -func DiscardReader(src io.Reader) -func MustReadReader(r io.Reader) []byte +func QuietFprint(w io.Writer, a ...any) +func QuietFprintf(w io.Writer, tpl string, vs ...any) +func QuietFprintln(w io.Writer, a ...any) +func QuietWriteString(w io.Writer, ss ...string) +// source at stdio/stdio.go +func DiscardReader(src io.Reader) +func ReadString(r io.Reader) string +func MustReadReader(r io.Reader) []byte +func NewIOReader(in any) io.Reader +func NewScanner(in any) *bufio.Scanner +func WriteByte(b byte) +func WriteBytes(bs []byte) +func WritelnBytes(bs []byte) +func WriteString(s string) +func Writeln(s string) // source at stdio/writer.go -func NewWriteWrapper(w io.Writer) *WriteWrapper +func NewWriteWrapper(w io.Writer) *WriteWrapper ``` ### Standard @@ -833,40 +1008,40 @@ func NewWriteWrapper(w io.Writer) *WriteWrapper ```go // source at stdutil/chan.go -func WaitCloseSignals(closer io.Closer) error -func Go(f func() error) error -func SignalHandler(ctx context.Context, signals ...os.Signal) (execute func() error, interrupt func(error)) +func WaitCloseSignals(closer io.Closer) error +func Go(f func() error) error +func SignalHandler(ctx context.Context, signals ...os.Signal) (execute func() error, interrupt func(error)) // source at stdutil/check.go -func IsNil(v any) bool -func IsEmpty(v any) bool -func IsFunc(val any) bool -func IsEqual(src, dst any) bool -func Contains(data, elem any) bool -func IsContains(data, elem any) bool -func CheckContains(data, elem any) (valid, found bool) -func ValueIsEmpty(v reflect.Value) bool -func ValueLen(v reflect.Value) int +func IsNil(v any) bool +func IsEmpty(v any) bool +func IsFunc(val any) bool +func IsEqual(src, dst any) bool +func Contains(data, elem any) bool +func IsContains(data, elem any) bool +func CheckContains(data, elem any) (valid, found bool) +func ValueIsEmpty(v reflect.Value) bool +func ValueLen(v reflect.Value) int // source at stdutil/conv.go -func ToString(v any) string -func MustString(v any) string -func TryString(v any) (string, error) -func BaseTypeVal(val any) (value any, err error) -func BaseTypeVal2(v reflect.Value) (value any, err error) +func ToString(v any) string +func MustString(v any) string +func TryString(v any) (string, error) +func BaseTypeVal(val any) (value any, err error) +func BaseTypeVal2(v reflect.Value) (value any, err error) // source at stdutil/gofunc.go -func FuncName(fn any) string -func CutFuncName(fullFcName string) (pkgPath, shortFnName string) -func PkgName(fullFcName string) string +func FuncName(fn any) string +func CutFuncName(fullFcName string) (pkgPath, shortFnName string) +func PkgName(fullFcName string) string // source at stdutil/stack.go -func GetCallStacks(all bool) []byte -func GetCallerInfo(skip int) string -func SimpleCallersInfo(skip, num int) []string -func GetCallersInfo(skip, max int) []string +func GetCallStacks(all bool) []byte +func GetCallerInfo(skip int) string +func SimpleCallersInfo(skip, num int) []string +func GetCallersInfo(skip, max int) []string // source at stdutil/stdutil.go -func DiscardE(_ error) {} -func PanicIfErr(err error) -func PanicIf(err error) -func Panicf(format string, v ...any) -func GoVersion() string +func DiscardE(_ error) +func PanicIfErr(err error) +func PanicIf(err error) +func Panicf(format string, v ...any) +func GoVersion() string ``` ### Structs @@ -875,28 +1050,49 @@ func GoVersion() string ```go // source at structs/alias.go -func NewAliases(checker func(alias string)) *Aliases +func NewAliases(checker func(alias string)) *Aliases // source at structs/convert.go -func ToMap(st any, optFns ...MapOptFunc) map[string]any -func MustToMap(st any, optFns ...MapOptFunc) map[string]any -func TryToMap(st any, optFns ...MapOptFunc) (map[string]any, error) -func StructToMap(st any, optFns ...MapOptFunc) (map[string]any, error) +func ToMap(st any, optFns ...MapOptFunc) map[string]any +func MustToMap(st any, optFns ...MapOptFunc) map[string]any +func TryToMap(st any, optFns ...MapOptFunc) (map[string]any, error) +func ToSMap(st any, optFns ...MapOptFunc) map[string]string +func TryToSMap(st any, optFns ...MapOptFunc) (map[string]string, error) +func MustToSMap(st any, optFns ...MapOptFunc) map[string]string +func ToString(st any, optFns ...MapOptFunc) string +func WithMapTagName(tagName string) MapOptFunc +func MergeAnonymous(opt *MapOptions) +func ExportPrivate(opt *MapOptions) +func StructToMap(st any, optFns ...MapOptFunc) (map[string]any, error) +// source at structs/copy.go +func MapStruct(srcSt, dstSt any) // source at structs/data.go -func NewData() *Data -// source at structs/setval.go -func InitDefaults(ptr any, optFns ...InitOptFunc) error -func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error +func NewData() *Data +func NewOrderedMap(len int) *OrderedMap +// source at structs/init.go +func InitStructSlice(opt *InitOptions) +func Init(ptr any, optFns ...InitOptFunc) error +func InitDefaults(ptr any, optFns ...InitOptFunc) error // source at structs/structs.go -func MapStruct(srcSt, dstSt any) +func IsExported(name string) bool +func IsUnexported(name string) bool // source at structs/tags.go -func ParseTags(st any, tagNames []string) (map[string]maputil.SMap, error) -func ParseReflectTags(rt reflect.Type, tagNames []string) (map[string]maputil.SMap, error) -func NewTagParser(tagNames ...string) *TagParser -func ParseTagValueDefault(field, tagVal string) (mp maputil.SMap, err error) -func ParseTagValueDefine(sep string, defines []string) TagValFunc -func ParseTagValueNamed(field, tagVal string, keys ...string) (mp maputil.SMap, err error) +func ParseTags(st any, tagNames []string) (map[string]maputil.SMap, error) +func ParseReflectTags(rt reflect.Type, tagNames []string) (map[string]maputil.SMap, error) +func NewTagParser(tagNames ...string) *TagParser +func ParseTagValueDefault(field, tagVal string) (mp maputil.SMap, err error) +func ParseTagValueQuick(tagVal string, defines []string) maputil.SMap +func ParseTagValueDefine(sep string, defines []string) TagValFunc +func ParseTagValueNamed(field, tagVal string, keys ...string) (mp maputil.SMap, err error) // source at structs/value.go -func NewValue(val any) *Value +func NewValue(val any) *Value +// source at structs/wrapper.go +func Wrap(src any) *Wrapper +func NewWrapper(src any) *Wrapper +func WrapValue(rv reflect.Value) *Wrapper +// source at structs/writer.go +func NewWriter(ptr any) *Wrapper +func WithParseDefault(opt *SetOptions) +func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error ``` ### Strings @@ -905,270 +1101,319 @@ func NewValue(val any) *Value ```go // source at strutil/bytes.go -func NewBuffer() *Buffer -func NewByteChanPool(maxSize int, width int, capWidth int) *ByteChanPool +func NewBuffer() *Buffer +func NewByteChanPool(maxSize, width, capWidth int) *ByteChanPool // source at strutil/check.go -func NoCaseEq(s, t string) bool -func IsNumChar(c byte) bool -func IsNumeric(s string) bool -func IsAlphabet(char uint8) bool -func IsAlphaNum(c uint8) bool -func StrPos(s, sub string) int -func BytePos(s string, bt byte) int -func HasOneSub(s string, subs []string) bool -func HasAllSubs(s string, subs []string) bool -func IsStartsOf(s string, prefixes []string) bool -func HasOnePrefix(s string, prefixes []string) bool -func HasPrefix(s string, prefix string) bool { return strings.HasPrefix(s, prefix) } -func IsStartOf(s, prefix string) bool { return strings.HasPrefix(s, prefix) } -func HasSuffix(s string, suffix string) bool { return strings.HasSuffix(s, suffix) } -func IsEndOf(s, suffix string) bool { return strings.HasSuffix(s, suffix) } -func IsValidUtf8(s string) bool { return utf8.ValidString(s) } -func IsSpace(c byte) bool -func IsEmpty(s string) bool { return len(s) == 0 } -func IsBlank(s string) bool -func IsNotBlank(s string) bool -func IsBlankBytes(bs []byte) bool -func IsSymbol(r rune) bool -func IsVersion(s string) bool -func Compare(s1, s2, op string) bool -func VersionCompare(v1, v2, op string) bool +func IsNumChar(c byte) bool +func IsNumeric(s string) bool +func IsAlphabet(char uint8) bool +func IsAlphaNum(c uint8) bool +func StrPos(s, sub string) int +func BytePos(s string, bt byte) int +func IEqual(s1, s2 string) bool +func NoCaseEq(s, t string) bool +func IContains(s, sub string) bool +func ContainsByte(s string, c byte) bool +func ContainsOne(s string, subs []string) bool +func HasOneSub(s string, subs []string) bool +func ContainsAll(s string, subs []string) bool +func HasAllSubs(s string, subs []string) bool +func IsStartsOf(s string, prefixes []string) bool +func HasOnePrefix(s string, prefixes []string) bool +func HasPrefix(s string, prefix string) bool +func IsStartOf(s, prefix string) bool +func HasSuffix(s string, suffix string) bool +func IsEndOf(s, suffix string) bool +func HasOneSuffix(s string, suffixes []string) bool +func IsValidUtf8(s string) bool +func IsSpace(c byte) bool +func IsEmpty(s string) bool +func IsBlank(s string) bool +func IsNotBlank(s string) bool +func IsBlankBytes(bs []byte) bool +func IsSymbol(r rune) bool +func HasEmpty(ss ...string) bool +func IsAllEmpty(ss ...string) bool +func IsVersion(s string) bool +func Compare(s1, s2, op string) bool +func VersionCompare(v1, v2, op string) bool +func SimpleMatch(s string, keywords []string) bool +func QuickMatch(pattern, s string) bool +func PathMatch(pattern, s string) bool +func GlobMatch(pattern, s string) bool +func LikeMatch(pattern, s string) bool +func MatchNodePath(pattern, s string, sep string) bool // source at strutil/convert.go -func Quote(s string) string { return strconv.Quote(s) } -func Unquote(s string) string -func Join(sep string, ss ...string) string { return strings.Join(ss, sep) } -func JoinList(sep string, ss []string) string { return strings.Join(ss, sep) } -func Implode(sep string, ss ...string) string { return strings.Join(ss, sep) } -func String(val any) (string, error) -func QuietString(in any) string -func MustString(in any) string -func StringOrErr(val any) (string, error) -func ToString(val any) (string, error) -func AnyToString(val any, defaultAsErr bool) (str string, err error) -func Byte2str(b []byte) string -func Byte2string(b []byte) string -func ToBytes(s string) (b []byte) -func ToBool(s string) (bool, error) -func QuietBool(s string) bool -func MustBool(s string) bool -func Bool(s string) (bool, error) -func Int(s string) (int, error) -func ToInt(s string) (int, error) -func QuietInt(s string) int -func MustInt(s string) int -func IntOrPanic(s string) int -func Int64(s string) int64 -func QuietInt64(s string) int64 -func ToInt64(s string) (int64, error) -func Int64OrErr(s string) (int64, error) -func MustInt64(s string) int64 -func Int64OrPanic(s string) int64 -func Ints(s string, sep ...string) []int -func ToInts(s string, sep ...string) ([]int, error) { return ToIntSlice(s, sep...) } -func ToIntSlice(s string, sep ...string) (ints []int, err error) -func ToArray(s string, sep ...string) []string { return ToSlice(s, sep...) } -func Strings(s string, sep ...string) []string { return ToSlice(s, sep...) } -func ToStrings(s string, sep ...string) []string { return ToSlice(s, sep...) } -func ToSlice(s string, sep ...string) []string -func ToOSArgs(s string) []string -func MustToTime(s string, layouts ...string) time.Time -func ToTime(s string, layouts ...string) (t time.Time, err error) -func ToDuration(s string) (time.Duration, error) +func Quote(s string) string +func Unquote(s string) string +func Join(sep string, ss ...string) string +func JoinList(sep string, ss []string) string +func JoinAny(sep string, parts ...any) string +func Implode(sep string, ss ...string) string +func String(val any) (string, error) +func ToString(val any) (string, error) +func QuietString(in any) string +func SafeString(in any) string +func MustString(in any) string +func StringOrErr(val any) (string, error) +func AnyToString(val any, defaultAsErr bool) (str string, err error) +func Byte2str(b []byte) string +func Byte2string(b []byte) string +func ToBytes(s string) (b []byte) +func ToBool(s string) (bool, error) +func QuietBool(s string) bool +func MustBool(s string) bool +func Bool(s string) (bool, error) +func Int(s string) (int, error) +func ToInt(s string) (int, error) +func Int2(s string) int +func QuietInt(s string) int +func MustInt(s string) int +func IntOrPanic(s string) int +func Int64(s string) int64 +func QuietInt64(s string) int64 +func ToInt64(s string) (int64, error) +func Int64OrErr(s string) (int64, error) +func MustInt64(s string) int64 +func Int64OrPanic(s string) int64 +func Ints(s string, sep ...string) []int +func ToInts(s string, sep ...string) ([]int, error) +func ToIntSlice(s string, sep ...string) (ints []int, err error) +func ToArray(s string, sep ...string) []string +func Strings(s string, sep ...string) []string +func ToStrings(s string, sep ...string) []string +func ToSlice(s string, sep ...string) []string +func ToOSArgs(s string) []string +func ToDuration(s string) (time.Duration, error) +// source at strutil/crypto.go +func Md5(src any) string +func MD5(src any) string +func GenMd5(src any) string +func Md5Bytes(src any) []byte +func HashPasswd(pwd, key string) string +func VerifyPasswd(pwdMAC, pwd, key string) bool // source at strutil/encode.go -func EscapeJS(s string) string -func EscapeHTML(s string) string -func AddSlashes(s string) string -func StripSlashes(s string) string -func Md5(src any) string -func MD5(src any) string { return Md5(src) } -func GenMd5(src any) string { return Md5(src) } -func Md5Bytes(src any) []byte -func URLEncode(s string) string -func URLDecode(s string) string -func B32Encode(str string) string -func B32Decode(str string) string -func B64Encode(str string) string -func B64EncodeBytes(src []byte) []byte -func B64Decode(str string) string -func B64DecodeBytes(str string) []byte -func Encoding(base int, typ BaseType) BaseEncoder +func EscapeJS(s string) string +func EscapeHTML(s string) string +func AddSlashes(s string) string +func StripSlashes(s string) string +func URLEncode(s string) string +func URLDecode(s string) string +func B32Encode(str string) string +func B32Decode(str string) string +func B64Encode(str string) string +func B64EncodeBytes(src []byte) []byte +func B64Decode(str string) string +func B64DecodeBytes(str string) []byte +func Encoding(base int, typ BaseType) BaseEncoder +// source at strutil/ext.go +func NewComparator(src, dst string) *SimilarComparator +func Similarity(s, t string, rate float32) (float32, bool) // source at strutil/filter.go -func Trim(s string, cutSet ...string) string -func Ltrim(s string, cutSet ...string) string { return TrimLeft(s, cutSet...) } -func LTrim(s string, cutSet ...string) string { return TrimLeft(s, cutSet...) } -func TrimLeft(s string, cutSet ...string) string -func Rtrim(s string, cutSet ...string) string { return TrimRight(s, cutSet...) } -func RTrim(s string, cutSet ...string) string { return TrimRight(s, cutSet...) } -func TrimRight(s string, cutSet ...string) string -func FilterEmail(s string) string +func Trim(s string, cutSet ...string) string +func Ltrim(s string, cutSet ...string) string +func LTrim(s string, cutSet ...string) string +func TrimLeft(s string, cutSet ...string) string +func Rtrim(s string, cutSet ...string) string +func RTrim(s string, cutSet ...string) string +func TrimRight(s string, cutSet ...string) string +func FilterEmail(s string) string // source at strutil/format.go -func Title(s string) string { return strings.ToTitle(s) } -func Lower(s string) string { return strings.ToLower(s) } -func Lowercase(s string) string { return strings.ToLower(s) } -func Upper(s string) string { return strings.ToUpper(s) } -func Uppercase(s string) string { return strings.ToUpper(s) } -func UpperWord(s string) string -func LowerFirst(s string) string -func UpperFirst(s string) string -func SnakeCase(s string, sep ...string) string -func Camel(s string, sep ...string) string { return CamelCase(s, sep...) } -func CamelCase(s string, sep ...string) string -func Indent(s, prefix string) string -func IndentBytes(b, prefix []byte) []byte +func Title(s string) string +func Lower(s string) string +func Lowercase(s string) string +func Upper(s string) string +func Uppercase(s string) string +func UpperWord(s string) string +func LowerFirst(s string) string +func UpperFirst(s string) string +func SnakeCase(s string, sep ...string) string +func Camel(s string, sep ...string) string +func CamelCase(s string, sep ...string) string +func Indent(s, prefix string) string +func IndentBytes(b, prefix []byte) []byte // source at strutil/id.go -func MicroTimeID() string -func MicroTimeHexID() string +func MicroTimeID() string +func MicroTimeHexID() string +func DatetimeNo(prefix string) string // source at strutil/padding.go -func Padding(s, pad string, length int, pos PosFlag) string -func PadLeft(s, pad string, length int) string -func PadRight(s, pad string, length int) string -func Resize(s string, length int, align PosFlag) string -func PadBytes(bs []byte, pad byte, length int, pos PosFlag) []byte -func PadBytesLeft(bs []byte, pad byte, length int) []byte -func PadBytesRight(bs []byte, pad byte, length int) []byte -func PadRunes(rs []rune, pad rune, length int, pos PosFlag) []rune -func PadRunesLeft(rs []rune, pad rune, length int) []rune -func PadRunesRight(rs []rune, pad rune, length int) []rune -func Repeat(s string, times int) string -func RepeatRune(char rune, times int) []rune -func RepeatBytes(char byte, times int) []byte +func Padding(s, pad string, length int, pos PosFlag) string +func PadLeft(s, pad string, length int) string +func PadRight(s, pad string, length int) string +func Resize(s string, length int, align PosFlag) string +func PadChars[T byte | rune](cs []T, pad T, length int, pos PosFlag) []T +func PadBytes(bs []byte, pad byte, length int, pos PosFlag) []byte +func PadBytesLeft(bs []byte, pad byte, length int) []byte +func PadBytesRight(bs []byte, pad byte, length int) []byte +func PadRunes(rs []rune, pad rune, length int, pos PosFlag) []rune +func PadRunesLeft(rs []rune, pad rune, length int) []rune +func PadRunesRight(rs []rune, pad rune, length int) []rune +func Repeat(s string, times int) string +func RepeatRune(char rune, times int) []rune +func RepeatBytes(char byte, times int) []byte +func RepeatChars[T byte | rune](char T, times int) []T +// source at strutil/parse.go +func MustToTime(s string, layouts ...string) time.Time +func ToTime(s string, layouts ...string) (t time.Time, err error) +func ParseSizeRange(expr string, opt *ParseSizeOpt) (min, max uint64, err error) +func SafeByteSize(sizeStr string) uint64 +func ToByteSize(sizeStr string) (uint64, error) // source at strutil/random.go -func RandomChars(ln int) string -func RandomCharsV2(ln int) string -func RandomCharsV3(ln int) string -func RandomBytes(length int) ([]byte, error) -func RandomString(length int) (string, error) +func RandomChars(ln int) string +func RandomCharsV2(ln int) string +func RandomCharsV3(ln int) string +func RandomBytes(length int) ([]byte, error) +func RandomString(length int) (string, error) +func RandWithTpl(n int, letters string) string // source at strutil/runes.go -func RuneIsWord(c rune) bool -func RuneIsLower(c rune) bool -func RuneIsUpper(c rune) bool -func RunePos(s string, ru rune) int { return strings.IndexRune(s, ru) } -func IsSpaceRune(r rune) bool -func Utf8Len(s string) int { return utf8.RuneCountInString(s) } -func Utf8len(s string) int { return utf8.RuneCountInString(s) } -func RuneCount(s string) int { return len([]rune(s)) } -func RuneWidth(r rune) int -func TextWidth(s string) int { return Utf8Width(s) } -func Utf8Width(s string) int { return RunesWidth([]rune(s)) } -func RunesWidth(rs []rune) (w int) -func TextTruncate(s string, w int, tail string) string { return Utf8Truncate(s, w, tail) } -func Utf8Truncate(s string, w int, tail string) string -func TextSplit(s string, w int) []string { return Utf8Split(s, w) } -func Utf8Split(s string, w int) []string -func TextWrap(s string, w int) string { return WidthWrap(s, w) } -func WidthWrap(s string, w int) string -func WordWrap(s string, w int) string -// source at strutil/similar_find.go -func NewComparator(src, dst string) *SimilarComparator -func Similarity(s, t string, rate float32) (float32, bool) +func RuneIsWord(c rune) bool +func RuneIsLower(c rune) bool +func RuneIsUpper(c rune) bool +func RunePos(s string, ru rune) int +func IsSpaceRune(r rune) bool +func Utf8Len(s string) int +func Utf8len(s string) int +func RuneCount(s string) int +func RuneWidth(r rune) int +func TextWidth(s string) int +func Utf8Width(s string) int +func RunesWidth(rs []rune) (w int) +func Truncate(s string, w int, tail string) string +func TextTruncate(s string, w int, tail string) string +func Utf8Truncate(s string, w int, tail string) string +func TextSplit(s string, w int) []string +func Utf8Split(s string, w int) []string +func TextWrap(s string, w int) string +func WidthWrap(s string, w int) string +func WordWrap(s string, w int) string // source at strutil/split.go -func Cut(s, sep string) (before string, after string, found bool) -func MustCut(s, sep string) (before string, after string) -func TrimCut(s, sep string) (string, string) -func SplitValid(s, sep string) (ss []string) { return Split(s, sep) } -func Split(s, sep string) (ss []string) -func SplitNValid(s, sep string, n int) (ss []string) { return SplitN(s, sep, n) } -func SplitN(s, sep string, n int) (ss []string) -func SplitTrimmed(s, sep string) (ss []string) -func SplitNTrimmed(s, sep string, n int) (ss []string) -func Substr(s string, pos, length int) string -func SplitInlineComment(val string) (string, string) +func Cut(s, sep string) (before string, after string, found bool) +func QuietCut(s, sep string) (before string, after string) +func MustCut(s, sep string) (before string, after string) +func TrimCut(s, sep string) (string, string) +func SplitKV(s, sep string) (string, string) +func SplitValid(s, sep string) (ss []string) +func Split(s, sep string) (ss []string) +func SplitNValid(s, sep string, n int) (ss []string) +func SplitN(s, sep string, n int) (ss []string) +func SplitTrimmed(s, sep string) (ss []string) +func SplitNTrimmed(s, sep string, n int) (ss []string) +func Substr(s string, pos, length int) string +func SplitInlineComment(val string, strict ...bool) (string, string) +func FirstLine(output string) string // source at strutil/strutil.go -func Replaces(str string, pairs map[string]string) string -func PrettyJSON(v any) (string, error) -func RenderTemplate(input string, data any, fns template.FuncMap, isFile ...bool) string -func RenderText(input string, data any, fns template.FuncMap, isFile ...bool) string -func WrapTag(s, tag string) string +func OrCond(cond bool, s1, s2 string) string +func OrElse(s, orVal string) string +func OrHandle(s string, fn func(s string) string) string +func Valid(ss ...string) string +func Replaces(str string, pairs map[string]string) string +func NewReplacer(pairs map[string]string) *strings.Replacer +func PrettyJSON(v any) (string, error) +func RenderTemplate(input string, data any, fns template.FuncMap, isFile ...bool) string +func RenderText(input string, data any, fns template.FuncMap, isFile ...bool) string +func WrapTag(s, tag string) string +func SubstrCount(s string, substr string, params ...uint64) (int, error) ``` -### System +### System Utils > Package `github.com/gookit/goutil/sysutil` ```go // source at sysutil/exec.go -func NewCmd(bin string, args ...string) *cmdr.Cmd -func FlushExec(bin string, args ...string) error -func QuickExec(cmdLine string, workDir ...string) (string, error) -func ExecLine(cmdLine string, workDir ...string) (string, error) -func ExecCmd(binName string, args []string, workDir ...string) (string, error) -func ShellExec(cmdLine string, shells ...string) (string, error) +func NewCmd(bin string, args ...string) *cmdr.Cmd +func FlushExec(bin string, args ...string) error +func QuickExec(cmdLine string, workDir ...string) (string, error) +func ExecLine(cmdLine string, workDir ...string) (string, error) +func ExecCmd(binName string, args []string, workDir ...string) (string, error) +func ShellExec(cmdLine string, shells ...string) (string, error) // source at sysutil/stack.go -func CallersInfos(skip, num int, filters ...func(file string, fc *runtime.Func) bool) []*CallerInfo +func CallersInfos(skip, num int, filters ...func(file string, fc *runtime.Func) bool) []*CallerInfo // source at sysutil/sysenv.go -func IsMSys() bool -func IsConsole(out io.Writer) bool -func IsTerminal(fd uintptr) bool -func StdIsTerminal() bool -func Hostname() string -func CurrentShell(onlyName bool) (path string) -func HasShellEnv(shell string) bool -func IsShellSpecialVar(c uint8) bool -func EnvPaths() []string -func FindExecutable(binName string) (string, error) -func Executable(binName string) (string, error) -func HasExecutable(binName string) bool -func SearchPath(keywords string) []string +func IsMSys() bool +func IsConsole(out io.Writer) bool +func IsTerminal(fd uintptr) bool +func StdIsTerminal() bool +func Hostname() string +func CurrentShell(onlyName bool) (path string) +func HasShellEnv(shell string) bool +func IsShellSpecialVar(c uint8) bool +func FindExecutable(binName string) (string, error) +func Executable(binName string) (string, error) +func HasExecutable(binName string) bool +func Getenv(name string, def ...string) string +func Environ() map[string]string +func EnvMapWith(newEnv map[string]string) map[string]string +func EnvPaths() []string +func SearchPath(keywords string, limit int) []string // source at sysutil/sysgo.go -func GoVersion() string -func ParseGoVersion(line string) (*GoInfo, error) -func OsGoInfo() (*GoInfo, error) +func GoVersion() string +func ParseGoVersion(line string) (*GoInfo, error) +func OsGoInfo() (*GoInfo, error) // source at sysutil/sysutil.go -func Workdir() string -func BinDir() string -func BinFile() string +func Workdir() string +func BinDir() string +func BinName() string +func BinFile() string +func Open(fileOrUrl string) error +func OpenBrowser(fileOrUrl string) error +func OpenFile(path string) error // source at sysutil/sysutil_nonwin.go -func IsWin() bool -func IsWindows() bool -func IsMac() bool -func IsDarwin() bool -func IsLinux() bool -func Kill(pid int, signal syscall.Signal) error -func ProcessExists(pid int) bool -func OpenBrowser(URL string) error +func Kill(pid int, signal syscall.Signal) error +func ProcessExists(pid int) bool +// source at sysutil/sysutil_unix.go +func IsWin() bool +func IsWindows() bool +func IsMac() bool +func IsDarwin() bool +func IsLinux() bool +func OpenURL(URL string) error // source at sysutil/user.go -func MustFindUser(uname string) *user.User -func LoginUser() *user.User -func CurrentUser() *user.User -func UHomeDir() string -func UserHomeDir() string -func HomeDir() string -func UserDir(subPath string) string -func UserCacheDir(subPath string) string -func UserConfigDir(subPath string) string -func ExpandPath(path string) string +func MustFindUser(uname string) *user.User +func LoginUser() *user.User +func CurrentUser() *user.User +func UHomeDir() string +func UserHomeDir() string +func HomeDir() string +func UserDir(subPath string) string +func UserCacheDir(subPath string) string +func UserConfigDir(subPath string) string +func ExpandPath(path string) string +func ExpandHome(path string) string // source at sysutil/user_nonwin.go -func ChangeUserByName(newUname string) (err error) -func ChangeUserUidGid(newUid int, newGid int) (err error) +func ChangeUserByName(newUname string) (err error) +func ChangeUserUidGid(newUid int, newGid int) (err error) ``` -### Testing +### Testing Utils > Package `github.com/gookit/goutil/testutil` ```go // source at testutil/buffer.go -func NewBuffer() *Buffer +func NewBuffer() *Buffer // source at testutil/envmock.go -func MockEnvValue(key, val string, fn func(nv string)) -func MockEnvValues(kvMap map[string]string, fn func()) -func MockOsEnvByText(envText string, fn func()) -func MockOsEnv(mp map[string]string, fn func()) -func ClearOSEnv() { os.Clearenv() } -func RevertOSEnv() -func MockCleanOsEnv(mp map[string]string, fn func()) +func MockEnvValue(key, val string, fn func(nv string)) +func MockEnvValues(kvMap map[string]string, fn func()) +func MockOsEnvByText(envText string, fn func()) +func MockOsEnv(mp map[string]string, fn func()) +func ClearOSEnv() +func RevertOSEnv() +func MockCleanOsEnv(mp map[string]string, fn func()) +// source at testutil/fsmock.go +func NewDirEnt(fpath string, isDir ...bool) *DirEnt // source at testutil/httpmock.go -func NewHttpRequest(method, path string, data *MD) *http.Request -func MockRequest(h http.Handler, method, path string, data *MD) *httptest.ResponseRecorder +func NewHttpRequest(method, path string, data *MD) *http.Request +func MockRequest(h http.Handler, method, path string, data *MD) *httptest.ResponseRecorder // source at testutil/testutil.go -func DiscardStdout() error -func ReadOutput() (s string) -func RewriteStdout() -func RestoreStdout(printData ...bool) (s string) -func RewriteStderr() -func RestoreStderr(printData ...bool) (s string) +func DiscardStdout() error +func ReadOutput() (s string) +func RewriteStdout() +func RestoreStdout(printData ...bool) (s string) +func RewriteStderr() +func RestoreStderr(printData ...bool) (s string) // source at testutil/writer.go -func NewTestWriter() *TestWriter +func NewTestWriter() *TestWriter ``` ### Timex @@ -1177,47 +1422,57 @@ func NewTestWriter() *TestWriter Provides an enhanced time.Time implementation, and add more commonly used functional methods. ```go +// source at timex/gotime.go +func SetLocalByName(tzName string) error +func NowAddDay(day int) time.Time +func NowAddHour(hour int) time.Time +func NowAddMinutes(minutes int) time.Time +func NowAddSec(seconds int) time.Time +func NowAddSeconds(seconds int) time.Time +func NowHourStart() time.Time +func NowHourEnd() time.Time +func AddDay(t time.Time, day int) time.Time +func AddHour(t time.Time, hour int) time.Time +func AddMinutes(t time.Time, minutes int) time.Time +func AddSeconds(t time.Time, seconds int) time.Time +func AddSec(t time.Time, seconds int) time.Time +func HourStart(t time.Time) time.Time +func HourEnd(t time.Time) time.Time +func DayStart(t time.Time) time.Time +func DayEnd(t time.Time) time.Time +func TodayStart() time.Time +func TodayEnd() time.Time // source at timex/template.go -func ToLayout(template string) string +func ToLayout(template string) string // source at timex/timex.go -func Now() *Time -func New(t time.Time) *Time -func Wrap(t time.Time) *Time -func FromTime(t time.Time) *Time -func Local() *Time -func FromUnix(sec int64) *Time -func FromDate(s string, template ...string) (*Time, error) -func FromString(s string, layouts ...string) (*Time, error) -func LocalByName(tzName string) *Time +func Now() *Time +func New(t time.Time) *Time +func Wrap(t time.Time) *Time +func FromTime(t time.Time) *Time +func Local() *Time +func FromUnix(sec int64) *Time +func FromDate(s string, template ...string) (*Time, error) +func FromString(s string, layouts ...string) (*Time, error) +func LocalByName(tzName string) *Time // source at timex/util.go -func NowUnix() int64 -func SetLocalByName(tzName string) error -func Format(t time.Time) string -func FormatBy(t time.Time, layout string) string -func Date(t time.Time, template string) string -func DateFormat(t time.Time, template string) string -func FormatByTpl(t time.Time, template string) string -func FormatUnix(sec int64) string -func FormatUnixBy(sec int64, layout string) string -func FormatUnixByTpl(sec int64, template string) string -func NowAddDay(day int) time.Time -func NowAddHour(hour int) time.Time -func NowAddMinutes(minutes int) time.Time -func NowAddSeconds(seconds int) time.Time -func NowHourStart() time.Time -func NowHourEnd() time.Time -func AddDay(t time.Time, day int) time.Time -func AddHour(t time.Time, hour int) time.Time -func AddMinutes(t time.Time, minutes int) time.Time -func AddSeconds(t time.Time, seconds int) time.Time -func HourStart(t time.Time) time.Time -func HourEnd(t time.Time) time.Time -func DayStart(t time.Time) time.Time -func DayEnd(t time.Time) time.Time -func TodayStart() time.Time -func TodayEnd() time.Time -func HowLongAgo(sec int64) string -func ToDuration(s string) (time.Duration, error) +func NowUnix() int64 +func Format(t time.Time) string +func FormatBy(t time.Time, layout string) string +func Date(t time.Time, template ...string) string +func Datetime(t time.Time, template ...string) string +func DateFormat(t time.Time, template string) string +func FormatByTpl(t time.Time, template string) string +func FormatUnix(sec int64, layout ...string) string +func FormatUnixBy(sec int64, layout string) string +func FormatUnixByTpl(sec int64, template ...string) string +func HowLongAgo(sec int64) string +func ToTime(s string, layouts ...string) (time.Time, error) +func ToDur(s string) (time.Duration, error) +func ToDuration(s string) (time.Duration, error) +func IsDuration(s string) bool +func TryToTime(s string, bt time.Time) (time.Time, error) +func InRange(dst, start, end time.Time) bool +func ParseRange(expr string, opt *ParseRangeOpt) (start, end time.Time, err error) ``` #### Timex Usage diff --git a/vendor/github.com/gookit/goutil/arrutil/README.md b/vendor/github.com/gookit/goutil/arrutil/README.md index 9f9351ced..1a1467a95 100644 --- a/vendor/github.com/gookit/goutil/arrutil/README.md +++ b/vendor/github.com/gookit/goutil/arrutil/README.md @@ -12,6 +12,8 @@ go get github.com/gookit/goutil/arrutil ## Functions API +> **Note**: doc by run `go doc ./arrutil` + ```go func AnyToString(arr any) string func CloneSlice(data any) interface{} diff --git a/vendor/github.com/gookit/goutil/arrutil/arrutil.go b/vendor/github.com/gookit/goutil/arrutil/arrutil.go index 6f2f1bb5a..dff26561f 100644 --- a/vendor/github.com/gookit/goutil/arrutil/arrutil.go +++ b/vendor/github.com/gookit/goutil/arrutil/arrutil.go @@ -104,11 +104,15 @@ func RandomOne[T any](arr []T) T { } // Unique value in the given slice data. -func Unique[T ~string | comdef.XintOrFloat](arr []T) []T { - valMap := make(map[T]struct{}, len(arr)) - uniArr := make([]T, 0, len(arr)) +func Unique[T ~string | comdef.XintOrFloat](list []T) []T { + if len(list) < 2 { + return list + } - for _, t := range arr { + valMap := make(map[T]struct{}, len(list)) + uniArr := make([]T, 0, len(list)) + + for _, t := range list { if _, ok := valMap[t]; !ok { valMap[t] = struct{}{} uniArr = append(uniArr, t) @@ -116,3 +120,13 @@ func Unique[T ~string | comdef.XintOrFloat](arr []T) []T { } return uniArr } + +// IndexOf value in given slice. +func IndexOf[T ~string | comdef.XintOrFloat](val T, list []T) int { + for i, v := range list { + if v == val { + return i + } + } + return -1 +} diff --git a/vendor/github.com/gookit/goutil/arrutil/check.go b/vendor/github.com/gookit/goutil/arrutil/check.go index 93c7d838e..60117f07c 100644 --- a/vendor/github.com/gookit/goutil/arrutil/check.go +++ b/vendor/github.com/gookit/goutil/arrutil/check.go @@ -4,6 +4,7 @@ import ( "reflect" "strings" + "github.com/gookit/goutil/comdef" "github.com/gookit/goutil/mathutil" ) @@ -40,12 +41,48 @@ func StringsHas(ss []string, val string) bool { return false } -// HasValue check array(strings, intXs, uintXs) should be contained the given value(int(X),string). -func HasValue(arr, val any) bool { - return Contains(arr, val) +// NotIn check the given value whether not in the list +func NotIn[T comdef.ScalarType](value T, list []T) bool { + return !In(value, list) } -// Contains check array(strings, intXs, uintXs) should be contained the given value(int(X),string). +// In check the given value whether in the list +func In[T comdef.ScalarType](value T, list []T) bool { + for _, elem := range list { + if elem == value { + return true + } + } + return false +} + +// ContainsAll check given values is sub-list of sample list. +func ContainsAll[T comdef.ScalarType](list, values []T) bool { + return IsSubList(values, list) +} + +// IsSubList check given values is sub-list of sample list. +func IsSubList[T comdef.ScalarType](values, list []T) bool { + for _, value := range values { + if !In(value, list) { + return false + } + } + return true +} + +// IsParent check given values is parent-list of samples. +func IsParent[T comdef.ScalarType](values, list []T) bool { + return IsSubList(list, values) +} + +// HasValue check array(strings, intXs, uintXs) should be contained the given value(int(X),string). +func HasValue(arr, val any) bool { return Contains(arr, val) } + +// Contains check slice/array(strings, intXs, uintXs) should be contained the given value(int(X),string). +// +// TIP: Difference the In(), Contains() will try to convert value type, +// and Contains() support array type. func Contains(arr, val any) bool { if val == nil || arr == nil { return false diff --git a/vendor/github.com/gookit/goutil/arrutil/collection.go b/vendor/github.com/gookit/goutil/arrutil/collection.go index f17fa95af..ac556bfa3 100644 --- a/vendor/github.com/gookit/goutil/arrutil/collection.go +++ b/vendor/github.com/gookit/goutil/arrutil/collection.go @@ -160,6 +160,58 @@ func CloneSlice(data any) any { return reflect.AppendSlice(reflect.New(reflect.SliceOf(typeOfData.Elem())).Elem(), reflect.ValueOf(data)).Interface() } +// Differences Produces the set difference of two slice according to a comparer function. +// +// first: the first slice. MUST BE A SLICE. +// second: the second slice. MUST BE A SLICE. +// fn: the comparer function. +// returns: the difference of the two slices. +func Differences[T any](first, second []T, fn Comparer) []T { + typeOfFirst := reflect.TypeOf(first) + if typeOfFirst.Kind() != reflect.Slice { + panic("collections.Excepts: first must be a slice") + } + + typeOfSecond := reflect.TypeOf(second) + if typeOfSecond.Kind() != reflect.Slice { + panic("collections.Excepts: second must be a slice") + } + + firstLen := len(first) + if firstLen == 0 { + return CloneSlice(second).([]T) + } + + secondLen := len(second) + if secondLen == 0 { + return CloneSlice(first).([]T) + } + + max := firstLen + if secondLen > firstLen { + max = secondLen + } + + result := make([]T, 0) + for i := 0; i < max; i++ { + if i < firstLen { + s := first[i] + if i, _ := TwowaySearch(second, s, fn); i < 0 { + result = append(result, s) + } + } + + if i < secondLen { + t := second[i] + if i, _ := TwowaySearch(first, t, fn); i < 0 { + result = append(result, t) + } + } + } + + return result +} + // Excepts Produces the set difference of two slice according to a comparer function. // // first: the first slice. MUST BE A SLICE. diff --git a/vendor/github.com/gookit/goutil/arrutil/collection_gte118.go b/vendor/github.com/gookit/goutil/arrutil/collection_gte118.go index f580c599e..fe406caf1 100644 --- a/vendor/github.com/gookit/goutil/arrutil/collection_gte118.go +++ b/vendor/github.com/gookit/goutil/arrutil/collection_gte118.go @@ -2,12 +2,14 @@ package arrutil // type MapFn func(obj T) (target V, find bool) -// Map an object list [object0{},object1{},...] to flatten list [object0.someKey, object1.someKey, ...] +// Map a list to new list +// +// eg: mapping [object0{},object1{},...] to flatten list [object0.someKey, object1.someKey, ...] func Map[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V { flatArr := make([]V, 0, len(list)) for _, obj := range list { - if target, find := mapFn(obj); find { + if target, ok := mapFn(obj); ok { flatArr = append(flatArr, target) } } diff --git a/vendor/github.com/gookit/goutil/arrutil/convert.go b/vendor/github.com/gookit/goutil/arrutil/convert.go index cff56bd09..34dbb3c43 100644 --- a/vendor/github.com/gookit/goutil/arrutil/convert.go +++ b/vendor/github.com/gookit/goutil/arrutil/convert.go @@ -6,7 +6,9 @@ import ( "strconv" "strings" + "github.com/gookit/goutil/comdef" "github.com/gookit/goutil/mathutil" + "github.com/gookit/goutil/reflects" "github.com/gookit/goutil/strutil" ) @@ -14,7 +16,7 @@ import ( var ErrInvalidType = errors.New("the input param type is invalid") /************************************************************* - * helper func for strings + * Join func for slice *************************************************************/ // JoinStrings alias of strings.Join @@ -27,32 +29,21 @@ func StringsJoin(sep string, ss ...string) string { return strings.Join(ss, sep) } -// StringsToInts string slice to int slice -func StringsToInts(ss []string) (ints []int, err error) { - for _, str := range ss { - iVal, err := strconv.Atoi(str) - if err != nil { - return nil, err +// JoinSlice join []any slice to string. +func JoinSlice(sep string, arr ...any) string { + if arr == nil { + return "" + } + + var sb strings.Builder + for i, v := range arr { + if i > 0 { + sb.WriteString(sep) } - - ints = append(ints, iVal) + sb.WriteString(strutil.QuietString(v)) } - return -} -// MustToStrings convert array or slice to []string -func MustToStrings(arr any) []string { - ret, _ := ToStrings(arr) - return ret -} - -// StringsToSlice convert []string to []any -func StringsToSlice(ss []string) []any { - args := make([]any, len(ss)) - for i, s := range ss { - args[i] = s - } - return args + return sb.String() } /************************************************************* @@ -88,14 +79,79 @@ func MustToInt64s(arr any) []int64 { func SliceToInt64s(arr []any) []int64 { i64s := make([]int64, len(arr)) for i, v := range arr { - i64s[i] = mathutil.MustInt64(v) + i64s[i] = mathutil.QuietInt64(v) } return i64s } +// StringsAsInts convert and ignore error +func StringsAsInts(ss []string) []int { + ints, _ := StringsTryInts(ss) + return ints +} + +// StringsToInts string slice to int slice +func StringsToInts(ss []string) (ints []int, err error) { + return StringsTryInts(ss) +} + +// StringsTryInts string slice to int slice +func StringsTryInts(ss []string) (ints []int, err error) { + for _, str := range ss { + iVal, err := strconv.Atoi(str) + if err != nil { + return nil, err + } + + ints = append(ints, iVal) + } + return +} + +// AnyToSlice convert any(allow: array,slice) to []any +func AnyToSlice(sl any) (ls []any, err error) { + rfKeys := reflect.ValueOf(sl) + if rfKeys.Kind() != reflect.Slice && rfKeys.Kind() != reflect.Array { + return nil, ErrInvalidType + } + + for i := 0; i < rfKeys.Len(); i++ { + ls = append(ls, rfKeys.Index(i).Interface()) + } + return +} + +// AnyToStrings convert array or slice to []string +func AnyToStrings(arr any) []string { + ret, _ := ToStrings(arr) + return ret +} + +// MustToStrings convert array or slice to []string +func MustToStrings(arr any) []string { + ret, err := ToStrings(arr) + if err != nil { + panic(err) + } + return ret +} + +// StringsToSlice convert []string to []any +func StringsToSlice(ss []string) []any { + args := make([]any, len(ss)) + for i, s := range ss { + args[i] = s + } + return args +} + // ToStrings convert any(allow: array,slice) to []string func ToStrings(arr any) (ret []string, err error) { rv := reflect.ValueOf(arr) + if rv.Kind() == reflect.String { + return []string{rv.String()}, nil + } + if rv.Kind() != reflect.Slice && rv.Kind() != reflect.Array { err = ErrInvalidType return @@ -114,13 +170,47 @@ func ToStrings(arr any) (ret []string, err error) { // SliceToStrings convert []any to []string func SliceToStrings(arr []any) []string { + return QuietStrings(arr) +} + +// QuietStrings convert []any to []string +func QuietStrings(arr []any) []string { ss := make([]string, len(arr)) for i, v := range arr { - ss[i] = strutil.MustString(v) + ss[i] = strutil.QuietString(v) } return ss } +// ConvType convert type of slice elements to new type slice, by the given newElemTyp type. +// +// Supports conversion between []string, []intX, []uintX, []floatX. +// +// Usage: +// +// ints, _ := arrutil.ConvType([]string{"12", "23"}, 1) // []int{12, 23} +func ConvType[T any, R any](arr []T, newElemTyp R) ([]R, error) { + newArr := make([]R, len(arr)) + elemTyp := reflect.TypeOf(newElemTyp) + + for i, elem := range arr { + var anyElem any = elem + // type is same. + if _, ok := anyElem.(R); ok { + newArr[i] = anyElem.(R) + continue + } + + // need conv type. + rfVal, err := reflects.ValueByType(elem, elemTyp) + if err != nil { + return nil, err + } + newArr[i] = rfVal.Interface().(R) + } + return newArr, nil +} + // AnyToString simple and quickly convert any array, slice to string func AnyToString(arr any) string { return NewFormatter(arr).Format() @@ -143,26 +233,40 @@ func ToString(arr []any) string { if i > 0 { sb.WriteByte(',') } - sb.WriteString(strutil.MustString(v)) + sb.WriteString(strutil.QuietString(v)) } sb.WriteByte(']') return sb.String() } -// JoinSlice join []any slice to string. -func JoinSlice(sep string, arr ...any) string { - if arr == nil { - return "" - } +// CombineToMap combine two slice to map[K]V. +// +// If keys length is greater than values, the extra keys will be ignored. +func CombineToMap[K comdef.SortedType, V any](keys []K, values []V) map[K]V { + ln := len(values) + mp := make(map[K]V, len(keys)) - var sb strings.Builder - for i, v := range arr { - if i > 0 { - sb.WriteString(sep) + for i, key := range keys { + if i >= ln { + break } - sb.WriteString(strutil.MustString(v)) + mp[key] = values[i] } + return mp +} - return sb.String() +// CombineToSMap combine two string-slice to map[string]string +func CombineToSMap(keys, values []string) map[string]string { + ln := len(values) + mp := make(map[string]string, len(keys)) + + for i, key := range keys { + if ln > i { + mp[key] = values[i] + } else { + mp[key] = "" + } + } + return mp } diff --git a/vendor/github.com/gookit/goutil/arrutil/enum.go b/vendor/github.com/gookit/goutil/arrutil/enum.go index 5897b2218..a5a70f0ec 100644 --- a/vendor/github.com/gookit/goutil/arrutil/enum.go +++ b/vendor/github.com/gookit/goutil/arrutil/enum.go @@ -42,6 +42,11 @@ func (ss Strings) Join(sep string) string { // Has given element func (ss Strings) Has(sub string) bool { + return ss.Contains(sub) +} + +// Contains given element +func (ss Strings) Contains(sub string) bool { for _, s := range ss { if s == sub { return true @@ -49,3 +54,11 @@ func (ss Strings) Has(sub string) bool { } return false } + +// First element value. +func (ss Strings) First() string { + if len(ss) > 0 { + return ss[0] + } + return "" +} diff --git a/vendor/github.com/gookit/goutil/basefn/basefunc.go b/vendor/github.com/gookit/goutil/basefn/basefunc.go new file mode 100644 index 000000000..decc3187d --- /dev/null +++ b/vendor/github.com/gookit/goutil/basefn/basefunc.go @@ -0,0 +1,80 @@ +// Package basefn provide some no-dependents util functions +package basefn + +import "fmt" + +// Panicf format panic message use fmt.Sprintf +func Panicf(format string, v ...any) { + panic(fmt.Sprintf(format, v...)) +} + +// MustOK if error is not empty, will panic +func MustOK(err error) { + if err != nil { + panic(err) + } +} + +// Must if error is not empty, will panic +func Must[T any](v T, err error) T { + if err != nil { + panic(err) + } + return v +} + +// ErrOnFail return input error on cond is false, otherwise return nil +func ErrOnFail(cond bool, err error) error { + return OrError(cond, err) +} + +// OrError return input error on cond is false, otherwise return nil +func OrError(cond bool, err error) error { + if !cond { + return err + } + return nil +} + +// FirstOr get first elem or elseVal +func FirstOr[T any](sl []T, elseVal T) T { + if len(sl) > 0 { + return sl[0] + } + return elseVal +} + +// OrValue get +func OrValue[T any](cond bool, okVal, elVal T) T { + if cond { + return okVal + } + return elVal +} + +// OrReturn call okFunc() on condition is true, else call elseFn() +func OrReturn[T any](cond bool, okFn, elseFn func() T) T { + if cond { + return okFn() + } + return elseFn() +} + +// ErrFunc type +type ErrFunc func() error + +// CallOn call func on condition is true +func CallOn(cond bool, fn ErrFunc) error { + if cond { + return fn() + } + return nil +} + +// CallOrElse call okFunc() on condition is true, else call elseFn() +func CallOrElse(cond bool, okFn, elseFn ErrFunc) error { + if cond { + return okFn() + } + return elseFn() +} diff --git a/vendor/github.com/gookit/goutil/fmtutil/time.go b/vendor/github.com/gookit/goutil/basefn/extfunc.go similarity index 62% rename from vendor/github.com/gookit/goutil/fmtutil/time.go rename to vendor/github.com/gookit/goutil/basefn/extfunc.go index d6ccfc4d6..15be452cf 100644 --- a/vendor/github.com/gookit/goutil/fmtutil/time.go +++ b/vendor/github.com/gookit/goutil/basefn/extfunc.go @@ -1,6 +1,28 @@ -package fmtutil +package basefn -import "fmt" +import ( + "fmt" +) + +// DataSize format bytes number friendly. eg: 1024 => 1KB, 1024*1024 => 1MB +// +// Usage: +// +// file, err := os.Open(path) +// fl, err := file.Stat() +// fmtSize := DataSize(fl.Size()) +func DataSize(size uint64) string { + switch { + case size < 1024: + return fmt.Sprintf("%dB", size) + case size < 1024*1024: + return fmt.Sprintf("%.2fK", float64(size)/1024) + case size < 1024*1024*1024: + return fmt.Sprintf("%.2fM", float64(size)/1024/1024) + default: + return fmt.Sprintf("%.2fG", float64(size)/1024/1024/1024) + } +} var timeFormats = [][]int{ {0}, diff --git a/vendor/github.com/gookit/goutil/byteutil/README.md b/vendor/github.com/gookit/goutil/byteutil/README.md new file mode 100644 index 000000000..89147dc7c --- /dev/null +++ b/vendor/github.com/gookit/goutil/byteutil/README.md @@ -0,0 +1,55 @@ +# Bytes Util + +Provide some commonly bytes util functions. + +## Install + +```shell +go get github.com/gookit/goutil/byteutil +``` + +## Go docs + +- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/byteutil) + +## Functions API + +> **Note**: doc by run `go doc ./byteutil` + +```go +func AppendAny(dst []byte, v any) []byte +func FirstLine(bs []byte) []byte +func IsNumChar(c byte) bool +func Md5(src any) []byte +func Random(length int) ([]byte, error) +func SafeString(bs []byte, err error) string +func StrOrErr(bs []byte, err error) (string, error) +func String(b []byte) string +func ToString(b []byte) string +type Buffer struct{ ... } +func NewBuffer() *Buffer +type BytesEncoder interface{ ... } +type ChanPool struct{ ... } +func NewChanPool(maxSize int, width int, capWidth int) *ChanPool +type StdEncoder struct{ ... } +func NewStdEncoder(encFn func(src []byte) []byte, decFn func(src []byte) ([]byte, error)) *StdEncoder +``` + +## Code Check & Testing + +```bash +gofmt -w -l ./ +golint ./... +``` + +**Testing**: + +```shell +go test -v ./byteutil/... +``` + +**Test limit by regexp**: + +```shell +go test -v -run ^TestSetByKeys ./byteutil/... +``` diff --git a/vendor/github.com/gookit/goutil/byteutil/buffer.go b/vendor/github.com/gookit/goutil/byteutil/buffer.go new file mode 100644 index 000000000..c0a6917fe --- /dev/null +++ b/vendor/github.com/gookit/goutil/byteutil/buffer.go @@ -0,0 +1,65 @@ +package byteutil + +import ( + "bytes" + "fmt" + "strings" +) + +// Buffer wrap and extends the bytes.Buffer +type Buffer struct { + bytes.Buffer +} + +// NewBuffer instance +func NewBuffer() *Buffer { + return &Buffer{} +} + +// WriteAny type value to buffer +func (b *Buffer) WriteAny(vs ...any) { + for _, v := range vs { + _, _ = b.Buffer.WriteString(fmt.Sprint(v)) + } +} + +// QuietWriteByte to buffer +func (b *Buffer) QuietWriteByte(c byte) { + _ = b.WriteByte(c) +} + +// QuietWritef write message to buffer +func (b *Buffer) QuietWritef(tpl string, vs ...any) { + _, _ = b.WriteString(fmt.Sprintf(tpl, vs...)) +} + +// Writeln write message to buffer with newline +func (b *Buffer) Writeln(ss ...string) { + b.QuietWriteln(ss...) +} + +// QuietWriteln write message to buffer with newline +func (b *Buffer) QuietWriteln(ss ...string) { + _, _ = b.WriteString(strings.Join(ss, "")) + _ = b.WriteByte('\n') +} + +// QuietWriteString to buffer +func (b *Buffer) QuietWriteString(ss ...string) { + _, _ = b.WriteString(strings.Join(ss, "")) +} + +// MustWriteString to buffer +func (b *Buffer) MustWriteString(ss ...string) { + _, err := b.WriteString(strings.Join(ss, "")) + if err != nil { + panic(err) + } +} + +// ResetAndGet buffer string. +func (b *Buffer) ResetAndGet() string { + s := b.String() + b.Reset() + return s +} diff --git a/vendor/github.com/gookit/goutil/byteutil/byteutil.go b/vendor/github.com/gookit/goutil/byteutil/byteutil.go new file mode 100644 index 000000000..45c394556 --- /dev/null +++ b/vendor/github.com/gookit/goutil/byteutil/byteutil.go @@ -0,0 +1,115 @@ +package byteutil + +import ( + "bytes" + "fmt" + "math/rand" + "strconv" + "time" + "unsafe" +) + +// Random bytes generate +func Random(length int) ([]byte, error) { + b := make([]byte, length) + // Note that err == nil only if we read len(b) bytes. + if _, err := rand.Read(b); err != nil { + return nil, err + } + return b, nil +} + +// FirstLine from command output +func FirstLine(bs []byte) []byte { + if i := bytes.IndexByte(bs, '\n'); i >= 0 { + return bs[0:i] + } + return bs +} + +// StrOrErr convert to string, return empty string on error. +func StrOrErr(bs []byte, err error) (string, error) { + if err != nil { + return "", err + } + return string(bs), err +} + +// SafeString convert to string, return empty string on error. +func SafeString(bs []byte, err error) string { + if err != nil { + return "" + } + return string(bs) +} + +// String unsafe convert bytes to string +func String(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +// ToString convert bytes to string +func ToString(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +// AppendAny append any value to byte slice +func AppendAny(dst []byte, v any) []byte { + if v == nil { + return append(dst, ""...) + } + + switch val := v.(type) { + case []byte: + dst = append(dst, val...) + case string: + dst = append(dst, val...) + case int: + dst = strconv.AppendInt(dst, int64(val), 10) + case int8: + dst = strconv.AppendInt(dst, int64(val), 10) + case int16: + dst = strconv.AppendInt(dst, int64(val), 10) + case int32: + dst = strconv.AppendInt(dst, int64(val), 10) + case int64: + dst = strconv.AppendInt(dst, val, 10) + case uint: + dst = strconv.AppendUint(dst, uint64(val), 10) + case uint8: + dst = strconv.AppendUint(dst, uint64(val), 10) + case uint16: + dst = strconv.AppendUint(dst, uint64(val), 10) + case uint32: + dst = strconv.AppendUint(dst, uint64(val), 10) + case uint64: + dst = strconv.AppendUint(dst, val, 10) + case float32: + dst = strconv.AppendFloat(dst, float64(val), 'f', -1, 32) + case float64: + dst = strconv.AppendFloat(dst, val, 'f', -1, 64) + case bool: + dst = strconv.AppendBool(dst, val) + case time.Time: + dst = val.AppendFormat(dst, time.RFC3339) + case time.Duration: + dst = strconv.AppendInt(dst, int64(val), 10) + case error: + dst = append(dst, val.Error()...) + case fmt.Stringer: + dst = append(dst, val.String()...) + default: + dst = append(dst, fmt.Sprint(v)...) + } + return dst +} + +// Cut bytes. like the strings.Cut() +func Cut(bs []byte, sep byte) (before, after []byte, found bool) { + if i := bytes.IndexByte(bs, sep); i >= 0 { + return bs[:i], bs[i+1:], true + } + + before = bs + return +} diff --git a/vendor/github.com/gookit/goutil/byteutil/bytex.go b/vendor/github.com/gookit/goutil/byteutil/bytex.go new file mode 100644 index 000000000..e66ea61f5 --- /dev/null +++ b/vendor/github.com/gookit/goutil/byteutil/bytex.go @@ -0,0 +1,19 @@ +// Package byteutil Provide some bytes utils functions or structs +package byteutil + +import ( + "crypto/md5" + "fmt" +) + +// Md5 Generate a 32-bit md5 bytes +func Md5(src any) []byte { + h := md5.New() + + if s, ok := src.(string); ok { + h.Write([]byte(s)) + } else { + h.Write([]byte(fmt.Sprint(src))) + } + return h.Sum(nil) +} diff --git a/vendor/github.com/gookit/goutil/byteutil/check.go b/vendor/github.com/gookit/goutil/byteutil/check.go new file mode 100644 index 000000000..7be9e4b4b --- /dev/null +++ b/vendor/github.com/gookit/goutil/byteutil/check.go @@ -0,0 +1,4 @@ +package byteutil + +// IsNumChar returns true if the given character is a numeric, otherwise false. +func IsNumChar(c byte) bool { return c >= '0' && c <= '9' } diff --git a/vendor/github.com/gookit/goutil/byteutil/encoder.go b/vendor/github.com/gookit/goutil/byteutil/encoder.go new file mode 100644 index 000000000..8ec965e5d --- /dev/null +++ b/vendor/github.com/gookit/goutil/byteutil/encoder.go @@ -0,0 +1,63 @@ +package byteutil + +import ( + "encoding/base64" + "encoding/hex" +) + +// BytesEncoder interface +type BytesEncoder interface { + Encode(src []byte) []byte + Decode(src []byte) ([]byte, error) +} + +// StdEncoder implement the BytesEncoder +type StdEncoder struct { + encodeFn func(src []byte) []byte + decodeFn func(src []byte) ([]byte, error) +} + +// NewStdEncoder instance +func NewStdEncoder(encFn func(src []byte) []byte, decFn func(src []byte) ([]byte, error)) *StdEncoder { + return &StdEncoder{ + encodeFn: encFn, + decodeFn: decFn, + } +} + +// Encode input +func (e *StdEncoder) Encode(src []byte) []byte { + return e.encodeFn(src) +} + +// Decode input +func (e *StdEncoder) Decode(src []byte) ([]byte, error) { + return e.decodeFn(src) +} + +var ( + // HexEncoder instance + HexEncoder = NewStdEncoder(func(src []byte) []byte { + dst := make([]byte, hex.EncodedLen(len(src))) + hex.Encode(dst, src) + return dst + }, func(src []byte) ([]byte, error) { + n, err := hex.Decode(src, src) + return src[:n], err + }) + + // B64Encoder instance + B64Encoder = NewStdEncoder(func(src []byte) []byte { + b64Dst := make([]byte, base64.StdEncoding.EncodedLen(len(src))) + base64.StdEncoding.Encode(b64Dst, src) + return b64Dst + }, func(src []byte) ([]byte, error) { + dBuf := make([]byte, base64.StdEncoding.DecodedLen(len(src))) + n, err := base64.StdEncoding.Decode(dBuf, src) + if err != nil { + return nil, err + } + + return dBuf[:n], err + }) +) diff --git a/vendor/github.com/gookit/goutil/byteutil/pool.go b/vendor/github.com/gookit/goutil/byteutil/pool.go new file mode 100644 index 000000000..c6f09e018 --- /dev/null +++ b/vendor/github.com/gookit/goutil/byteutil/pool.go @@ -0,0 +1,64 @@ +package byteutil + +// ChanPool struct +// +// Usage: +// +// bp := strutil.NewByteChanPool(500, 1024, 1024) +// buf:=bp.Get() +// defer bp.Put(buf) +// // use buf do something ... +// +// refer https://www.flysnow.org/2020/08/21/golang-chan-byte-pool.html +// from https://github.com/minio/minio/blob/master/internal/bpool/bpool.go +type ChanPool struct { + c chan []byte + w int + wcap int +} + +// NewChanPool instance +func NewChanPool(maxSize int, width int, capWidth int) *ChanPool { + return &ChanPool{ + c: make(chan []byte, maxSize), + w: width, + wcap: capWidth, + } +} + +// Get gets a []byte from the BytePool, or creates a new one if none are +// available in the pool. +func (bp *ChanPool) Get() (b []byte) { + select { + case b = <-bp.c: + // reuse existing buffer + default: + // create new buffer + if bp.wcap > 0 { + b = make([]byte, bp.w, bp.wcap) + } else { + b = make([]byte, bp.w) + } + } + return +} + +// Put returns the given Buffer to the BytePool. +func (bp *ChanPool) Put(b []byte) { + select { + case bp.c <- b: + // buffer went back into pool + default: + // buffer didn't go back into pool, just discard + } +} + +// Width returns the width of the byte arrays in this pool. +func (bp *ChanPool) Width() (n int) { + return bp.w +} + +// WidthCap returns the cap width of the byte arrays in this pool. +func (bp *ChanPool) WidthCap() (n int) { + return bp.wcap +} diff --git a/vendor/github.com/gookit/goutil/cliutil/cmdline/builder.go b/vendor/github.com/gookit/goutil/cliutil/cmdline/builder.go index 41cf7f32c..1dceeec77 100644 --- a/vendor/github.com/gookit/goutil/cliutil/cmdline/builder.go +++ b/vendor/github.com/gookit/goutil/cliutil/cmdline/builder.go @@ -2,27 +2,28 @@ package cmdline import ( "strings" + + "github.com/gookit/goutil/strutil" ) // LineBuilder build command line string. // codes refer from strings.Builder type LineBuilder struct { - buf []byte + strings.Builder } // NewBuilder create func NewBuilder(binFile string, args ...string) *LineBuilder { b := &LineBuilder{} - b.AddArg(binFile) + + if binFile != "" { + b.AddArg(binFile) + } + b.AddArray(args) return b } -// LineBuild build command line string by given args. -func LineBuild(binFile string, args []string) string { - return NewBuilder(binFile, args...).String() -} - // AddArg to builder func (b *LineBuilder) AddArg(arg string) { _, _ = b.WriteString(arg) @@ -40,52 +41,45 @@ func (b *LineBuilder) AddArray(args []string) { } } +// AddAny args to builder +func (b *LineBuilder) AddAny(args ...any) { + for _, arg := range args { + _, _ = b.WriteString(strutil.SafeString(arg)) + } +} + // WriteString arg string to the builder, will auto quote special string. // refer strconv.Quote() func (b *LineBuilder) WriteString(a string) (int, error) { var quote byte - if strings.ContainsRune(a, '"') { + if pos := strings.IndexByte(a, '"'); pos > -1 { quote = '\'' - } else if a == "" || strings.ContainsRune(a, '\'') || strings.ContainsRune(a, ' ') { + // fix: a = `--pretty=format:"one two three"` + if pos > 0 && '"' == a[len(a)-1] { + quote = 0 + } + } else if pos := strings.IndexByte(a, '\''); pos > -1 { + quote = '"' + // fix: a = "--pretty=format:'one two three'" + if pos > 0 && '\'' == a[len(a)-1] { + quote = 0 + } + } else if a == "" || strings.ContainsRune(a, ' ') { quote = '"' } - // add sep on first write. - if b.buf != nil { - b.buf = append(b.buf, ' ') + // add sep on not-first write. + if b.Len() != 0 { + _ = b.WriteByte(' ') } - // no quote char + // no quote char OR not need quote if quote == 0 { - b.buf = append(b.buf, a...) - return len(a) + 1, nil + return b.Builder.WriteString(a) } - b.buf = append(b.buf, quote) // add start quote - b.buf = append(b.buf, a...) - b.buf = append(b.buf, quote) // add end quote - return len(a) + 3, nil + _ = b.WriteByte(quote) // add start quote + n, err := b.Builder.WriteString(a) + _ = b.WriteByte(quote) // add end quote + return n, err } - -// String to command line string -func (b *LineBuilder) String() string { - return string(b.buf) -} - -// Len of the builder -func (b *LineBuilder) Len() int { - return len(b.buf) -} - -// Reset builder -func (b *LineBuilder) Reset() { - b.buf = nil -} - -// grow copies the buffer to a new, larger buffer so that there are at least n -// bytes of capacity beyond len(b.buf). -// func (b *LineBuilder) grow(n int) { -// buf := make([]byte, len(b.buf), 2*cap(b.buf)+n) -// copy(buf, b.buf) -// b.buf = buf -// } diff --git a/vendor/github.com/gookit/goutil/cliutil/cmdline/cmdline.go b/vendor/github.com/gookit/goutil/cliutil/cmdline/cmdline.go index 6dbebc50d..d048720fe 100644 --- a/vendor/github.com/gookit/goutil/cliutil/cmdline/cmdline.go +++ b/vendor/github.com/gookit/goutil/cliutil/cmdline/cmdline.go @@ -1,2 +1,12 @@ // Package cmdline provide quick build and parse cmd line string. package cmdline + +// LineBuild build command line string by given args. +func LineBuild(binFile string, args []string) string { + return NewBuilder(binFile, args...).String() +} + +// ParseLine input command line text. alias of the StringToOSArgs() +func ParseLine(line string) []string { + return NewParser(line).Parse() +} diff --git a/vendor/github.com/gookit/goutil/cliutil/cmdline/parser.go b/vendor/github.com/gookit/goutil/cliutil/cmdline/parser.go index 0e4ffa621..e59f0928c 100644 --- a/vendor/github.com/gookit/goutil/cliutil/cmdline/parser.go +++ b/vendor/github.com/gookit/goutil/cliutil/cmdline/parser.go @@ -1,9 +1,13 @@ package cmdline import ( - "os" + "bytes" "os/exec" "strings" + + "github.com/gookit/goutil/comdef" + "github.com/gookit/goutil/internal/comfunc" + "github.com/gookit/goutil/strutil" ) // LineParser struct @@ -11,7 +15,7 @@ import ( type LineParser struct { parsed bool // Line the full input command line text - // eg `kite top sub -a "the a message" --foo val1 --bar "val 2"` + // eg `kite top sub -a "this is a message" --foo val1 --bar "val 2"` Line string // ParseEnv parse ENV var on the line. ParseEnv bool @@ -19,6 +23,11 @@ type LineParser struct { nodes []string // the parsed args args []string + + // temp value + quoteChar byte + quoteIndex int // if > 0, mark is not on start + tempNode bytes.Buffer } // NewParser create @@ -26,11 +35,10 @@ func NewParser(line string) *LineParser { return &LineParser{Line: line} } -// ParseLine input command line text. alias of the StringToOSArgs() -func ParseLine(line string) []string { - p := &LineParser{Line: line} - - return p.Parse() +// WithParseEnv with parse ENV var +func (p *LineParser) WithParseEnv() *LineParser { + p.ParseEnv = true + return p } // AlsoEnvParse input command line text to os.Args, will parse ENV var @@ -39,90 +47,13 @@ func (p *LineParser) AlsoEnvParse() []string { return p.Parse() } -// Parse input command line text to os.Args -func (p *LineParser) Parse() []string { - if p.parsed { - return p.args - } +// NewExecCmd quick create exec.Cmd by cmdline string +func (p *LineParser) NewExecCmd() *exec.Cmd { + // parse get bin and args + binName, args := p.BinAndArgs() - p.parsed = true - p.Line = strings.TrimSpace(p.Line) - if p.Line == "" { - return p.args - } - - // enable parse Env var - if p.ParseEnv { - p.Line = os.ExpandEnv(p.Line) - } - - p.nodes = strings.Split(p.Line, " ") - if len(p.nodes) == 1 { - p.args = p.nodes - return p.args - } - - // temp value - var quoteChar, fullNode string - for _, node := range p.nodes { - if node == "" { - continue - } - - nodeLen := len(node) - start, end := node[:1], node[nodeLen-1:] - - var clearTemp bool - if start == "'" || start == `"` { - noStart := node[1:] - if quoteChar == "" { // start - // only one words. eg: `-m "msg"` - if end == start { - p.args = append(p.args, node[1:nodeLen-1]) - continue - } - - fullNode += noStart - quoteChar = start - } else if quoteChar == start { // invalid. eg: `-m "this is "message` `-m "this is "message"` - p.appendWithPrefix(strings.Trim(node, quoteChar), fullNode) - clearTemp = true // clear temp value - } else if quoteChar == end { // eg: `"has inner 'quote'"` - p.appendWithPrefix(node[:nodeLen-1], fullNode) - clearTemp = true // clear temp value - } else { // goon. eg: `-m "the 'some' message"` - fullNode += " " + node - } - } else if end == "'" || end == `"` { - noEnd := node[:nodeLen-1] - if quoteChar == "" { // end - p.appendWithPrefix(noEnd, fullNode) - clearTemp = true // clear temp value - } else if quoteChar == end { // end - p.appendWithPrefix(noEnd, fullNode) - clearTemp = true // clear temp value - } else { // goon. eg: `-m "the 'some' message"` - fullNode += " " + node - } - } else { - if quoteChar != "" { - fullNode += " " + node - } else { - p.args = append(p.args, node) - } - } - - // clear temp value - if clearTemp { - quoteChar, fullNode = "", "" - } - } - - if fullNode != "" { - p.args = append(p.args, fullNode) - } - - return p.args + // create a new Cmd instance + return exec.Command(binName, args...) } // BinAndArgs get binName and args @@ -141,19 +72,103 @@ func (p *LineParser) BinAndArgs() (bin string, args []string) { return } -// NewExecCmd quick create exec.Cmd by cmdline string -func (p *LineParser) NewExecCmd() *exec.Cmd { - // parse get bin and args - binName, args := p.BinAndArgs() +// Parse input command line text to os.Args +func (p *LineParser) Parse() []string { + if p.parsed { + return p.args + } - // create a new Cmd instance - return exec.Command(binName, args...) + p.parsed = true + p.Line = strings.TrimSpace(p.Line) + if p.Line == "" { + return p.args + } + + // enable parse Env var + if p.ParseEnv { + p.Line = comfunc.ParseEnvVar(p.Line, nil) + } + + p.nodes = strings.Split(p.Line, " ") + if len(p.nodes) == 1 { + p.args = p.nodes + return p.args + } + + for i := 0; i < len(p.nodes); i++ { + node := p.nodes[i] + if node == "" { + continue + } + + p.parseNode(node) + } + + p.nodes = p.nodes[:0] + if p.tempNode.Len() > 0 { + p.appendTempNode() + } + return p.args } -func (p *LineParser) appendWithPrefix(node, prefix string) { - if prefix != "" { - p.args = append(p.args, prefix+" "+node) +func (p *LineParser) parseNode(node string) { + maxIdx := len(node) - 1 + start, end := node[0], node[maxIdx] + + // in quotes + if p.quoteChar != 0 { + p.tempNode.WriteByte(' ') + + // end quotes + if end == p.quoteChar { + if p.quoteIndex > 0 { + p.tempNode.WriteString(node) // eg: node="--pretty=format:'one two'" + } else { + p.tempNode.WriteString(node[:maxIdx]) // remove last quote + } + p.appendTempNode() + } else { // goon ... write to temp node + p.tempNode.WriteString(node) + } + return + } + + // quote start + if start == comdef.DoubleQuote || start == comdef.SingleQuote { + // only one words. eg: `-m "msg"` + if end == start { + p.args = append(p.args, node[1:maxIdx]) + return + } + + p.quoteChar = start + p.tempNode.WriteString(node[1:]) + } else if end == comdef.DoubleQuote || end == comdef.SingleQuote { + p.args = append(p.args, node) // only one node: `msg"` } else { - p.args = append(p.args, node) + // eg: --pretty=format:'one two three' + if strutil.ContainsByte(node, comdef.DoubleQuote) { + p.quoteIndex = 1 // mark is not on start + p.quoteChar = comdef.DoubleQuote + } else if strutil.ContainsByte(node, comdef.SingleQuote) { + p.quoteIndex = 1 + p.quoteChar = comdef.SingleQuote + } + + // in quote, append to temp-node + if p.quoteChar != 0 { + p.tempNode.WriteString(node) + } else { + p.args = append(p.args, node) + } } } + +func (p *LineParser) appendTempNode() { + p.args = append(p.args, p.tempNode.String()) + + // reset context value + p.quoteChar = 0 + p.quoteIndex = 0 + p.tempNode.Reset() +} diff --git a/vendor/github.com/gookit/goutil/comdef/consts.go b/vendor/github.com/gookit/goutil/comdef/consts.go index 1534a196b..5f1cf2b1f 100644 --- a/vendor/github.com/gookit/goutil/comdef/consts.go +++ b/vendor/github.com/gookit/goutil/comdef/consts.go @@ -23,3 +23,5 @@ const ( // NoIdx invalid index or length const NoIdx = -1 + +// const VarPathReg = `(\w[\w-]*(?:\.[\w-]+)*)` diff --git a/vendor/github.com/gookit/goutil/comdef/types.go b/vendor/github.com/gookit/goutil/comdef/types.go index 0a26e2903..2fe167f2d 100644 --- a/vendor/github.com/gookit/goutil/comdef/types.go +++ b/vendor/github.com/gookit/goutil/comdef/types.go @@ -7,12 +7,11 @@ type Int interface { // Uint interface type type Uint interface { - ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr } // Xint interface type. all int or uint types type Xint interface { - // equal: ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint32 | ~uint64 Int | Uint } @@ -26,11 +25,19 @@ type IntOrFloat interface { Int | Float } -// XintOrFloat interface type. all (x)int and float types +// XintOrFloat interface type. all int, uint and float types type XintOrFloat interface { Int | Uint | Float } +// SortedType interface type. +// that supports the operators < <= >= >. +// +// contains: (x)int, float, ~string types +type SortedType interface { + Int | Uint | Float | ~string +} + // ScalarType interface type. // // contains: (x)int, float, ~string, ~bool types diff --git a/vendor/github.com/gookit/goutil/envutil/README.md b/vendor/github.com/gookit/goutil/envutil/README.md index 1f727c7e1..85d18c90b 100644 --- a/vendor/github.com/gookit/goutil/envutil/README.md +++ b/vendor/github.com/gookit/goutil/envutil/README.md @@ -14,6 +14,8 @@ go get github.com/gookit/goutil/envutil ## Functions API +> **Note**: doc by run `go doc ./envutil` + ```go func Environ() map[string]string func GetBool(name string, def ...bool) bool diff --git a/vendor/github.com/gookit/goutil/envutil/envutil.go b/vendor/github.com/gookit/goutil/envutil/envutil.go index 9680cefdd..f9a312da2 100644 --- a/vendor/github.com/gookit/goutil/envutil/envutil.go +++ b/vendor/github.com/gookit/goutil/envutil/envutil.go @@ -1,3 +1,4 @@ +// Package envutil provide some commonly ENV util functions. package envutil import ( @@ -41,9 +42,27 @@ func ParseValue(val string) (newVal string) { return comfunc.ParseEnvVar(val, ValueGetter) } -// SetEnvs to os -func SetEnvs(mp map[string]string) { +// SetEnvMap set multi ENV(string-map) to os +func SetEnvMap(mp map[string]string) { for key, value := range mp { _ = os.Setenv(key, value) } } + +// SetEnvs set multi k-v ENV pairs to os +func SetEnvs(kvPairs ...string) { + if len(kvPairs)%2 == 1 { + panic("envutil.SetEnvs: odd argument count") + } + + for i := 0; i < len(kvPairs); i += 2 { + _ = os.Setenv(kvPairs[i], kvPairs[i+1]) + } +} + +// UnsetEnvs from os +func UnsetEnvs(keys ...string) { + for _, key := range keys { + _ = os.Unsetenv(key) + } +} diff --git a/vendor/github.com/gookit/goutil/envutil/get.go b/vendor/github.com/gookit/goutil/envutil/get.go index 19b7ee439..6250c5b38 100644 --- a/vendor/github.com/gookit/goutil/envutil/get.go +++ b/vendor/github.com/gookit/goutil/envutil/get.go @@ -2,6 +2,7 @@ package envutil import ( "os" + "path/filepath" "github.com/gookit/goutil/internal/comfunc" "github.com/gookit/goutil/strutil" @@ -40,7 +41,45 @@ func GetBool(name string, def ...bool) bool { return false } +// GetMulti ENV values by input names. +func GetMulti(names ...string) map[string]string { + valMap := make(map[string]string, len(names)) + + for _, name := range names { + if val := os.Getenv(name); val != "" { + valMap[name] = val + } + } + return valMap +} + +// EnvPaths get and split $PATH to []string +func EnvPaths() []string { + return filepath.SplitList(os.Getenv("PATH")) +} + +// EnvMap like os.Environ, but will returns key-value map[string]string data. +func EnvMap() map[string]string { return comfunc.Environ() } + // Environ like os.Environ, but will returns key-value map[string]string data. -func Environ() map[string]string { - return comfunc.Environ() +func Environ() map[string]string { return comfunc.Environ() } + +// SearchEnvKeys values by given keywords +func SearchEnvKeys(keywords string) map[string]string { + return SearchEnv(keywords, false) +} + +// SearchEnv values by given keywords +func SearchEnv(keywords string, matchValue bool) map[string]string { + founded := make(map[string]string) + + for name, val := range comfunc.Environ() { + if strutil.IContains(name, keywords) { + founded[name] = val + } else if matchValue && strutil.IContains(val, keywords) { + founded[name] = val + } + } + + return founded } diff --git a/vendor/github.com/gookit/goutil/errorx/assert.go b/vendor/github.com/gookit/goutil/errorx/assert.go new file mode 100644 index 000000000..696b45885 --- /dev/null +++ b/vendor/github.com/gookit/goutil/errorx/assert.go @@ -0,0 +1,61 @@ +package errorx + +import ( + "errors" + "fmt" + + "github.com/gookit/goutil/arrutil" + "github.com/gookit/goutil/comdef" + "github.com/gookit/goutil/internal/comfunc" +) + +// IsTrue assert result is true, otherwise will return error +func IsTrue(result bool, fmtAndArgs ...any) error { + if !result { + return errors.New(formatErrMsg("result should be True", fmtAndArgs)) + } + return nil +} + +// IsFalse assert result is false, otherwise will return error +func IsFalse(result bool, fmtAndArgs ...any) error { + if result { + return errors.New(formatErrMsg("result should be False", fmtAndArgs)) + } + return nil +} + +// IsIn value should be in the list, otherwise will return error +func IsIn[T comdef.ScalarType](value T, list []T, fmtAndArgs ...any) error { + if arrutil.NotIn(value, list) { + var errMsg string + if len(fmtAndArgs) > 0 { + errMsg = comfunc.FormatTplAndArgs(fmtAndArgs) + } else { + errMsg = fmt.Sprintf("value should be in the %v", list) + } + return errors.New(errMsg) + } + return nil +} + +// NotIn value should not be in the list, otherwise will return error +func NotIn[T comdef.ScalarType](value T, list []T, fmtAndArgs ...any) error { + if arrutil.In(value, list) { + var errMsg string + if len(fmtAndArgs) > 0 { + errMsg = comfunc.FormatTplAndArgs(fmtAndArgs) + } else { + errMsg = fmt.Sprintf("value should not be in the %v", list) + } + return errors.New(errMsg) + } + return nil +} + +func formatErrMsg(errMsg string, fmtAndArgs []any) string { + if len(fmtAndArgs) > 0 { + errMsg = comfunc.FormatTplAndArgs(fmtAndArgs) + } + return errMsg +} diff --git a/vendor/github.com/gookit/goutil/errorx/errors.go b/vendor/github.com/gookit/goutil/errorx/errors.go index a87c2b3c7..0dffef5d4 100644 --- a/vendor/github.com/gookit/goutil/errorx/errors.go +++ b/vendor/github.com/gookit/goutil/errorx/errors.go @@ -37,6 +37,11 @@ func Fail(code int, msg string) ErrorR { return &errorR{code: code, msg: msg} } +// Failf code with error response +func Failf(code int, tpl string, v ...any) ErrorR { + return &errorR{code: code, msg: fmt.Sprintf(tpl, v...)} +} + // Suc success response reply func Suc(msg string) ErrorR { return &errorR{code: 0, msg: msg} @@ -110,6 +115,8 @@ func (e ErrMap) One() error { // Errors multi error list type Errors []error + +// ErrList alias for Errors type ErrList = Errors // Error string diff --git a/vendor/github.com/gookit/goutil/errorx/util.go b/vendor/github.com/gookit/goutil/errorx/util.go index 54cf9ea60..8558785a2 100644 --- a/vendor/github.com/gookit/goutil/errorx/util.go +++ b/vendor/github.com/gookit/goutil/errorx/util.go @@ -5,11 +5,31 @@ import ( "fmt" ) +// E new a raw go error. alias of errors.New() +func E(msg string) error { + return errors.New(msg) +} + +// Err new a raw go error. alias of errors.New() +func Err(msg string) error { + return errors.New(msg) +} + // Raw new a raw go error. alias of errors.New() func Raw(msg string) error { return errors.New(msg) } +// Ef new a raw go error. alias of errors.New() +func Ef(tpl string, vars ...any) error { + return fmt.Errorf(tpl, vars...) +} + +// Errf new a raw go error. alias of errors.New() +func Errf(tpl string, vars ...any) error { + return fmt.Errorf(tpl, vars...) +} + // Rawf new a raw go error. alias of errors.New() func Rawf(tpl string, vars ...any) error { return fmt.Errorf(tpl, vars...) @@ -54,7 +74,7 @@ func ToErrorX(err error) (ex *ErrorX, ok bool) { return } -// Has check err has contains target, or err is eq target. +// Has contains target error, or err is eq target. // alias of errors.Is() func Has(err, target error) bool { return errors.Is(err, target) diff --git a/vendor/github.com/gookit/goutil/fmtutil/fmtutil.go b/vendor/github.com/gookit/goutil/fmtutil/fmtutil.go deleted file mode 100644 index fcfd27ddd..000000000 --- a/vendor/github.com/gookit/goutil/fmtutil/fmtutil.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package fmtutil provide some format util functions. -package fmtutil diff --git a/vendor/github.com/gookit/goutil/fmtutil/format.go b/vendor/github.com/gookit/goutil/fmtutil/format.go deleted file mode 100644 index a403d40ce..000000000 --- a/vendor/github.com/gookit/goutil/fmtutil/format.go +++ /dev/null @@ -1,116 +0,0 @@ -package fmtutil - -import ( - "encoding/json" - "fmt" - "strconv" - "strings" - "unicode" -) - -// data size -const ( - OneKByte = 1024 - OneMByte = 1024 * 1024 - OneGByte = 1024 * 1024 -) - -// DataSize format bytes number friendly. -// -// Usage: -// -// file, err := os.Open(path) -// fl, err := file.Stat() -// fmtSize := DataSize(fl.Size()) -func DataSize(size uint64) string { - switch { - case size < 1024: - return fmt.Sprintf("%dB", size) - case size < 1024*1024: - return fmt.Sprintf("%.2fK", float64(size)/1024) - case size < 1024*1024*1024: - return fmt.Sprintf("%.2fM", float64(size)/1024/1024) - default: - return fmt.Sprintf("%.2fG", float64(size)/1024/1024/1024) - } -} - -// SizeToString alias of the DataSize -func SizeToString(size uint64) string { return DataSize(size) } - -// StringToByte alias of the ParseByte -func StringToByte(sizeStr string) uint64 { return ParseByte(sizeStr) } - -// ParseByte converts size string like 1GB/1g or 12mb/12M into an unsigned integer number of bytes -func ParseByte(sizeStr string) uint64 { - sizeStr = strings.TrimSpace(sizeStr) - lastPos := len(sizeStr) - 1 - if lastPos < 1 { - return 0 - } - - if sizeStr[lastPos] == 'b' || sizeStr[lastPos] == 'B' { - // last second char is k,m,g - lastSec := sizeStr[lastPos-1] - if lastSec > 'A' { - lastPos -= 1 - } - } - - multiplier := float64(1) - switch unicode.ToLower(rune(sizeStr[lastPos])) { - case 'k': - multiplier = 1 << 10 - sizeStr = strings.TrimSpace(sizeStr[:lastPos]) - case 'm': - multiplier = 1 << 20 - sizeStr = strings.TrimSpace(sizeStr[:lastPos]) - case 'g': - multiplier = 1 << 30 - sizeStr = strings.TrimSpace(sizeStr[:lastPos]) - default: // b - multiplier = 1 - sizeStr = strings.TrimSpace(sizeStr[:lastPos]) - } - - size, _ := strconv.ParseFloat(sizeStr, 64) - if size < 0 { - return 0 - } - - return uint64(size * multiplier) -} - -// PrettyJSON get pretty Json string -func PrettyJSON(v any) (string, error) { - out, err := json.MarshalIndent(v, "", " ") - return string(out), err -} - -// StringsToInts string slice to int slice. -// Deprecated: please use the arrutil.StringsToInts() -func StringsToInts(ss []string) (ints []int, err error) { - for _, str := range ss { - iVal, err := strconv.Atoi(str) - if err != nil { - return []int{}, err - } - - ints = append(ints, iVal) - } - return -} - -// ArgsWithSpaces it like Println, will add spaces for each argument -func ArgsWithSpaces(args []any) (message string) { - if ln := len(args); ln == 0 { - message = "" - } else if ln == 1 { - message = fmt.Sprint(args[0]) - } else { - message = fmt.Sprintln(args...) - // clear last "\n" - message = message[:len(message)-1] - } - return -} diff --git a/vendor/github.com/gookit/goutil/fsutil/README.md b/vendor/github.com/gookit/goutil/fsutil/README.md new file mode 100644 index 000000000..0515b6d5e --- /dev/null +++ b/vendor/github.com/gookit/goutil/fsutil/README.md @@ -0,0 +1,136 @@ +# FileSystem Util + +`fsutil` Provide some commonly file system util functions. + +## Install + +```shell +go get github.com/gookit/goutil/fsutil +``` + +## Go docs + +- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/fsutil) + +## Find files + +```go +// find all files in dir +fsutil.FindInDir("./", func(filePath string, de fs.DirEntry) error { + fmt.Println(filePath) + return nil +}) + +// find files with filters +fsutil.FindInDir("./", func(filePath string, de fs.DirEntry) error { + fmt.Println(filePath) + return nil +}, fsutil.ExcludeDotFile) +``` + +## Functions API + +> **Note**: doc by run `go doc ./fsutil` + +```go +func ApplyFilters(fPath string, ent fs.DirEntry, filters []FilterFunc) bool +func CopyFile(srcPath, dstPath string) error +func CreateFile(fpath string, filePerm, dirPerm os.FileMode, fileFlag ...int) (*os.File, error) +func DeleteIfExist(fPath string) error +func DeleteIfFileExist(fPath string) error +func Dir(fpath string) string +func DiscardReader(src io.Reader) +func ExcludeDotFile(_ string, ent fs.DirEntry) bool +func Expand(pathStr string) string +func ExpandPath(pathStr string) string +func Extname(fpath string) string +func FileExists(path string) bool +func FileExt(fpath string) string +func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error) +func GetContents(in any) []byte +func GlobWithFunc(pattern string, fn func(filePath string) error) (err error) +func IsAbsPath(aPath string) bool +func IsDir(path string) bool +func IsFile(path string) bool +func IsImageFile(path string) bool +func IsZipFile(filepath string) bool +func JoinPaths(elem ...string) string +func JoinSubPaths(basePath string, elem ...string) string +func LineScanner(in any) *bufio.Scanner +func MimeType(path string) (mime string) +func MkDirs(perm os.FileMode, dirPaths ...string) error +func MkParentDir(fpath string) error +func MkSubDirs(perm os.FileMode, parentDir string, subDirs ...string) error +func Mkdir(dirPath string, perm os.FileMode) error +func MustCopyFile(srcPath, dstPath string) +func MustCreateFile(filePath string, filePerm, dirPerm os.FileMode) *os.File +func MustReadFile(filePath string) []byte +func MustReadReader(r io.Reader) []byte +func MustRemove(fPath string) +func Name(fpath string) string +func NewIOReader(in any) (r io.Reader, err error) +func OSTempDir(pattern string) (string, error) +func OSTempFile(pattern string) (*os.File, error) +func OnlyFindDir(_ string, ent fs.DirEntry) bool +func OnlyFindFile(_ string, ent fs.DirEntry) bool +func OpenAppendFile(filepath string) (*os.File, error) +func OpenFile(filepath string, flag int, perm os.FileMode) (*os.File, error) +func OpenReadFile(filepath string) (*os.File, error) +func OpenTruncFile(filepath string) (*os.File, error) +func PathExists(path string) bool +func PathMatch(pattern, s string) bool +func PathName(fpath string) string +func PutContents(filePath string, data any, fileFlag ...int) (int, error) +func QuickOpenFile(filepath string, fileFlag ...int) (*os.File, error) +func QuietRemove(fPath string) +func ReadAll(in any) []byte +func ReadExistFile(filePath string) []byte +func ReadFile(filePath string) []byte +func ReadOrErr(in any) ([]byte, error) +func ReadReader(r io.Reader) []byte +func ReadString(in any) string +func ReadStringOrErr(in any) (string, error) +func ReaderMimeType(r io.Reader) (mime string) +func Realpath(pathStr string) string +func Remove(fPath string) error +func ResolvePath(pathStr string) string +func RmFileIfExist(fPath string) error +func RmIfExist(fPath string) error +func SearchNameUp(dirPath, name string) string +func SearchNameUpx(dirPath, name string) (string, bool) +func SlashPath(path string) string +func SplitPath(pathStr string) (dir, name string) +func Suffix(fpath string) string +func TempDir(dir, pattern string) (string, error) +func TempFile(dir, pattern string) (*os.File, error) +func TextScanner(in any) *scanner.Scanner +func ToAbsPath(p string) string +func UnixPath(path string) string +func Unzip(archive, targetDir string) (err error) +func WalkDir(dir string, fn fs.WalkDirFunc) error +func WriteFile(filePath string, data any, perm os.FileMode, fileFlag ...int) error +func WriteOSFile(f *os.File, data any) (n int, err error) +type FilterFunc func(fPath string, ent fs.DirEntry) bool +func ExcludeSuffix(ss ...string) FilterFunc +func IncludeSuffix(ss ...string) FilterFunc +type HandleFunc func(fPath string, ent fs.DirEntry) error +``` + +## Code Check & Testing + +```bash +gofmt -w -l ./ +golint ./... +``` + +**Testing**: + +```shell +go test -v ./fsutil/... +``` + +**Test limit by regexp**: + +```shell +go test -v -run ^TestSetByKeys ./fsutil/... +``` diff --git a/vendor/github.com/gookit/goutil/fsutil/check.go b/vendor/github.com/gookit/goutil/fsutil/check.go index 4411b3988..918ea7819 100644 --- a/vendor/github.com/gookit/goutil/fsutil/check.go +++ b/vendor/github.com/gookit/goutil/fsutil/check.go @@ -4,14 +4,17 @@ import ( "bytes" "os" "path" + "path/filepath" ) +// perm for create dir or file var ( - // DefaultDirPerm perm and flags for create log file DefaultDirPerm os.FileMode = 0775 DefaultFilePerm os.FileMode = 0665 OnlyReadFilePerm os.FileMode = 0444 +) +var ( // DefaultFileFlags for create and write DefaultFileFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND // OnlyReadFileFlags open file for read @@ -41,7 +44,7 @@ func PathExists(path string) bool { // IsDir reports whether the named directory exists. func IsDir(path string) bool { - if path == "" { + if path == "" || len(path) > 468 { return false } @@ -58,7 +61,7 @@ func FileExists(path string) bool { // IsFile reports whether the named file or directory exists. func IsFile(path string) bool { - if path == "" { + if path == "" || len(path) > 468 { return false } @@ -70,7 +73,13 @@ func IsFile(path string) bool { // IsAbsPath is abs path. func IsAbsPath(aPath string) bool { - return path.IsAbs(aPath) + if len(aPath) > 0 { + if aPath[0] == '/' { + return true + } + return filepath.IsAbs(aPath) + } + return false } // ImageMimeTypes refer net/http package @@ -118,3 +127,12 @@ func IsZipFile(filepath string) bool { return bytes.Equal(buf, []byte("PK\x03\x04")) } + +// PathMatch check for a string. alias of path.Match() +func PathMatch(pattern, s string) bool { + ok, err := path.Match(pattern, s) + if err != nil { + ok = false + } + return ok +} diff --git a/vendor/github.com/gookit/goutil/fsutil/find.go b/vendor/github.com/gookit/goutil/fsutil/find.go new file mode 100644 index 000000000..66d76e760 --- /dev/null +++ b/vendor/github.com/gookit/goutil/fsutil/find.go @@ -0,0 +1,156 @@ +package fsutil + +import ( + "io/fs" + "os" + "path/filepath" + + "github.com/gookit/goutil/arrutil" + "github.com/gookit/goutil/strutil" +) + +// SearchNameUp find file/dir name in dirPath or parent dirs, +// return the name of directory path +// +// Usage: +// +// repoDir := fsutil.SearchNameUp("/path/to/dir", ".git") +func SearchNameUp(dirPath, name string) string { + dir, _ := SearchNameUpx(dirPath, name) + return dir +} + +// SearchNameUpx find file/dir name in dirPath or parent dirs, +// return the name of directory path and dir is changed. +func SearchNameUpx(dirPath, name string) (string, bool) { + var level int + dirPath = ToAbsPath(dirPath) + + for { + namePath := filepath.Join(dirPath, name) + if PathExists(namePath) { + return dirPath, level > 0 + } + + level++ + prevLn := len(dirPath) + dirPath = filepath.Dir(dirPath) + if prevLn == len(dirPath) { + return "", false + } + } +} + +// WalkDir walks the file tree rooted at root, calling fn for each file or +// directory in the tree, including root. +func WalkDir(dir string, fn fs.WalkDirFunc) error { + return filepath.WalkDir(dir, fn) +} + +// GlobWithFunc handle matched file +// +// - TIP: will be not find in subdir. +func GlobWithFunc(pattern string, fn func(filePath string) error) (err error) { + files, err := filepath.Glob(pattern) + if err != nil { + return err + } + + for _, filePath := range files { + err = fn(filePath) + if err != nil { + break + } + } + return +} + +type ( + // FilterFunc type for FindInDir + // + // - return False will skip handle the file. + FilterFunc func(fPath string, ent fs.DirEntry) bool + + // HandleFunc type for FindInDir + HandleFunc func(fPath string, ent fs.DirEntry) error +) + +// OnlyFindDir on find +func OnlyFindDir(_ string, ent fs.DirEntry) bool { + return ent.IsDir() +} + +// OnlyFindFile on find +func OnlyFindFile(_ string, ent fs.DirEntry) bool { + return !ent.IsDir() +} + +// ExcludeNames on find +func ExcludeNames(names ...string) FilterFunc { + return func(_ string, ent fs.DirEntry) bool { + return !arrutil.StringsHas(names, ent.Name()) + } +} + +// IncludeSuffix on find +func IncludeSuffix(ss ...string) FilterFunc { + return func(_ string, ent fs.DirEntry) bool { + return strutil.HasOneSuffix(ent.Name(), ss) + } +} + +// ExcludeDotFile on find +func ExcludeDotFile(_ string, ent fs.DirEntry) bool { + return ent.Name()[0] != '.' +} + +// ExcludeSuffix on find +func ExcludeSuffix(ss ...string) FilterFunc { + return func(_ string, ent fs.DirEntry) bool { + return !strutil.HasOneSuffix(ent.Name(), ss) + } +} + +// ApplyFilters handle +func ApplyFilters(fPath string, ent fs.DirEntry, filters []FilterFunc) bool { + for _, filter := range filters { + if !filter(fPath, ent) { + return true + } + } + return false +} + +// FindInDir code refer the go pkg: path/filepath.glob() +// +// - TIP: will be not find in subdir. +// +// filters: return false will skip the file. +func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error) { + fi, err := os.Stat(dir) + if err != nil || !fi.IsDir() { + return // ignore I/O error + } + + // names, _ := d.Readdirnames(-1) + // sort.Strings(names) + + des, err := os.ReadDir(dir) + if err != nil { + return + } + + for _, ent := range des { + filePath := dir + "/" + ent.Name() + + // apply filters + if len(filters) > 0 && ApplyFilters(filePath, ent, filters) { + continue + } + + if err := handleFn(filePath, ent); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/gookit/goutil/fsutil/fsutil.go b/vendor/github.com/gookit/goutil/fsutil/fsutil.go index 36ac591b3..c4517ca18 100644 --- a/vendor/github.com/gookit/goutil/fsutil/fsutil.go +++ b/vendor/github.com/gookit/goutil/fsutil/fsutil.go @@ -5,6 +5,10 @@ import ( "io" "net/http" "os" + "path/filepath" + "strings" + + "github.com/gookit/goutil/internal/comfunc" ) const ( @@ -77,3 +81,49 @@ func ReaderMimeType(r io.Reader) (mime string) { return http.DetectContentType(buf[:n]) } + +// JoinPaths elements, alias of filepath.Join() +func JoinPaths(elem ...string) string { + return filepath.Join(elem...) +} + +// JoinSubPaths elements, like the filepath.Join() +func JoinSubPaths(basePath string, elem ...string) string { + paths := make([]string, len(elem)+1) + paths[0] = basePath + copy(paths[1:], elem) + return filepath.Join(paths...) +} + +// SlashPath alias of filepath.ToSlash +func SlashPath(path string) string { + return filepath.ToSlash(path) +} + +// UnixPath like of filepath.ToSlash, but always replace +func UnixPath(path string) string { + if !strings.ContainsRune(path, '\\') { + return path + } + return strings.ReplaceAll(path, "\\", "/") +} + +// ToAbsPath convert process. will expand home dir +// +// TIP: will don't check path +func ToAbsPath(p string) string { + if len(p) == 0 || IsAbsPath(p) { + return p + } + + // expand home dir + if p[0] == '~' { + return comfunc.ExpandHome(p) + } + + wd, err := os.Getwd() + if err != nil { + return p + } + return filepath.Join(wd, p) +} diff --git a/vendor/github.com/gookit/goutil/fsutil/info.go b/vendor/github.com/gookit/goutil/fsutil/info.go index 14e680664..32ab28b96 100644 --- a/vendor/github.com/gookit/goutil/fsutil/info.go +++ b/vendor/github.com/gookit/goutil/fsutil/info.go @@ -1,7 +1,6 @@ package fsutil import ( - "io/ioutil" "os" "path" "path/filepath" @@ -9,116 +8,60 @@ import ( "github.com/gookit/goutil/internal/comfunc" ) -// Dir get dir path, without last name. -func Dir(fpath string) string { - return filepath.Dir(fpath) -} +// Dir get dir path from filepath, without last name. +func Dir(fpath string) string { return filepath.Dir(fpath) } // PathName get file/dir name from full path -func PathName(fpath string) string { - return path.Base(fpath) -} +func PathName(fpath string) string { return path.Base(fpath) } -// Name get file/dir name from full path +// Name get file/dir name from full path. +// +// eg: path/to/main.go => main.go func Name(fpath string) string { + if fpath == "" { + return "" + } return filepath.Base(fpath) } // FileExt get filename ext. alias of path.Ext() -func FileExt(fpath string) string { - return path.Ext(fpath) +// +// eg: path/to/main.go => ".go" +func FileExt(fpath string) string { return path.Ext(fpath) } + +// Extname get filename ext. alias of path.Ext() +// +// eg: path/to/main.go => "go" +func Extname(fpath string) string { + if ext := path.Ext(fpath); len(ext) > 0 { + return ext[1:] + } + return "" } // Suffix get filename ext. alias of path.Ext() -func Suffix(fpath string) string { - return path.Ext(fpath) -} +// +// eg: path/to/main.go => ".go" +func Suffix(fpath string) string { return path.Ext(fpath) } // Expand will parse first `~` as user home dir path. func Expand(pathStr string) string { - return comfunc.ExpandPath(pathStr) + return comfunc.ExpandHome(pathStr) } // ExpandPath will parse `~` as user home dir path. func ExpandPath(pathStr string) string { - return comfunc.ExpandPath(pathStr) + return comfunc.ExpandHome(pathStr) } -// Realpath returns the shortest path name equivalent to path by purely lexical processing. -func Realpath(pathStr string) string { - return path.Clean(pathStr) +// ResolvePath will parse `~` and env var in path +func ResolvePath(pathStr string) string { + pathStr = comfunc.ExpandHome(pathStr) + // return comfunc.ParseEnvVar() + return os.ExpandEnv(pathStr) } // SplitPath splits path immediately following the final Separator, separating it into a directory and file name component func SplitPath(pathStr string) (dir, name string) { return filepath.Split(pathStr) } - -// GlobWithFunc handle matched file -func GlobWithFunc(pattern string, fn func(filePath string) error) (err error) { - files, err := filepath.Glob(pattern) - if err != nil { - return err - } - - for _, filePath := range files { - err = fn(filePath) - if err != nil { - break - } - } - return -} - -type ( - // FilterFunc type for FindInDir - FilterFunc func(fPath string, fi os.FileInfo) bool - // HandleFunc type for FindInDir - HandleFunc func(fPath string, fi os.FileInfo) error -) - -// FindInDir code refer the go pkg: path/filepath.glob() -// -// filters: return false will skip the file. -func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error) { - fi, err := os.Stat(dir) - if err != nil { - return // ignore I/O error - } - if !fi.IsDir() { - return // ignore I/O error - } - - // names, _ := d.Readdirnames(-1) - // sort.Strings(names) - - stats, err := ioutil.ReadDir(dir) - if err != nil { - return - } - - for _, fi := range stats { - baseName := fi.Name() - filePath := dir + "/" + baseName - - // call filters - if len(filters) > 0 { - var filtered = false - for _, filter := range filters { - if !filter(filePath, fi) { - filtered = true - break - } - } - - if filtered { - continue - } - } - - if err := handleFn(filePath, fi); err != nil { - return err - } - } - return nil -} diff --git a/vendor/github.com/gookit/goutil/fsutil/info_nonwin.go b/vendor/github.com/gookit/goutil/fsutil/info_nonwin.go new file mode 100644 index 000000000..67c7cfece --- /dev/null +++ b/vendor/github.com/gookit/goutil/fsutil/info_nonwin.go @@ -0,0 +1,19 @@ +//go:build !windows + +package fsutil + +import ( + "path" + + "github.com/gookit/goutil/internal/comfunc" +) + +// Realpath returns the shortest path name equivalent to path by purely lexical processing. +func Realpath(pathStr string) string { + pathStr = comfunc.ExpandHome(pathStr) + + if !IsAbsPath(pathStr) { + pathStr = JoinSubPaths(comfunc.Workdir(), pathStr) + } + return path.Clean(pathStr) +} diff --git a/vendor/github.com/gookit/goutil/fsutil/info_windows.go b/vendor/github.com/gookit/goutil/fsutil/info_windows.go new file mode 100644 index 000000000..dc21ebe76 --- /dev/null +++ b/vendor/github.com/gookit/goutil/fsutil/info_windows.go @@ -0,0 +1,17 @@ +package fsutil + +import ( + "path/filepath" + + "github.com/gookit/goutil/internal/comfunc" +) + +// Realpath returns the shortest path name equivalent to path by purely lexical processing. +func Realpath(pathStr string) string { + pathStr = comfunc.ExpandHome(pathStr) + + if !IsAbsPath(pathStr) { + pathStr = JoinSubPaths(comfunc.Workdir(), pathStr) + } + return filepath.Clean(pathStr) +} diff --git a/vendor/github.com/gookit/goutil/fsutil/operate.go b/vendor/github.com/gookit/goutil/fsutil/operate.go index 310252428..5bf1e600a 100644 --- a/vendor/github.com/gookit/goutil/fsutil/operate.go +++ b/vendor/github.com/gookit/goutil/fsutil/operate.go @@ -4,11 +4,13 @@ import ( "archive/zip" "fmt" "io" - "io/ioutil" + "io/fs" "os" "path" "path/filepath" "strings" + + "github.com/gookit/goutil/basefn" ) // Mkdir alias of os.MkdirAll() @@ -16,6 +18,27 @@ func Mkdir(dirPath string, perm os.FileMode) error { return os.MkdirAll(dirPath, perm) } +// MkDirs batch make multi dirs at once +func MkDirs(perm os.FileMode, dirPaths ...string) error { + for _, dirPath := range dirPaths { + if err := os.MkdirAll(dirPath, perm); err != nil { + return err + } + } + return nil +} + +// MkSubDirs batch make multi sub-dirs at once +func MkSubDirs(perm os.FileMode, parentDir string, subDirs ...string) error { + for _, dirName := range subDirs { + dirPath := parentDir + "/" + dirName + if err := os.MkdirAll(dirPath, perm); err != nil { + return err + } + } + return nil +} + // MkParentDir quick create parent dir func MkParentDir(fpath string) error { dirPath := filepath.Dir(fpath) @@ -25,61 +48,11 @@ func MkParentDir(fpath string) error { return nil } -// DiscardReader anything from the reader -func DiscardReader(src io.Reader) { - _, _ = io.Copy(ioutil.Discard, src) -} - -// MustReadFile read file contents, will panic on error -func MustReadFile(filePath string) []byte { - bs, err := ioutil.ReadFile(filePath) - if err != nil { - panic(err) - } - - return bs -} - -// MustReadReader read contents from io.Reader, will panic on error -func MustReadReader(r io.Reader) []byte { - // TODO go 1.16+ bs, err := io.ReadAll(r) - bs, err := ioutil.ReadAll(r) - if err != nil { - panic(err) - } - return bs -} - -// GetContents read contents from path or io.Reader, will panic on error -func GetContents(in any) []byte { - if fPath, ok := in.(string); ok { - return MustReadFile(fPath) - } - - if r, ok := in.(io.Reader); ok { - return MustReadReader(r) - } - - panic("invalid type of input") -} - -// ReadExistFile read file contents if existed, will panic on error -func ReadExistFile(filePath string) []byte { - if IsFile(filePath) { - bs, err := ioutil.ReadFile(filePath) - if err != nil { - panic(err) - } - return bs - } - return nil -} - // ************************************************************ // open/create files // ************************************************************ -// some flag consts for open file +// some commonly flag consts for open file const ( FsCWAFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND // create, append write-only FsCWTFlags = os.O_CREATE | os.O_WRONLY | os.O_TRUNC // create, override write-only @@ -88,6 +61,10 @@ const ( ) // OpenFile like os.OpenFile, but will auto create dir. +// +// Usage: +// +// file, err := OpenFile("path/to/file.txt", FsCWFlags, 0666) func OpenFile(filepath string, flag int, perm os.FileMode) (*os.File, error) { fileDir := path.Dir(filepath) if err := os.MkdirAll(fileDir, DefaultDirPerm); err != nil { @@ -101,20 +78,39 @@ func OpenFile(filepath string, flag int, perm os.FileMode) (*os.File, error) { return file, nil } -/* TODO MustOpenFile() */ - -// QuickOpenFile like os.OpenFile, open for write, if not exists, will create it. +// MustOpenFile like os.OpenFile, but will auto create dir. // -// Tip: file flag default is FsCWAFlags -func QuickOpenFile(filepath string, fileFlag ...int) (*os.File, error) { - flag := FsCWAFlags - if len(fileFlag) > 0 { - flag = fileFlag[0] +// Usage: +// +// file := MustOpenFile("path/to/file.txt", FsCWFlags, 0666) +func MustOpenFile(filepath string, flag int, perm os.FileMode) *os.File { + file, err := OpenFile(filepath, flag, perm) + if err != nil { + panic(err) } + return file +} +// QuickOpenFile like os.OpenFile, open for append write. if not exists, will create it. +// +// Alias of OpenAppendFile() +func QuickOpenFile(filepath string, fileFlag ...int) (*os.File, error) { + flag := basefn.FirstOr(fileFlag, FsCWAFlags) return OpenFile(filepath, flag, DefaultFilePerm) } +// OpenAppendFile like os.OpenFile, open for append write. if not exists, will create it. +func OpenAppendFile(filepath string, filePerm ...os.FileMode) (*os.File, error) { + perm := basefn.FirstOr(filePerm, DefaultFilePerm) + return OpenFile(filepath, FsCWAFlags, perm) +} + +// OpenTruncFile like os.OpenFile, open for override write. if not exists, will create it. +func OpenTruncFile(filepath string, filePerm ...os.FileMode) (*os.File, error) { + perm := basefn.FirstOr(filePerm, DefaultFilePerm) + return OpenFile(filepath, FsCWTFlags, perm) +} + // OpenReadFile like os.OpenFile, open file for read contents func OpenReadFile(filepath string) (*os.File, error) { return os.OpenFile(filepath, FsRFlags, OnlyReadFilePerm) @@ -134,11 +130,7 @@ func CreateFile(fpath string, filePerm, dirPerm os.FileMode, fileFlag ...int) (* } } - flag := FsCWTFlags - if len(fileFlag) > 0 { - flag = fileFlag[0] - } - + flag := basefn.FirstOr(fileFlag, FsCWAFlags) return os.OpenFile(fpath, flag, filePerm) } @@ -151,105 +143,6 @@ func MustCreateFile(filePath string, filePerm, dirPerm os.FileMode) *os.File { return file } -// ************************************************************ -// write, copy files -// ************************************************************ - -// PutContents create file and write contents to file at once. -// -// data type allow: string, []byte, io.Reader -// -// Tip: file flag default is FsCWAFlags -// -// Usage: -// -// fsutil.PutContents(filePath, contents, fsutil.FsCWTFlags) -func PutContents(filePath string, data any, fileFlag ...int) (int, error) { - // create and open file - dstFile, err := QuickOpenFile(filePath, fileFlag...) - if err != nil { - return 0, err - } - - defer dstFile.Close() - switch typData := data.(type) { - case []byte: - return dstFile.Write(typData) - case string: - return dstFile.WriteString(typData) - case io.Reader: // eg: buffer - n, err := io.Copy(dstFile, typData) - return int(n), err - default: - panic("PutContents: data type only allow: []byte, string, io.Reader") - } -} - -// WriteFile create file and write contents to file, can set perm for file. -// -// data type allow: string, []byte, io.Reader -// -// Tip: file flag default is FsCWTFlags -// -// Usage: -// -// fsutil.WriteFile(filePath, contents, 0666, fsutil.FsCWAFlags) -func WriteFile(filePath string, data any, perm os.FileMode, fileFlag ...int) error { - flag := FsCWTFlags - if len(fileFlag) > 0 { - flag = fileFlag[0] - } - - f, err := os.OpenFile(filePath, flag, perm) - if err != nil { - return err - } - - switch typData := data.(type) { - case []byte: - _, err = f.Write(typData) - case string: - _, err = f.WriteString(typData) - case io.Reader: // eg: buffer - _, err = io.Copy(f, typData) - default: - _ = f.Close() - panic("WriteFile: data type only allow: []byte, string, io.Reader") - } - - if err1 := f.Close(); err1 != nil && err == nil { - err = err1 - } - return err -} - -// CopyFile copy a file to another file path. -func CopyFile(srcPath, dstPath string) error { - srcFile, err := os.OpenFile(srcPath, FsRFlags, 0) - if err != nil { - return err - } - defer srcFile.Close() - - // create and open file - dstFile, err := QuickOpenFile(dstPath, FsCWTFlags) - if err != nil { - return err - } - defer dstFile.Close() - - _, err = io.Copy(dstFile, srcFile) - return err -} - -// MustCopyFile copy file to another path. -func MustCopyFile(srcPath, dstPath string) { - err := CopyFile(srcPath, dstPath) - if err != nil { - panic(err) - } -} - // ************************************************************ // remove files // ************************************************************ @@ -302,6 +195,18 @@ func DeleteIfFileExist(fPath string) error { return nil } +// RemoveSub removes all sub files and dirs of dirPath, but not remove dirPath. +func RemoveSub(dirPath string, fns ...FilterFunc) error { + return FindInDir(dirPath, func(fPath string, ent fs.DirEntry) error { + if ent.IsDir() { + if err := RemoveSub(fPath, fns...); err != nil { + return err + } + } + return os.Remove(fPath) + }, fns...) +} + // ************************************************************ // other operates // ************************************************************ @@ -319,7 +224,6 @@ func Unzip(archive, targetDir string) (err error) { } for _, file := range reader.File { - if strings.Contains(file.Name, "..") { return fmt.Errorf("illegal file path in zip: %v", file.Name) } diff --git a/vendor/github.com/gookit/goutil/fsutil/opread.go b/vendor/github.com/gookit/goutil/fsutil/opread.go new file mode 100644 index 000000000..23f552332 --- /dev/null +++ b/vendor/github.com/gookit/goutil/fsutil/opread.go @@ -0,0 +1,137 @@ +package fsutil + +import ( + "bufio" + "errors" + "io" + "os" + "text/scanner" +) + +// NewIOReader instance by input file path or io.Reader +func NewIOReader(in any) (r io.Reader, err error) { + switch typIn := in.(type) { + case string: // as file path + return OpenReadFile(typIn) + case io.Reader: + return typIn, nil + } + return nil, errors.New("invalid input type, allow: string, io.Reader") +} + +// DiscardReader anything from the reader +func DiscardReader(src io.Reader) { + _, _ = io.Copy(io.Discard, src) +} + +// ReadFile read file contents, will panic on error +func ReadFile(filePath string) []byte { + return MustReadFile(filePath) +} + +// MustReadFile read file contents, will panic on error +func MustReadFile(filePath string) []byte { + bs, err := os.ReadFile(filePath) + if err != nil { + panic(err) + } + return bs +} + +// ReadReader read contents from io.Reader, will panic on error +func ReadReader(r io.Reader) []byte { return MustReadReader(r) } + +// MustReadReader read contents from io.Reader, will panic on error +func MustReadReader(r io.Reader) []byte { + bs, err := io.ReadAll(r) + if err != nil { + panic(err) + } + return bs +} + +// ReadString read contents from path or io.Reader, will panic on in type error +func ReadString(in any) string { + return string(GetContents(in)) +} + +// ReadStringOrErr read contents from path or io.Reader, will panic on in type error +func ReadStringOrErr(in any) (string, error) { + r, err := NewIOReader(in) + if err != nil { + return "", err + } + + bs, err := io.ReadAll(r) + if err != nil { + return "", err + } + return string(bs), nil +} + +// ReadAll read contents from path or io.Reader, will panic on in type error +func ReadAll(in any) []byte { return GetContents(in) } + +// GetContents read contents from path or io.Reader, will panic on in type error +func GetContents(in any) []byte { + r, err := NewIOReader(in) + if err != nil { + panic(err) + } + return MustReadReader(r) +} + +// ReadOrErr read contents from path or io.Reader, will panic on in type error +func ReadOrErr(in any) ([]byte, error) { + r, err := NewIOReader(in) + if err != nil { + return nil, err + } + return io.ReadAll(r) +} + +// ReadExistFile read file contents if existed, will panic on error +func ReadExistFile(filePath string) []byte { + if IsFile(filePath) { + bs, err := os.ReadFile(filePath) + if err != nil { + panic(err) + } + return bs + } + return nil +} + +// TextScanner from filepath or io.Reader, will panic on in type error +// +// Usage: +// +// s := fsutil.TextScanner("/path/to/file") +// for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() { +// fmt.Printf("%s: %s\n", s.Position, s.TokenText()) +// } +func TextScanner(in any) *scanner.Scanner { + var s scanner.Scanner + r, err := NewIOReader(in) + if err != nil { + panic(err) + } + + s.Init(r) + s.Filename = "text-scanner" + return &s +} + +// LineScanner create from filepath or io.Reader +// +// s := fsutil.LineScanner("/path/to/file") +// for s.Scan() { +// fmt.Println(s.Text()) +// } +func LineScanner(in any) *bufio.Scanner { + r, err := NewIOReader(in) + if err != nil { + panic(err) + } + return bufio.NewScanner(r) +} diff --git a/vendor/github.com/gookit/goutil/fsutil/opwrite.go b/vendor/github.com/gookit/goutil/fsutil/opwrite.go new file mode 100644 index 000000000..d264a3ca0 --- /dev/null +++ b/vendor/github.com/gookit/goutil/fsutil/opwrite.go @@ -0,0 +1,101 @@ +package fsutil + +import ( + "io" + "os" + + "github.com/gookit/goutil/basefn" +) + +// ************************************************************ +// write, copy files +// ************************************************************ + +// PutContents create file and write contents to file at once. +// +// data type allow: string, []byte, io.Reader +// +// Tip: file flag default is FsCWTFlags (override write) +// +// Usage: +// +// fsutil.PutContents(filePath, contents, fsutil.FsCWAFlags) // append write +func PutContents(filePath string, data any, fileFlag ...int) (int, error) { + f, err := QuickOpenFile(filePath, basefn.FirstOr(fileFlag, FsCWTFlags)) + if err != nil { + return 0, err + } + + return WriteOSFile(f, data) +} + +// WriteFile create file and write contents to file, can set perm for file. +// +// data type allow: string, []byte, io.Reader +// +// Tip: file flag default is FsCWTFlags (override write) +// +// Usage: +// +// fsutil.WriteFile(filePath, contents, fsutil.DefaultFilePerm, fsutil.FsCWAFlags) +func WriteFile(filePath string, data any, perm os.FileMode, fileFlag ...int) error { + flag := basefn.FirstOr(fileFlag, FsCWTFlags) + f, err := OpenFile(filePath, flag, perm) + if err != nil { + return err + } + + _, err = WriteOSFile(f, data) + return err +} + +// WriteOSFile write data to give os.File, then close file. +// +// data type allow: string, []byte, io.Reader +func WriteOSFile(f *os.File, data any) (n int, err error) { + switch typData := data.(type) { + case []byte: + n, err = f.Write(typData) + case string: + n, err = f.WriteString(typData) + case io.Reader: // eg: buffer + var n64 int64 + n64, err = io.Copy(f, typData) + n = int(n64) + default: + _ = f.Close() + panic("WriteFile: data type only allow: []byte, string, io.Reader") + } + + if err1 := f.Close(); err1 != nil && err == nil { + err = err1 + } + return n, err +} + +// CopyFile copy a file to another file path. +func CopyFile(srcPath, dstPath string) error { + srcFile, err := os.OpenFile(srcPath, FsRFlags, 0) + if err != nil { + return err + } + defer srcFile.Close() + + // create and open file + dstFile, err := QuickOpenFile(dstPath, FsCWTFlags) + if err != nil { + return err + } + defer dstFile.Close() + + _, err = io.Copy(dstFile, srcFile) + return err +} + +// MustCopyFile copy file to another path. +func MustCopyFile(srcPath, dstPath string) { + err := CopyFile(srcPath, dstPath) + if err != nil { + panic(err) + } +} diff --git a/vendor/github.com/gookit/goutil/func.go b/vendor/github.com/gookit/goutil/func.go new file mode 100644 index 000000000..0573f1d10 --- /dev/null +++ b/vendor/github.com/gookit/goutil/func.go @@ -0,0 +1,37 @@ +package goutil + +import "github.com/gookit/goutil/stdutil" + +// FuncName get func name +func FuncName(f any) string { + return stdutil.FuncName(f) +} + +// Go is a basic promise implementation: it wraps calls a function in a goroutine +// and returns a channel which will later return the function's return value. +func Go(f func() error) error { + ch := make(chan error) + go func() { + ch <- f() + }() + return <-ch +} + +// ErrFunc type +type ErrFunc func() error + +// CallOn call func on condition is true +func CallOn(cond bool, fn ErrFunc) error { + if cond { + return fn() + } + return nil +} + +// CallOrElse call okFunc() on condition is true, else call elseFn() +func CallOrElse(cond bool, okFn, elseFn ErrFunc) error { + if cond { + return okFn() + } + return elseFn() +} diff --git a/vendor/github.com/gookit/goutil/goutil.go b/vendor/github.com/gookit/goutil/goutil.go index 97a1fe90e..6298c9f74 100644 --- a/vendor/github.com/gookit/goutil/goutil.go +++ b/vendor/github.com/gookit/goutil/goutil.go @@ -11,14 +11,9 @@ import ( // Value alias of stdutil.Value type Value = stdutil.Value -// Go is a basic promise implementation: it wraps calls a function in a goroutine -// and returns a channel which will later return the function's return value. -func Go(f func() error) error { - ch := make(chan error) - go func() { - ch <- f() - }() - return <-ch +// Panicf format panic message use fmt.Sprintf +func Panicf(format string, v ...any) { + panic(fmt.Sprintf(format, v...)) } // PanicIfErr if error is not empty, will panic @@ -42,14 +37,12 @@ func MustOK(err error) { } } -// Panicf format panic message use fmt.Sprintf -func Panicf(format string, v ...any) { - panic(fmt.Sprintf(format, v...)) -} - -// FuncName get func name -func FuncName(f any) string { - return stdutil.FuncName(f) +// Must if error is not empty, will panic +func Must[T any](v T, err error) T { + if err != nil { + panic(err) + } + return v } // PkgName get current package name. alias of stdutil.PkgName() @@ -61,3 +54,32 @@ func FuncName(f any) string { func PkgName(funcName string) string { return stdutil.PkgName(funcName) } + +// ErrOnFail return input error on cond is false, otherwise return nil +func ErrOnFail(cond bool, err error) error { + return OrError(cond, err) +} + +// OrError return input error on cond is false, otherwise return nil +func OrError(cond bool, err error) error { + if !cond { + return err + } + return nil +} + +// OrValue get +func OrValue[T any](cond bool, okVal, elVal T) T { + if cond { + return okVal + } + return elVal +} + +// OrReturn call okFunc() on condition is true, else call elseFn() +func OrReturn[T any](cond bool, okFn, elseFn func() T) T { + if cond { + return okFn() + } + return elseFn() +} diff --git a/vendor/github.com/gookit/goutil/group.go b/vendor/github.com/gookit/goutil/group.go index 770c9e236..90a3204da 100644 --- a/vendor/github.com/gookit/goutil/group.go +++ b/vendor/github.com/gookit/goutil/group.go @@ -3,6 +3,7 @@ package goutil import ( "context" + "github.com/gookit/goutil/structs" "golang.org/x/sync/errgroup" ) @@ -44,3 +45,38 @@ func (g *ErrGroup) Add(handlers ...func() error) { g.Go(handler) } } + +// RunFn func +type RunFn func(ctx *structs.Data) error + +// QuickRun struct +type QuickRun struct { + ctx *structs.Data + // err error + fns []RunFn +} + +// NewQuickRun instance +func NewQuickRun() *QuickRun { + return &QuickRun{ + ctx: structs.NewData(), + } +} + +// Add func for run +func (p *QuickRun) Add(fns ...RunFn) *QuickRun { + p.fns = append(p.fns, fns...) + return p +} + +// Run all func +func (p *QuickRun) Run() error { + for i, fn := range p.fns { + p.ctx.Set("index", i) + + if err := fn(p.ctx); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/gookit/goutil/internal/comfunc/comfunc.go b/vendor/github.com/gookit/goutil/internal/comfunc/comfunc.go index 017bed6c3..bd9e7525c 100644 --- a/vendor/github.com/gookit/goutil/internal/comfunc/comfunc.go +++ b/vendor/github.com/gookit/goutil/internal/comfunc/comfunc.go @@ -1,9 +1,12 @@ package comfunc import ( + "fmt" "os" "regexp" + "strconv" "strings" + "time" ) // Environ like os.Environ, but will returns key-value map[string]string data. @@ -65,7 +68,6 @@ func ParseEnvVar(val string, getFn func(string) string) (newVal string) { if len(ss) == 2 { name, def = strings.TrimSpace(ss[0]), strings.TrimSpace(ss[1]) } else { - def = eVar // use raw value name = strings.TrimSpace(ss[0]) } @@ -77,3 +79,96 @@ func ParseEnvVar(val string, getFn func(string) string) (newVal string) { return eVal }) } + +// FormatTplAndArgs message +func FormatTplAndArgs(fmtAndArgs []any) string { + if len(fmtAndArgs) == 0 || fmtAndArgs == nil { + return "" + } + + ln := len(fmtAndArgs) + first := fmtAndArgs[0] + + if ln == 1 { + if msgAsStr, ok := first.(string); ok { + return msgAsStr + } + return fmt.Sprintf("%+v", first) + } + + // is template string. + if tplStr, ok := first.(string); ok { + return fmt.Sprintf(tplStr, fmtAndArgs[1:]...) + } + return fmt.Sprint(fmtAndArgs...) +} + +var ( + // TIP: extend unit d,w + // time.ParseDuration() is not supported. eg: "1d", "2w" + durStrReg = regexp.MustCompile(`^(-?\d+)(ns|us|µs|ms|s|m|h|d|w)$`) + // match long duration string, such as "1hour", "2hours", "3minutes", "4mins", "5days", "1weeks" + // time.ParseDuration() is not supported. + durStrRegL = regexp.MustCompile(`^(-?\d+)([a-zA-Z]{3,})$`) +) + +// IsDuration check the string is a duration string. +func IsDuration(s string) bool { + if s == "0" || durStrReg.MatchString(s) { + return true + } + return durStrRegL.MatchString(s) +} + +// ToDuration parses a duration string. such as "300ms", "-1.5h" or "2h45m". +// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". +// +// Diff of time.ParseDuration: +// - support extend unit d, w at the end of string. such as "1d", "2w". +// - support long string unit at end. such as "1hour", "2hours", "3minutes", "4mins", "5days", "1weeks". +// +// If the string is not a valid duration string, it will return an error. +func ToDuration(s string) (time.Duration, error) { + ln := len(s) + if ln == 0 { + return 0, fmt.Errorf("empty duration string") + } + + s = strings.ToLower(s) + if s == "0" { + return 0, nil + } + + // extend unit d,w, time.ParseDuration() is not supported. eg: "1d", "2w" + if lastUnit := s[ln-1]; lastUnit == 'd' { + s = s + "ay" + } else if lastUnit == 'w' { + s = s + "eek" + } + + // long unit, time.ParseDuration() is not supported. eg: "-3sec" => [3sec -3 sec] + ss := durStrRegL.FindStringSubmatch(s) + if len(ss) == 3 { + num, unit := ss[1], ss[2] + + // convert to short unit + switch unit { + case "week", "weeks": + // max unit is hour, so need convert by 24 * 7 * n + n, _ := strconv.Atoi(num) + s = strconv.Itoa(n*24*7) + "h" + case "day", "days": + // max unit is hour, so need convert by 24 * n + n, _ := strconv.Atoi(num) + s = strconv.Itoa(n*24) + "h" + case "hour", "hours": + s = num + "h" + case "min", "mins", "minute", "minutes": + s = num + "m" + case "sec", "secs", "second", "seconds": + s = num + "s" + } + } + + return time.ParseDuration(s) +} diff --git a/vendor/github.com/gookit/goutil/internal/comfunc/sysfunc.go b/vendor/github.com/gookit/goutil/internal/comfunc/sysfunc.go index a78296717..aef90e947 100644 --- a/vendor/github.com/gookit/goutil/internal/comfunc/sysfunc.go +++ b/vendor/github.com/gookit/goutil/internal/comfunc/sysfunc.go @@ -8,8 +8,14 @@ import ( "strings" ) -// ExpandPath will parse first `~` as user home dir path. -func ExpandPath(pathStr string) string { +// Workdir get +func Workdir() string { + dir, _ := os.Getwd() + return dir +} + +// ExpandHome will parse first `~` as user home dir path. +func ExpandHome(pathStr string) string { if len(pathStr) == 0 { return pathStr } @@ -26,7 +32,6 @@ func ExpandPath(pathStr string) string { if err != nil { return pathStr } - return homeDir + pathStr[1:] } @@ -47,7 +52,7 @@ func ExecCmd(binName string, args []string, workDir ...string) (string, error) { } // ShellExec exec command by shell -// cmdLine eg. "ls -al" +// cmdLine e.g. "ls -al" func ShellExec(cmdLine string, shells ...string) (string, error) { // shell := "/bin/sh" shell := "sh" @@ -73,23 +78,26 @@ var curShell string // // eg "/bin/zsh" "/bin/bash". // if onlyName=true, will return "zsh", "bash" -func CurrentShell(onlyName bool) (path string) { +func CurrentShell(onlyName bool) (binPath string) { var err error if curShell == "" { - path, err = ShellExec("echo $SHELL") - if err != nil { - return "" + binPath = os.Getenv("SHELL") + if len(binPath) == 0 { + binPath, err = ShellExec("echo $SHELL") + if err != nil { + return "" + } } - path = strings.TrimSpace(path) + binPath = strings.TrimSpace(binPath) // cache result - curShell = path + curShell = binPath } else { - path = curShell + binPath = curShell } - if onlyName && len(path) > 0 { - path = filepath.Base(path) + if onlyName && len(binPath) > 0 { + binPath = filepath.Base(binPath) } return } @@ -106,6 +114,5 @@ func HasShellEnv(shell string) bool { if err != nil { return false } - return strings.TrimSpace(out) == "OK" } diff --git a/vendor/github.com/gookit/goutil/jsonutil/jsonutil.go b/vendor/github.com/gookit/goutil/jsonutil/jsonutil.go index 11be66ed3..c7d392d8e 100644 --- a/vendor/github.com/gookit/goutil/jsonutil/jsonutil.go +++ b/vendor/github.com/gookit/goutil/jsonutil/jsonutil.go @@ -5,7 +5,6 @@ import ( "bytes" "encoding/json" "io" - "io/ioutil" "os" "regexp" "strings" @@ -18,7 +17,16 @@ func WriteFile(filePath string, data any) error { if err != nil { return err } - return ioutil.WriteFile(filePath, jsonBytes, 0664) + return os.WriteFile(filePath, jsonBytes, 0664) +} + +// WritePretty write pretty data to JSON file +func WritePretty(filePath string, data any) error { + bs, err := json.MarshalIndent(data, "", " ") + if err != nil { + return err + } + return os.WriteFile(filePath, bs, 0664) } // ReadFile Read JSON file data @@ -92,6 +100,35 @@ func Mapping(src, dst any) error { return Decode(bts, dst) } +// IsJSON check if the string is valid JSON. (Note: uses json.Unmarshal) +func IsJSON(s string) bool { + if s == "" { + return false + } + + var js json.RawMessage + return json.Unmarshal([]byte(s), &js) == nil +} + +// IsJSONFast simple and fast check input string is valid JSON. +func IsJSONFast(s string) bool { + ln := len(s) + if ln < 2 { + return false + } + if ln == 2 { + return s == "{}" || s == "[]" + } + + // object + if s[0] == '{' { + return s[ln-1] == '}' && s[1] == '"' + } + + // array + return s[0] == '[' && s[ln-1] == ']' +} + // `(?s:` enable match multi line var jsonMLComments = regexp.MustCompile(`(?s:/\*.*?\*/\s*)`) diff --git a/vendor/github.com/gookit/goutil/maputil/check.go b/vendor/github.com/gookit/goutil/maputil/check.go index d2c10ff2b..1f3194465 100644 --- a/vendor/github.com/gookit/goutil/maputil/check.go +++ b/vendor/github.com/gookit/goutil/maputil/check.go @@ -21,7 +21,25 @@ func HasKey(mp, key any) (ok bool) { return } -// HasAllKeys check of the given map. +// HasOneKey check of the given map. return the first exist key +func HasOneKey(mp any, keys ...any) (ok bool, key any) { + rftVal := reflect.Indirect(reflect.ValueOf(mp)) + if rftVal.Kind() != reflect.Map { + return + } + + for _, key = range keys { + for _, keyRv := range rftVal.MapKeys() { + if reflects.IsEqual(keyRv.Interface(), key) { + return true, key + } + } + } + + return false, nil +} + +// HasAllKeys check of the given map. return the first not exist key func HasAllKeys(mp any, keys ...any) (ok bool, noKey any) { rftVal := reflect.Indirect(reflect.ValueOf(mp)) if rftVal.Kind() != reflect.Map { diff --git a/vendor/github.com/gookit/goutil/maputil/convert.go b/vendor/github.com/gookit/goutil/maputil/convert.go index da77b89f2..4ded4cfde 100644 --- a/vendor/github.com/gookit/goutil/maputil/convert.go +++ b/vendor/github.com/gookit/goutil/maputil/convert.go @@ -1,9 +1,12 @@ package maputil import ( + "errors" "reflect" "strings" + "github.com/gookit/goutil/arrutil" + "github.com/gookit/goutil/comdef" "github.com/gookit/goutil/reflects" "github.com/gookit/goutil/strutil" ) @@ -15,22 +18,54 @@ func KeyToLower(src map[string]string) map[string]string { k = strings.ToLower(k) newMp[k] = v } - return newMp } // ToStringMap convert map[string]any to map[string]string func ToStringMap(src map[string]any) map[string]string { - newMp := make(map[string]string, len(src)) + strMp := make(map[string]string, len(src)) for k, v := range src { - newMp[k] = strutil.MustString(v) + strMp[k] = strutil.SafeString(v) } - - return newMp + return strMp } -// HttpQueryString convert map[string]any data to http query string. -func HttpQueryString(data map[string]any) string { +// CombineToSMap combine two string-slice to SMap(map[string]string) +func CombineToSMap(keys, values []string) SMap { + return arrutil.CombineToSMap(keys, values) +} + +// CombineToMap combine two any slice to map[K]V. alias of arrutil.CombineToMap +func CombineToMap[K comdef.SortedType, V any](keys []K, values []V) map[K]V { + return arrutil.CombineToMap(keys, values) +} + +// ToAnyMap convert map[TYPE1]TYPE2 to map[string]any +func ToAnyMap(mp any) map[string]any { + amp, _ := TryAnyMap(mp) + return amp +} + +// TryAnyMap convert map[TYPE1]TYPE2 to map[string]any +func TryAnyMap(mp any) (map[string]any, error) { + if aMp, ok := mp.(map[string]any); ok { + return aMp, nil + } + + rv := reflect.Indirect(reflect.ValueOf(mp)) + if rv.Kind() != reflect.Map { + return nil, errors.New("input is not a map value") + } + + anyMp := make(map[string]any, rv.Len()) + for _, key := range rv.MapKeys() { + anyMp[key.String()] = rv.MapIndex(key).Interface() + } + return anyMp, nil +} + +// HTTPQueryString convert map[string]any data to http query string. +func HTTPQueryString(data map[string]any) string { ss := make([]string, 0, len(data)) for k, v := range data { ss = append(ss, k+"="+strutil.QuietString(v)) diff --git a/vendor/github.com/gookit/goutil/maputil/data.go b/vendor/github.com/gookit/goutil/maputil/data.go index 6720d514b..4049a8ba5 100644 --- a/vendor/github.com/gookit/goutil/maputil/data.go +++ b/vendor/github.com/gookit/goutil/maputil/data.go @@ -3,12 +3,15 @@ package maputil import ( "strings" + "github.com/gookit/goutil/arrutil" "github.com/gookit/goutil/mathutil" "github.com/gookit/goutil/strutil" ) // Data an map data type type Data map[string]any + +// Map alias of Data type Map = Data // Has value on the data map @@ -123,6 +126,14 @@ func (d Data) Int64(key string) int64 { return 0 } +// Uint value get +func (d Data) Uint(key string) uint64 { + if val, ok := d.GetByPath(key); ok { + return mathutil.QuietUint(val) + } + return 0 +} + // Str value get by key func (d Data) Str(key string) string { if val, ok := d.GetByPath(key); ok { @@ -137,14 +148,15 @@ func (d Data) Bool(key string) bool { if !ok { return false } - if bl, ok := val.(bool); ok { - return bl - } - if str, ok := val.(string); ok { - return strutil.QuietBool(str) + switch tv := val.(type) { + case string: + return strutil.QuietBool(tv) + case bool: + return tv + default: + return false } - return false } // Strings get []string value @@ -154,10 +166,16 @@ func (d Data) Strings(key string) []string { return nil } - if ss, ok := val.([]string); ok { - return ss + switch typVal := val.(type) { + case string: + return []string{typVal} + case []string: + return typVal + case []any: + return arrutil.SliceToStrings(typVal) + default: + return nil } - return nil } // StrSplit get strings by split key value @@ -176,6 +194,11 @@ func (d Data) StringsByStr(key string) []string { return nil } +// StrMap get map[string]string value +func (d Data) StrMap(key string) map[string]string { + return d.StringMap(key) +} + // StringMap get map[string]string value func (d Data) StringMap(key string) map[string]string { val, ok := d.GetByPath(key) @@ -183,10 +206,14 @@ func (d Data) StringMap(key string) map[string]string { return nil } - if smp, ok := val.(map[string]string); ok { - return smp + switch tv := val.(type) { + case map[string]string: + return tv + case map[string]any: + return ToStringMap(tv) + default: + return nil } - return nil } // Sub get sub value as new Data @@ -217,3 +244,17 @@ func (d Data) ToStringMap() map[string]string { func (d Data) String() string { return ToString(d) } + +// Load other data to current data map +func (d Data) Load(sub map[string]any) { + for name, val := range sub { + d[name] = val + } +} + +// LoadSMap to data +func (d Data) LoadSMap(smp map[string]string) { + for name, val := range smp { + d[name] = val + } +} diff --git a/vendor/github.com/gookit/goutil/maputil/get.go b/vendor/github.com/gookit/goutil/maputil/get.go index 151d918cf..c2236c47a 100644 --- a/vendor/github.com/gookit/goutil/maputil/get.go +++ b/vendor/github.com/gookit/goutil/maputil/get.go @@ -6,6 +6,12 @@ import ( "strings" ) +// some consts for separators +const ( + Wildcard = "*" + PathSep = "." +) + // DeepGet value by key path. eg "top" "top.sub" func DeepGet(mp map[string]any, path string) (val any) { val, _ = GetByPath(path, mp) @@ -25,48 +31,96 @@ func GetByPath(path string, mp map[string]any) (val any, ok bool) { } // no sub key - if len(mp) == 0 || !strings.ContainsRune(path, '.') { + if len(mp) == 0 || strings.IndexByte(path, '.') < 1 { return nil, false } // has sub key. eg. "top.sub" keys := strings.Split(path, ".") - topK := keys[0] + return GetByPathKeys(mp, keys) +} + +// GetByPathKeys get value by path keys from a map(map[string]any). eg "top" "top.sub" +// +// Example: +// +// mp := map[string]any{ +// "top": map[string]any{ +// "sub": "value", +// }, +// } +// val, ok := GetByPathKeys(mp, []string{"top", "sub"}) // return "value", true +func GetByPathKeys(mp map[string]any, keys []string) (val any, ok bool) { + kl := len(keys) + if kl == 0 { + return mp, true + } // find top item data use top key var item any + + topK := keys[0] if item, ok = mp[topK]; !ok { return } - for _, k := range keys[1:] { + // find sub item data use sub key + for i, k := range keys[1:] { switch tData := item.(type) { - case map[string]string: // is simple map + case map[string]string: // is string map if item, ok = tData[k]; !ok { return } - case map[string]any: // is map(decode from toml/json) + case map[string]any: // is map(decode from toml/json/yaml) if item, ok = tData[k]; !ok { return } - case map[any]any: // is map(decode from yaml) + case map[any]any: // is map(decode from yaml.v2) if item, ok = tData[k]; !ok { return } - case []any: // is a slice - if item, ok = getBySlice(k, tData); !ok { - return + case []map[string]any: // is an any-map slice + if k == Wildcard { + if kl == i+2 { + return tData, true + } + + sl := make([]any, 0, len(tData)) + for _, v := range tData { + if val, ok = GetByPathKeys(v, keys[i+2:]); ok { + sl = append(sl, val) + } + } + return sl, true } - case []string, []int, []float32, []float64, []bool, []rune: - slice := reflect.ValueOf(tData) - sData := make([]any, slice.Len()) - for i := 0; i < slice.Len(); i++ { - sData[i] = slice.Index(i).Interface() + + // k is index number + idx, err := strconv.Atoi(k) + if err != nil { + return nil, false } - if item, ok = getBySlice(k, sData); !ok { - return + + if idx >= len(tData) { + return nil, false } - default: // error + item = tData[idx] + default: + rv := reflect.ValueOf(tData) + // check is slice + if rv.Kind() == reflect.Slice { + i, err := strconv.Atoi(k) + if err != nil { + return nil, false + } + if i >= rv.Len() { + return nil, false + } + + item = rv.Index(i).Interface() + continue + } + + // as error return nil, false } } @@ -74,17 +128,6 @@ func GetByPath(path string, mp map[string]any) (val any, ok bool) { return item, true } -func getBySlice(k string, slice []any) (val any, ok bool) { - i, err := strconv.ParseInt(k, 10, 64) - if err != nil { - return nil, false - } - if size := int64(len(slice)); i >= size { - return nil, false - } - return slice[i], true -} - // Keys get all keys of the given map. func Keys(mp any) (keys []string) { rftVal := reflect.Indirect(reflect.ValueOf(mp)) @@ -101,14 +144,26 @@ func Keys(mp any) (keys []string) { // Values get all values from the given map. func Values(mp any) (values []any) { - rftVal := reflect.Indirect(reflect.ValueOf(mp)) - if rftVal.Kind() != reflect.Map { + rv := reflect.Indirect(reflect.ValueOf(mp)) + if rv.Kind() != reflect.Map { return } - values = make([]any, 0, rftVal.Len()) - for _, key := range rftVal.MapKeys() { - values = append(values, rftVal.MapIndex(key).Interface()) + values = make([]any, 0, rv.Len()) + for _, key := range rv.MapKeys() { + values = append(values, rv.MapIndex(key).Interface()) } return } + +// EachAnyMap iterates the given map and calls the given function for each item. +func EachAnyMap(mp any, fn func(key string, val any)) { + rv := reflect.Indirect(reflect.ValueOf(mp)) + if rv.Kind() != reflect.Map { + panic("not a map value") + } + + for _, key := range rv.MapKeys() { + fn(key.String(), rv.MapIndex(key).Interface()) + } +} diff --git a/vendor/github.com/gookit/goutil/maputil/maputil.go b/vendor/github.com/gookit/goutil/maputil/maputil.go index e661f06d9..4e047c289 100644 --- a/vendor/github.com/gookit/goutil/maputil/maputil.go +++ b/vendor/github.com/gookit/goutil/maputil/maputil.go @@ -16,6 +16,26 @@ const ( KeySepChar = '.' ) +// SimpleMerge simple merge two data map by string key. +// will merge the src to dst map +func SimpleMerge(src, dst map[string]any) map[string]any { + if len(src) == 0 { + return dst + } + + if len(dst) == 0 { + return src + } + + for key, val := range src { + dst[key] = val + } + return dst +} + +// func DeepMerge(src, dst map[string]any, deep int) map[string]any { TODO +// } + // MergeSMap simple merge two string map. merge src to dst map func MergeSMap(src, dst map[string]string, ignoreCase bool) map[string]string { return MergeStringMap(src, dst, ignoreCase) @@ -23,6 +43,13 @@ func MergeSMap(src, dst map[string]string, ignoreCase bool) map[string]string { // MergeStringMap simple merge two string map. merge src to dst map func MergeStringMap(src, dst map[string]string, ignoreCase bool) map[string]string { + if len(src) == 0 { + return dst + } + if len(dst) == 0 { + return src + } + for k, v := range src { if ignoreCase { k = strings.ToLower(k) diff --git a/vendor/github.com/gookit/goutil/maputil/smap.go b/vendor/github.com/gookit/goutil/maputil/smap.go index d38598214..47c9ad991 100644 --- a/vendor/github.com/gookit/goutil/maputil/smap.go +++ b/vendor/github.com/gookit/goutil/maputil/smap.go @@ -111,6 +111,15 @@ func (m SMap) Values() []string { return ss } +// ToKVPairs slice convert. eg: {k1:v1,k2:v2} => {k1,v1,k2,v2} +func (m SMap) ToKVPairs() []string { + pairs := make([]string, 0, len(m)*2) + for k, v := range m { + pairs = append(pairs, k, v) + } + return pairs +} + // String data to string func (m SMap) String() string { return ToString2(m) diff --git a/vendor/github.com/gookit/goutil/mathutil/check.go b/vendor/github.com/gookit/goutil/mathutil/check.go index 45c499275..055fc4df0 100644 --- a/vendor/github.com/gookit/goutil/mathutil/check.go +++ b/vendor/github.com/gookit/goutil/mathutil/check.go @@ -1,6 +1,8 @@ package mathutil -// Compare intX,floatX value by given op. returns `srcVal op(=,!=,<,<=,>,>=) dstVal` +import "github.com/gookit/goutil/comdef" + +// Compare any intX,floatX value by given op. returns `srcVal op(=,!=,<,<=,>,>=) dstVal` // // Usage: // @@ -42,40 +44,54 @@ func Compare(srcVal, dstVal any, op string) (ok bool) { return CompInt64(srcInt, dstInt, op) } -// CompInt64 compare int64, returns the srcI64 op dstI64 -func CompInt64(srcI64, dstI64 int64, op string) (ok bool) { +// CompInt compare int,uint value. returns `srcVal op(=,!=,<,<=,>,>=) dstVal` +func CompInt[T comdef.Xint](srcVal, dstVal T, op string) (ok bool) { + return CompValue(srcVal, dstVal, op) +} + +// CompInt64 compare int64 value. returns `srcVal op(=,!=,<,<=,>,>=) dstVal` +func CompInt64(srcVal, dstVal int64, op string) bool { + return CompValue(srcVal, dstVal, op) +} + +// CompFloat compare float64,float32 value. returns `srcVal op(=,!=,<,<=,>,>=) dstVal` +func CompFloat[T comdef.Float](srcVal, dstVal T, op string) (ok bool) { + return CompValue(srcVal, dstVal, op) +} + +// CompValue compare intX,uintX,floatX value. returns `srcVal op(=,!=,<,<=,>,>=) dstVal` +func CompValue[T comdef.XintOrFloat](srcVal, dstVal T, op string) (ok bool) { switch op { case "<", "lt": - ok = srcI64 < dstI64 + ok = srcVal < dstVal case "<=", "lte": - ok = srcI64 <= dstI64 + ok = srcVal <= dstVal case ">", "gt": - ok = srcI64 > dstI64 + ok = srcVal > dstVal case ">=", "gte": - ok = srcI64 >= dstI64 + ok = srcVal >= dstVal case "=", "eq": - ok = srcI64 == dstI64 + ok = srcVal == dstVal case "!=", "ne", "neq": - ok = srcI64 != dstI64 + ok = srcVal != dstVal } return } -// CompFloat compare float64 -func CompFloat(srcF64, dstF64 float64, op string) (ok bool) { - switch op { - case "<", "lt": - ok = srcF64 < dstF64 - case "<=", "lte": - ok = srcF64 <= dstF64 - case ">", "gt": - ok = srcF64 > dstF64 - case ">=", "gte": - ok = srcF64 >= dstF64 - case "=", "eq": - ok = srcF64 == dstF64 - case "!=", "ne", "neq": - ok = srcF64 != dstF64 +// InRange check if val in int/float range [min, max] +func InRange[T comdef.IntOrFloat](val, min, max T) bool { + return val >= min && val <= max +} + +// OutRange check if val not in int/float range [min, max] +func OutRange[T comdef.IntOrFloat](val, min, max T) bool { + return val < min || val > max +} + +// InUintRange check if val in unit range [min, max] +func InUintRange[T comdef.Uint](val, min, max T) bool { + if max == 0 { + return val >= min } - return + return val >= min && val <= max } diff --git a/vendor/github.com/gookit/goutil/mathutil/convert.go b/vendor/github.com/gookit/goutil/mathutil/convert.go index 2b64ba682..4034e3918 100644 --- a/vendor/github.com/gookit/goutil/mathutil/convert.go +++ b/vendor/github.com/gookit/goutil/mathutil/convert.go @@ -172,6 +172,12 @@ func Int64(in any) (int64, error) { return ToInt64(in) } +// SafeInt64 convert value to int64, will ignore error +func SafeInt64(in any) int64 { + i64, _ := ToInt64(in) + return i64 +} + // QuietInt64 convert value to int64, will ignore error func QuietInt64(in any) int64 { i64, _ := ToInt64(in) @@ -366,7 +372,7 @@ func TryToString(val any, defaultAsErr bool) (str string, err error) { case int32: // same as `rune` str = strconv.Itoa(int(value)) case int64: - str = strconv.Itoa(int(value)) + str = strconv.FormatInt(value, 10) case uint: str = strconv.FormatUint(uint64(value), 10) case uint8: @@ -382,8 +388,8 @@ func TryToString(val any, defaultAsErr bool) (str string, err error) { case float64: str = strconv.FormatFloat(value, 'f', -1, 64) case time.Duration: - str = strconv.FormatUint(uint64(value.Nanoseconds()), 10) - case json.Number: + str = strconv.FormatInt(int64(value), 10) + case fmt.Stringer: str = value.String() default: if defaultAsErr { diff --git a/vendor/github.com/gookit/goutil/mathutil/mathutil.go b/vendor/github.com/gookit/goutil/mathutil/mathutil.go index c379efc5b..52a2e145d 100644 --- a/vendor/github.com/gookit/goutil/mathutil/mathutil.go +++ b/vendor/github.com/gookit/goutil/mathutil/mathutil.go @@ -1,10 +1,42 @@ +// Package mathutil provide math(int, number) util functions. eg: convert, math calc, random package mathutil -import "math" +import ( + "math" -// MaxFloat compare and return max value -func MaxFloat(x, y float64) float64 { - return math.Max(x, y) + "github.com/gookit/goutil/comdef" +) + +// Min compare two value and return max value +func Min[T comdef.XintOrFloat](x, y T) T { + if x < y { + return x + } + return y +} + +// Max compare two value and return max value +func Max[T comdef.XintOrFloat](x, y T) T { + if x > y { + return x + } + return y +} + +// SwapMin compare and always return [min, max] value +func SwapMin[T comdef.XintOrFloat](x, y T) (T, T) { + if x < y { + return x, y + } + return y, x +} + +// SwapMax compare and always return [max, min] value +func SwapMax[T comdef.XintOrFloat](x, y T) (T, T) { + if x > y { + return x, y + } + return y, x } // MaxInt compare and return max value @@ -38,3 +70,16 @@ func SwapMaxI64(x, y int64) (int64, int64) { } return y, x } + +// MaxFloat compare and return max value +func MaxFloat(x, y float64) float64 { + return math.Max(x, y) +} + +// OrElse return s OR nv(new-value) on s is empty +func OrElse[T comdef.XintOrFloat](in, nv T) T { + if in != 0 { + return in + } + return nv +} diff --git a/vendor/github.com/gookit/goutil/mathutil/number.go b/vendor/github.com/gookit/goutil/mathutil/number.go index 8c5d29b47..5b1359463 100644 --- a/vendor/github.com/gookit/goutil/mathutil/number.go +++ b/vendor/github.com/gookit/goutil/mathutil/number.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/gookit/goutil/fmtutil" + "github.com/gookit/goutil/basefn" ) // IsNumeric returns true if the given character is a numeric, otherwise false. @@ -17,7 +17,6 @@ func Percent(val, total int) float64 { if total == 0 { return float64(0) } - return (float64(val) / float64(total)) * 100 } @@ -26,12 +25,13 @@ func ElapsedTime(startTime time.Time) string { return fmt.Sprintf("%.3f", time.Since(startTime).Seconds()*1000) } -// DataSize format value. alias format.DataSize() +// DataSize format value to data size string. eg: 1024 => 1KB, 1024*1024 => 1MB +// alias format.DataSize() func DataSize(size uint64) string { - return fmtutil.DataSize(size) + return basefn.DataSize(size) } // HowLongAgo calc time. alias format.HowLongAgo() func HowLongAgo(sec int64) string { - return fmtutil.HowLongAgo(sec) + return basefn.HowLongAgo(sec) } diff --git a/vendor/github.com/gookit/goutil/reflects/check.go b/vendor/github.com/gookit/goutil/reflects/check.go index c7e5c0390..acd256454 100644 --- a/vendor/github.com/gookit/goutil/reflects/check.go +++ b/vendor/github.com/gookit/goutil/reflects/check.go @@ -5,7 +5,7 @@ import ( "reflect" ) -// HasChild check. eg: array, slice, map, struct +// HasChild type check. eg: array, slice, map, struct func HasChild(v reflect.Value) bool { switch v.Kind() { case reflect.Array, reflect.Slice, reflect.Map, reflect.Struct: @@ -14,6 +14,34 @@ func HasChild(v reflect.Value) bool { return false } +// IsArrayOrSlice check. eg: array, slice +func IsArrayOrSlice(k reflect.Kind) bool { + return k == reflect.Slice || k == reflect.Array +} + +// IsSimpleKind kind in: string, bool, intX, uintX, floatX +func IsSimpleKind(k reflect.Kind) bool { + if reflect.String == k { + return true + } + return k > reflect.Invalid && k <= reflect.Float64 +} + +// IsAnyInt check is intX or uintX type +func IsAnyInt(k reflect.Kind) bool { + return k >= reflect.Int && k <= reflect.Uintptr +} + +// IsIntx check is intX or uintX type +func IsIntx(k reflect.Kind) bool { + return k >= reflect.Int && k <= reflect.Int64 +} + +// IsUintX check is intX or uintX type +func IsUintX(k reflect.Kind) bool { + return k >= reflect.Uint && k <= reflect.Uintptr +} + // IsNil reflect value func IsNil(v reflect.Value) bool { switch v.Kind() { diff --git a/vendor/github.com/gookit/goutil/reflects/conv.go b/vendor/github.com/gookit/goutil/reflects/conv.go index 16fa6fe9f..1dfa303a5 100644 --- a/vendor/github.com/gookit/goutil/reflects/conv.go +++ b/vendor/github.com/gookit/goutil/reflects/conv.go @@ -38,12 +38,19 @@ func BaseTypeVal(v reflect.Value) (value any, err error) { // ValueByType create reflect.Value by give reflect.Type func ValueByType(val any, typ reflect.Type) (rv reflect.Value, err error) { - if typ.Kind() <= reflect.Float64 { + // handle kind: string, bool, intX, uintX, floatX + if typ.Kind() == reflect.String || typ.Kind() <= reflect.Float64 { return ValueByKind(val, typ.Kind()) } - // check type. like map, slice newRv := reflect.ValueOf(val) + + // try auto convert slice type + if IsArrayOrSlice(newRv.Kind()) && IsArrayOrSlice(typ.Kind()) { + return ConvSlice(newRv, typ.Elem()) + } + + // check type. like map if newRv.Type() == typ { return newRv, nil } @@ -123,6 +130,34 @@ func ValueByKind(val any, kind reflect.Kind) (rv reflect.Value, err error) { return } +// ConvSlice make new type slice from old slice, will auto convert element type. +// +// TIPs: +// +// Only support kind: string, bool, intX, uintX, floatX +func ConvSlice(oldSlRv reflect.Value, newElemTyp reflect.Type) (rv reflect.Value, err error) { + if !IsArrayOrSlice(oldSlRv.Kind()) { + panic("only allow array or slice type value") + } + + // do not need convert type + if oldSlRv.Type().Elem() == newElemTyp { + return oldSlRv, nil + } + + newSlTyp := reflect.SliceOf(newElemTyp) + newSlRv := reflect.MakeSlice(newSlTyp, 0, 0) + for i := 0; i < oldSlRv.Len(); i++ { + newElemV, err := ValueByKind(oldSlRv.Index(i).Interface(), newElemTyp.Kind()) + if err != nil { + return reflect.Value{}, err + } + + newSlRv = reflect.Append(newSlRv, newElemV) + } + return newSlRv, nil +} + // String convert func String(rv reflect.Value) string { s, _ := ValToString(rv, false) diff --git a/vendor/github.com/gookit/goutil/reflects/util.go b/vendor/github.com/gookit/goutil/reflects/util.go index 41b21e81b..44d17166b 100644 --- a/vendor/github.com/gookit/goutil/reflects/util.go +++ b/vendor/github.com/gookit/goutil/reflects/util.go @@ -4,6 +4,7 @@ import ( "fmt" "reflect" "strconv" + "unsafe" ) // Elem returns the value that the interface v contains @@ -49,23 +50,56 @@ func Len(v reflect.Value) int { return -1 } -// SliceSubKind get sub-elem kind of the array, slice, variadic-var. +// SliceSubKind get sub-elem kind of the array, slice, variadic-var. alias SliceElemKind() +func SliceSubKind(typ reflect.Type) reflect.Kind { + return SliceElemKind(typ) +} + +// SliceElemKind get sub-elem kind of the array, slice, variadic-var. // // Usage: // -// SliceSubKind(reflect.TypeOf([]string{"abc"})) // reflect.String -func SliceSubKind(typ reflect.Type) reflect.Kind { - if typ.Kind() == reflect.Slice { +// SliceElemKind(reflect.TypeOf([]string{"abc"})) // reflect.String +func SliceElemKind(typ reflect.Type) reflect.Kind { + if typ.Kind() == reflect.Slice || typ.Kind() == reflect.Array { return typ.Elem().Kind() } return reflect.Invalid } -// SetValue to a reflect.Value +// UnexportedValue quickly get unexported value by reflect.Value +// +// NOTE: this method is unsafe, use it carefully. +// should ensure rv is addressable by field.CanAddr() +// +// refer: https://stackoverflow.com/questions/42664837/how-to-access-unexported-struct-fields +func UnexportedValue(rv reflect.Value) any { + if rv.CanAddr() { + // create new value from addr, now can be read and set. + return reflect.NewAt(rv.Type(), unsafe.Pointer(rv.UnsafeAddr())).Elem().Interface() + } + + // If the rv is not addressable this trick won't work, but you can create an addressable copy like this + rs2 := reflect.New(rv.Type()).Elem() + rs2.Set(rv) + rv = rs2.Field(0) + rv = reflect.NewAt(rv.Type(), unsafe.Pointer(rv.UnsafeAddr())).Elem() + // Now rv can be read. TIP: Setting will succeed but only affects the temporary copy. + return rv.Interface() +} + +// SetUnexportedValue quickly set unexported field value by reflect +// +// NOTE: this method is unsafe, use it carefully. +// should ensure rv is addressable by field.CanAddr() +func SetUnexportedValue(rv reflect.Value, value any) { + reflect.NewAt(rv.Type(), unsafe.Pointer(rv.UnsafeAddr())).Elem().Set(reflect.ValueOf(value)) +} + +// SetValue to a `reflect.Value`. will auto convert type if needed. func SetValue(rv reflect.Value, val any) error { // get real type of the ptr value if rv.Kind() == reflect.Ptr { - // init if is nil if rv.IsNil() { elemTyp := rv.Type().Elem() rv.Set(reflect.New(elemTyp)) @@ -76,12 +110,44 @@ func SetValue(rv reflect.Value, val any) error { } rv1, err := ValueByType(val, rv.Type()) - if err != nil { - return err + if err == nil { + rv.Set(rv1) + } + return err +} + +// SetRValue to a `reflect.Value`. will direct set value without convert type. +func SetRValue(rv, val reflect.Value) { + if rv.Kind() == reflect.Ptr { + if rv.IsNil() { + elemTyp := rv.Type().Elem() + rv.Set(reflect.New(elemTyp)) + } + rv = reflect.Indirect(rv) } - rv.Set(rv1) - return nil + rv.Set(val) +} + +// EachMap process any map data +func EachMap(mp reflect.Value, fn func(key, val reflect.Value)) { + if fn == nil { + return + } + if mp.Kind() != reflect.Map { + panic("only allow map value data") + } + + for _, key := range mp.MapKeys() { + fn(key, mp.MapIndex(key)) + } +} + +// EachStrAnyMap process any map data as string key and any value +func EachStrAnyMap(mp reflect.Value, fn func(key string, val any)) { + EachMap(mp, func(key, val reflect.Value) { + fn(String(key), val.Interface()) + }) } // FlatFunc custom collect handle func diff --git a/vendor/github.com/gookit/goutil/stdio/ioutil.go b/vendor/github.com/gookit/goutil/stdio/ioutil.go index 64b0861ec..1bf65c0f4 100644 --- a/vendor/github.com/gookit/goutil/stdio/ioutil.go +++ b/vendor/github.com/gookit/goutil/stdio/ioutil.go @@ -7,8 +7,8 @@ import ( ) // QuietFprint to writer, will ignore error -func QuietFprint(w io.Writer, ss ...string) { - _, _ = fmt.Fprint(w, strings.Join(ss, "")) +func QuietFprint(w io.Writer, a ...any) { + _, _ = fmt.Fprint(w, a...) } // QuietFprintf to writer, will ignore error @@ -17,25 +17,11 @@ func QuietFprintf(w io.Writer, tpl string, vs ...any) { } // QuietFprintln to writer, will ignore error -func QuietFprintln(w io.Writer, ss ...string) { - _, _ = fmt.Fprintln(w, strings.Join(ss, "")) +func QuietFprintln(w io.Writer, a ...any) { + _, _ = fmt.Fprintln(w, a...) } // QuietWriteString to writer, will ignore error func QuietWriteString(w io.Writer, ss ...string) { _, _ = io.WriteString(w, strings.Join(ss, "")) } - -// DiscardReader anything from the reader -func DiscardReader(src io.Reader) { - _, _ = io.Copy(io.Discard, src) -} - -// MustReadReader read contents from io.Reader, will panic on error -func MustReadReader(r io.Reader) []byte { - bs, err := io.ReadAll(r) - if err != nil { - panic(err) - } - return bs -} diff --git a/vendor/github.com/gookit/goutil/stdio/stdio.go b/vendor/github.com/gookit/goutil/stdio/stdio.go index 8a0146e8c..48f773863 100644 --- a/vendor/github.com/gookit/goutil/stdio/stdio.go +++ b/vendor/github.com/gookit/goutil/stdio/stdio.go @@ -1,2 +1,89 @@ // Package stdio provide some standard IO util functions. package stdio + +import ( + "bufio" + "bytes" + "io" + "os" + "strings" +) + +// DiscardReader anything from the reader +func DiscardReader(src io.Reader) { + _, _ = io.Copy(io.Discard, src) +} + +// ReadString read contents from io.Reader +func ReadString(r io.Reader) string { + bs, err := io.ReadAll(r) + if err != nil { + return "" + } + return string(bs) +} + +// MustReadReader read contents from io.Reader, will panic on error +func MustReadReader(r io.Reader) []byte { + bs, err := io.ReadAll(r) + if err != nil { + panic(err) + } + return bs +} + +// NewIOReader instance by input: string, bytes, io.Reader +func NewIOReader(in any) io.Reader { + switch typIn := in.(type) { + case []byte: + return bytes.NewReader(typIn) + case string: + return strings.NewReader(typIn) + case io.Reader: + return typIn + } + panic("invalid input type for create reader") +} + +// NewScanner instance by input data or reader +func NewScanner(in any) *bufio.Scanner { + switch typIn := in.(type) { + case io.Reader: + return bufio.NewScanner(typIn) + case []byte: + return bufio.NewScanner(bytes.NewReader(typIn)) + case string: + return bufio.NewScanner(strings.NewReader(typIn)) + case *bufio.Scanner: + return typIn + default: + panic("invalid input type for create scanner") + } +} + +// WriteByte to stdout +func WriteByte(b byte) { + _, _ = os.Stdout.Write([]byte{b}) +} + +// WriteBytes to stdout +func WriteBytes(bs []byte) { + _, _ = os.Stdout.Write(bs) +} + +// WritelnBytes to stdout +func WritelnBytes(bs []byte) { + _, _ = os.Stdout.Write(bs) + _, _ = os.Stdout.Write([]byte("\n")) +} + +// WriteString to stdout +func WriteString(s string) { + _, _ = os.Stdout.WriteString(s) +} + +// Writeln to stdout +func Writeln(s string) { + _, _ = os.Stdout.WriteString(s) + _, _ = os.Stdout.Write([]byte("\n")) +} diff --git a/vendor/github.com/gookit/goutil/stdio/writer.go b/vendor/github.com/gookit/goutil/stdio/writer.go index 76070cbb1..8a9b9fc23 100644 --- a/vendor/github.com/gookit/goutil/stdio/writer.go +++ b/vendor/github.com/gookit/goutil/stdio/writer.go @@ -33,6 +33,9 @@ func (w *WriteWrapper) WriteByte(c byte) error { // WriteString data func (w *WriteWrapper) WriteString(s string) (n int, err error) { + if sw, ok := w.Out.(io.StringWriter); ok { + return sw.WriteString(s) + } return w.Out.Write([]byte(s)) } diff --git a/vendor/github.com/gookit/goutil/structs/convert.go b/vendor/github.com/gookit/goutil/structs/convert.go index 3535f89bf..8a379fe02 100644 --- a/vendor/github.com/gookit/goutil/structs/convert.go +++ b/vendor/github.com/gookit/goutil/structs/convert.go @@ -2,7 +2,11 @@ package structs import ( "errors" + "fmt" "reflect" + + "github.com/gookit/goutil/maputil" + "github.com/gookit/goutil/reflects" ) // ToMap quickly convert structs to map by reflect @@ -25,16 +29,73 @@ func TryToMap(st any, optFns ...MapOptFunc) (map[string]any, error) { return StructToMap(st, optFns...) } +// ToSMap quickly and safe convert structs to map[string]string by reflect +func ToSMap(st any, optFns ...MapOptFunc) map[string]string { + mp, _ := StructToMap(st, optFns...) + return maputil.ToStringMap(mp) +} + +// TryToSMap quickly convert structs to map[string]string by reflect +func TryToSMap(st any, optFns ...MapOptFunc) (map[string]string, error) { + mp, err := StructToMap(st, optFns...) + if err != nil { + return nil, err + } + return maputil.ToStringMap(mp), nil +} + +// MustToSMap alias of ToStringMap(), but will panic on error +func MustToSMap(st any, optFns ...MapOptFunc) map[string]string { + mp, err := StructToMap(st, optFns...) + if err != nil { + panic(err) + } + return maputil.ToStringMap(mp) +} + +// ToString quickly format struct to string +func ToString(st any, optFns ...MapOptFunc) string { + mp, err := StructToMap(st, optFns...) + if err == nil { + return maputil.ToString(mp) + } + return fmt.Sprint(st) +} + const defaultFieldTag = "json" -// MapOptions struct +// MapOptions for convert struct to map type MapOptions struct { + // TagName for map filed. default is "json" TagName string + // ParseDepth for parse. TODO support depth + ParseDepth int + // MergeAnonymous struct fields to parent map. default is true + MergeAnonymous bool + // ExportPrivate export private fields. default is false + ExportPrivate bool } // MapOptFunc define type MapOptFunc func(opt *MapOptions) +// WithMapTagName set tag name for map field +func WithMapTagName(tagName string) MapOptFunc { + return func(opt *MapOptions) { + opt.TagName = tagName + } +} + +// MergeAnonymous merge anonymous struct fields to parent map +func MergeAnonymous(opt *MapOptions) { + opt.MergeAnonymous = true +} + +// ExportPrivate merge anonymous struct fields to parent map +func ExportPrivate(opt *MapOptions) { + opt.ExportPrivate = true +} + // StructToMap quickly convert structs to map[string]any by reflect. // Can custom export field name by tag `json` or custom tag func StructToMap(st any, optFns ...MapOptFunc) (map[string]any, error) { @@ -43,13 +104,9 @@ func StructToMap(st any, optFns ...MapOptFunc) (map[string]any, error) { return mp, nil } - obj := reflect.ValueOf(st) - if obj.Kind() == reflect.Ptr { - obj = obj.Elem() - } - + obj := reflect.Indirect(reflect.ValueOf(st)) if obj.Kind() != reflect.Struct { - return mp, errors.New("must be an struct") + return mp, errors.New("must be an struct value") } opt := &MapOptions{TagName: defaultFieldTag} @@ -57,23 +114,25 @@ func StructToMap(st any, optFns ...MapOptFunc) (map[string]any, error) { fn(opt) } - mp, err := structToMap(obj, opt.TagName) + _, err := structToMap(obj, opt, mp) return mp, err } -func structToMap(obj reflect.Value, tagName string) (map[string]any, error) { - refType := obj.Type() - mp := make(map[string]any) +func structToMap(obj reflect.Value, opt *MapOptions, mp map[string]any) (map[string]any, error) { + if mp == nil { + mp = make(map[string]any) + } + refType := obj.Type() for i := 0; i < obj.NumField(); i++ { ft := refType.Field(i) name := ft.Name - // skip don't exported field - if name[0] >= 'a' && name[0] <= 'z' { + // skip un-exported field + if !opt.ExportPrivate && IsUnexported(name) { continue } - tagVal, ok := ft.Tag.Lookup(tagName) + tagVal, ok := ft.Tag.Lookup(opt.TagName) if ok && tagVal != "" { sMap, err := ParseTagValueDefault(name, tagVal) if err != nil { @@ -81,24 +140,33 @@ func structToMap(obj reflect.Value, tagName string) (map[string]any, error) { } name = sMap.Default("name", name) - // un-exported field - if name == "" { + if name == "" { // un-exported field continue } } - field := obj.Field(i) + field := reflect.Indirect(obj.Field(i)) if field.Kind() == reflect.Struct { - sub, err := structToMap(field, tagName) - if err != nil { - return nil, err + // collect anonymous struct values to parent. + if ft.Anonymous && opt.MergeAnonymous { + _, err := structToMap(field, opt, mp) + if err != nil { + return nil, err + } + } else { // collect struct values to submap + sub, err := structToMap(field, opt, nil) + if err != nil { + return nil, err + } + mp[name] = sub } - mp[name] = sub continue } if field.CanInterface() { mp[name] = field.Interface() + } else if field.CanAddr() { // for unexported field + mp[name] = reflects.UnexportedValue(field) } } diff --git a/vendor/github.com/gookit/goutil/structs/copy.go b/vendor/github.com/gookit/goutil/structs/copy.go new file mode 100644 index 000000000..d42735160 --- /dev/null +++ b/vendor/github.com/gookit/goutil/structs/copy.go @@ -0,0 +1,6 @@ +package structs + +// MapStruct simple copy src struct value to dst struct +// func MapStruct(srcSt, dstSt any) { +// // TODO +// } diff --git a/vendor/github.com/gookit/goutil/structs/data.go b/vendor/github.com/gookit/goutil/structs/data.go index 63702110b..ae0081898 100644 --- a/vendor/github.com/gookit/goutil/structs/data.go +++ b/vendor/github.com/gookit/goutil/structs/data.go @@ -173,3 +173,22 @@ func (d *Data) BoolVal(key string) bool { func (d *Data) String() string { return maputil.ToString(d.data) } + +// OrderedMap data TODO +type OrderedMap struct { + maputil.Data + len int + keys []string + // vals []any +} + +// NewOrderedMap instance. +func NewOrderedMap(len int) *OrderedMap { + return &OrderedMap{len: len} +} + +// Set key and value to map +func (om *OrderedMap) Set(key string, val any) { + om.keys = append(om.keys, key) + om.Data.Set(key, val) +} diff --git a/vendor/github.com/gookit/goutil/structs/init.go b/vendor/github.com/gookit/goutil/structs/init.go new file mode 100644 index 000000000..7999b857e --- /dev/null +++ b/vendor/github.com/gookit/goutil/structs/init.go @@ -0,0 +1,179 @@ +package structs + +import ( + "errors" + "reflect" + + "github.com/gookit/goutil/internal/comfunc" + "github.com/gookit/goutil/reflects" + "github.com/gookit/goutil/strutil" +) + +const defaultInitTag = "default" + +// InitOptFunc define +type InitOptFunc func(opt *InitOptions) + +// InitOptions struct +type InitOptions struct { + // TagName default value tag name. tag: default + TagName string + // ParseEnv var name on default value. eg: `default:"${APP_ENV}"` + // + // default: false + ParseEnv bool + // ValueHook before set value hook TODO + ValueHook func(val string) any +} + +// Init struct default value by field "default" tag. +func Init(ptr any, optFns ...InitOptFunc) error { + return InitDefaults(ptr, optFns...) +} + +// InitDefaults init struct default value by field "default" tag. +// +// TIPS: +// +// Support init field types: string, bool, intX, uintX, floatX, array, slice +// +// Example: +// +// type User1 struct { +// Name string `default:"inhere"` +// Age int32 `default:"30"` +// } +// +// u1 := &User1{} +// err = structs.InitDefaults(u1) +// fmt.Printf("%+v\n", u1) // Output: {Name:inhere Age:30} +func InitDefaults(ptr any, optFns ...InitOptFunc) error { + rv := reflect.ValueOf(ptr) + if rv.Kind() != reflect.Ptr { + return errors.New("must be provider an pointer value") + } + + rv = rv.Elem() + if rv.Kind() != reflect.Struct { + return errors.New("must be provider an struct value") + } + + opt := &InitOptions{TagName: defaultInitTag} + for _, fn := range optFns { + fn(opt) + } + + return initDefaults(rv, opt) +} + +func initDefaults(rv reflect.Value, opt *InitOptions) error { + rt := rv.Type() + + for i := 0; i < rt.NumField(); i++ { + sf := rt.Field(i) + // skip don't exported field + if IsUnexported(sf.Name) { + continue + } + + val, hasTag := sf.Tag.Lookup(opt.TagName) + if !hasTag || val == "-" { + continue + } + + fv := rv.Field(i) + if fv.Kind() == reflect.Struct { + if err := initDefaults(fv, opt); err != nil { + return err + } + continue + } + + // skip on field has value + if !fv.IsZero() { + // special: handle for pointer struct field + if fv.Kind() == reflect.Pointer { + fv = fv.Elem() + if fv.Kind() == reflect.Struct { + if err := initDefaults(fv, opt); err != nil { + return err + } + } + } + continue + } + + // handle for pointer field + if fv.Kind() == reflect.Pointer { + if fv.IsNil() { + fv.Set(reflect.New(fv.Type().Elem())) + } + + fv = fv.Elem() + if fv.Kind() == reflect.Struct { + if err := initDefaults(fv, opt); err != nil { + return err + } + continue + } + } else if fv.Kind() == reflect.Slice { + el := sf.Type.Elem() + isPtr := el.Kind() == reflect.Pointer + if isPtr { + el = el.Elem() + } + + // init sub struct in slice. like `[]SubStruct` or `[]*SubStruct` + if el.Kind() == reflect.Struct { + // make sub-struct and init. like: `SubStruct` + subFv := reflect.New(el) + subFvE := subFv.Elem() + if err := initDefaults(subFvE, opt); err != nil { + return err + } + + // make new slice and set value. + newFv := reflect.MakeSlice(reflect.SliceOf(sf.Type.Elem()), 0, 1) + if isPtr { + newFv = reflect.Append(newFv, subFv) + } else { + newFv = reflect.Append(newFv, subFvE) + } + fv.Set(newFv) + continue + } + } + + if err := initDefaultValue(fv, val, opt.ParseEnv); err != nil { + return err + } + } + + return nil +} + +func initDefaultValue(fv reflect.Value, val string, parseEnv bool) error { + if val == "" || !fv.CanSet() { + return nil + } + + // parse env var + if parseEnv { + val = comfunc.ParseEnvVar(val, nil) + } + + var anyVal any = val + + // simple slice: convert simple kind(string,intX,uintX,...) to slice. eg: "1,2,3" => []int{1,2,3} + if reflects.IsArrayOrSlice(fv.Kind()) && reflects.IsSimpleKind(reflects.SliceElemKind(fv.Type())) { + ss := strutil.SplitTrimmed(val, ",") + valRv, err := reflects.ConvSlice(reflect.ValueOf(ss), fv.Type().Elem()) + if err == nil { + reflects.SetRValue(fv, valRv) + } + return err + } + + // set value + return reflects.SetValue(fv, anyVal) +} diff --git a/vendor/github.com/gookit/goutil/structs/structs.go b/vendor/github.com/gookit/goutil/structs/structs.go index fb09db0ce..c671f21cd 100644 --- a/vendor/github.com/gookit/goutil/structs/structs.go +++ b/vendor/github.com/gookit/goutil/structs/structs.go @@ -1,7 +1,15 @@ // Package structs Provide some extends util functions for struct. eg: tag parse, struct init, value set package structs -// MapStruct simple copy src struct value to dst struct -// func MapStruct(srcSt, dstSt any) { -// // TODO -// } +// IsExported field name on struct +func IsExported(name string) bool { + return name[0] >= 'A' && name[0] <= 'Z' +} + +// IsUnexported field name on struct +func IsUnexported(name string) bool { + if name[0] == '_' { + return true + } + return name[0] >= 'a' && name[0] <= 'z' +} diff --git a/vendor/github.com/gookit/goutil/structs/tags.go b/vendor/github.com/gookit/goutil/structs/tags.go index b6118998d..01ab9ff4d 100644 --- a/vendor/github.com/gookit/goutil/structs/tags.go +++ b/vendor/github.com/gookit/goutil/structs/tags.go @@ -204,6 +204,14 @@ func ParseTagValueDefault(field, tagVal string) (mp maputil.SMap, err error) { return } +// ParseTagValueQuick quick parse tag value string by sep(;) +func ParseTagValueQuick(tagVal string, defines []string) maputil.SMap { + parseFn := ParseTagValueDefine(";", defines) + + mp, _ := parseFn("", tagVal) + return mp +} + // ParseTagValueDefine parse tag value string by given defines. // // Examples: @@ -235,7 +243,11 @@ func ParseTagValueDefine(sep string, defines []string) TagValFunc { // ParseTagValueNamed parse k-v tag value string. it's like INI format contents. // -// eg: "name=int0;shorts=i;required=true;desc=int option message" +// Examples: +// +// eg: "name=val0;shorts=i;required=true;desc=a message" +// => +// {name: val0, shorts: i, required: true, desc: a message} func ParseTagValueNamed(field, tagVal string, keys ...string) (mp maputil.SMap, err error) { ss := strutil.Split(tagVal, ";") ln := len(ss) @@ -250,8 +262,7 @@ func ParseTagValueNamed(field, tagVal string, keys ...string) (mp maputil.SMap, return } - kvNodes := strings.SplitN(s, "=", 2) - key, val := kvNodes[0], strings.TrimSpace(kvNodes[1]) + key, val := strutil.TrimCut(s, "=") if len(keys) > 0 && !arrutil.StringsHas(keys, key) { err = fmt.Errorf("parse tag error on field '%s': invalid key name '%s'", field, key) return diff --git a/vendor/github.com/gookit/goutil/structs/value.go b/vendor/github.com/gookit/goutil/structs/value.go index 481e8a32b..52ae940c9 100644 --- a/vendor/github.com/gookit/goutil/structs/value.go +++ b/vendor/github.com/gookit/goutil/structs/value.go @@ -35,6 +35,11 @@ func (v *Value) Val() any { return v.V } +// Val get +// func (v *Value) ValOr[T any](defVal T) T { +// return v.V +// } + // Int value get func (v *Value) Int() int { if v.V == nil { @@ -102,7 +107,7 @@ func (v *Value) Strings() (ss []string) { return } -// SplitToStrings split string value to strings +// SplitToStrings split string value to strings. sep default is comma(,) func (v *Value) SplitToStrings(sep ...string) (ss []string) { if v.V == nil { return @@ -114,7 +119,7 @@ func (v *Value) SplitToStrings(sep ...string) (ss []string) { return } -// SplitToInts split string value to []int +// SplitToInts split string value to []int. sep default is comma(,) func (v *Value) SplitToInts(sep ...string) (ss []int) { if v.V == nil { return diff --git a/vendor/github.com/gookit/goutil/structs/wrapper.go b/vendor/github.com/gookit/goutil/structs/wrapper.go new file mode 100644 index 000000000..59bfe3b6d --- /dev/null +++ b/vendor/github.com/gookit/goutil/structs/wrapper.go @@ -0,0 +1,51 @@ +package structs + +import "reflect" + +// Wrapper struct for read or set field value TODO +type Wrapper struct { + // src any // source data struct + rv reflect.Value + + // FieldTagName field name for read/write value. default tag: json + FieldTagName string +} + +// Wrap create a struct wrapper +func Wrap(src any) *Wrapper { + return NewWrapper(src) +} + +// NewWrapper create a struct wrapper +func NewWrapper(src any) *Wrapper { + return WrapValue(reflect.ValueOf(src)) +} + +// WrapValue create a struct wrapper +func WrapValue(rv reflect.Value) *Wrapper { + rv = reflect.Indirect(rv) + if rv.Kind() != reflect.Struct { + panic("must be provider an struct value") + } + + return &Wrapper{rv: rv} +} + +// Get field value by name +func (r *Wrapper) Get(name string) any { + val, ok := r.Lookup(name) + if !ok { + return nil + } + return val +} + +// Lookup field value by name +func (r *Wrapper) Lookup(name string) (val any, ok bool) { + fv := r.rv.FieldByName(name) + if !fv.IsValid() { + return + } + + return fv.Interface(), true +} diff --git a/vendor/github.com/gookit/goutil/structs/setval.go b/vendor/github.com/gookit/goutil/structs/writer.go similarity index 50% rename from vendor/github.com/gookit/goutil/structs/setval.go rename to vendor/github.com/gookit/goutil/structs/writer.go index 6461540f0..ac306e110 100644 --- a/vendor/github.com/gookit/goutil/structs/setval.go +++ b/vendor/github.com/gookit/goutil/structs/writer.go @@ -5,111 +5,22 @@ import ( "fmt" "reflect" - "github.com/gookit/goutil/internal/comfunc" + "github.com/gookit/goutil/maputil" "github.com/gookit/goutil/reflects" ) -const defaultInitTag = "default" - -// InitOptFunc define -type InitOptFunc func(opt *InitOptions) - -// InitOptions struct -type InitOptions struct { - // TagName default value tag name. tag: default - TagName string - // ParseEnv var name on default value. eg: `default:"${APP_ENV}"` - // - // default: false - ParseEnv bool - // ValueHook before set value hook TODO - ValueHook func(val string) any -} - -// InitDefaults init struct default value by field "default" tag. -// -// TIPS: -// -// Only support init set: string, bool, intX, uintX, floatX -// -// Example: -// -// type User1 struct { -// Name string `default:"inhere"` -// Age int32 `default:"30"` -// } -// -// u1 := &User1{} -// err = structs.InitDefaults(u1) -// fmt.Printf("%+v\n", u1) -// // Output: {Name:inhere Age:30} -func InitDefaults(ptr any, optFns ...InitOptFunc) error { +// NewWriter create a struct writer +func NewWriter(ptr any) *Wrapper { rv := reflect.ValueOf(ptr) - if rv.Kind() != reflect.Ptr { - return errors.New("must be provider an pointer value") + if rv.Kind() != reflect.Pointer { + panic("must be provider an pointer value") } - rv = rv.Elem() - if rv.Kind() != reflect.Struct { - return errors.New("must be provider an struct value") - } - - opt := &InitOptions{TagName: defaultInitTag} - for _, fn := range optFns { - fn(opt) - } - - return initDefaults(rv, opt) -} - -func initDefaults(rv reflect.Value, opt *InitOptions) error { - rt := rv.Type() - - for i := 0; i < rt.NumField(); i++ { - ft := rt.Field(i) - // skip don't exported field - if ft.Name[0] >= 'a' && ft.Name[0] <= 'z' { - continue - } - - fv := rv.Field(i) - if fv.Kind() == reflect.Struct { - if err := initDefaults(fv, opt); err != nil { - return err - } - continue - } - - // skip on field has value - if !fv.IsZero() { - continue - } - - tagVal := ft.Tag.Get(opt.TagName) - if err := initDefaultValue(fv, tagVal, opt.ParseEnv); err != nil { - return err - } - } - - return nil -} - -func initDefaultValue(fv reflect.Value, val string, parseEnv bool) error { - if val == "" || !fv.CanSet() { - return nil - } - - // parse env var - if parseEnv { - val = comfunc.ParseEnvVar(val, nil) - } - - // set value - return reflects.SetValue(fv, val) + return WrapValue(rv) } /************************************************************* - * load values to a struct + * set values to a struct *************************************************************/ // SetOptFunc define @@ -127,15 +38,22 @@ type SetOptions struct { // // see InitDefaults() ParseDefault bool + // DefaultValTag name. tag: default DefaultValTag string + // ParseDefaultEnv parse env var on default tag. eg: `default:"${APP_ENV}"` // // default: false ParseDefaultEnv bool } -// SetValues set data values to struct ptr +// WithParseDefault value by tag "default" +func WithParseDefault(opt *SetOptions) { + opt.ParseDefault = true +} + +// SetValues set values to struct ptr from map data. // // TIPS: // @@ -152,7 +70,7 @@ func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error { } opt := &SetOptions{ - FieldTagName: "json", + FieldTagName: defaultFieldTag, DefaultValTag: defaultInitTag, } @@ -163,7 +81,7 @@ func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error { } func setValues(rv reflect.Value, data map[string]any, opt *SetOptions) error { - if data == nil || len(data) == 0 { + if len(data) == 0 { return nil } @@ -184,7 +102,6 @@ func setValues(rv reflect.Value, data map[string]any, opt *SetOptions) error { if err != nil { return err } - name = info.Get("name") } @@ -192,20 +109,27 @@ func setValues(rv reflect.Value, data map[string]any, opt *SetOptions) error { val, ok := data[name] // set field value by default tag. - if !ok && fv.IsZero() { + if !ok && opt.ParseDefault && fv.IsZero() { defVal := ft.Tag.Get(opt.DefaultValTag) - if err := initDefaultValue(fv, defVal, opt.ParseDefaultEnv); err != nil { return err } continue } + // handle for pointer field + if fv.Kind() == reflect.Pointer { + if fv.IsNil() { + fv.Set(reflect.New(fv.Type().Elem())) + } + fv = fv.Elem() + } + // field is struct if fv.Kind() == reflect.Struct { - asMp, ok := val.(map[string]any) - if !ok { - return fmt.Errorf("field is struct, must provide map data value") + asMp, err := maputil.TryAnyMap(val) + if err != nil { + return fmt.Errorf("must provide map data for field %q, err=%v", ft.Name, err) } if err := setValues(fv, asMp, opt); err != nil { diff --git a/vendor/github.com/gookit/goutil/strutil/bytes.go b/vendor/github.com/gookit/goutil/strutil/bytes.go index fd1f54973..1b39b1832 100644 --- a/vendor/github.com/gookit/goutil/strutil/bytes.go +++ b/vendor/github.com/gookit/goutil/strutil/bytes.go @@ -1,69 +1,17 @@ package strutil import ( - "bytes" - "fmt" - "strings" + "github.com/gookit/goutil/byteutil" ) // Buffer wrap and extends the bytes.Buffer -type Buffer struct { - bytes.Buffer -} +type Buffer = byteutil.Buffer // NewBuffer instance func NewBuffer() *Buffer { return &Buffer{} } -// WriteAny type value to buffer -func (b *Buffer) WriteAny(vs ...any) { - for _, v := range vs { - _, _ = b.Buffer.WriteString(fmt.Sprint(v)) - } -} - -// QuietWriteByte to buffer -func (b *Buffer) QuietWriteByte(c byte) { - _ = b.WriteByte(c) -} - -// QuietWritef write message to buffer -func (b *Buffer) QuietWritef(tpl string, vs ...any) { - _, _ = b.WriteString(fmt.Sprintf(tpl, vs...)) -} - -// Writeln write message to buffer with newline -func (b *Buffer) Writeln(ss ...string) { - b.QuietWriteln(ss...) -} - -// QuietWriteln write message to buffer with newline -func (b *Buffer) QuietWriteln(ss ...string) { - _, _ = b.WriteString(strings.Join(ss, "")) - _ = b.WriteByte('\n') -} - -// QuietWriteString to buffer -func (b *Buffer) QuietWriteString(ss ...string) { - _, _ = b.WriteString(strings.Join(ss, "")) -} - -// MustWriteString to buffer -func (b *Buffer) MustWriteString(ss ...string) { - _, err := b.WriteString(strings.Join(ss, "")) - if err != nil { - panic(err) - } -} - -// ResetAndGet buffer string. -func (b *Buffer) ResetAndGet() string { - s := b.String() - b.Reset() - return s -} - // ByteChanPool struct // // Usage: @@ -72,57 +20,9 @@ func (b *Buffer) ResetAndGet() string { // buf:=bp.Get() // defer bp.Put(buf) // // use buf do something ... -// -// refer https://www.flysnow.org/2020/08/21/golang-chan-byte-pool.html -// from https://github.com/minio/minio/blob/master/internal/bpool/bpool.go -type ByteChanPool struct { - c chan []byte - w int - wcap int -} +type ByteChanPool = byteutil.ChanPool // NewByteChanPool instance -func NewByteChanPool(maxSize int, width int, capWidth int) *ByteChanPool { - return &ByteChanPool{ - c: make(chan []byte, maxSize), - w: width, - wcap: capWidth, - } -} - -// Get gets a []byte from the BytePool, or creates a new one if none are -// available in the pool. -func (bp *ByteChanPool) Get() (b []byte) { - select { - case b = <-bp.c: - // reuse existing buffer - default: - // create new buffer - if bp.wcap > 0 { - b = make([]byte, bp.w, bp.wcap) - } else { - b = make([]byte, bp.w) - } - } - return -} - -// Put returns the given Buffer to the BytePool. -func (bp *ByteChanPool) Put(b []byte) { - select { - case bp.c <- b: - // buffer went back into pool - default: - // buffer didn't go back into pool, just discard - } -} - -// Width returns the width of the byte arrays in this pool. -func (bp *ByteChanPool) Width() (n int) { - return bp.w -} - -// WidthCap returns the cap width of the byte arrays in this pool. -func (bp *ByteChanPool) WidthCap() (n int) { - return bp.wcap +func NewByteChanPool(maxSize, width, capWidth int) *ByteChanPool { + return byteutil.NewChanPool(maxSize, width, capWidth) } diff --git a/vendor/github.com/gookit/goutil/strutil/check.go b/vendor/github.com/gookit/goutil/strutil/check.go index 194dd641e..8654d8412 100644 --- a/vendor/github.com/gookit/goutil/strutil/check.go +++ b/vendor/github.com/gookit/goutil/strutil/check.go @@ -1,6 +1,7 @@ package strutil import ( + "path" "regexp" "strings" "unicode" @@ -10,22 +11,13 @@ import ( // Equal check, alias of strings.EqualFold var Equal = strings.EqualFold -// NoCaseEq check two strings is equals and case-insensitivity -func NoCaseEq(s, t string) bool { - return strings.EqualFold(s, t) -} - // IsNumChar returns true if the given character is a numeric, otherwise false. -func IsNumChar(c byte) bool { - return c >= '0' && c <= '9' -} +func IsNumChar(c byte) bool { return c >= '0' && c <= '9' } var numReg = regexp.MustCompile(`^\d+$`) // IsNumeric returns true if the given string is a numeric, otherwise false. -func IsNumeric(s string) bool { - return numReg.MatchString(s) -} +func IsNumeric(s string) bool { return numReg.MatchString(s) } // IsAlphabet char func IsAlphabet(char uint8) bool { @@ -47,16 +39,31 @@ func IsAlphaNum(c uint8) bool { } // StrPos alias of the strings.Index -func StrPos(s, sub string) int { - return strings.Index(s, sub) -} +func StrPos(s, sub string) int { return strings.Index(s, sub) } // BytePos alias of the strings.IndexByte -func BytePos(s string, bt byte) int { - return strings.IndexByte(s, bt) +func BytePos(s string, bt byte) int { return strings.IndexByte(s, bt) } + +// IEqual ignore case check given two string is equals. +func IEqual(s1, s2 string) bool { return strings.EqualFold(s1, s2) } + +// NoCaseEq check two strings is equals and case-insensitivity +func NoCaseEq(s, t string) bool { return strings.EqualFold(s, t) } + +// IContains ignore case check substr in the given string. +func IContains(s, sub string) bool { + return strings.Contains(strings.ToLower(s), strings.ToLower(sub)) } -// HasOneSub substr in the given string. +// ContainsByte in given string. +func ContainsByte(s string, c byte) bool { + return strings.IndexByte(s, c) >= 0 +} + +// ContainsOne substr(s) in the given string. alias of HasOneSub() +func ContainsOne(s string, subs []string) bool { return HasOneSub(s, subs) } + +// HasOneSub substr(s) in the given string. func HasOneSub(s string, subs []string) bool { for _, sub := range subs { if strings.Contains(s, sub) { @@ -66,6 +73,9 @@ func HasOneSub(s string, subs []string) bool { return false } +// ContainsAll substr(s) in the given string. alias of HasAllSubs() +func ContainsAll(s string, subs []string) bool { return HasAllSubs(s, subs) } + // HasAllSubs all substr in the given string. func HasAllSubs(s string, subs []string) bool { for _, sub := range subs { @@ -103,6 +113,16 @@ func HasSuffix(s string, suffix string) bool { return strings.HasSuffix(s, suffi // IsEndOf alias of the strings.HasSuffix func IsEndOf(s, suffix string) bool { return strings.HasSuffix(s, suffix) } +// HasOneSuffix the string end withs one of the subs +func HasOneSuffix(s string, suffixes []string) bool { + for _, suffix := range suffixes { + if strings.HasSuffix(s, suffix) { + return true + } + } + return false +} + // IsValidUtf8 valid utf8 string check func IsValidUtf8(s string) bool { return utf8.ValidString(s) } @@ -112,22 +132,16 @@ func IsValidUtf8(s string) bool { return utf8.ValidString(s) } var spaceTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // IsSpace returns true if the given character is a space, otherwise false. -func IsSpace(c byte) bool { - return spaceTable[c] == 1 -} +func IsSpace(c byte) bool { return spaceTable[c] == 1 } // IsEmpty returns true if the given string is empty. func IsEmpty(s string) bool { return len(s) == 0 } // IsBlank returns true if the given string is all space characters. -func IsBlank(s string) bool { - return IsBlankBytes([]byte(s)) -} +func IsBlank(s string) bool { return IsBlankBytes([]byte(s)) } // IsNotBlank returns true if the given string is not blank. -func IsNotBlank(s string) bool { - return !IsBlankBytes([]byte(s)) -} +func IsNotBlank(s string) bool { return !IsBlankBytes([]byte(s)) } // IsBlankBytes returns true if the given []byte is all space characters. func IsBlankBytes(bs []byte) bool { @@ -140,21 +154,35 @@ func IsBlankBytes(bs []byte) bool { } // IsSymbol reports whether the rune is a symbolic character. -func IsSymbol(r rune) bool { - return unicode.IsSymbol(r) +func IsSymbol(r rune) bool { return unicode.IsSymbol(r) } + +// HasEmpty value for input strings +func HasEmpty(ss ...string) bool { + for _, s := range ss { + if s == "" { + return true + } + } + return false +} + +// IsAllEmpty for input strings +func IsAllEmpty(ss ...string) bool { + for _, s := range ss { + if s != "" { + return false + } + } + return true } var verRegex = regexp.MustCompile(`^[0-9][\d.]+(-\w+)?$`) // IsVersion number. eg: 1.2.0 -func IsVersion(s string) bool { - return verRegex.MatchString(s) -} +func IsVersion(s string) bool { return verRegex.MatchString(s) } // Compare for two string. -func Compare(s1, s2, op string) bool { - return VersionCompare(s1, s2, op) -} +func Compare(s1, s2, op string) bool { return VersionCompare(s1, s2, op) } // VersionCompare for two version string. func VersionCompare(v1, v2, op string) bool { @@ -173,3 +201,128 @@ func VersionCompare(v1, v2, op string) bool { return v1 == v2 } } + +// SimpleMatch all sub-string in the give text string. +// +// Difference the ContainsAll: +// +// - start with ^ for exclude contains check. +// - end with $ for check end with keyword. +func SimpleMatch(s string, keywords []string) bool { + for _, keyword := range keywords { + kln := len(keyword) + if kln == 0 { + continue + } + + // exclude + if kln > 1 && keyword[0] == '^' { + if strings.Contains(s, keyword[1:]) { + return false + } + continue + } + + // end with + if kln > 1 && keyword[kln-1] == '$' { + return strings.HasSuffix(s, keyword[:kln-1]) + } + + // include + if !strings.Contains(s, keyword) { + return false + } + } + return true +} + +// QuickMatch check for a string. pattern can be a sub string. +func QuickMatch(pattern, s string) bool { + if strings.ContainsRune(pattern, '*') { + return GlobMatch(pattern, s) + } + return strings.Contains(s, pattern) +} + +// PathMatch check for a string match the pattern. alias of the path.Match() +// +// TIP: `*` can match any char, not contain `/`. +func PathMatch(pattern, s string) bool { + ok, err := path.Match(pattern, s) + if err != nil { + ok = false + } + return ok +} + +// GlobMatch check for a string match the pattern. +// +// Difference with PathMatch() is: `*` can match any char, contain `/`. +func GlobMatch(pattern, s string) bool { + // replace `/` to `S` for path.Match + pattern = strings.Replace(pattern, "/", "S", -1) + s = strings.Replace(s, "/", "S", -1) + + ok, err := path.Match(pattern, s) + if err != nil { + ok = false + } + return ok +} + +// LikeMatch simple check for a string match the pattern. pattern like the SQL LIKE. +func LikeMatch(pattern, s string) bool { + ln := len(pattern) + if ln < 2 { + return false + } + + // eg `%abc` `%abc%` + if pattern[0] == '%' { + if ln > 2 && pattern[ln-1] == '%' { + return strings.Contains(s, pattern[1:ln-1]) + } else { + return strings.HasSuffix(s, pattern[1:]) + } + } + + // eg `abc%` + if pattern[ln-1] == '%' { + return strings.HasPrefix(s, pattern[:ln-1]) + } + return pattern == s +} + +// MatchNodePath check for a string match the pattern. +// +// Use on pattern: +// - `*` match any to sep +// - `**` match any to end. only allow at start or end on pattern. +// +// Example: +// +// strutil.MatchNodePath() +func MatchNodePath(pattern, s string, sep string) bool { + if pattern == "**" || pattern == s { + return true + } + if pattern == "" { + return len(s) == 0 + } + + if i := strings.Index(pattern, "**"); i >= 0 { + if i == 0 { // at start + return strings.HasSuffix(s, pattern[2:]) + } + return strings.HasPrefix(s, pattern[:len(pattern)-2]) + } + + pattern = strings.Replace(pattern, sep, "/", -1) + s = strings.Replace(s, sep, "/", -1) + + ok, err := path.Match(pattern, s) + if err != nil { + ok = false + } + return ok +} diff --git a/vendor/github.com/gookit/goutil/strutil/convert.go b/vendor/github.com/gookit/goutil/strutil/convert.go index 5ae183a25..067876d40 100644 --- a/vendor/github.com/gookit/goutil/strutil/convert.go +++ b/vendor/github.com/gookit/goutil/strutil/convert.go @@ -1,7 +1,6 @@ package strutil import ( - "encoding/json" "errors" "fmt" "reflect" @@ -17,8 +16,10 @@ import ( ) var ( - ErrDateLayout = errors.New("invalid date layout string") - ErrInvalidParam = errors.New("invalid input parameter") + // ErrDateLayout error + ErrDateLayout = errors.New("invalid date layout string") + // ErrInvalidParam error + ErrInvalidParam = errors.New("invalid input for parse time") // some regex for convert string. toSnakeReg = regexp.MustCompile("[A-Z][a-z]") @@ -39,6 +40,8 @@ var ( func Quote(s string) string { return strconv.Quote(s) } // Unquote remove start and end quotes by single-quote or double-quote +// +// tip: strconv.Unquote cannot unquote single-quote func Unquote(s string) string { ln := len(s) if ln < 2 { @@ -70,6 +73,16 @@ func Join(sep string, ss ...string) string { return strings.Join(ss, sep) } // JoinList alias of strings.Join func JoinList(sep string, ss []string) string { return strings.Join(ss, sep) } +// JoinAny type to string +func JoinAny(sep string, parts ...any) string { + ss := make([]string, 0, len(parts)) + for _, part := range parts { + ss = append(ss, QuietString(part)) + } + + return strings.Join(ss, sep) +} + // Implode alias of strings.Join func Implode(sep string, ss ...string) string { return strings.Join(ss, sep) } @@ -77,39 +90,48 @@ func Implode(sep string, ss ...string) string { return strings.Join(ss, sep) } * convert value to string *************************************************************/ -// String convert val to string +// String convert value to string, return error on failed func String(val any) (string, error) { return AnyToString(val, true) } +// ToString convert value to string, return error on failed +func ToString(val any) (string, error) { + return AnyToString(val, true) +} + // QuietString convert value to string, will ignore error func QuietString(in any) string { val, _ := AnyToString(in, false) return val } -// MustString convert value to string, TODO will panic on error -func MustString(in any) string { +// SafeString convert value to string, will ignore error +func SafeString(in any) string { val, _ := AnyToString(in, false) return val } +// MustString convert value to string, will panic on error +func MustString(in any) string { + val, err := AnyToString(in, false) + if err != nil { + panic(err) + } + return val +} + // StringOrErr convert value to string, return error on failed func StringOrErr(val any) (string, error) { return AnyToString(val, true) } -// ToString convert value to string -func ToString(val any) (string, error) { - return AnyToString(val, true) -} - // AnyToString convert value to string. // -// if defaultAsErr: +// For defaultAsErr: // -// False will use fmt.Sprint convert complex type -// True will return error on fail. +// - False will use fmt.Sprint convert complex type +// - True will return error on fail. func AnyToString(val any, defaultAsErr bool) (str string, err error) { if val == nil { return @@ -147,8 +169,8 @@ func AnyToString(val any, defaultAsErr bool) (str string, err error) { case []byte: str = string(value) case time.Duration: - str = value.String() - case json.Number: + str = strconv.FormatInt(int64(value), 10) + case fmt.Stringer: str = value.String() default: if defaultAsErr { @@ -203,7 +225,10 @@ func QuietBool(s string) bool { // MustBool convert, will panic on error func MustBool(s string) bool { - val, _ := comfunc.StrToBool(strings.TrimSpace(s)) + val, err := comfunc.StrToBool(strings.TrimSpace(s)) + if err != nil { + panic(err) + } return val } @@ -226,6 +251,12 @@ func ToInt(s string) (int, error) { return strconv.Atoi(strings.TrimSpace(s)) } +// Int2 convert string to int, will ignore error +func Int2(s string) int { + val, _ := ToInt(s) + return val +} + // QuietInt convert string to int, will ignore error func QuietInt(s string) int { val, _ := ToInt(s) @@ -234,8 +265,7 @@ func QuietInt(s string) int { // MustInt convert string to int, will panic on error func MustInt(s string) int { - val, _ := ToInt(s) - return val + return IntOrPanic(s) } // IntOrPanic convert value to int, will panic on error @@ -334,87 +364,8 @@ func ToSlice(s string, sep ...string) []string { // return cliutil.StringToOSArgs(s) // error: import cycle not allowed // } -// MustToTime convert date string to time.Time -func MustToTime(s string, layouts ...string) time.Time { - t, err := ToTime(s, layouts...) - if err != nil { - panic(err) - } - return t -} - -// auto match use some commonly layouts. -// key is layout length. -var layoutMap = map[int][]string{ - 6: {"200601", "060102", time.Kitchen}, - 8: {"20060102"}, - 10: {"2006-01-02"}, - 13: {"2006-01-02 15"}, - 15: {time.Stamp}, - 16: {"2006-01-02 15:04"}, - 19: {"2006-01-02 15:04:05", time.RFC822, time.StampMilli}, - 20: {"2006-01-02 15:04:05Z"}, - 21: {time.RFC822Z}, - 22: {time.StampMicro}, - 24: {time.ANSIC}, - 25: {time.RFC3339, time.StampNano}, - // 26: {time.Layout}, // must go >= 1.19 - 28: {time.UnixDate}, - 29: {time.RFC1123}, - 30: {time.RFC850}, - 31: {time.RFC1123Z}, - 35: {time.RFC3339Nano}, -} - -// ToTime convert date string to time.Time -func ToTime(s string, layouts ...string) (t time.Time, err error) { - // custom layout - if len(layouts) > 0 { - if len(layouts[0]) > 0 { - return time.Parse(layouts[0], s) - } - - err = ErrDateLayout - return - } - - // auto match use some commonly layouts. - strLn := len(s) - maybeLayouts, ok := layoutMap[strLn] - if !ok { - err = ErrInvalidParam - return - } - - var hasAlphaT bool - if pos := strings.IndexByte(s, 'T'); pos > 0 && pos < 12 { - hasAlphaT = true - } - - hasSlashR := strings.IndexByte(s, '/') > 0 - for _, layout := range maybeLayouts { - // date string has "T". eg: "2006-01-02T15:04:05" - if hasAlphaT { - layout = strings.Replace(layout, " ", "T", 1) - } - - // date string has "/". eg: "2006/01/02 15:04:05" - if hasSlashR { - layout = strings.Replace(layout, "-", "/", -1) - } - - t, err = time.Parse(layout, s) - if err == nil { - return - } - } - - // t, err = time.ParseInLocation(layout, s, time.Local) - return -} - // ToDuration parses a duration string. such as "300ms", "-1.5h" or "2h45m". // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". func ToDuration(s string) (time.Duration, error) { - return time.ParseDuration(s) + return comfunc.ToDuration(s) } diff --git a/vendor/github.com/gookit/goutil/strutil/crypto.go b/vendor/github.com/gookit/goutil/strutil/crypto.go new file mode 100644 index 000000000..7c5337118 --- /dev/null +++ b/vendor/github.com/gookit/goutil/strutil/crypto.go @@ -0,0 +1,54 @@ +package strutil + +import ( + "crypto/hmac" + "crypto/md5" + "crypto/sha256" + "encoding/hex" + "fmt" +) + +// Md5 Generate a 32-bit md5 string +func Md5(src any) string { + return hex.EncodeToString(Md5Bytes(src)) +} + +// MD5 Generate a 32-bit md5 string +func MD5(src any) string { return Md5(src) } + +// GenMd5 Generate a 32-bit md5 string +func GenMd5(src any) string { return Md5(src) } + +// Md5Bytes Generate a 32-bit md5 bytes +func Md5Bytes(src any) []byte { + h := md5.New() + if s, ok := src.(string); ok { + h.Write([]byte(s)) + } else { + h.Write([]byte(fmt.Sprint(src))) + } + return h.Sum(nil) +} + +// HashPasswd for quick hash an input password string +func HashPasswd(pwd, key string) string { + hm := hmac.New(sha256.New, []byte(key)) + hm.Write([]byte(pwd)) + + return hex.EncodeToString(hm.Sum(nil)) +} + +// VerifyPasswd for quick verify input password is valid +// +// - pwdMAC from db or config, generated by EncryptPasswd() +func VerifyPasswd(pwdMAC, pwd, key string) bool { + decBts, err := hex.DecodeString(pwdMAC) + if err != nil { + return false + } + + hm := hmac.New(sha256.New, []byte(key)) + hm.Write([]byte(pwd)) + + return hmac.Equal(decBts, hm.Sum(nil)) +} diff --git a/vendor/github.com/gookit/goutil/strutil/encode.go b/vendor/github.com/gookit/goutil/strutil/encode.go index a102e1881..70d50a8e1 100644 --- a/vendor/github.com/gookit/goutil/strutil/encode.go +++ b/vendor/github.com/gookit/goutil/strutil/encode.go @@ -2,11 +2,8 @@ package strutil import ( "bytes" - "crypto/md5" "encoding/base32" "encoding/base64" - "encoding/hex" - "fmt" "net/url" "strings" "text/template" @@ -73,28 +70,6 @@ func StripSlashes(s string) string { // -------------------- encode -------------------- // -// Md5 Generate a 32-bit md5 string -func Md5(src any) string { - return hex.EncodeToString(Md5Bytes(src)) -} - -// MD5 Generate a 32-bit md5 string -func MD5(src any) string { return Md5(src) } - -// GenMd5 Generate a 32-bit md5 string -func GenMd5(src any) string { return Md5(src) } - -// Md5Bytes Generate a 32-bit md5 bytes -func Md5Bytes(src any) []byte { - h := md5.New() - if s, ok := src.(string); ok { - h.Write([]byte(s)) - } else { - h.Write([]byte(fmt.Sprint(src))) - } - return h.Sum(nil) -} - // URLEncode encode url string. func URLEncode(s string) string { if pos := strings.IndexRune(s, '?'); pos > -1 { // escape query data diff --git a/vendor/github.com/gookit/goutil/strutil/similar_find.go b/vendor/github.com/gookit/goutil/strutil/ext.go similarity index 51% rename from vendor/github.com/gookit/goutil/strutil/similar_find.go rename to vendor/github.com/gookit/goutil/strutil/ext.go index 94c4fe188..31134830d 100644 --- a/vendor/github.com/gookit/goutil/strutil/similar_find.go +++ b/vendor/github.com/gookit/goutil/strutil/ext.go @@ -1,6 +1,12 @@ package strutil +import ( + "fmt" + "strings" +) + // SimilarComparator definition +// // links: // // https://github.com/mkideal/cli/blob/master/fuzzy.go @@ -13,7 +19,17 @@ func NewComparator(src, dst string) *SimilarComparator { return &SimilarComparator{src, dst} } +// Similarity calc for two string. +// +// Usage: +// +// rate, ok := Similarity("hello", "he") +func Similarity(s, t string, rate float32) (float32, bool) { + return NewComparator(s, t).Similar(rate) +} + // Similar by minDifferRate +// // Usage: // // c := NewComparator("hello", "he") @@ -56,7 +72,6 @@ func min(x, y float32) float32 { if x < y { return x } - return y } @@ -64,14 +79,67 @@ func max(x, y int) int { if x > y { return x } - return y } -// Similarity calc for two string. -// Usage: -// -// rate, ok := Similarity("hello", "he") -func Similarity(s, t string, rate float32) (float32, bool) { - return NewComparator(s, t).Similar(rate) +// Builder struct +type Builder struct { + strings.Builder +} + +// Write bytes and no error report +func (b *Builder) Write(p []byte) { + _, _ = b.Builder.Write(p) +} + +// WriteRune and no error report +func (b *Builder) WriteRune(r rune) { + _, _ = b.Builder.WriteRune(r) +} + +// WriteByteNE write byte and no error report +func (b *Builder) WriteByteNE(c byte) { + _ = b.WriteByte(c) +} + +// WriteString to builder +func (b *Builder) WriteString(s string) { + _, _ = b.Builder.WriteString(s) +} + +// Writef write string by fmt.Sprintf formatted +func (b *Builder) Writef(tpl string, vs ...any) { + _, _ = b.Builder.WriteString(fmt.Sprintf(tpl, vs...)) +} + +// Writeln write string with newline. +func (b *Builder) Writeln(s string) { + _, _ = b.Builder.WriteString(s) + _ = b.WriteByte('\n') +} + +// WriteAny write any type value. +func (b *Builder) WriteAny(v any) { + _, _ = b.Builder.WriteString(QuietString(v)) +} + +// WriteAnys write any type values. +func (b *Builder) WriteAnys(vs ...any) { + for _, v := range vs { + _, _ = b.Builder.WriteString(QuietString(v)) + } +} + +// WriteMulti write multi byte at once. +func (b *Builder) WriteMulti(bs ...byte) { + for _, b2 := range bs { + _ = b.WriteByte(b2) + } +} + +// WriteStrings write multi string at once. +func (b *Builder) WriteStrings(ss ...string) { + for _, s := range ss { + _, _ = b.Builder.WriteString(s) + } } diff --git a/vendor/github.com/gookit/goutil/strutil/id.go b/vendor/github.com/gookit/goutil/strutil/id.go index 053ba10d7..028264916 100644 --- a/vendor/github.com/gookit/goutil/strutil/id.go +++ b/vendor/github.com/gookit/goutil/strutil/id.go @@ -1,6 +1,9 @@ package strutil import ( + "hash/crc32" + "math/rand" + "os" "strconv" "time" @@ -33,3 +36,33 @@ func MicroTimeHexID() string { return strconv.FormatInt(ms, 16) + strconv.FormatInt(int64(ri), 16) } + +// DatetimeNo generate. can use for order-no. +// +// No prefix, return like: 2023041410484904074285478388(len: 28) +func DatetimeNo(prefix string) string { + nt := time.Now() + pl := len(prefix) + bs := make([]byte, 0, 28+pl) + if pl > 0 { + bs = append(bs, prefix...) + } + + // micro datatime + bs = nt.AppendFormat(bs, "20060102150405.000000") + bs[14+pl] = '0' + + // host + name, err := os.Hostname() + if err != nil { + name = "default" + } + c32 := crc32.ChecksumIEEE([]byte(name)) // eg: 4006367001 + bs = strconv.AppendUint(bs, uint64(c32%99), 10) + + // rand 10000 - 99999 + rand.Seed(nt.UnixNano()) + bs = strconv.AppendInt(bs, 10000+rand.Int63n(89999), 10) + + return string(bs) +} diff --git a/vendor/github.com/gookit/goutil/strutil/padding.go b/vendor/github.com/gookit/goutil/strutil/padding.go index 97a06b4d4..30cb042cc 100644 --- a/vendor/github.com/gookit/goutil/strutil/padding.go +++ b/vendor/github.com/gookit/goutil/strutil/padding.go @@ -160,6 +160,10 @@ func RepeatBytes(char byte, times int) []byte { // RepeatChars repeat a byte char. func RepeatChars[T byte | rune](char T, times int) []T { + if times <= 0 { + return make([]T, 0) + } + chars := make([]T, 0, times) for i := 0; i < times; i++ { chars = append(chars, char) diff --git a/vendor/github.com/gookit/goutil/strutil/parse.go b/vendor/github.com/gookit/goutil/strutil/parse.go new file mode 100644 index 000000000..82fd51920 --- /dev/null +++ b/vendor/github.com/gookit/goutil/strutil/parse.go @@ -0,0 +1,229 @@ +package strutil + +import ( + "errors" + "strconv" + "strings" + "time" + "unicode" + + "github.com/gookit/goutil/byteutil" +) + +// MustToTime convert date string to time.Time +func MustToTime(s string, layouts ...string) time.Time { + t, err := ToTime(s, layouts...) + if err != nil { + panic(err) + } + return t +} + +// auto match use some commonly layouts. +// key is layout length. +var layoutMap = map[int][]string{ + 6: {"200601", "060102", time.Kitchen}, + 8: {"20060102"}, + 10: {"2006-01-02"}, + 13: {"2006-01-02 15"}, + 15: {time.Stamp}, + 16: {"2006-01-02 15:04"}, + 19: {"2006-01-02 15:04:05", time.RFC822, time.StampMilli}, + 20: {"2006-01-02 15:04:05Z"}, + 21: {time.RFC822Z}, + 22: {time.StampMicro}, + 23: {"2006-01-02 15:04:05.000", "2006-01-02 15:04:05.999"}, + 24: {time.ANSIC}, + 25: {time.RFC3339, time.StampNano}, + // time.Layout}, // must go >= 1.19 + 26: {"2006-01-02 15:04:05.000000"}, + 28: {time.UnixDate}, + 29: {time.RFC1123, "2006-01-02 15:04:05.000000000"}, + 30: {time.RFC850}, + 31: {time.RFC1123Z}, + 35: {time.RFC3339Nano}, +} + +// ToTime convert date string to time.Time +func ToTime(s string, layouts ...string) (t time.Time, err error) { + // custom layout + if len(layouts) > 0 { + if len(layouts[0]) > 0 { + return time.Parse(layouts[0], s) + } + + err = ErrDateLayout + return + } + + // auto match use some commonly layouts. + strLn := len(s) + maybeLayouts, ok := layoutMap[strLn] + if !ok { + err = ErrInvalidParam + return + } + + var hasAlphaT bool + if pos := strings.IndexByte(s, 'T'); pos > 0 && pos < 12 { + hasAlphaT = true + } + + hasSlashR := strings.IndexByte(s, '/') > 0 + for _, layout := range maybeLayouts { + // date string has "T". eg: "2006-01-02T15:04:05" + if hasAlphaT { + layout = strings.Replace(layout, " ", "T", 1) + } + + // date string has "/". eg: "2006/01/02 15:04:05" + if hasSlashR { + layout = strings.Replace(layout, "-", "/", -1) + } + + t, err = time.Parse(layout, s) + if err == nil { + return + } + } + + // t, err = time.ParseInLocation(layout, s, time.Local) + return +} + +// ParseSizeOpt parse size expression options +type ParseSizeOpt struct { + // OneAsMax if only one size value, use it as max size. default is false + OneAsMax bool + // SepChar is the separator char for time range string. default is '~' + SepChar byte + // KeywordFn is the function for parse keyword time string. + KeywordFn func(string) (min, max uint64, err error) +} + +func ensureOpt(opt *ParseSizeOpt) *ParseSizeOpt { + if opt == nil { + opt = &ParseSizeOpt{SepChar: '~'} + } else { + if opt.SepChar == 0 { + opt.SepChar = '~' + } + } + return opt +} + +// ErrInvalidSizeExpr invalid size expression error +var ErrInvalidSizeExpr = errors.New("invalid size expr") + +// ParseSizeRange parse range size expression to min and max size. +// +// Expression format: +// +// "1KB~2MB" => 1KB to 2MB +// "-1KB" => <1KB +// "~1MB" => <1MB +// "< 1KB" => <1KB +// "1KB" => >1KB +// "1KB~" => >1KB +// ">1KB" => >1KB +// "+1KB" => >1KB +func ParseSizeRange(expr string, opt *ParseSizeOpt) (min, max uint64, err error) { + opt = ensureOpt(opt) + expr = strings.TrimSpace(expr) + if expr == "" { + err = ErrInvalidSizeExpr + return + } + + // parse size range. eg: "1KB~2MB" + if strings.IndexByte(expr, '~') > -1 { + s1, s2 := TrimCut(expr, "~") + if s1 != "" { + min, err = ToByteSize(s1) + if err != nil { + return + } + } + + if s2 != "" { + max, err = ToByteSize(s2) + } + return + } + + // parse single size. eg: "1KB" + if byteutil.IsNumChar(expr[0]) { + min, err = ToByteSize(expr) + if err != nil { + return + } + if opt.OneAsMax { + max = min + } + return + } + + // parse with prefix. eg: "<1KB", ">= 1KB", "-1KB", "+1KB" + switch expr[0] { + case '<', '-': + max, err = ToByteSize(strings.Trim(expr[1:], " =")) + case '>', '+': + min, err = ToByteSize(strings.Trim(expr[1:], " =")) + default: + // parse keyword. eg: "small", "large" + if opt.KeywordFn != nil { + min, max, err = opt.KeywordFn(expr) + } else { + err = ErrInvalidSizeExpr + } + } + return +} + +// SafeByteSize converts size string like 1GB/1g or 12mb/12M into an unsigned integer number of bytes +func SafeByteSize(sizeStr string) uint64 { + size, _ := ToByteSize(sizeStr) + return size +} + +// ToByteSize converts size string like 1GB/1g or 12mb/12M into an unsigned integer number of bytes +func ToByteSize(sizeStr string) (uint64, error) { + sizeStr = strings.TrimSpace(sizeStr) + lastPos := len(sizeStr) - 1 + if lastPos < 0 { + return 0, nil + } + + if sizeStr[lastPos] == 'b' || sizeStr[lastPos] == 'B' { + // last second char is k,m,g,t + lastSec := sizeStr[lastPos-1] + if lastSec > 'A' { + lastPos-- + } + } else if IsNumChar(sizeStr[lastPos]) { // not unit suffix. eg: 346 + return strconv.ParseUint(sizeStr, 10, 32) + } + + multiplier := float64(1) + switch unicode.ToLower(rune(sizeStr[lastPos])) { + case 'k': + multiplier = 1 << 10 + case 'm': + multiplier = 1 << 20 + case 'g': + multiplier = 1 << 30 + case 't': + multiplier = 1 << 40 + case 'p': + multiplier = 1 << 50 + default: // b + multiplier = 1 + } + + sizeNum := strings.TrimSpace(sizeStr[:lastPos]) + size, err := strconv.ParseFloat(sizeNum, 64) + if err != nil { + return 0, err + } + return uint64(size * multiplier), nil +} diff --git a/vendor/github.com/gookit/goutil/strutil/random.go b/vendor/github.com/gookit/goutil/strutil/random.go index 562039b27..fe404a017 100644 --- a/vendor/github.com/gookit/goutil/strutil/random.go +++ b/vendor/github.com/gookit/goutil/strutil/random.go @@ -1,20 +1,27 @@ package strutil import ( - "crypto/rand" "encoding/base64" mathRand "math/rand" "time" + + "github.com/gookit/goutil/byteutil" ) // some consts string chars const ( - Numbers = "0123456789" + Numbers = "0123456789" + HexChars = "0123456789abcdef" + AlphaBet = "abcdefghijklmnopqrstuvwxyz" AlphaBet1 = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz" + AlphaNum = "abcdefghijklmnopqrstuvwxyz0123456789" AlphaNum2 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" AlphaNum3 = "0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz" + + Base62Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + Base64Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/" ) // RandomChars generate give length random chars at `a-z` @@ -58,17 +65,11 @@ func RandomCharsV3(ln int) string { // RandomBytes generate func RandomBytes(length int) ([]byte, error) { - b := make([]byte, length) - _, err := rand.Read(b) - // Note that err == nil only if we read len(b) bytes. - if err != nil { - return nil, err - } - - return b, nil + return byteutil.Random(length) } // RandomString generate. +// // Example: // // // this will give us a 44 byte, base64 encoded output @@ -81,3 +82,20 @@ func RandomString(length int) (string, error) { b, err := RandomBytes(length) return base64.URLEncoding.EncodeToString(b), err } + +// RandWithTpl generate random string with give template +func RandWithTpl(n int, letters string) string { + if len(letters) == 0 { + letters = AlphaNum2 + } + + ln := len(letters) + cs := make([]byte, n) + for i := 0; i < n; i++ { + mathRand.Seed(int64(time.Now().Nanosecond())) + idx := mathRand.Intn(ln) + cs[i] = letters[idx] + } + + return byteutil.String(cs) +} diff --git a/vendor/github.com/gookit/goutil/strutil/runes.go b/vendor/github.com/gookit/goutil/strutil/runes.go index 41754f568..7acb46d80 100644 --- a/vendor/github.com/gookit/goutil/strutil/runes.go +++ b/vendor/github.com/gookit/goutil/strutil/runes.go @@ -88,6 +88,9 @@ func RunesWidth(rs []rune) (w int) { return w } +// Truncate alias of the Utf8Truncate() +func Truncate(s string, w int, tail string) string { return Utf8Truncate(s, w, tail) } + // TextTruncate alias of the Utf8Truncate() func TextTruncate(s string, w int, tail string) string { return Utf8Truncate(s, w, tail) } diff --git a/vendor/github.com/gookit/goutil/strutil/split.go b/vendor/github.com/gookit/goutil/strutil/split.go index bc423528b..807139e67 100644 --- a/vendor/github.com/gookit/goutil/strutil/split.go +++ b/vendor/github.com/gookit/goutil/strutil/split.go @@ -14,9 +14,19 @@ func Cut(s, sep string) (before string, after string, found bool) { return s, "", false } +// QuietCut always returns two substring. +func QuietCut(s, sep string) (before string, after string) { + before, after, _ = Cut(s, sep) + return +} + // MustCut always returns two substring. func MustCut(s, sep string) (before string, after string) { - before, after, _ = Cut(s, sep) + var ok bool + before, after, ok = Cut(s, sep) + if !ok { + panic("cannot split input string to two nodes") + } return } @@ -26,6 +36,9 @@ func TrimCut(s, sep string) (string, string) { return strings.TrimSpace(before), strings.TrimSpace(after) } +// SplitKV split string to key and value. +func SplitKV(s, sep string) (string, string) { return TrimCut(s, sep) } + // SplitValid string to slice. will trim each item and filter empty string node. func SplitValid(s, sep string) (ss []string) { return Split(s, sep) } @@ -113,14 +126,34 @@ func Substr(s string, pos, length int) string { return string(runes[pos:stopIdx]) } -// SplitInlineComment for a text string. -func SplitInlineComment(val string) (string, string) { - if pos := strings.IndexByte(val, '#'); pos > -1 { - return strings.TrimRight(val[0:pos], " "), val[pos:] +// SplitInlineComment for an inline text string. +func SplitInlineComment(val string, strict ...bool) (string, string) { + // strict check: must with space + if len(strict) > 0 && strict[0] { + if pos := strings.Index(val, " #"); pos > -1 { + return strings.TrimRight(val[0:pos], " "), val[pos+1:] + } + + if pos := strings.Index(val, " //"); pos > -1 { + return strings.TrimRight(val[0:pos], " "), val[pos+1:] + } + } else { + if pos := strings.IndexByte(val, '#'); pos > -1 { + return strings.TrimRight(val[0:pos], " "), val[pos:] + } + + if pos := strings.Index(val, "//"); pos > -1 { + return strings.TrimRight(val[0:pos], " "), val[pos:] + } } - if pos := strings.Index(val, "//"); pos > -1 { - return strings.TrimRight(val[0:pos], " "), val[pos:] - } return val, "" } + +// FirstLine from command output +func FirstLine(output string) string { + if i := strings.IndexByte(output, '\n'); i >= 0 { + return output[0:i] + } + return output +} diff --git a/vendor/github.com/gookit/goutil/strutil/strutil.go b/vendor/github.com/gookit/goutil/strutil/strutil.go index ea1f69d5d..f90229343 100644 --- a/vendor/github.com/gookit/goutil/strutil/strutil.go +++ b/vendor/github.com/gookit/goutil/strutil/strutil.go @@ -4,11 +4,47 @@ package strutil import ( "bytes" "encoding/json" + "errors" "fmt" "strings" "text/template" ) +// OrCond return s1 on cond is True, OR return s2. +// Like: cond ? s1 : s2 +func OrCond(cond bool, s1, s2 string) string { + if cond { + return s1 + } + return s2 +} + +// OrElse return s OR orVal(new-value) on s is empty +func OrElse(s, orVal string) string { + if s != "" { + return s + } + return orVal +} + +// OrHandle return fn(s) on s is not empty. +func OrHandle(s string, fn func(s string) string) string { + if s != "" { + return fn(s) + } + return s +} + +// Valid return first not empty element. +func Valid(ss ...string) string { + for _, s := range ss { + if s != "" { + return s + } + } + return "" +} + // Replaces replace multi strings // // pairs: {old1: new1, old2: new2, ...} @@ -17,12 +53,16 @@ import ( // // strings.NewReplacer("old1", "new1", "old2", "new2").Replace(str) func Replaces(str string, pairs map[string]string) string { + return NewReplacer(pairs).Replace(str) +} + +// NewReplacer instance +func NewReplacer(pairs map[string]string) *strings.Replacer { ss := make([]string, len(pairs)*2) for old, newVal := range pairs { ss = append(ss, old, newVal) } - - return strings.NewReplacer(ss...).Replace(str) + return strings.NewReplacer(ss...) } // PrettyJSON get pretty Json string @@ -88,3 +128,35 @@ func WrapTag(s, tag string) string { } return fmt.Sprintf("<%s>%s", tag, s, tag) } + +// SubstrCount returns the number of times the substr substring occurs in the s string. +// Actually, it comes from strings.Count(). +// s The string to search in +// substr The substring to search for +// params[0] The offset where to start counting. +// params[1] The maximum length after the specified offset to search for the substring. +func SubstrCount(s string, substr string, params ...uint64) (int, error) { + larg := len(params) + hasArgs := larg != 0 + if hasArgs && larg > 2 { + return 0, errors.New("too many parameters") + } + if !hasArgs { + return strings.Count(s, substr), nil + } + strlen := len(s) + offset := 0 + end := strlen + if hasArgs { + offset = int(params[0]) + if larg == 2 { + length := int(params[1]) + end = offset + length + } + if end > strlen { + end = strlen + } + } + s = string([]rune(s)[offset:end]) + return strings.Count(s, substr), nil +} diff --git a/vendor/github.com/gookit/goutil/strutil/textutil/textutil.go b/vendor/github.com/gookit/goutil/strutil/textutil/textutil.go new file mode 100644 index 000000000..0c65d9b96 --- /dev/null +++ b/vendor/github.com/gookit/goutil/strutil/textutil/textutil.go @@ -0,0 +1,64 @@ +// Package textutil provide some extra text handle util +package textutil + +import ( + "fmt" + "strings" + + "github.com/gookit/goutil/arrutil" + "github.com/gookit/goutil/maputil" + "github.com/gookit/goutil/strutil" +) + +// ReplaceVars by regex replace given tpl vars. +// +// If format is empty, will use {const defaultVarFormat} +func ReplaceVars(text string, vars map[string]any, format string) string { + return NewVarReplacer(format).Replace(text, vars) +} + +// RenderSMap by regex replace given tpl vars. +// +// If format is empty, will use {const defaultVarFormat} +func RenderSMap(text string, vars map[string]string, format string) string { + return NewVarReplacer(format).RenderSimple(text, vars) +} + +// IsMatchAll keywords in the give text string. +// +// TIP: can use ^ for exclude match. +func IsMatchAll(s string, keywords []string) bool { + return strutil.SimpleMatch(s, keywords) +} + +// ParseInlineINI parse config string to string-map. it's like INI format contents. +// +// Examples: +// +// eg: "name=val0;shorts=i;required=true;desc=a message" +// => +// {name: val0, shorts: i, required: true, desc: a message} +func ParseInlineINI(tagVal string, keys ...string) (mp maputil.SMap, err error) { + ss := strutil.Split(tagVal, ";") + ln := len(ss) + if ln == 0 { + return + } + + mp = make(maputil.SMap, ln) + for _, s := range ss { + if !strings.ContainsRune(s, '=') { + err = fmt.Errorf("parse inline config error: must match `KEY=VAL`") + return + } + + key, val := strutil.TrimCut(s, "=") + if len(keys) > 0 && !arrutil.StringsHas(keys, key) { + err = fmt.Errorf("parse inline config error: invalid key name %q", key) + return + } + + mp[key] = val + } + return +} diff --git a/vendor/github.com/gookit/goutil/strutil/textutil/var_replacer.go b/vendor/github.com/gookit/goutil/strutil/textutil/var_replacer.go new file mode 100644 index 000000000..29ef2c3d4 --- /dev/null +++ b/vendor/github.com/gookit/goutil/strutil/textutil/var_replacer.go @@ -0,0 +1,213 @@ +package textutil + +import ( + "reflect" + "regexp" + "strings" + + "github.com/gookit/goutil/arrutil" + "github.com/gookit/goutil/internal/comfunc" + "github.com/gookit/goutil/maputil" + "github.com/gookit/goutil/strutil" +) + +const defaultVarFormat = "{{,}}" + +// FallbackFn type +type FallbackFn = func(name string) (val string, ok bool) + +// VarReplacer struct +type VarReplacer struct { + init bool + + Left, Right string + lLen, rLen int + + varReg *regexp.Regexp + // flatten sub map in vars + flatSubs bool + parseEnv bool + // support parse default value. eg: {{ name | inhere }} + parseDef bool + // keepMissVars list. default False: will clear on each replace + keepMissVars bool + // missing vars list + missVars []string + // NotFound handler + NotFound FallbackFn +} + +// NewVarReplacer instance +func NewVarReplacer(format string, opFns ...func(vp *VarReplacer)) *VarReplacer { + vp := &VarReplacer{flatSubs: true} + for _, fn := range opFns { + fn(vp) + } + return vp.WithFormat(format) +} + +// NewFullReplacer instance +func NewFullReplacer(format string) *VarReplacer { + return NewVarReplacer(format, func(vp *VarReplacer) { + vp.WithParseEnv().WithParseDefault().KeepMissingVars() + }) +} + +// DisableFlatten on the input vars map +func (r *VarReplacer) DisableFlatten() *VarReplacer { + r.flatSubs = false + return r +} + +// KeepMissingVars on the replacement handle +func (r *VarReplacer) KeepMissingVars() *VarReplacer { + r.keepMissVars = true + return r +} + +// WithParseDefault value on the input template contents +func (r *VarReplacer) WithParseDefault() *VarReplacer { + r.parseDef = true + return r +} + +// WithParseEnv on the input vars value +func (r *VarReplacer) WithParseEnv() *VarReplacer { + r.parseEnv = true + return r +} + +// OnNotFound var handle +func (r *VarReplacer) OnNotFound(fn FallbackFn) *VarReplacer { + r.NotFound = fn + return r +} + +// WithFormat custom var template +func (r *VarReplacer) WithFormat(format string) *VarReplacer { + r.Left, r.Right = strutil.QuietCut(strutil.OrElse(format, defaultVarFormat), ",") + r.Init() + return r +} + +// Init var matcher +func (r *VarReplacer) Init() *VarReplacer { + if !r.init { + r.lLen, r.rLen = len(r.Left), len(r.Right) + if r.Right != "" { + r.varReg = regexp.MustCompile(regexp.QuoteMeta(r.Left) + `([\w\s\|.-]+)` + regexp.QuoteMeta(r.Right)) + } else { + // no right tag. eg: $name, $user.age + r.varReg = regexp.MustCompile(regexp.QuoteMeta(r.Left) + `(\w[\w-]*(?:\.[\w-]+)*)`) + } + } + + return r +} + +// ParseVars the text contents and collect vars +func (r *VarReplacer) ParseVars(s string) []string { + ss := arrutil.StringsMap(r.varReg.FindAllString(s, -1), func(val string) string { + return strings.TrimSpace(val[r.lLen : len(val)-r.rLen]) + }) + + return arrutil.Unique(ss) +} + +// Render any-map vars in the text contents +func (r *VarReplacer) Render(s string, tplVars map[string]any) string { + return r.Replace(s, tplVars) +} + +// Replace any-map vars in the text contents +func (r *VarReplacer) Replace(s string, tplVars map[string]any) string { + if !strings.Contains(s, r.Left) { + return s + } + if !r.parseDef && len(tplVars) == 0 { + return s + } + + var varMap map[string]string + + if r.flatSubs { + varMap = make(map[string]string, len(tplVars)*2) + maputil.FlatWithFunc(tplVars, func(path string, val reflect.Value) { + if val.Kind() == reflect.String { + if r.parseEnv { + varMap[path] = comfunc.ParseEnvVar(val.String(), nil) + } else { + varMap[path] = val.String() + } + } else { + varMap[path] = strutil.QuietString(val.Interface()) + } + }) + } else { + varMap = maputil.ToStringMap(tplVars) + } + + return r.Init().doReplace(s, varMap) +} + +// ReplaceSMap string-map vars in the text contents +func (r *VarReplacer) ReplaceSMap(s string, varMap map[string]string) string { + return r.RenderSimple(s, varMap) +} + +// RenderSimple string-map vars in the text contents. alias of ReplaceSMap() +func (r *VarReplacer) RenderSimple(s string, varMap map[string]string) string { + if len(varMap) == 0 || !strings.Contains(s, r.Left) { + return s + } + + if r.parseEnv { + for name, val := range varMap { + varMap[name] = comfunc.ParseEnvVar(val, nil) + } + } + + return r.Init().doReplace(s, varMap) +} + +// MissVars list +func (r *VarReplacer) MissVars() []string { + return r.missVars +} + +// ResetMissVars list +func (r *VarReplacer) ResetMissVars() { + r.missVars = make([]string, 0) +} + +// Replace string-map vars in the text contents +func (r *VarReplacer) doReplace(s string, varMap map[string]string) string { + if !r.keepMissVars { + r.missVars = make([]string, 0) // clear on each replace + } + + return r.varReg.ReplaceAllStringFunc(s, func(sub string) string { + name := strings.TrimSpace(sub[r.lLen : len(sub)-r.rLen]) + + var defVal string + if r.parseDef && strings.ContainsRune(name, '|') { + name, defVal = strutil.TrimCut(name, "|") + } + + if val, ok := varMap[name]; ok { + return val + } + + if r.NotFound != nil { + if val, ok := r.NotFound(name); ok { + return val + } + } + + if len(defVal) > 0 { + return defVal + } + r.missVars = append(r.missVars, name) + return sub + }) +} diff --git a/vendor/github.com/gookit/goutil/strutil/value.go b/vendor/github.com/gookit/goutil/strutil/value.go index 7e4bdc251..73c31aa7b 100644 --- a/vendor/github.com/gookit/goutil/strutil/value.go +++ b/vendor/github.com/gookit/goutil/strutil/value.go @@ -15,71 +15,84 @@ func (s *Value) Set(val string) error { } // IsEmpty check -func (s *Value) IsEmpty() bool { - return string(*s) == "" +func (s Value) IsEmpty() bool { + return string(s) == "" +} + +// IsBlank check +func (s Value) IsBlank() bool { + return strings.TrimSpace(string(s)) == "" } // IsStartWith prefix -func (s *Value) IsStartWith(sub string) bool { - return strings.HasPrefix(string(*s), sub) +func (s Value) IsStartWith(sub string) bool { + return strings.HasPrefix(string(s), sub) } // HasPrefix prefix -func (s *Value) HasPrefix(sub string) bool { - return strings.HasPrefix(string(*s), sub) +func (s Value) HasPrefix(sub string) bool { + return strings.HasPrefix(string(s), sub) } // IsEndWith suffix -func (s *Value) IsEndWith(sub string) bool { - return strings.HasSuffix(string(*s), sub) +func (s Value) IsEndWith(sub string) bool { + return strings.HasSuffix(string(s), sub) } // HasSuffix suffix -func (s *Value) HasSuffix(sub string) bool { - return strings.HasSuffix(string(*s), sub) +func (s Value) HasSuffix(sub string) bool { + return strings.HasSuffix(string(s), sub) } // Bytes string to bytes -func (s *Value) Bytes() []byte { - return []byte(*s) +func (s Value) Bytes() []byte { + return []byte(s) } // Val string -func (s *Value) Val() string { - return string(*s) +func (s Value) Val() string { + return string(s) } // Int convert -func (s *Value) Int() int { - return QuietInt(string(*s)) +func (s Value) Int() int { + return QuietInt(string(s)) } // Int64 convert -func (s *Value) Int64() int64 { - return QuietInt64(string(*s)) +func (s Value) Int64() int64 { + return QuietInt64(string(s)) } // Bool convert -func (s *Value) Bool() bool { - return QuietBool(string(*s)) +func (s Value) Bool() bool { + return QuietBool(string(s)) } // Value string -func (s *Value) String() string { - return string(*s) +func (s Value) String() string { + return string(s) +} + +// OrElse string +func (s Value) OrElse(or string) string { + if s != "" { + return string(s) + } + return or } // Split string -func (s *Value) Split(sep string) []string { - return strings.Split(string(*s), sep) +func (s Value) Split(sep string) []string { + return strings.Split(string(s), sep) } // SplitN string -func (s *Value) SplitN(sep string, n int) []string { - return strings.SplitN(string(*s), sep, n) +func (s Value) SplitN(sep string, n int) []string { + return strings.SplitN(string(s), sep, n) } -// TrimSpace string and return new -func (s *Value) TrimSpace() Value { - return Value(strings.TrimSpace(string(*s))) +// WithTrimSpace string and return new +func (s Value) WithTrimSpace() Value { + return Value(strings.TrimSpace(string(s))) } diff --git a/vendor/github.com/gookit/goutil/sysutil/cmdr/cmd.go b/vendor/github.com/gookit/goutil/sysutil/cmdr/cmd.go index b3f09d2b0..f91a41184 100644 --- a/vendor/github.com/gookit/goutil/sysutil/cmdr/cmd.go +++ b/vendor/github.com/gookit/goutil/sysutil/cmdr/cmd.go @@ -8,9 +8,9 @@ import ( "os/exec" "path/filepath" - "github.com/gookit/color" "github.com/gookit/goutil" "github.com/gookit/goutil/arrutil" + "github.com/gookit/goutil/cliutil/cmdline" "github.com/gookit/goutil/internal/comfunc" ) @@ -19,7 +19,10 @@ type Cmd struct { *exec.Cmd // Name of the command Name string - // inited bool + // DryRun if True, not real execute command + DryRun bool + // Vars mapping + Vars map[string]string // BeforeRun hook BeforeRun func(c *Cmd) @@ -27,37 +30,39 @@ type Cmd struct { AfterRun func(c *Cmd, err error) } -// WrapGoCmd instance -func WrapGoCmd(cmd *exec.Cmd) *Cmd { - return &Cmd{Cmd: cmd} -} - // NewGitCmd instance func NewGitCmd(subCmd string, args ...string) *Cmd { return NewCmd("git", subCmd).AddArgs(args) } +// NewCmdline instance +// +// see exec.Command +func NewCmdline(line string) *Cmd { + bin, args := cmdline.NewParser(line).WithParseEnv().BinAndArgs() + return NewCmd(bin, args...) +} + // NewCmd instance // // see exec.Command func NewCmd(bin string, args ...string) *Cmd { - return &Cmd{ - Cmd: exec.Command(bin, args...), - } + return WrapGoCmd(exec.Command(bin, args...)) } // CmdWithCtx create new instance with context. // // see exec.CommandContext func CmdWithCtx(ctx context.Context, bin string, args ...string) *Cmd { - return &Cmd{ - Cmd: exec.CommandContext(ctx, bin, args...), - } + return WrapGoCmd(exec.CommandContext(ctx, bin, args...)) } -// PrintCmdline on before exec -func PrintCmdline(c *Cmd) { - color.Yellowln(">", c.Cmdline()) +// WrapGoCmd instance +func WrapGoCmd(cmd *exec.Cmd) *Cmd { + return &Cmd{ + Cmd: cmd, + Vars: make(map[string]string), + } } // ------------------------------------------------- @@ -70,6 +75,18 @@ func (c *Cmd) Config(fn func(c *Cmd)) *Cmd { return c } +// WithDryRun on exec command +func (c *Cmd) WithDryRun(dryRun bool) *Cmd { + c.DryRun = dryRun + return c +} + +// PrintCmdline on exec command +func (c *Cmd) PrintCmdline() *Cmd { + c.BeforeRun = PrintCmdline + return c +} + // OnBefore exec add hook func (c *Cmd) OnBefore(fn func(c *Cmd)) *Cmd { c.BeforeRun = fn @@ -99,7 +116,7 @@ func (c *Cmd) lookPath(name string) { c.Path = lp } if err != nil { - goutil.Panicf("look %q path error: %s", name, err.Error()) + goutil.Panicf("cmdr: look %q path error: %v", name, err) } } } @@ -116,21 +133,60 @@ func (c *Cmd) WithWorkDir(dir string) *Cmd { return c } -// WorkDirOnNot set, returns the current object -func (c *Cmd) WorkDirOnNot(dir string) *Cmd { - if c.Dir == "" { +// WorkDirOnNE set workdir on input is not empty +func (c *Cmd) WorkDirOnNE(dir string) *Cmd { + if dir == "" { c.Dir = dir } return c } -// OutputToStd output to OS stdout and error -func (c *Cmd) OutputToStd() *Cmd { +// WithEnvMap override set new ENV for run +func (c *Cmd) WithEnvMap(mp map[string]string) *Cmd { + if ln := len(mp); ln > 0 { + c.Env = make([]string, 0, ln) + for key, val := range mp { + c.Env = append(c.Env, key+"="+val) + } + } + return c +} + +// AppendEnv to the os ENV for run command +func (c *Cmd) AppendEnv(mp map[string]string) *Cmd { + if len(mp) > 0 { + // init env data + if c.Env == nil { + c.Env = os.Environ() + } + + for name, val := range mp { + c.Env = append(c.Env, name+"="+val) + } + } + + return c +} + +// OutputToOS output to OS stdout and error +func (c *Cmd) OutputToOS() *Cmd { + return c.ToOSStdoutStderr() +} + +// ToOSStdoutStderr output to OS stdout and error +func (c *Cmd) ToOSStdoutStderr() *Cmd { c.Stdout = os.Stdout c.Stderr = os.Stderr return c } +// ToOSStdout output to OS stdout +func (c *Cmd) ToOSStdout() *Cmd { + c.Stdout = os.Stdout + c.Stderr = os.Stdout + return c +} + // WithStdin returns the current argument func (c *Cmd) WithStdin(in io.Reader) *Cmd { c.Stdin = in @@ -204,6 +260,20 @@ func (c *Cmd) WithArgsIf(args []string, exprOk bool) *Cmd { return c } +// WithVars add vars and returns the current object +func (c *Cmd) WithVars(vs map[string]string) *Cmd { + if len(vs) > 0 { + c.Vars = vs + } + return c +} + +// SetVar add var and returns the current object +func (c *Cmd) SetVar(name, val string) *Cmd { + c.Vars[name] = val + return c +} + // ------------------------------------------------- // helper command // ------------------------------------------------- @@ -249,6 +319,11 @@ func (c *Cmd) ResetArgs() { } } +// Workdir of the command +func (c *Cmd) Workdir() string { + return c.Dir +} + // Cmdline to command line func (c *Cmd) Cmdline() string { return comfunc.Cmdline(c.Args) @@ -280,6 +355,11 @@ func (c *Cmd) Success() bool { return c.Run() == nil } +// HasStdout output setting. +func (c *Cmd) HasStdout() bool { + return c.Stdout != nil +} + // SafeLines run and return output as lines func (c *Cmd) SafeLines() []string { ss, _ := c.OutputLines() @@ -310,6 +390,10 @@ func (c *Cmd) Output() (string, error) { c.BeforeRun(c) } + if c.DryRun { + return "DRY-RUN: ok", nil + } + output, err := c.Cmd.Output() if c.AfterRun != nil { @@ -324,6 +408,10 @@ func (c *Cmd) CombinedOutput() (string, error) { c.BeforeRun(c) } + if c.DryRun { + return "DRY-RUN: ok", nil + } + output, err := c.Cmd.CombinedOutput() if c.AfterRun != nil { @@ -341,8 +429,7 @@ func (c *Cmd) MustRun() { // FlushRun runs command and flush output to stdout func (c *Cmd) FlushRun() error { - c.OutputToStd() - return c.Run() + return c.ToOSStdoutStderr().Run() } // Run runs command @@ -351,6 +438,10 @@ func (c *Cmd) Run() error { c.BeforeRun(c) } + if c.DryRun { + return nil + } + // do running err := c.Cmd.Run() @@ -358,13 +449,13 @@ func (c *Cmd) Run() error { c.AfterRun(c, err) } return err - - // if IsWindows() { - // return c.Spawn() - // } - // return c.Exec() } +// if IsWindows() { +// return c.Spawn() +// } +// return c.Exec() + // Spawn runs command with spawn(3) // func (c *Cmd) Spawn() error { // return c.Cmd.Run() diff --git a/vendor/github.com/gookit/goutil/sysutil/cmdr/cmdr.go b/vendor/github.com/gookit/goutil/sysutil/cmdr/cmdr.go index 695d98678..09161c474 100644 --- a/vendor/github.com/gookit/goutil/sysutil/cmdr/cmdr.go +++ b/vendor/github.com/gookit/goutil/sysutil/cmdr/cmdr.go @@ -1,7 +1,20 @@ // Package cmdr Provide for quick build and run a cmd, batch run multi cmd tasks package cmdr -import "strings" +import ( + "strings" + + "github.com/gookit/color" +) + +// PrintCmdline on before exec +func PrintCmdline(c *Cmd) { + if c.DryRun { + color.Yellowln("DRY-RUN>", c.Cmdline()) + } else { + color.Yellowln(">", c.Cmdline()) + } +} // OutputLines split output to lines func OutputLines(output string) []string { diff --git a/vendor/github.com/gookit/goutil/sysutil/cmdr/runner.go b/vendor/github.com/gookit/goutil/sysutil/cmdr/runner.go index c01ffd977..35f81dd79 100644 --- a/vendor/github.com/gookit/goutil/sysutil/cmdr/runner.go +++ b/vendor/github.com/gookit/goutil/sysutil/cmdr/runner.go @@ -2,12 +2,15 @@ package cmdr import ( "fmt" + "strings" "github.com/gookit/color" "github.com/gookit/goutil/arrutil" + "github.com/gookit/goutil/cliutil/cmdline" "github.com/gookit/goutil/errorx" "github.com/gookit/goutil/maputil" "github.com/gookit/goutil/mathutil" + "github.com/gookit/goutil/strutil/textutil" ) // Task struct @@ -24,6 +27,7 @@ type Task struct { PrevCond func(prev *Task) bool } +// NewTask instance func NewTask(cmd *Cmd) *Task { return &Task{ Cmd: cmd, @@ -44,6 +48,24 @@ func (t *Task) ensureID(idx int) { t.ID = id } +var rpl = textutil.NewVarReplacer("$").DisableFlatten() + +// RunWith command +func (t *Task) RunWith(ctx maputil.Data) error { + cmdVars := ctx.StringMap("cmdVars") + + if len(cmdVars) > 0 { + // rpl := strutil.NewReplacer(cmdVars) + for i, val := range t.Cmd.Args { + if strings.ContainsRune(val, '$') { + t.Cmd.Args[i] = rpl.RenderSimple(val, cmdVars) + } + } + } + + return t.Run() +} + // Run command func (t *Task) Run() error { if t.BeforeRun != nil { @@ -74,6 +96,9 @@ func (t *Task) IsSuccess() bool { return t.err == nil } +// RunnerHookFn func +type RunnerHookFn func(r *Runner, t *Task) bool + // Runner use for batch run multi task commands type Runner struct { prev *Task @@ -84,14 +109,19 @@ type Runner struct { Errs errorx.ErrMap // TODO Concurrent run - // common workdir - // wordDir string + + // Workdir common workdir + Workdir string + // EnvMap will append to task.Cmd on run + EnvMap map[string]string // Params for add custom params Params maputil.Map // DryRun dry run all commands DryRun bool + // OutToStd stdout and stderr + OutToStd bool // IgnoreErr continue on error IgnoreErr bool // BeforeRun hooks on each task. return false to skip current task. @@ -109,12 +139,19 @@ func NewRunner(fns ...func(rr *Runner)) *Runner { Params: make(maputil.Map), } + rr.OutToStd = true for _, fn := range fns { fn(rr) } return rr } +// WithOutToStd set +func (r *Runner) WithOutToStd() *Runner { + r.OutToStd = true + return r +} + // Add multitask at once func (r *Runner) Add(tasks ...*Task) *Runner { for _, task := range tasks { @@ -148,26 +185,32 @@ func (r *Runner) AddCmd(cmds ...*Cmd) *Runner { // GitCmd quick a git command task func (r *Runner) GitCmd(subCmd string, args ...string) *Runner { - r.AddTask(&Task{ + return r.AddTask(&Task{ Cmd: NewGitCmd(subCmd, args...), }) - return r } // CmdWithArgs a command task func (r *Runner) CmdWithArgs(cmdName string, args ...string) *Runner { - r.AddTask(&Task{ + return r.AddTask(&Task{ Cmd: NewCmd(cmdName, args...), }) - return r } // CmdWithAnys a command task func (r *Runner) CmdWithAnys(cmdName string, args ...any) *Runner { - r.AddTask(&Task{ + return r.AddTask(&Task{ Cmd: NewCmd(cmdName, arrutil.SliceToStrings(args)...), }) - return r +} + +// AddCmdline as a command task +func (r *Runner) AddCmdline(line string) *Runner { + bin, args := cmdline.NewParser(line).BinAndArgs() + + return r.AddTask(&Task{ + Cmd: NewCmd(bin, args...), + }) } // Run all tasks @@ -183,13 +226,14 @@ func (r *Runner) Run() error { } if r.DryRun { - color.Infof("DRY-RUN: task #%d execute completed\n", i+1) + color.Infof("DRY-RUN: task#%d execute completed\n\n", i+1) continue } if !r.RunTask(task) { break } + fmt.Println() // with newline. } if len(r.Errs) == 0 { @@ -198,12 +242,30 @@ func (r *Runner) Run() error { return r.Errs } +// StepRun one command +func (r *Runner) StepRun() error { + return nil // TODO +} + // RunTask command func (r *Runner) RunTask(task *Task) (goon bool) { + if len(r.EnvMap) > 0 { + task.Cmd.AppendEnv(r.EnvMap) + } + + if r.OutToStd && !task.Cmd.HasStdout() { + task.Cmd.ToOSStdoutStderr() + } + + // common workdir + if r.Workdir != "" && task.Cmd.Dir == "" { + task.Cmd.WithWorkDir(r.Workdir) + } + // do running - if err := task.Run(); err != nil { + if err := task.RunWith(r.Params); err != nil { r.Errs[task.ID] = err - color.Errorf("Task #%d run error: %s\n", task.Index()+1, err) + color.Errorf("Task#%d run error: %s\n", task.Index()+1, err) // not ignore error, stop. if !r.IgnoreErr { @@ -225,6 +287,14 @@ func (r *Runner) Len() int { return len(r.tasks) } +// Reset instance +func (r *Runner) Reset() *Runner { + r.prev = nil + r.tasks = make([]*Task, 0) + r.idMap = make(map[string]int, 0) + return r +} + // TaskIDs get func (r *Runner) TaskIDs() []string { ss := make([]string, 0, len(r.idMap)) diff --git a/vendor/github.com/gookit/goutil/sysutil/process/pidfile.go b/vendor/github.com/gookit/goutil/sysutil/process/pidfile.go deleted file mode 100644 index e225c8b40..000000000 --- a/vendor/github.com/gookit/goutil/sysutil/process/pidfile.go +++ /dev/null @@ -1,70 +0,0 @@ -package process - -import ( - "os" - - "github.com/gookit/goutil/fsutil" - "github.com/gookit/goutil/mathutil" - "github.com/gookit/goutil/strutil" -) - -// PidFile struct -type PidFile struct { - pid int - file string - // body string -} - -// NewPidFile instance -func NewPidFile(file string) *PidFile { - return &PidFile{ - file: file, - } -} - -// Exists of th pid file -func (pf *PidFile) Exists() bool { - return fsutil.FileExist(pf.file) -} - -// File path -func (pf *PidFile) File() string { - return pf.file -} - -// PID value -func (pf *PidFile) PID() int { - if pf.pid > 0 { - return pf.pid - } - - if fsutil.FileExist(pf.file) { - bts, err := os.ReadFile(pf.file) - if err == nil { - pf.pid = strutil.QuietInt(string(bts)) - } - } - - return pf.pid -} - -// String PID value string -func (pf *PidFile) String() string { - return mathutil.String(pf.pid) -} - -// SetPID value -func (pf *PidFile) SetPID(val int) int { - pf.pid = val - return pf.pid -} - -// Save PID value to file -func (pf *PidFile) Save() error { - if pf.pid < 1 { - return nil - } - - _, err := fsutil.PutContents(pf.file, pf.String()) - return err -} diff --git a/vendor/github.com/gookit/goutil/sysutil/process/process.go b/vendor/github.com/gookit/goutil/sysutil/process/process.go deleted file mode 100644 index 6e839f1d5..000000000 --- a/vendor/github.com/gookit/goutil/sysutil/process/process.go +++ /dev/null @@ -1,9 +0,0 @@ -// Package process Provide some process handle util functions -package process - -import "os" - -// PID get process ID -func PID() int { - return os.Getpid() -} diff --git a/vendor/github.com/gookit/goutil/sysutil/process/process_darwin.go b/vendor/github.com/gookit/goutil/sysutil/process/process_darwin.go deleted file mode 100644 index 187ce94cb..000000000 --- a/vendor/github.com/gookit/goutil/sysutil/process/process_darwin.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build darwin -// +build darwin - -package process - -import ( - "golang.org/x/sys/unix" -) - -// Exists check process running by given pid -func Exists(pid int) bool { - // OS X does not have a proc filesystem. - // Use kill -0 pid to judge if the process exists. - err := unix.Kill(pid, 0) - return err == nil -} diff --git a/vendor/github.com/gookit/goutil/sysutil/process/process_unix.go b/vendor/github.com/gookit/goutil/sysutil/process/process_unix.go deleted file mode 100644 index 407a013b3..000000000 --- a/vendor/github.com/gookit/goutil/sysutil/process/process_unix.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build !windows && !darwin -// +build !windows,!darwin - -package process - -import ( - "os" - "path/filepath" - "strconv" -) - -// Exists check process running by given pid -func Exists(pid int) bool { - if _, err := os.Stat(filepath.Join("/proc", strconv.Itoa(pid))); err == nil { - return true - } - return false -} diff --git a/vendor/github.com/gookit/goutil/sysutil/process/process_windows.go b/vendor/github.com/gookit/goutil/sysutil/process/process_windows.go deleted file mode 100644 index 77238c08d..000000000 --- a/vendor/github.com/gookit/goutil/sysutil/process/process_windows.go +++ /dev/null @@ -1,28 +0,0 @@ -package process - -import ( - "golang.org/x/sys/windows" -) - -const ( - processQueryLimitedInformation = 0x1000 - - stillActive = 259 -) - -// Exists check process running by given pid -func Exists(pid int) bool { - h, err := windows.OpenProcess(processQueryLimitedInformation, false, uint32(pid)) - if err != nil { - return false - } - - var c uint32 - err = windows.GetExitCodeProcess(h, &c) - _ = windows.Close(h) - - if err != nil { - return c == stillActive - } - return true -} diff --git a/vendor/github.com/gookit/goutil/sysutil/sysenv.go b/vendor/github.com/gookit/goutil/sysutil/sysenv.go index 8e5303f0a..6573208bc 100644 --- a/vendor/github.com/gookit/goutil/sysutil/sysenv.go +++ b/vendor/github.com/gookit/goutil/sysutil/sysenv.go @@ -90,11 +90,6 @@ func IsShellSpecialVar(c uint8) bool { return false } -// EnvPaths get and split $PATH to []string -func EnvPaths() []string { - return filepath.SplitList(os.Getenv("PATH")) -} - // FindExecutable in the system // // Usage: @@ -104,7 +99,7 @@ func FindExecutable(binName string) (string, error) { return exec.LookPath(binName) } -// Executable find in the system +// Executable find in the system, alias of FindExecutable() // // Usage: // @@ -123,25 +118,65 @@ func HasExecutable(binName string) bool { return err == nil } +// Getenv get ENV value by key name, can with default value +func Getenv(name string, def ...string) string { + val := os.Getenv(name) + if val == "" && len(def) > 0 { + val = def[0] + } + return val +} + +// Environ like os.Environ, but will returns key-value map[string]string data. +func Environ() map[string]string { return comfunc.Environ() } + +// EnvMapWith like os.Environ, but will return key-value map[string]string data. +func EnvMapWith(newEnv map[string]string) map[string]string { + envMp := comfunc.Environ() + for name, value := range newEnv { + envMp[name] = value + } + return envMp +} + +// EnvPaths get and split $PATH to []string +func EnvPaths() []string { + return filepath.SplitList(os.Getenv("PATH")) +} + // SearchPath search executable files in the system $PATH // // Usage: // // sysutil.SearchPath("go") -func SearchPath(keywords string) []string { +func SearchPath(keywords string, limit int) []string { path := os.Getenv("PATH") ptn := "*" + keywords + "*" - list := make([]string, 0) + + checked := make(map[string]bool) for _, dir := range filepath.SplitList(path) { + // Unix shell semantics: path element "" means "." if dir == "" { - // Unix shell semantics: path element "" means "." dir = "." } + // mark dir is checked + if _, ok := checked[dir]; ok { + continue + } + + checked[dir] = true matches, err := filepath.Glob(filepath.Join(dir, ptn)) if err == nil && len(matches) > 0 { list = append(list, matches...) + size := len(list) + + // limit result size + if limit > 0 && size >= limit { + list = list[:limit] + break + } } } diff --git a/vendor/github.com/gookit/goutil/sysutil/sysutil.go b/vendor/github.com/gookit/goutil/sysutil/sysutil.go index 5be3456ed..60fe67b60 100644 --- a/vendor/github.com/gookit/goutil/sysutil/sysutil.go +++ b/vendor/github.com/gookit/goutil/sysutil/sysutil.go @@ -3,7 +3,7 @@ package sysutil import ( "os" - "path" + "path/filepath" ) // Workdir get @@ -14,11 +14,34 @@ func Workdir() string { // BinDir get func BinDir() string { - binFile := os.Args[0] - return path.Dir(binFile) + return filepath.Dir(os.Args[0]) +} + +// BinName get +func BinName() string { + return filepath.Base(os.Args[0]) } // BinFile get func BinFile() string { return os.Args[0] } + +// Open file or url address +func Open(fileOrURL string) error { + return OpenURL(fileOrURL) +} + +// OpenBrowser file or url address +func OpenBrowser(fileOrURL string) error { + return OpenURL(fileOrURL) +} + +// OpenFile opens new browser window for the file path. +func OpenFile(path string) error { + fpath, err := filepath.Abs(path) + if err != nil { + return err + } + return OpenURL("file://" + fpath) +} diff --git a/vendor/github.com/gookit/goutil/sysutil/sysutil_darwin.go b/vendor/github.com/gookit/goutil/sysutil/sysutil_darwin.go new file mode 100644 index 000000000..3fe2da509 --- /dev/null +++ b/vendor/github.com/gookit/goutil/sysutil/sysutil_darwin.go @@ -0,0 +1,36 @@ +package sysutil + +import "os/exec" + +// IsWin system. linux windows darwin +func IsWin() bool { return false } + +// IsWindows system. linux windows darwin +func IsWindows() bool { return false } + +// IsMac system +func IsMac() bool { return true } + +// IsDarwin system +func IsDarwin() bool { return true } + +// IsLinux system +func IsLinux() bool { return false } + +// OpenURL Open browser URL +// +// Mac: +// +// open 'https://github.com/inhere' +// +// Linux: +// +// xdg-open URL +// x-www-browser 'https://github.com/inhere' +// +// Windows: +// +// cmd /c start https://github.com/inhere +func OpenURL(URL string) error { + return exec.Command("open", URL).Run() +} diff --git a/vendor/github.com/gookit/goutil/sysutil/sysutil_nonwin.go b/vendor/github.com/gookit/goutil/sysutil/sysutil_nonwin.go index d7446ede4..bfafdbdee 100644 --- a/vendor/github.com/gookit/goutil/sysutil/sysutil_nonwin.go +++ b/vendor/github.com/gookit/goutil/sysutil/sysutil_nonwin.go @@ -1,39 +1,11 @@ //go:build !windows -// +build !windows package sysutil import ( - "os/exec" - "runtime" "syscall" ) -// IsWin system. linux windows darwin -func IsWin() bool { - return false -} - -// IsWindows system. linux windows darwin -func IsWindows() bool { - return false -} - -// IsMac system -func IsMac() bool { - return runtime.GOOS == "darwin" -} - -// IsDarwin system -func IsDarwin() bool { - return runtime.GOOS == "darwin" -} - -// IsLinux system -func IsLinux() bool { - return runtime.GOOS == "linux" -} - // Kill a process by pid func Kill(pid int, signal syscall.Signal) error { return syscall.Kill(pid, signal) @@ -43,26 +15,3 @@ func Kill(pid int, signal syscall.Signal) error { func ProcessExists(pid int) bool { return nil == syscall.Kill(pid, 0) } - -// OpenBrowser Open browser URL -// -// Mac: -// -// open 'https://github.com/inhere' -// -// Linux: -// -// xdg-open URL -// x-www-browser 'https://github.com/inhere' -// -// Windows: -// -// cmd /c start https://github.com/inhere -func OpenBrowser(URL string) error { - bin := "x-www-browser" - if IsDarwin() { - bin = "open" - } - - return exec.Command(bin, URL).Run() -} diff --git a/vendor/github.com/gookit/goutil/sysutil/sysutil_unix.go b/vendor/github.com/gookit/goutil/sysutil/sysutil_unix.go new file mode 100644 index 000000000..8059b09ab --- /dev/null +++ b/vendor/github.com/gookit/goutil/sysutil/sysutil_unix.go @@ -0,0 +1,54 @@ +//go:build !windows && !darwin + +package sysutil + +import ( + "os/exec" + "strings" +) + +// IsWin system. linux windows darwin +func IsWin() bool { return false } + +// IsWindows system. linux windows darwin +func IsWindows() bool { return false } + +// IsMac system +func IsMac() bool { return false } + +// IsDarwin system +func IsDarwin() bool { return false } + +// IsLinux system +func IsLinux() bool { + return true +} + +// There are multiple possible providers to open a browser on linux +// One of them is xdg-open, another is x-www-browser, then there's www-browser, etc. +// Look for one that exists and run it +var openBins = []string{"xdg-open", "x-www-browser", "www-browser"} + +// OpenURL Open file or browser URL +// +// Mac: +// +// open 'https://github.com/inhere' +// +// Linux: +// +// xdg-open URL +// x-www-browser 'https://github.com/inhere' +// +// Windows: +// +// cmd /c start https://github.com/inhere +func OpenURL(URL string) error { + for _, bin := range openBins { + if _, err := exec.LookPath(bin); err == nil { + return exec.Command(bin, URL).Run() + } + } + + return &exec.Error{Name: strings.Join(openBins, ","), Err: exec.ErrNotFound} +} diff --git a/vendor/github.com/gookit/goutil/sysutil/sysutil_windows.go b/vendor/github.com/gookit/goutil/sysutil/sysutil_windows.go index b53cdc3aa..ba458b174 100644 --- a/vendor/github.com/gookit/goutil/sysutil/sysutil_windows.go +++ b/vendor/github.com/gookit/goutil/sysutil/sysutil_windows.go @@ -5,36 +5,25 @@ package sysutil import ( "errors" - "os/exec" "syscall" - "github.com/gookit/goutil/sysutil/process" + "golang.org/x/sys/windows" ) // IsWin system. linux windows darwin -func IsWin() bool { - return true -} +func IsWin() bool { return true } // IsWindows system. linux windows darwin -func IsWindows() bool { - return true -} +func IsWindows() bool { return true } // IsMac system -func IsMac() bool { - return false -} +func IsMac() bool { return false } // IsDarwin system -func IsDarwin() bool { - return false -} +func IsDarwin() bool { return false } // IsLinux system -func IsLinux() bool { - return false -} +func IsLinux() bool { return false } // Kill a process by pid func Kill(pid int, signal syscall.Signal) error { @@ -43,10 +32,12 @@ func Kill(pid int, signal syscall.Signal) error { // ProcessExists check process exists by pid func ProcessExists(pid int) bool { - return process.Exists(pid) + panic("TIP: please use sysutil/process.Exists()") } -// OpenBrowser Open browser URL +// OpenURL Open file or browser URL +// +// - refers https://github.com/pkg/browser // // Mac: // @@ -60,6 +51,7 @@ func ProcessExists(pid int) bool { // Windows: // // cmd /c start https://github.com/inhere -func OpenBrowser(URL string) error { - return exec.Command("cmd", "/c", "start", URL).Run() +func OpenURL(url string) error { + // return exec.Command("cmd", "/C", "start", URL).Run() + return windows.ShellExecute(0, nil, windows.StringToUTF16Ptr(url), nil, nil, windows.SW_SHOWNORMAL) } diff --git a/vendor/github.com/gookit/goutil/sysutil/user.go b/vendor/github.com/gookit/goutil/sysutil/user.go index 4eeb6336b..76353857d 100644 --- a/vendor/github.com/gookit/goutil/sysutil/user.go +++ b/vendor/github.com/gookit/goutil/sysutil/user.go @@ -46,11 +46,9 @@ var homeDir string // UserHomeDir is alias of os.UserHomeDir, but ignore error func UserHomeDir() string { - if homeDir != "" { - return homeDir + if homeDir == "" { + homeDir, _ = os.UserHomeDir() } - - homeDir, _ = os.UserHomeDir() return homeDir } @@ -78,6 +76,7 @@ func UserConfigDir(subPath string) string { } // ExpandPath will parse `~` as user home dir path. -func ExpandPath(path string) string { - return comfunc.ExpandPath(path) -} +func ExpandPath(path string) string { return comfunc.ExpandHome(path) } + +// ExpandHome will parse `~` as user home dir path. +func ExpandHome(path string) string { return comfunc.ExpandHome(path) } diff --git a/vendor/github.com/gookit/goutil/sysutil/user_nonwin.go b/vendor/github.com/gookit/goutil/sysutil/user_nonwin.go index 556ee6ea8..6c52f5ac1 100644 --- a/vendor/github.com/gookit/goutil/sysutil/user_nonwin.go +++ b/vendor/github.com/gookit/goutil/sysutil/user_nonwin.go @@ -18,9 +18,9 @@ func ChangeUserByName(newUname string) (err error) { } // ChangeUserUidGid change work user by new username uid,gid -func ChangeUserUidGid(newUid int, newGid int) (err error) { - if newUid > 0 { - err = syscall.Setuid(newUid) +func ChangeUserUidGid(newUID int, newGid int) (err error) { + if newUID > 0 { + err = syscall.Setuid(newUID) // update group id if err == nil && newGid > 0 { diff --git a/vendor/github.com/imdario/mergo/CONTRIBUTING.md b/vendor/github.com/imdario/mergo/CONTRIBUTING.md new file mode 100644 index 000000000..0a1ff9f94 --- /dev/null +++ b/vendor/github.com/imdario/mergo/CONTRIBUTING.md @@ -0,0 +1,112 @@ + +# Contributing to mergo + +First off, thanks for taking the time to contribute! ❤️ + +All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉 + +> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about: +> - Star the project +> - Tweet about it +> - Refer this project in your project's readme +> - Mention the project at local meetups and tell your friends/colleagues + + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [I Have a Question](#i-have-a-question) +- [I Want To Contribute](#i-want-to-contribute) +- [Reporting Bugs](#reporting-bugs) +- [Suggesting Enhancements](#suggesting-enhancements) + +## Code of Conduct + +This project and everyone participating in it is governed by the +[mergo Code of Conduct](https://github.com/imdario/mergoblob/master/CODE_OF_CONDUCT.md). +By participating, you are expected to uphold this code. Please report unacceptable behavior +to <>. + + +## I Have a Question + +> If you want to ask a question, we assume that you have read the available [Documentation](https://pkg.go.dev/github.com/imdario/mergo). + +Before you ask a question, it is best to search for existing [Issues](https://github.com/imdario/mergo/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first. + +If you then still feel the need to ask a question and need clarification, we recommend the following: + +- Open an [Issue](https://github.com/imdario/mergo/issues/new). +- Provide as much context as you can about what you're running into. +- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant. + +We will then take care of the issue as soon as possible. + +## I Want To Contribute + +> ### Legal Notice +> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license. + +### Reporting Bugs + + +#### Before Submitting a Bug Report + +A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible. + +- Make sure that you are using the latest version. +- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](). If you are looking for support, you might want to check [this section](#i-have-a-question)). +- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/imdario/mergoissues?q=label%3Abug). +- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue. +- Collect information about the bug: +- Stack trace (Traceback) +- OS, Platform and Version (Windows, Linux, macOS, x86, ARM) +- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant. +- Possibly your input and the output +- Can you reliably reproduce the issue? And can you also reproduce it with older versions? + + +#### How Do I Submit a Good Bug Report? + +> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to . + + +We use GitHub issues to track bugs and errors. If you run into an issue with the project: + +- Open an [Issue](https://github.com/imdario/mergo/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.) +- Explain the behavior you would expect and the actual behavior. +- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case. +- Provide the information you collected in the previous section. + +Once it's filed: + +- The project team will label the issue accordingly. +- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced. +- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be implemented by someone. + +### Suggesting Enhancements + +This section guides you through submitting an enhancement suggestion for mergo, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions. + + +#### Before Submitting an Enhancement + +- Make sure that you are using the latest version. +- Read the [documentation]() carefully and find out if the functionality is already covered, maybe by an individual configuration. +- Perform a [search](https://github.com/imdario/mergo/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. +- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library. + + +#### How Do I Submit a Good Enhancement Suggestion? + +Enhancement suggestions are tracked as [GitHub issues](https://github.com/imdario/mergo/issues). + +- Use a **clear and descriptive title** for the issue to identify the suggestion. +- Provide a **step-by-step description of the suggested enhancement** in as many details as possible. +- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you. +- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. +- **Explain why this enhancement would be useful** to most mergo users. You may also want to point out the other projects that solved it better and which could serve as inspiration. + + +## Attribution +This guide is based on the **contributing-gen**. [Make your own](https://github.com/bttger/contributing-gen)! diff --git a/vendor/github.com/imdario/mergo/README.md b/vendor/github.com/imdario/mergo/README.md index 7e6f7aeee..4f0287498 100644 --- a/vendor/github.com/imdario/mergo/README.md +++ b/vendor/github.com/imdario/mergo/README.md @@ -1,6 +1,5 @@ # Mergo - [![GoDoc][3]][4] [![GitHub release][5]][6] [![GoCard][7]][8] @@ -9,6 +8,7 @@ [![Sourcegraph][11]][12] [![FOSSA Status][13]][14] [![Become my sponsor][15]][16] +[![Tidelift][17]][18] [1]: https://travis-ci.org/imdario/mergo.png [2]: https://travis-ci.org/imdario/mergo @@ -26,6 +26,8 @@ [14]: https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield [15]: https://img.shields.io/github/sponsors/imdario [16]: https://github.com/sponsors/imdario +[17]: https://tidelift.com/badges/package/go/github.com%2Fimdario%2Fmergo +[18]: https://tidelift.com/subscription/pkg/go-github.com-imdario-mergo A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements. @@ -55,7 +57,6 @@ If Mergo is useful to you, consider buying me a coffee, a beer, or making a mont ### Mergo in the wild -- [cli/cli](https://github.com/cli/cli) - [moby/moby](https://github.com/moby/moby) - [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes) - [vmware/dispatch](https://github.com/vmware/dispatch) diff --git a/vendor/github.com/imdario/mergo/SECURITY.md b/vendor/github.com/imdario/mergo/SECURITY.md new file mode 100644 index 000000000..a5de61f77 --- /dev/null +++ b/vendor/github.com/imdario/mergo/SECURITY.md @@ -0,0 +1,14 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 0.3.x | :white_check_mark: | +| < 0.3 | :x: | + +## Security contact information + +To report a security vulnerability, please use the +[Tidelift security contact](https://tidelift.com/security). +Tidelift will coordinate the fix and disclosure. diff --git a/vendor/github.com/imdario/mergo/map.go b/vendor/github.com/imdario/mergo/map.go index a13a7ee46..b50d5c2a4 100644 --- a/vendor/github.com/imdario/mergo/map.go +++ b/vendor/github.com/imdario/mergo/map.go @@ -44,7 +44,7 @@ func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, conf } } // Remember, remember... - visited[h] = &visit{addr, typ, seen} + visited[h] = &visit{typ, seen, addr} } zeroValue := reflect.Value{} switch dst.Kind() { @@ -58,7 +58,7 @@ func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, conf } fieldName := field.Name fieldName = changeInitialCase(fieldName, unicode.ToLower) - if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v)) || overwrite) { + if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v), !config.ShouldNotDereference) || overwrite) { dstMap[fieldName] = src.Field(i).Interface() } } @@ -142,7 +142,7 @@ func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error { func _map(dst, src interface{}, opts ...func(*Config)) error { if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr { - return ErrNonPointerAgument + return ErrNonPointerArgument } var ( vDst, vSrc reflect.Value diff --git a/vendor/github.com/imdario/mergo/merge.go b/vendor/github.com/imdario/mergo/merge.go index 8b4e2f47a..0ef9b2138 100644 --- a/vendor/github.com/imdario/mergo/merge.go +++ b/vendor/github.com/imdario/mergo/merge.go @@ -38,10 +38,11 @@ func isExportedComponent(field *reflect.StructField) bool { } type Config struct { + Transformers Transformers Overwrite bool + ShouldNotDereference bool AppendSlice bool TypeCheck bool - Transformers Transformers overwriteWithEmptyValue bool overwriteSliceWithEmptyValue bool sliceDeepCopy bool @@ -76,7 +77,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co } } // Remember, remember... - visited[h] = &visit{addr, typ, seen} + visited[h] = &visit{typ, seen, addr} } if config.Transformers != nil && !isReflectNil(dst) && dst.IsValid() { @@ -95,7 +96,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co } } } else { - if dst.CanSet() && (isReflectNil(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc) { + if dst.CanSet() && (isReflectNil(dst) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc) { dst.Set(src) } } @@ -110,7 +111,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co } if src.Kind() != reflect.Map { - if overwrite { + if overwrite && dst.CanSet() { dst.Set(src) } return @@ -162,7 +163,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co dstSlice = reflect.ValueOf(dstElement.Interface()) } - if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice && !sliceDeepCopy { + if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy { if typeCheck && srcSlice.Type() != dstSlice.Type() { return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type()) } @@ -194,22 +195,38 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co dst.SetMapIndex(key, dstSlice) } } - if dstElement.IsValid() && !isEmptyValue(dstElement) && (reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map || reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice) { - continue + + if dstElement.IsValid() && !isEmptyValue(dstElement, !config.ShouldNotDereference) { + if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice { + continue + } + if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map && reflect.TypeOf(dstElement.Interface()).Kind() == reflect.Map { + continue + } } - if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement)) { + if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement, !config.ShouldNotDereference)) { if dst.IsNil() { dst.Set(reflect.MakeMap(dst.Type())) } dst.SetMapIndex(key, srcElement) } } + + // Ensure that all keys in dst are deleted if they are not in src. + if overwriteWithEmptySrc { + for _, key := range dst.MapKeys() { + srcElement := src.MapIndex(key) + if !srcElement.IsValid() { + dst.SetMapIndex(key, reflect.Value{}) + } + } + } case reflect.Slice: if !dst.CanSet() { break } - if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice && !sliceDeepCopy { + if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy { dst.Set(src) } else if config.AppendSlice { if src.Type() != dst.Type() { @@ -244,12 +261,18 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co if src.Kind() != reflect.Interface { if dst.IsNil() || (src.Kind() != reflect.Ptr && overwrite) { - if dst.CanSet() && (overwrite || isEmptyValue(dst)) { + if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) { dst.Set(src) } } else if src.Kind() == reflect.Ptr { - if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { - return + if !config.ShouldNotDereference { + if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { + return + } + } else { + if overwriteWithEmptySrc || (overwrite && !src.IsNil()) || dst.IsNil() { + dst.Set(src) + } } } else if dst.Elem().Type() == src.Type() { if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil { @@ -262,7 +285,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co } if dst.IsNil() || overwrite { - if dst.CanSet() && (overwrite || isEmptyValue(dst)) { + if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) { dst.Set(src) } break @@ -275,7 +298,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co break } default: - mustSet := (isEmptyValue(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc) + mustSet := (isEmptyValue(dst, !config.ShouldNotDereference) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc) if mustSet { if dst.CanSet() { dst.Set(src) @@ -326,6 +349,12 @@ func WithOverrideEmptySlice(config *Config) { config.overwriteSliceWithEmptyValue = true } +// WithoutDereference prevents dereferencing pointers when evaluating whether they are empty +// (i.e. a non-nil pointer is never considered empty). +func WithoutDereference(config *Config) { + config.ShouldNotDereference = true +} + // WithAppendSlice will make merge append slices instead of overwriting it. func WithAppendSlice(config *Config) { config.AppendSlice = true @@ -344,7 +373,7 @@ func WithSliceDeepCopy(config *Config) { func merge(dst, src interface{}, opts ...func(*Config)) error { if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr { - return ErrNonPointerAgument + return ErrNonPointerArgument } var ( vDst, vSrc reflect.Value diff --git a/vendor/github.com/imdario/mergo/mergo.go b/vendor/github.com/imdario/mergo/mergo.go index 9fe362d47..0a721e2d8 100644 --- a/vendor/github.com/imdario/mergo/mergo.go +++ b/vendor/github.com/imdario/mergo/mergo.go @@ -20,7 +20,7 @@ var ( ErrNotSupported = errors.New("only structs, maps, and slices are supported") ErrExpectedMapAsDestination = errors.New("dst was expected to be a map") ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct") - ErrNonPointerAgument = errors.New("dst must be a pointer") + ErrNonPointerArgument = errors.New("dst must be a pointer") ) // During deepMerge, must keep track of checks that are @@ -28,13 +28,13 @@ var ( // checks in progress are true when it reencounters them. // Visited are stored in a map indexed by 17 * a1 + a2; type visit struct { - ptr uintptr typ reflect.Type next *visit + ptr uintptr } // From src/pkg/encoding/json/encode.go. -func isEmptyValue(v reflect.Value) bool { +func isEmptyValue(v reflect.Value, shouldDereference bool) bool { switch v.Kind() { case reflect.Array, reflect.Map, reflect.Slice, reflect.String: return v.Len() == 0 @@ -50,7 +50,10 @@ func isEmptyValue(v reflect.Value) bool { if v.IsNil() { return true } - return isEmptyValue(v.Elem()) + if shouldDereference { + return isEmptyValue(v.Elem(), shouldDereference) + } + return false case reflect.Func: return v.IsNil() case reflect.Invalid: diff --git a/vendor/github.com/mattn/go-isatty/isatty_bsd.go b/vendor/github.com/mattn/go-isatty/isatty_bsd.go index 39bbcf00f..d569c0c94 100644 --- a/vendor/github.com/mattn/go-isatty/isatty_bsd.go +++ b/vendor/github.com/mattn/go-isatty/isatty_bsd.go @@ -1,5 +1,5 @@ -//go:build (darwin || freebsd || openbsd || netbsd || dragonfly) && !appengine -// +build darwin freebsd openbsd netbsd dragonfly +//go:build (darwin || freebsd || openbsd || netbsd || dragonfly || hurd) && !appengine +// +build darwin freebsd openbsd netbsd dragonfly hurd // +build !appengine package isatty diff --git a/vendor/github.com/nats-io/nats-server/v2/server/accounts.go b/vendor/github.com/nats-io/nats-server/v2/server/accounts.go index 8cd21e125..4c132475a 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/accounts.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/accounts.go @@ -958,7 +958,7 @@ func (a *Account) isLeafNodeClusterIsolated(cluster string) bool { if len(a.leafClusters) > 1 { return false } - return a.leafClusters[cluster] > 0 + return a.leafClusters[cluster] == uint64(a.nleafs) } // Helper function to remove leaf nodes. If number of leafnodes gets large @@ -1429,7 +1429,7 @@ func (a *Account) sendTrackingLatency(si *serviceImport, responder *client) bool } sl.RequestStart = time.Unix(0, si.ts-int64(reqRTT)).UTC() sl.ServiceLatency = serviceRTT - respRTT - sl.TotalLatency = sl.Requestor.RTT + serviceRTT + sl.TotalLatency = reqRTT + serviceRTT if respRTT > 0 { sl.SystemLatency = time.Since(ts) sl.TotalLatency += sl.SystemLatency @@ -3784,10 +3784,11 @@ func (ur *URLAccResolver) Fetch(name string) (string, error) { return _EMPTY_, fmt.Errorf("could not fetch <%q>: %v", redactURLString(url), err) } else if resp == nil { return _EMPTY_, fmt.Errorf("could not fetch <%q>: no response", redactURLString(url)) - } else if resp.StatusCode != http.StatusOK { - return _EMPTY_, fmt.Errorf("could not fetch <%q>: %v", redactURLString(url), resp.Status) } defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return _EMPTY_, fmt.Errorf("could not fetch <%q>: %v", redactURLString(url), resp.Status) + } body, err := io.ReadAll(resp.Body) if err != nil { return _EMPTY_, err diff --git a/vendor/github.com/nats-io/nats-server/v2/server/client.go b/vendor/github.com/nats-io/nats-server/v2/server/client.go index 48337db6c..bcedd1340 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/client.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/client.go @@ -1989,7 +1989,9 @@ func (c *client) authViolation() { ErrAuthentication.Error(), c.opts.Username) } else { - c.Errorf(ErrAuthentication.Error()) + if c.srv != nil { + c.Errorf(ErrAuthentication.Error()) + } } if c.isMqtt() { c.mqttEnqueueConnAck(mqttConnAckRCNotAuthorized, false) @@ -2165,6 +2167,25 @@ func (c *client) generateClientInfoJSON(info Info) []byte { info.MaxPayload = c.mpay if c.isWebsocket() { info.ClientConnectURLs = info.WSConnectURLs + if c.srv != nil { // Otherwise lame duck info can panic + c.srv.websocket.mu.RLock() + info.TLSAvailable = c.srv.websocket.tls + if c.srv.websocket.server != nil { + if tc := c.srv.websocket.server.TLSConfig; tc != nil { + info.TLSRequired = !tc.InsecureSkipVerify + } + } + if c.srv.websocket.listener != nil { + laddr := c.srv.websocket.listener.Addr().String() + if h, p, err := net.SplitHostPort(laddr); err == nil { + if p, err := strconv.Atoi(p); err == nil { + info.Host = h + info.Port = p + } + } + } + c.srv.websocket.mu.RUnlock() + } } info.WSConnectURLs = nil // Generate the info json diff --git a/vendor/github.com/nats-io/nats-server/v2/server/const.go b/vendor/github.com/nats-io/nats-server/v2/server/const.go index 6b18e4d3f..b53007ebb 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/const.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/const.go @@ -41,7 +41,7 @@ var ( const ( // VERSION is the current version for the server. - VERSION = "2.9.17" + VERSION = "2.9.19" // PROTO is the currently supported protocol. // 0 was the original diff --git a/vendor/github.com/nats-io/nats-server/v2/server/consumer.go b/vendor/github.com/nats-io/nats-server/v2/server/consumer.go index c9fd40558..daa921b0d 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/consumer.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/consumer.go @@ -1417,9 +1417,10 @@ func (o *consumer) deleteNotActive() { defer ticker.Stop() for range ticker.C { js.mu.RLock() - ca := js.consumerAssignment(acc, stream, name) + nca := js.consumerAssignment(acc, stream, name) js.mu.RUnlock() - if ca != nil { + // Make sure this is not a new consumer with the same name. + if nca != nil && nca == ca { s.Warnf("Consumer assignment for '%s > %s > %s' not cleaned up, retrying", acc, stream, name) meta.ForwardProposal(removeEntry) } else { @@ -3255,10 +3256,10 @@ func (o *consumer) hbTimer() (time.Duration, *time.Timer) { // Should only be called from consumer leader. func (o *consumer) checkAckFloor() { o.mu.RLock() - mset, closed, asflr := o.mset, o.closed, o.asflr + mset, closed, asflr, numPending := o.mset, o.closed, o.asflr, len(o.pending) o.mu.RUnlock() - if closed || mset == nil { + if asflr == 0 || closed || mset == nil { return } @@ -3270,19 +3271,46 @@ func (o *consumer) checkAckFloor() { return } - // Process all messages that no longer exist. - for seq := asflr + 1; seq < ss.FirstSeq; seq++ { - // Check if this message was pending. + // Check which linear space is less to walk. + if ss.FirstSeq-asflr-1 < uint64(numPending) { + // Process all messages that no longer exist. + for seq := asflr + 1; seq < ss.FirstSeq; seq++ { + // Check if this message was pending. + o.mu.RLock() + p, isPending := o.pending[seq] + var rdc uint64 = 1 + if o.rdc != nil { + rdc = o.rdc[seq] + } + o.mu.RUnlock() + // If it was pending for us, get rid of it. + if isPending { + o.processTerm(seq, p.Sequence, rdc) + } + } + } else if numPending > 0 { + // here it shorter to walk pending. + // toTerm is seq, dseq, rcd for each entry. + toTerm := make([]uint64, 0, numPending*3) o.mu.RLock() - p, isPending := o.pending[seq] - var rdc uint64 = 1 - if o.rdc != nil { - rdc = o.rdc[seq] + for seq, p := range o.pending { + if seq < ss.FirstSeq { + var dseq uint64 = 1 + if p != nil { + dseq = p.Sequence + } + var rdc uint64 = 1 + if o.rdc != nil { + rdc = o.rdc[seq] + } + toTerm = append(toTerm, seq, dseq, rdc) + } } o.mu.RUnlock() - // If it was pending for us, get rid of it. - if isPending { - o.processTerm(seq, p.Sequence, rdc) + + for i := 0; i < len(toTerm); i += 3 { + seq, dseq, rdc := toTerm[i], toTerm[i+1], toTerm[i+2] + o.processTerm(seq, dseq, rdc) } } @@ -3315,15 +3343,21 @@ func (o *consumer) checkAckFloor() { func (o *consumer) processInboundAcks(qch chan struct{}) { // Grab the server lock to watch for server quit. o.mu.RLock() - s := o.srv + s, mset := o.srv, o.mset hasInactiveThresh := o.cfg.InactiveThreshold > 0 o.mu.RUnlock() + if s == nil || mset == nil { + return + } + // We will check this on entry and periodically. o.checkAckFloor() // How often we will check for ack floor drift. - var ackFloorCheck = 30 * time.Second + // Spread these out for large numbers on a server restart. + delta := time.Duration(rand.Int63n(int64(time.Minute))) + var ackFloorCheck = time.Minute + delta for { select { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/events.go b/vendor/github.com/nats-io/nats-server/v2/server/events.go index 00c1d3ed5..c954467a1 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/events.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/events.go @@ -99,25 +99,26 @@ type inSysMsg struct { // Used to send and receive messages from inside the server. type internal struct { - account *Account - client *client - seq uint64 - sid int - servers map[string]*serverUpdate - sweeper *time.Timer - stmr *time.Timer - replies map[string]msgHandler - sendq *ipQueue[*pubMsg] - recvq *ipQueue[*inSysMsg] - resetCh chan struct{} - wg sync.WaitGroup - sq *sendq - orphMax time.Duration - chkOrph time.Duration - statsz time.Duration - cstatsz time.Duration - shash string - inboxPre string + account *Account + client *client + seq uint64 + sid int + servers map[string]*serverUpdate + sweeper *time.Timer + stmr *time.Timer + replies map[string]msgHandler + sendq *ipQueue[*pubMsg] + recvq *ipQueue[*inSysMsg] + resetCh chan struct{} + wg sync.WaitGroup + sq *sendq + orphMax time.Duration + chkOrph time.Duration + statsz time.Duration + cstatsz time.Duration + shash string + inboxPre string + remoteStatsSub *subscription } // ServerStatsMsg is sent periodically with stats updates. @@ -642,8 +643,6 @@ func (s *Server) checkRemoteServers() { // Grab RSS and PCPU // Server lock will be held but released. func (s *Server) updateServerUsage(v *ServerStats) { - s.mu.Unlock() - defer s.mu.Lock() var vss int64 pse.ProcUsage(&v.CPU, &v.Mem, &vss) v.Cores = runtime.NumCPU() @@ -679,6 +678,32 @@ func routeStat(r *client) *RouteStat { func (s *Server) sendStatsz(subj string) { var m ServerStatsMsg s.updateServerUsage(&m.Stats) + + s.mu.RLock() + defer s.mu.RUnlock() + + // Check that we have a system account, etc. + if s.sys == nil || s.sys.account == nil { + return + } + + // if we are running standalone, check for interest. + if s.standAloneMode() { + // Check if we even have interest in this subject. + sacc := s.sys.account + rr := sacc.sl.Match(subj) + totalSubs := len(rr.psubs) + len(rr.qsubs) + if totalSubs == 0 { + return + } else if totalSubs == 1 && len(rr.psubs) == 1 { + // For the broadcast subject we listen to that ourselves with no echo for remote updates. + // If we are the only ones listening do not send either. + if rr.psubs[0] == s.sys.remoteStatsSub { + return + } + } + } + m.Stats.Start = s.start m.Stats.Connections = len(s.clients) m.Stats.TotalConnections = s.totalClients @@ -722,14 +747,12 @@ func (s *Server) sendStatsz(subj string) { gw.RUnlock() } // Active Servers - m.Stats.ActiveServers = 1 - if s.sys != nil { - m.Stats.ActiveServers += len(s.sys.servers) - } + m.Stats.ActiveServers = len(s.sys.servers) + 1 + // JetStream if js := s.js; js != nil { jStat := &JetStreamVarz{} - s.mu.Unlock() + s.mu.RUnlock() js.mu.RLock() c := js.config c.StoreDir = _EMPTY_ @@ -771,7 +794,7 @@ func (s *Server) sendStatsz(subj string) { } } m.Stats.JetStream = jStat - s.mu.Lock() + s.mu.RLock() } // Send message. s.sendInternalMsg(subj, _EMPTY_, &m.Server, &m) @@ -790,13 +813,12 @@ func (s *Server) heartbeatStatsz() { } s.sys.stmr.Reset(s.sys.cstatsz) } - s.sendStatsz(fmt.Sprintf(serverStatsSubj, s.info.ID)) + // Do in separate Go routine. + go s.sendStatszUpdate() } func (s *Server) sendStatszUpdate() { - s.mu.Lock() - s.sendStatsz(fmt.Sprintf(serverStatsSubj, s.info.ID)) - s.mu.Unlock() + s.sendStatsz(fmt.Sprintf(serverStatsSubj, s.ID())) } // This should be wrapChk() to setup common locking. @@ -897,8 +919,11 @@ func (s *Server) initEventTracking() { } // Listen for statsz from others. subject = fmt.Sprintf(serverStatsSubj, "*") - if _, err := s.sysSubscribe(subject, s.noInlineCallback(s.remoteServerUpdate)); err != nil { + if sub, err := s.sysSubscribe(subject, s.noInlineCallback(s.remoteServerUpdate)); err != nil { s.Errorf("Error setting up internal tracking: %v", err) + } else { + // Keep track of this one. + s.sys.remoteStatsSub = sub } // Listen for all server shutdowns. subject = fmt.Sprintf(shutdownEventSubj, "*") @@ -1338,7 +1363,8 @@ func (s *Server) processNewServer(si *ServerInfo) { } } // Announce ourselves.. - s.sendStatsz(fmt.Sprintf(serverStatsSubj, s.info.ID)) + // Do this in a separate Go routine. + go s.sendStatszUpdate() } // If GW is enabled on this server and there are any leaf node connections, @@ -1612,9 +1638,7 @@ func (s *Server) statszReq(sub *subscription, c *client, _ *Account, subject, re return } } - s.mu.Lock() s.sendStatsz(reply) - s.mu.Unlock() } var errSkipZreq = errors.New("filtered response") diff --git a/vendor/github.com/nats-io/nats-server/v2/server/filestore.go b/vendor/github.com/nats-io/nats-server/v2/server/filestore.go index 8fd7b20dc..7b56d8891 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/filestore.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/filestore.go @@ -4665,7 +4665,16 @@ func (fs *fileStore) loadLast(subj string, sm *StoreMsg) (lsm *StoreMsg, err err mb.mu.Unlock() return nil, err } - _, _, l := mb.filteredPendingLocked(subj, wc, mb.first.seq) + var l uint64 + // Optimize if subject is not a wildcard. + if !wc { + if ss := mb.fss[subj]; ss != nil { + l = ss.Last + } + } + if l == 0 { + _, _, l = mb.filteredPendingLocked(subj, wc, mb.first.seq) + } if l > 0 { if mb.cacheNotLoaded() { if err := mb.loadMsgsWithLock(); err != nil { @@ -5108,10 +5117,6 @@ func compareFn(subject string) func(string, string) bool { // PurgeEx will remove messages based on subject filters, sequence and number of messages to keep. // Will return the number of purged messages. func (fs *fileStore) PurgeEx(subject string, sequence, keep uint64) (purged uint64, err error) { - if sequence > 1 && keep > 0 { - return 0, ErrPurgeArgMismatch - } - if subject == _EMPTY_ || subject == fwcs { if keep == 0 && (sequence == 0 || sequence == 1) { return fs.Purge() diff --git a/vendor/github.com/nats-io/nats-server/v2/server/gateway.go b/vendor/github.com/nats-io/nats-server/v2/server/gateway.go index afc756832..8d194978e 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/gateway.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/gateway.go @@ -870,9 +870,7 @@ func (s *Server) createGateway(cfg *gatewayCfg, url *url.URL, conn net.Conn) { // Announce ourselves again to new connections. if solicit && s.EventsEnabled() { - s.mu.Lock() - s.sendStatsz(fmt.Sprintf(serverStatsSubj, s.info.ID)) - s.mu.Unlock() + s.sendStatszUpdate() } } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/ipqueue.go b/vendor/github.com/nats-io/nats-server/v2/server/ipqueue.go index 8519043b1..b26a749ed 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/ipqueue.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/ipqueue.go @@ -104,6 +104,9 @@ func (q *ipQueue[T]) push(e T) int { // emptied the queue. So the caller should never assume that pop() will // return a slice of 1 or more, it could return `nil`. func (q *ipQueue[T]) pop() []T { + if q == nil { + return nil + } var elts []T q.Lock() if q.pos == 0 { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/jetstream.go b/vendor/github.com/nats-io/nats-server/v2/server/jetstream.go index 0c0381ce1..4956d909b 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/jetstream.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/jetstream.go @@ -1020,9 +1020,9 @@ func (a *Account) EnableJetStream(limits map[string]JetStreamAccountLimits) erro return fmt.Errorf("jetstream can not be enabled on the system account") } - s.mu.Lock() + s.mu.RLock() sendq := s.sys.sendq - s.mu.Unlock() + s.mu.RUnlock() // No limits means we dynamically set up limits. // We also place limits here so we know that the account is configured for JetStream. @@ -1054,12 +1054,15 @@ func (a *Account) EnableJetStream(limits map[string]JetStreamAccountLimits) erro jsa := &jsAccount{js: js, account: a, limits: limits, streams: make(map[string]*stream), sendq: sendq, usage: make(map[string]*jsaStorage)} jsa.storeDir = filepath.Join(js.config.StoreDir, a.Name) - jsa.usageMu.Lock() - jsa.utimer = time.AfterFunc(usageTick, jsa.sendClusterUsageUpdateTimer) - // Cluster mode updates to resource usage, but we always will turn on. System internal prevents echos. - jsa.updatesPub = fmt.Sprintf(jsaUpdatesPubT, a.Name, sysNode) - jsa.updatesSub, _ = s.sysSubscribe(fmt.Sprintf(jsaUpdatesSubT, a.Name), jsa.remoteUpdateUsage) - jsa.usageMu.Unlock() + // A single server does not need to do the account updates at this point. + if js.cluster != nil || !s.standAloneMode() { + jsa.usageMu.Lock() + jsa.utimer = time.AfterFunc(usageTick, jsa.sendClusterUsageUpdateTimer) + // Cluster mode updates to resource usage. System internal prevents echos. + jsa.updatesPub = fmt.Sprintf(jsaUpdatesPubT, a.Name, sysNode) + jsa.updatesSub, _ = s.sysSubscribe(fmt.Sprintf(jsaUpdatesSubT, a.Name), jsa.remoteUpdateUsage) + jsa.usageMu.Unlock() + } js.accounts[a.Name] = jsa js.mu.Unlock() @@ -1209,22 +1212,23 @@ func (a *Account) EnableJetStream(limits map[string]JetStreamAccountLimits) erro // Check if we are encrypted. keyFile := filepath.Join(mdir, JetStreamMetaFileKey) - if key, err := os.ReadFile(keyFile); err == nil { + keyBuf, err := os.ReadFile(keyFile) + if err == nil { s.Debugf(" Stream metafile is encrypted, reading encrypted keyfile") - if len(key) < minMetaKeySize { - s.Warnf(" Bad stream encryption key length of %d", len(key)) + if len(keyBuf) < minMetaKeySize { + s.Warnf(" Bad stream encryption key length of %d", len(keyBuf)) continue } // Decode the buffer before proceeding. - nbuf, err := s.decryptMeta(sc, key, buf, a.Name, fi.Name()) + nbuf, err := s.decryptMeta(sc, keyBuf, buf, a.Name, fi.Name()) if err != nil { // See if we are changing ciphers. switch sc { case ChaCha: - nbuf, err = s.decryptMeta(AES, key, buf, a.Name, fi.Name()) + nbuf, err = s.decryptMeta(AES, keyBuf, buf, a.Name, fi.Name()) osc, convertingCiphers = AES, true case AES: - nbuf, err = s.decryptMeta(ChaCha, key, buf, a.Name, fi.Name()) + nbuf, err = s.decryptMeta(ChaCha, keyBuf, buf, a.Name, fi.Name()) osc, convertingCiphers = ChaCha, true } if err != nil { @@ -1234,9 +1238,6 @@ func (a *Account) EnableJetStream(limits map[string]JetStreamAccountLimits) erro } buf = nbuf plaintext = false - - // Remove the key file to have system regenerate with the new cipher. - os.Remove(keyFile) } var cfg FileStreamInfo @@ -1288,6 +1289,8 @@ func (a *Account) EnableJetStream(limits map[string]JetStreamAccountLimits) erro s.Noticef(" Encrypting stream '%s > %s'", a.Name, cfg.StreamConfig.Name) } else if convertingCiphers { s.Noticef(" Converting from %s to %s for stream '%s > %s'", osc, sc, a.Name, cfg.StreamConfig.Name) + // Remove the key file to have system regenerate with the new cipher. + os.Remove(keyFile) } } @@ -1295,6 +1298,13 @@ func (a *Account) EnableJetStream(limits map[string]JetStreamAccountLimits) erro mset, err := a.addStream(&cfg.StreamConfig) if err != nil { s.Warnf(" Error recreating stream %q: %v", cfg.Name, err) + // If we removed a keyfile from above make sure to put it back. + if convertingCiphers { + err := os.WriteFile(keyFile, keyBuf, defaultFilePerms) + if err != nil { + s.Warnf(" Error replacing meta keyfile for stream %q: %v", cfg.Name, err) + } + } continue } if !cfg.Created.IsZero() { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go b/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go index 035f8658f..3a86f5874 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go @@ -2808,12 +2808,12 @@ func (js *jetStream) applyStreamEntries(mset *stream, ce *CommittedEntry, isReco panic(err.Error()) } // Ignore if we are recovering and we have already processed. - if isRecovering { - if mset.state().FirstSeq <= sp.LastSeq { - // Make sure all messages from the purge are gone. - mset.store.Compact(sp.LastSeq + 1) + if isRecovering && (sp.Request == nil || sp.Request.Sequence == 0) { + if sp.Request == nil { + sp.Request = &JSApiStreamPurgeRequest{Sequence: sp.LastSeq} + } else { + sp.Request.Sequence = sp.LastSeq } - continue } s := js.server() @@ -3107,7 +3107,9 @@ func (js *jetStream) processStreamAssignment(sa *streamAssignment) bool { accStreams = make(map[string]*streamAssignment) } else if osa := accStreams[stream]; osa != nil && osa != sa { // Copy over private existing state from former SA. - sa.Group.node = osa.Group.node + if sa.Group != nil { + sa.Group.node = osa.Group.node + } sa.consumers = osa.consumers sa.responded = osa.responded sa.err = osa.err @@ -3198,7 +3200,9 @@ func (js *jetStream) processUpdateStreamAssignment(sa *streamAssignment) { } // Copy over private existing state from former SA. - sa.Group.node = osa.Group.node + if sa.Group != nil { + sa.Group.node = osa.Group.node + } sa.consumers = osa.consumers sa.err = osa.err @@ -3216,7 +3220,9 @@ func (js *jetStream) processUpdateStreamAssignment(sa *streamAssignment) { sa.responded = false } else { // Make sure to clean up any old node in case this stream moves back here. - sa.Group.node = nil + if sa.Group != nil { + sa.Group.node = nil + } } js.mu.Unlock() @@ -3400,6 +3406,7 @@ func (js *jetStream) processClusterCreateStream(acc *Account, sa *streamAssignme s, rg := js.srv, sa.Group alreadyRunning := rg.node != nil storage := sa.Config.Storage + restore := sa.Restore js.mu.RUnlock() // Process the raft group and make sure it's running if needed. @@ -3408,11 +3415,13 @@ func (js *jetStream) processClusterCreateStream(acc *Account, sa *streamAssignme // If we are restoring, create the stream if we are R>1 and not the preferred who handles the // receipt of the snapshot itself. shouldCreate := true - if sa.Restore != nil { + if restore != nil { if len(rg.Peers) == 1 || rg.node != nil && rg.node.ID() == rg.Preferred { shouldCreate = false } else { + js.mu.Lock() sa.Restore = nil + js.mu.Unlock() } } @@ -3784,7 +3793,9 @@ func (js *jetStream) processConsumerAssignment(ca *consumerAssignment) { } else if oca := sa.consumers[ca.Name]; oca != nil { wasExisting = true // Copy over private existing state from former SA. - ca.Group.node = oca.Group.node + if ca.Group != nil { + ca.Group.node = oca.Group.node + } ca.responded = oca.responded ca.err = oca.err } @@ -3899,8 +3910,12 @@ func (js *jetStream) processConsumerRemoval(ca *consumerAssignment) { var needDelete bool if accStreams := cc.streams[ca.Client.serviceAccount()]; accStreams != nil { if sa := accStreams[ca.Stream]; sa != nil && sa.consumers != nil && sa.consumers[ca.Name] != nil { - needDelete = true - delete(sa.consumers, ca.Name) + oca := sa.consumers[ca.Name] + // Make sure this removal is for what we have, otherwise ignore. + if ca.Group != nil && oca.Group != nil && ca.Group.Name == oca.Group.Name { + needDelete = true + delete(sa.consumers, ca.Name) + } } } js.mu.Unlock() diff --git a/vendor/github.com/nats-io/nats-server/v2/server/leafnode.go b/vendor/github.com/nats-io/nats-server/v2/server/leafnode.go index 2c727adff..05d94db24 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/leafnode.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/leafnode.go @@ -1422,7 +1422,7 @@ func (s *Server) addLeafNodeConnection(c *client, srvName, clusterName string, c } // If we have a specified JetStream domain we will want to add a mapping to // allow access cross domain for each non-system account. - if opts.JetStreamDomain != _EMPTY_ && acc != sysAcc && opts.JetStream { + if opts.JetStreamDomain != _EMPTY_ && opts.JetStream && acc != nil && acc != sysAcc { for src, dest := range generateJSMappingTable(opts.JetStreamDomain) { if err := acc.AddMapping(src, dest); err != nil { c.Debugf("Error adding JetStream domain mapping: %s", err.Error()) diff --git a/vendor/github.com/nats-io/nats-server/v2/server/memstore.go b/vendor/github.com/nats-io/nats-server/v2/server/memstore.go index e6f13f046..0b71e9069 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/memstore.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/memstore.go @@ -614,10 +614,6 @@ func (ms *memStore) expireMsgs() { // PurgeEx will remove messages based on subject filters, sequence and number of messages to keep. // Will return the number of purged messages. func (ms *memStore) PurgeEx(subject string, sequence, keep uint64) (purged uint64, err error) { - if sequence > 1 && keep > 0 { - return 0, ErrPurgeArgMismatch - } - if subject == _EMPTY_ || subject == fwcs { if keep == 0 && (sequence == 0 || sequence == 1) { return ms.Purge() @@ -868,6 +864,10 @@ func (ms *memStore) LoadLastMsg(subject string, smp *StoreMsg) (*StoreMsg, error if subject == _EMPTY_ || subject == fwcs { sm, ok = ms.msgs[ms.state.LastSeq] + } else if subjectIsLiteral(subject) { + if ss := ms.fss[subject]; ss != nil && ss.Msgs > 0 { + sm, ok = ms.msgs[ss.Last] + } } else if ss := ms.filteredStateLocked(1, subject, true); ss.Msgs > 0 { sm, ok = ms.msgs[ss.Last] } @@ -899,8 +899,8 @@ func (ms *memStore) LoadNextMsg(filter string, wc bool, start uint64, smp *Store isAll := filter == _EMPTY_ || filter == fwcs - // Skip scan of mb.fss is number of messages in the block are less than - // 1/2 the number of subjects in mb.fss. Or we have a wc and lots of fss entries. + // Skip scan of ms.fss is number of messages in the block are less than + // 1/2 the number of subjects in ms.fss. Or we have a wc and lots of fss entries. const linearScanMaxFSS = 256 doLinearScan := isAll || 2*int(ms.state.LastSeq-start) < len(ms.fss) || (wc && len(ms.fss) > linearScanMaxFSS) diff --git a/vendor/github.com/nats-io/nats-server/v2/server/raft.go b/vendor/github.com/nats-io/nats-server/v2/server/raft.go index 1d6b2d621..e625c47b2 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/raft.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/raft.go @@ -1474,12 +1474,6 @@ func (n *raft) Peers() []*Peer { // Update our known set of peers. func (n *raft) UpdateKnownPeers(knownPeers []string) { n.Lock() - // If this is a scale up, let the normal add peer logic take precedence. - // Otherwise if the new peers are slow to start we stall ourselves. - if len(knownPeers) > len(n.peers) { - n.Unlock() - return - } // Process like peer state update. ps := &peerState{knownPeers, len(knownPeers), n.extSt} n.processPeerState(ps) @@ -1816,9 +1810,11 @@ func (n *raft) runAsFollower() { } else if n.isCatchingUp() { n.debug("Not switching to candidate, catching up") // Check to see if our catchup has stalled. + n.Lock() if n.catchupStalled() { n.cancelCatchup() } + n.Unlock() } else { n.switchToCandidate() return diff --git a/vendor/github.com/nats-io/nats-server/v2/server/route.go b/vendor/github.com/nats-io/nats-server/v2/server/route.go index 9f9dba7eb..90da6b1e3 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/route.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/route.go @@ -1949,12 +1949,12 @@ func (c *client) processRouteConnect(srv *Server, arg []byte, lang string) error c.closeConnection(WrongGateway) return ErrWrongGateway } - var perms *RoutePermissions - //TODO this check indicates srv may be nil. see srv usage below - if srv != nil { - perms = srv.getOpts().Cluster.Permissions + + if srv == nil { + return ErrServerNotRunning } + perms := srv.getOpts().Cluster.Permissions clusterName := srv.ClusterName() // If we have a cluster name set, make sure it matches ours. diff --git a/vendor/github.com/nats-io/nats-server/v2/server/server.go b/vendor/github.com/nats-io/nats-server/v2/server/server.go index e9bc025d8..ec6239900 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/server.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/server.go @@ -3560,7 +3560,10 @@ func (s *Server) lameDuckMode() { numClients := int64(len(s.clients)) batch := 1 // Sleep interval between each client connection close. - si := dur / numClients + var si int64 + if numClients != 0 { + si = dur / numClients + } if si < 1 { // Should not happen (except in test with very small LD duration), but // if there are too many clients, batch the number of close and diff --git a/vendor/github.com/nats-io/nats-server/v2/server/store.go b/vendor/github.com/nats-io/nats-server/v2/server/store.go index a08e92c6b..9a1b4792d 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/store.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/store.go @@ -61,8 +61,6 @@ var ( ErrInvalidSequence = errors.New("invalid sequence") // ErrSequenceMismatch is returned when storing a raw message and the expected sequence is wrong. ErrSequenceMismatch = errors.New("expected sequence does not match store") - // ErrPurgeArgMismatch is returned when PurgeEx is called with sequence > 1 and keep > 0. - ErrPurgeArgMismatch = errors.New("sequence > 1 && keep > 0 not allowed") ) // StoreMsg is the stored message format for messages that are retained by the Store layer. diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stream.go b/vendor/github.com/nats-io/nats-server/v2/server/stream.go index 9a3355d78..81c13e659 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/stream.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/stream.go @@ -1671,7 +1671,7 @@ func (mset *stream) purge(preq *JSApiStreamPurgeRequest) (purged uint64, err err mset.mu.RUnlock() return 0, errors.New("sealed stream") } - store := mset.store + store, mlseq := mset.store, mset.lseq mset.mu.RUnlock() if preq != nil { @@ -1683,11 +1683,17 @@ func (mset *stream) purge(preq *JSApiStreamPurgeRequest) (purged uint64, err err return purged, err } - // Purge consumers. + // Grab our stream state. var state StreamState store.FastState(&state) fseq, lseq := state.FirstSeq, state.LastSeq + // Check if our last has moved past what our original last sequence was, if so reset. + if lseq > mlseq { + mset.setLastSeq(lseq) + } + + // Purge consumers. // Check for filtered purge. if preq != nil && preq.Subject != _EMPTY_ { ss := store.FilteredState(state.FirstSeq, preq.Subject) @@ -1788,7 +1794,9 @@ func gatherSourceMirrorSubjects(subjects []string, cfg *StreamConfig, acc *Accou // Return the subjects for a stream source. func (a *Account) streamSourceSubjects(ss *StreamSource, seen map[string]bool) (subjects []string, hasExt bool) { - if ss != nil && ss.External != nil { + if ss == nil { + return nil, false + } else if ss.External != nil { return nil, true } @@ -2397,7 +2405,14 @@ func (mset *stream) setupMirrorConsumer() error { // Check if we need to skip messages. if state.LastSeq != ccr.ConsumerInfo.Delivered.Stream { - mset.skipMsgs(state.LastSeq+1, ccr.ConsumerInfo.Delivered.Stream) + // Check to see if delivered is past our last and we have no msgs. This will help the + // case when mirroring a stream that has a very high starting sequence number. + if state.Msgs == 0 && ccr.ConsumerInfo.Delivered.Stream > state.LastSeq { + mset.store.PurgeEx(_EMPTY_, ccr.ConsumerInfo.Delivered.Stream+1, 0) + mset.lseq = ccr.ConsumerInfo.Delivered.Stream + } else { + mset.skipMsgs(state.LastSeq+1, ccr.ConsumerInfo.Delivered.Stream) + } } // Capture consumer name. @@ -4490,7 +4505,9 @@ func (mset *stream) resetAndWaitOnConsumers() { } node.Delete() } - o.monitorWg.Wait() + if o.isMonitorRunning() { + o.monitorWg.Wait() + } } } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/sublist.go b/vendor/github.com/nats-io/nats-server/v2/server/sublist.go index 47d45999f..48375b6b4 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/sublist.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/sublist.go @@ -615,7 +615,7 @@ func (s *Sublist) reduceCacheCount() { // Helper function for auto-expanding remote qsubs. func isRemoteQSub(sub *subscription) bool { - return sub != nil && sub.queue != nil && sub.client != nil && sub.client.kind == ROUTER + return sub != nil && sub.queue != nil && sub.client != nil && (sub.client.kind == ROUTER || sub.client.kind == LEAF) } // UpdateRemoteQSub should be called when we update the weight of an existing diff --git a/vendor/github.com/nats-io/nats.go/.golangci.yaml b/vendor/github.com/nats-io/nats.go/.golangci.yaml new file mode 100644 index 000000000..5bbb3e7be --- /dev/null +++ b/vendor/github.com/nats-io/nats.go/.golangci.yaml @@ -0,0 +1,10 @@ +issues: + max-issues-per-linter: 0 + max-same-issues: 0 + exclude-rules: + - linters: + - errcheck + text: "Unsubscribe" + - linters: + - errcheck + text: "msg.Ack" diff --git a/vendor/github.com/nats-io/nats.go/.travis.yml b/vendor/github.com/nats-io/nats.go/.travis.yml index 7268558a1..a5f06f459 100644 --- a/vendor/github.com/nats-io/nats.go/.travis.yml +++ b/vendor/github.com/nats-io/nats.go/.travis.yml @@ -5,6 +5,7 @@ go: go_import_path: github.com/nats-io/nats.go install: - go get -t ./... +- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin - if [[ "$TRAVIS_GO_VERSION" =~ 1.20 ]]; then go install github.com/mattn/goveralls@latest; go install github.com/wadey/gocovmerge@latest; @@ -18,6 +19,7 @@ before_script: find . -type f -name "*.go" | xargs misspell -error -locale US; GOFLAGS="-mod=mod -modfile=go_test.mod" staticcheck ./...; fi +- golangci-lint run ./jetstream/... script: - go test -modfile=go_test.mod -v -run=TestNoRace -p=1 ./... --failfast -vet=off - if [[ "$TRAVIS_GO_VERSION" =~ 1.20 ]]; then ./scripts/cov.sh TRAVIS; else go test -modfile=go_test.mod -race -v -p=1 ./... --failfast -vet=off; fi diff --git a/vendor/github.com/nats-io/nats.go/README.md b/vendor/github.com/nats-io/nats.go/README.md index c65ae777a..02e3d4efd 100644 --- a/vendor/github.com/nats-io/nats.go/README.md +++ b/vendor/github.com/nats-io/nats.go/README.md @@ -29,7 +29,7 @@ When using or transitioning to Go modules support: ```bash # Go client latest or explicit version go get github.com/nats-io/nats.go/@latest -go get github.com/nats-io/nats.go/@v1.24.0 +go get github.com/nats-io/nats.go/@v1.27.0 # For latest NATS Server, add /v2 at the end go get github.com/nats-io/nats-server/v2 @@ -92,6 +92,13 @@ nc.Close() ## JetStream Basic Usage +> __NOTE__ +> +> We encourage you to try out a new, simplified version on JetStream API. +> The new API is currently in preview and is available under `jetstream` package. +> +> You can find more information on the new API [here](https://github.com/nats-io/nats.go/blob/main/jetstream/README.md) + ```go import "github.com/nats-io/nats.go" diff --git a/vendor/github.com/nats-io/nats.go/context.go b/vendor/github.com/nats-io/nats.go/context.go index 39f8e5d0f..8c3c45478 100644 --- a/vendor/github.com/nats-io/nats.go/context.go +++ b/vendor/github.com/nats-io/nats.go/context.go @@ -136,9 +136,8 @@ func (s *Subscription) nextMsgWithContext(ctx context.Context, pullSubInternal, } if err := s.processNextMsgDelivered(msg); err != nil { return nil, err - } else { - return msg, nil } + return msg, nil default: // If internal and we don't want to wait, signal that there is no // message in the internal queue. diff --git a/vendor/github.com/nats-io/nats.go/enc.go b/vendor/github.com/nats-io/nats.go/enc.go index 3af20d265..450fa76c8 100644 --- a/vendor/github.com/nats-io/nats.go/enc.go +++ b/vendor/github.com/nats-io/nats.go/enc.go @@ -265,5 +265,5 @@ func (c *EncodedConn) Drain() error { // LastError reports the last error encountered via the Connection. func (c *EncodedConn) LastError() error { - return c.Conn.err + return c.Conn.LastError() } diff --git a/vendor/github.com/nats-io/nats.go/go_test.mod b/vendor/github.com/nats-io/nats.go/go_test.mod index b84703606..ebdf14346 100644 --- a/vendor/github.com/nats-io/nats.go/go_test.mod +++ b/vendor/github.com/nats-io/nats.go/go_test.mod @@ -4,17 +4,19 @@ go 1.19 require ( github.com/golang/protobuf v1.4.2 - github.com/nats-io/nats-server/v2 v2.9.6 - github.com/nats-io/nkeys v0.3.0 + github.com/klauspost/compress v1.16.5 + github.com/nats-io/nats-server/v2 v2.9.16 + github.com/nats-io/nkeys v0.4.4 github.com/nats-io/nuid v1.0.1 + go.uber.org/goleak v1.2.1 + golang.org/x/text v0.9.0 google.golang.org/protobuf v1.23.0 ) require ( - github.com/klauspost/compress v1.15.11 // indirect github.com/minio/highwayhash v1.0.2 // indirect - github.com/nats-io/jwt/v2 v2.3.0 // indirect - golang.org/x/crypto v0.5.0 // indirect - golang.org/x/sys v0.4.0 // indirect - golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect + github.com/nats-io/jwt/v2 v2.4.1 // indirect + golang.org/x/crypto v0.8.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/time v0.3.0 // indirect ) diff --git a/vendor/github.com/nats-io/nats.go/go_test.sum b/vendor/github.com/nats-io/nats.go/go_test.sum index 1662e18a7..e1cf0d614 100644 --- a/vendor/github.com/nats-io/nats.go/go_test.sum +++ b/vendor/github.com/nats-io/nats.go/go_test.sum @@ -1,3 +1,4 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -9,31 +10,31 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= -github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= +github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= -github.com/nats-io/jwt/v2 v2.3.0 h1:z2mA1a7tIf5ShggOFlR1oBPgd6hGqcDYsISxZByUzdI= -github.com/nats-io/jwt/v2 v2.3.0/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= -github.com/nats-io/nats-server/v2 v2.9.6 h1:RTtK+rv/4CcliOuqGsy58g7MuWkBaWmF5TUNwuUo9Uw= -github.com/nats-io/nats-server/v2 v2.9.6/go.mod h1:AB6hAnGZDlYfqb7CTAm66ZKMZy9DpfierY1/PbpvI2g= -github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= -github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= +github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4= +github.com/nats-io/jwt/v2 v2.4.1/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI= +github.com/nats-io/nats-server/v2 v2.9.16 h1:SuNe6AyCcVy0g5326wtyU8TdqYmcPqzTjhkHojAjprc= +github.com/nats-io/nats-server/v2 v2.9.16/go.mod h1:z1cc5Q+kqJkz9mLUdlcSsdYnId4pyImHjNgoh6zxSC0= +github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA= +github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y= -golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -43,3 +44,4 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/vendor/github.com/nats-io/nats.go/internal/parser/parse.go b/vendor/github.com/nats-io/nats.go/internal/parser/parse.go new file mode 100644 index 000000000..7eab8addd --- /dev/null +++ b/vendor/github.com/nats-io/nats.go/internal/parser/parse.go @@ -0,0 +1,104 @@ +// Copyright 2020-2022 The NATS 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 parser + +import ( + "errors" + "fmt" +) + +const ( + AckDomainTokenPos = iota + 2 + AckAccHashTokenPos + AckStreamTokenPos + AckConsumerTokenPos + AckNumDeliveredTokenPos + AckStreamSeqTokenPos + AckConsumerSeqTokenPos + AckTimestampSeqTokenPos + AckNumPendingTokenPos +) + +var ErrInvalidSubjectFormat = errors.New("invalid format of ACK subject") + +// Quick parser for positive numbers in ack reply encoding. +// NOTE: This parser does not detect uint64 overflow +func ParseNum(d string) (n uint64) { + if len(d) == 0 { + return 0 + } + + // ASCII numbers 0-9 + const ( + asciiZero = 48 + asciiNine = 57 + ) + + for _, dec := range d { + if dec < asciiZero || dec > asciiNine { + return 0 + } + n = n*10 + uint64(dec) - asciiZero + } + return +} + +func GetMetadataFields(subject string) ([]string, error) { + v1TokenCounts, v2TokenCounts := 9, 12 + + var start int + tokens := make([]string, 0, v2TokenCounts) + for i := 0; i < len(subject); i++ { + if subject[i] == '.' { + tokens = append(tokens, subject[start:i]) + start = i + 1 + } + } + tokens = append(tokens, subject[start:]) + // + // Newer server will include the domain name and account hash in the subject, + // and a token at the end. + // + // Old subject was: + // $JS.ACK....... + // + // New subject would be: + // $JS.ACK.......... + // + // v1 has 9 tokens, v2 has 12, but we must not be strict on the 12th since + // it may be removed in the future. Also, the library has no use for it. + // The point is that a v2 ACK subject is valid if it has at least 11 tokens. + // + tokensLen := len(tokens) + // If lower than 9 or more than 9 but less than 11, report an error + if tokensLen < v1TokenCounts || (tokensLen > v1TokenCounts && tokensLen < v2TokenCounts-1) { + return nil, ErrInvalidSubjectFormat + } + if tokens[0] != "$JS" || tokens[1] != "ACK" { + return nil, fmt.Errorf("%w: subject should start with $JS.ACK", ErrInvalidSubjectFormat) + } + // For v1 style, we insert 2 empty tokens (domain and hash) so that the + // rest of the library references known fields at a constant location. + if tokensLen == v1TokenCounts { + // Extend the array (we know the backend is big enough) + tokens = append(tokens[:AckDomainTokenPos+2], tokens[AckDomainTokenPos:]...) + // Clear the domain and hash tokens + tokens[AckDomainTokenPos], tokens[AckAccHashTokenPos] = "", "" + + } else if tokens[AckDomainTokenPos] == "_" { + // If domain is "_", replace with empty value. + tokens[AckDomainTokenPos] = "" + } + return tokens, nil +} diff --git a/vendor/github.com/nats-io/nats.go/js.go b/vendor/github.com/nats-io/nats.go/js.go index c1be7627d..76a55d468 100644 --- a/vendor/github.com/nats-io/nats.go/js.go +++ b/vendor/github.com/nats-io/nats.go/js.go @@ -27,6 +27,7 @@ import ( "sync/atomic" "time" + "github.com/nats-io/nats.go/internal/parser" "github.com/nats-io/nuid" ) @@ -678,6 +679,15 @@ func (js *js) newAsyncReply() string { return sb.String() } +func (js *js) cleanupReplySub() { + js.mu.Lock() + if js.rsub != nil { + js.rsub.Unsubscribe() + js.rsub = nil + } + js.mu.Unlock() +} + // registerPAF will register for a PubAckFuture. func (js *js) registerPAF(id string, paf *pubAckFuture) (int, int) { js.mu.Lock() @@ -1468,6 +1478,7 @@ func (js *js) subscribe(subj, queue string, cb MsgHandler, ch chan *Msg, isSync, isDurable = o.cfg.Durable != _EMPTY_ consumerBound = o.bound ctx = o.ctx + skipCInfo = o.skipCInfo notFoundErr bool lookupErr bool nc = js.nc @@ -1541,8 +1552,8 @@ func (js *js) subscribe(subj, queue string, cb MsgHandler, ch chan *Msg, isSync, // With an explicit durable name, we can lookup the consumer first // to which it should be attaching to. - // If bind to ordered consumer is true, skip the lookup. - if consumer != _EMPTY_ { + // If SkipConsumerLookup was used, do not call consumer info. + if consumer != _EMPTY_ && !o.skipCInfo { info, err = js.ConsumerInfo(stream, consumer) notFoundErr = errors.Is(err, ErrConsumerNotFound) lookupErr = err == ErrJetStreamNotEnabled || err == ErrTimeout || err == context.DeadlineExceeded @@ -1563,6 +1574,19 @@ func (js *js) subscribe(subj, queue string, cb MsgHandler, ch chan *Msg, isSync, if !(isPullMode && lookupErr && consumerBound) { return nil, err } + case skipCInfo: + // When skipping consumer info, need to rely on the manually passed sub options + // to match the expected behavior from the subscription. + hasFC, hbi = o.cfg.FlowControl, o.cfg.Heartbeat + hasHeartbeats = hbi > 0 + maxap = o.cfg.MaxAckPending + deliver = o.cfg.DeliverSubject + if consumerBound { + break + } + + // When not bound to a consumer already, proceed to create. + fallthrough default: // Attempt to create consumer if not found nor using Bind. shouldCreate = true @@ -1572,7 +1596,6 @@ func (js *js) subscribe(subj, queue string, cb MsgHandler, ch chan *Msg, isSync, deliver = nc.NewInbox() cfg.DeliverSubject = deliver } - // Do filtering always, server will clear as needed. cfg.FilterSubject = subj @@ -1610,6 +1633,8 @@ func (js *js) subscribe(subj, queue string, cb MsgHandler, ch chan *Msg, isSync, if isPullMode { nms = fmt.Sprintf(js.apiSubj(apiRequestNextT), stream, consumer) deliver = nc.NewInbox() + // for pull consumers, create a wildcard subscription to differentiate pull requests + deliver += ".*" } // In case this has a context, then create a child context that @@ -1657,8 +1682,14 @@ func (js *js) subscribe(subj, queue string, cb MsgHandler, ch chan *Msg, isSync, } // If we are creating or updating let's process that request. + consName := o.cfg.Name if shouldCreate { - info, err := js.upsertConsumer(stream, cfg.Durable, ccreq.Config) + if cfg.Durable != "" { + consName = cfg.Durable + } else if consName == "" { + consName = getHash(nuid.Next()) + } + info, err := js.upsertConsumer(stream, consName, ccreq.Config) if err != nil { var apiErr *APIError if ok := errors.As(err, &apiErr); !ok { @@ -1853,11 +1884,11 @@ func (sub *Subscription) checkOrderedMsgs(m *Msg) bool { } // Normal message here. - tokens, err := getMetadataFields(m.Reply) + tokens, err := parser.GetMetadataFields(m.Reply) if err != nil { return false } - sseq, dseq := uint64(parseNum(tokens[ackStreamSeqTokenPos])), uint64(parseNum(tokens[ackConsumerSeqTokenPos])) + sseq, dseq := parser.ParseNum(tokens[ackStreamSeqTokenPos]), parser.ParseNum(tokens[ackConsumerSeqTokenPos]) jsi := sub.jsi if dseq != jsi.dseq { @@ -1961,40 +1992,22 @@ func (sub *Subscription) resetOrderedConsumer(sseq uint64) { cfg.DeliverPolicy = DeliverByStartSequencePolicy cfg.OptStartSeq = sseq - ccSubj := fmt.Sprintf(apiLegacyConsumerCreateT, jsi.stream) - j, err := json.Marshal(jsi.ccreq) js := jsi.js sub.mu.Unlock() + consName := nuid.Next() + cinfo, err := js.upsertConsumer(jsi.stream, consName, cfg) if err != nil { - pushErr(err) - return - } - - resp, err := nc.Request(js.apiSubj(ccSubj), j, js.opts.wait) - if err != nil { - if errors.Is(err, ErrNoResponders) || errors.Is(err, ErrTimeout) { + var apiErr *APIError + if errors.Is(err, ErrJetStreamNotEnabled) || errors.Is(err, ErrTimeout) { // if creating consumer failed, retry return - } - pushErr(err) - return - } - - var cinfo consumerResponse - err = json.Unmarshal(resp.Data, &cinfo) - if err != nil { - pushErr(err) - return - } - - if cinfo.Error != nil { - if cinfo.Error.ErrorCode == JSErrCodeInsufficientResourcesErr { + } else if errors.As(err, &apiErr) && apiErr.ErrorCode == JSErrCodeInsufficientResourcesErr { // retry for insufficient resources, as it may mean that client is connected to a running // server in cluster while the server hosting R1 JetStream resources is restarting return } - pushErr(cinfo.Error) + pushErr(err) return } @@ -2107,7 +2120,7 @@ func (nc *Conn) checkForSequenceMismatch(msg *Msg, s *Subscription, jsi *jsSub) return } - tokens, err := getMetadataFields(ctrl) + tokens, err := parser.GetMetadataFields(ctrl) if err != nil { return } @@ -2125,7 +2138,7 @@ func (nc *Conn) checkForSequenceMismatch(msg *Msg, s *Subscription, jsi *jsSub) if ldseq != dseq { // Dispatch async error including details such as // from where the consumer could be restarted. - sseq := parseNum(tokens[ackStreamSeqTokenPos]) + sseq := parser.ParseNum(tokens[ackStreamSeqTokenPos]) if ordered { s.mu.Lock() s.resetOrderedConsumer(jsi.sseq + 1) @@ -2133,8 +2146,8 @@ func (nc *Conn) checkForSequenceMismatch(msg *Msg, s *Subscription, jsi *jsSub) } else { ecs := &ErrConsumerSequenceMismatch{ StreamResumeSequence: uint64(sseq), - ConsumerSequence: uint64(parseNum(dseq)), - LastConsumerSequence: uint64(parseNum(ldseq)), + ConsumerSequence: parser.ParseNum(dseq), + LastConsumerSequence: parser.ParseNum(ldseq), } nc.handleConsumerSequenceMismatch(s, ecs) } @@ -2163,6 +2176,22 @@ type subOpts struct { // For an ordered consumer. ordered bool ctx context.Context + + // To disable calling ConsumerInfo + skipCInfo bool +} + +// SkipConsumerLookup will omit lookipng up consumer when [Bind], [Durable] +// or [ConsumerName] are provided. +// +// NOTE: This setting may cause an existing consumer to be overwritten. Also, +// because consumer lookup is skipped, all consumer options like AckPolicy, +// DeliverSubject etc. need to be provided even if consumer already exists. +func SkipConsumerLookup() SubOpt { + return subOptFn(func(opts *subOpts) error { + opts.skipCInfo = true + return nil + }) } // OrderedConsumer will create a FIFO direct/ephemeral consumer for in order delivery of messages. @@ -2192,7 +2221,7 @@ func Description(description string) SubOpt { } // Durable defines the consumer name for JetStream durable subscribers. -// This function will return ErrInvalidConsumerName in the name contains +// This function will return ErrInvalidConsumerName if the name contains // any dot ".". func Durable(consumer string) SubOpt { return subOptFn(func(opts *subOpts) error { @@ -2486,6 +2515,14 @@ func ConsumerMemoryStorage() SubOpt { }) } +// ConsumerName sets the name for a consumer. +func ConsumerName(name string) SubOpt { + return subOptFn(func(opts *subOpts) error { + opts.cfg.Name = name + return nil + }) +} + func (sub *Subscription) ConsumerInfo() (*ConsumerInfo, error) { sub.mu.Lock() // TODO(dlc) - Better way to mark especially if we attach. @@ -2583,12 +2620,12 @@ func checkMsg(msg *Msg, checkSts, isNoWait bool) (usrMsg bool, err error) { err = ErrTimeout } case jetStream409Sts: - if strings.Contains(strings.ToLower(string(msg.Header.Get(descrHdr))), "consumer deleted") { + if strings.Contains(strings.ToLower(msg.Header.Get(descrHdr)), "consumer deleted") { err = ErrConsumerDeleted break } - if strings.Contains(strings.ToLower(string(msg.Header.Get(descrHdr))), "leadership change") { + if strings.Contains(strings.ToLower(msg.Header.Get(descrHdr)), "leadership change") { err = ErrConsumerLeadershipChanged break } @@ -2629,7 +2666,7 @@ func (sub *Subscription) Fetch(batch int, opts ...PullOpt) ([]*Msg, error) { nc := sub.conn nms := sub.jsi.nms - rply := sub.jsi.deliver + rply, _ := newFetchInbox(jsi.deliver) js := sub.jsi.js pmc := len(sub.mch) > 0 @@ -2753,7 +2790,7 @@ func (sub *Subscription) Fetch(batch int, opts ...PullOpt) ([]*Msg, error) { err = sendReq() } else if err == ErrTimeout && len(msgs) == 0 { // If we get a 408, we will bail if we already collected some - // messages, otherwise ignore and go back calling NextMsg. + // messages, otherwise ignore and go back calling nextMsg. err = nil } } @@ -2766,6 +2803,28 @@ func (sub *Subscription) Fetch(batch int, opts ...PullOpt) ([]*Msg, error) { return msgs, nil } +// newFetchInbox returns subject used as reply subject when sending pull requests +// as well as request ID. For non-wildcard subject, request ID is empty and +// passed subject is not transformed +func newFetchInbox(subj string) (string, string) { + if !strings.HasSuffix(subj, ".*") { + return subj, "" + } + reqID := nuid.Next() + var sb strings.Builder + sb.WriteString(subj[:len(subj)-1]) + sb.WriteString(reqID) + return sb.String(), reqID +} + +func subjectMatchesReqID(subject, reqID string) bool { + subjectParts := strings.Split(subject, ".") + if len(subjectParts) < 2 { + return false + } + return subjectParts[len(subjectParts)-1] == reqID +} + // MessageBatch provides methods to retrieve messages consumed using [Subscribe.FetchBatch]. type MessageBatch interface { // Messages returns a channel on which messages will be published. @@ -2835,7 +2894,7 @@ func (sub *Subscription) FetchBatch(batch int, opts ...PullOpt) (MessageBatch, e nc := sub.conn nms := sub.jsi.nms - rply := sub.jsi.deliver + rply, reqID := newFetchInbox(sub.jsi.deliver) js := sub.jsi.js pmc := len(sub.mch) > 0 @@ -2964,6 +3023,10 @@ func (sub *Subscription) FetchBatch(batch int, opts ...PullOpt) (MessageBatch, e usrMsg, err = checkMsg(msg, true, false) if err != nil { if err == ErrTimeout { + if reqID != "" && !subjectMatchesReqID(msg.Subject, reqID) { + // ignore timeout message from server if it comes from a different pull request + continue + } err = nil } break @@ -3199,60 +3262,6 @@ const ( ackNumPendingTokenPos = 10 ) -func getMetadataFields(subject string) ([]string, error) { - const v1TokenCounts = 9 - const v2TokenCounts = 12 - const noDomainName = "_" - - const btsep = '.' - tsa := [v2TokenCounts]string{} - start, tokens := 0, tsa[:0] - for i := 0; i < len(subject); i++ { - if subject[i] == btsep { - tokens = append(tokens, subject[start:i]) - start = i + 1 - } - } - tokens = append(tokens, subject[start:]) - // - // Newer server will include the domain name and account hash in the subject, - // and a token at the end. - // - // Old subject was: - // $JS.ACK....... - // - // New subject would be: - // $JS.ACK.......... - // - // v1 has 9 tokens, v2 has 12, but we must not be strict on the 12th since - // it may be removed in the future. Also, the library has no use for it. - // The point is that a v2 ACK subject is valid if it has at least 11 tokens. - // - l := len(tokens) - // If lower than 9 or more than 9 but less than 11, report an error - if l < v1TokenCounts || (l > v1TokenCounts && l < v2TokenCounts-1) { - return nil, ErrNotJSMessage - } - if tokens[0] != "$JS" || tokens[1] != "ACK" { - return nil, ErrNotJSMessage - } - // For v1 style, we insert 2 empty tokens (domain and hash) so that the - // rest of the library references known fields at a constant location. - if l == 9 { - // Extend the array (we know the backend is big enough) - tokens = append(tokens, _EMPTY_, _EMPTY_) - // Move to the right anything that is after "ACK" token. - copy(tokens[ackDomainTokenPos+2:], tokens[ackDomainTokenPos:]) - // Clear the domain and hash tokens - tokens[ackDomainTokenPos], tokens[ackAccHashTokenPos] = _EMPTY_, _EMPTY_ - - } else if tokens[ackDomainTokenPos] == noDomainName { - // If domain is "_", replace with empty value. - tokens[ackDomainTokenPos] = _EMPTY_ - } - return tokens, nil -} - // Metadata retrieves the metadata from a JetStream message. This method will // return an error for non-JetStream Msgs. func (m *Msg) Metadata() (*MsgMetadata, error) { @@ -3260,45 +3269,24 @@ func (m *Msg) Metadata() (*MsgMetadata, error) { return nil, err } - tokens, err := getMetadataFields(m.Reply) + tokens, err := parser.GetMetadataFields(m.Reply) if err != nil { return nil, err } meta := &MsgMetadata{ Domain: tokens[ackDomainTokenPos], - NumDelivered: uint64(parseNum(tokens[ackNumDeliveredTokenPos])), - NumPending: uint64(parseNum(tokens[ackNumPendingTokenPos])), - Timestamp: time.Unix(0, parseNum(tokens[ackTimestampSeqTokenPos])), + NumDelivered: parser.ParseNum(tokens[ackNumDeliveredTokenPos]), + NumPending: parser.ParseNum(tokens[ackNumPendingTokenPos]), + Timestamp: time.Unix(0, int64(parser.ParseNum(tokens[ackTimestampSeqTokenPos]))), Stream: tokens[ackStreamTokenPos], Consumer: tokens[ackConsumerTokenPos], } - meta.Sequence.Stream = uint64(parseNum(tokens[ackStreamSeqTokenPos])) - meta.Sequence.Consumer = uint64(parseNum(tokens[ackConsumerSeqTokenPos])) + meta.Sequence.Stream = parser.ParseNum(tokens[ackStreamSeqTokenPos]) + meta.Sequence.Consumer = parser.ParseNum(tokens[ackConsumerSeqTokenPos]) return meta, nil } -// Quick parser for positive numbers in ack reply encoding. -func parseNum(d string) (n int64) { - if len(d) == 0 { - return -1 - } - - // ASCII numbers 0-9 - const ( - asciiZero = 48 - asciiNine = 57 - ) - - for _, dec := range d { - if dec < asciiZero || dec > asciiNine { - return -1 - } - n = n*10 + (int64(dec) - asciiZero) - } - return n -} - // AckPolicy determines how the consumer should acknowledge delivered messages. type AckPolicy int @@ -3632,3 +3620,17 @@ func (st *StorageType) UnmarshalJSON(data []byte) error { } return nil } + +// Length of our hash used for named consumers. +const nameHashLen = 8 + +// Computes a hash for the given `name`. +func getHash(name string) string { + sha := sha256.New() + sha.Write([]byte(name)) + b := sha.Sum(nil) + for i := 0; i < nameHashLen; i++ { + b[i] = rdigits[int(b[i]%base)] + } + return string(b[:nameHashLen]) +} diff --git a/vendor/github.com/nats-io/nats.go/jsm.go b/vendor/github.com/nats-io/nats.go/jsm.go index fbf85f5f6..a2b68417b 100644 --- a/vendor/github.com/nats-io/nats.go/jsm.go +++ b/vendor/github.com/nats-io/nats.go/jsm.go @@ -1110,7 +1110,7 @@ func (js *js) getMsg(name string, mreq *apiMsgGetRequest, opts ...JSOpt) (*RawSt var hdr Header if len(msg.Header) > 0 { - hdr, err = decodeHeadersMsg(msg.Header) + hdr, err = DecodeHeadersMsg(msg.Header) if err != nil { return nil, err } diff --git a/vendor/github.com/nats-io/nats.go/kv.go b/vendor/github.com/nats-io/nats.go/kv.go index 5f416bc52..9ad1b22d2 100644 --- a/vendor/github.com/nats-io/nats.go/kv.go +++ b/vendor/github.com/nats-io/nats.go/kv.go @@ -23,6 +23,8 @@ import ( "strings" "sync" "time" + + "github.com/nats-io/nats.go/internal/parser" ) // KeyValueManager is used to manage KeyValue stores. @@ -889,7 +891,7 @@ func (kv *kvs) Watch(keys string, opts ...WatchOpt) (KeyWatcher, error) { w := &watcher{updates: make(chan KeyValueEntry, 256), ctx: o.ctx} update := func(m *Msg) { - tokens, err := getMetadataFields(m.Reply) + tokens, err := parser.GetMetadataFields(m.Reply) if err != nil { return } @@ -907,7 +909,7 @@ func (kv *kvs) Watch(keys string, opts ...WatchOpt) (KeyWatcher, error) { op = KeyValuePurge } } - delta := uint64(parseNum(tokens[ackNumPendingTokenPos])) + delta := parser.ParseNum(tokens[ackNumPendingTokenPos]) w.mu.Lock() defer w.mu.Unlock() if !o.ignoreDeletes || (op != KeyValueDelete && op != KeyValuePurge) { @@ -915,8 +917,8 @@ func (kv *kvs) Watch(keys string, opts ...WatchOpt) (KeyWatcher, error) { bucket: kv.name, key: subj, value: m.Data, - revision: uint64(parseNum(tokens[ackStreamSeqTokenPos])), - created: time.Unix(0, parseNum(tokens[ackTimestampSeqTokenPos])), + revision: parser.ParseNum(tokens[ackStreamSeqTokenPos]), + created: time.Unix(0, int64(parser.ParseNum(tokens[ackTimestampSeqTokenPos]))), delta: delta, op: op, } diff --git a/vendor/github.com/nats-io/nats.go/nats.go b/vendor/github.com/nats-io/nats.go/nats.go index ee53ea910..00ec369d0 100644 --- a/vendor/github.com/nats-io/nats.go/nats.go +++ b/vendor/github.com/nats-io/nats.go/nats.go @@ -47,7 +47,7 @@ import ( // Default Constants const ( - Version = "1.24.0" + Version = "1.27.0" DefaultURL = "nats://127.0.0.1:4222" DefaultPort = 4222 DefaultMaxReconnect = 60 @@ -211,6 +211,13 @@ type ErrHandler func(*Conn, *Subscription, error) // JWT for this user. type UserJWTHandler func() (string, error) +// TLSCertHandler is used to fetch and return tls certificate. +type TLSCertHandler func() (tls.Certificate, error) + +// RootCAsHandler is used to fetch and return a set of root certificate +// authorities that clients use when verifying server certificates. +type RootCAsHandler func() (*x509.CertPool, error) + // SignatureHandler is used to sign a nonce from the server while // authenticating with nkeys. The user should sign the nonce and // return the raw signature. The client will base64 encode this to @@ -299,6 +306,13 @@ type Options struct { // transports. TLSConfig *tls.Config + // TLSCertCB is used to fetch and return custom tls certificate. + TLSCertCB TLSCertHandler + + // RootCAsCB is used to fetch and return a set of root certificate + // authorities that clients use when verifying server certificates. + RootCAsCB RootCAsHandler + // AllowReconnect enables reconnection logic to be used when we // encounter a disconnect from the current server. AllowReconnect bool @@ -510,31 +524,32 @@ type Conn struct { mu sync.RWMutex // Opts holds the configuration of the Conn. // Modifying the configuration of a running Conn is a race. - Opts Options - wg sync.WaitGroup - srvPool []*srv - current *srv - urls map[string]struct{} // Keep track of all known URLs (used by processInfo) - conn net.Conn - bw *natsWriter - br *natsReader - fch chan struct{} - info serverInfo - ssid int64 - subsMu sync.RWMutex - subs map[int64]*Subscription - ach *asyncCallbacksHandler - pongs []chan struct{} - scratch [scratchSize]byte - status Status - initc bool // true if the connection is performing the initial connect - err error - ps *parseState - ptmr *time.Timer - pout int - ar bool // abort reconnect - rqch chan struct{} - ws bool // true if a websocket connection + Opts Options + wg sync.WaitGroup + srvPool []*srv + current *srv + urls map[string]struct{} // Keep track of all known URLs (used by processInfo) + conn net.Conn + bw *natsWriter + br *natsReader + fch chan struct{} + info serverInfo + ssid int64 + subsMu sync.RWMutex + subs map[int64]*Subscription + ach *asyncCallbacksHandler + pongs []chan struct{} + scratch [scratchSize]byte + status Status + statListeners map[Status][]chan Status + initc bool // true if the connection is performing the initial connect + err error + ps *parseState + ptmr *time.Timer + pout int + ar bool // abort reconnect + rqch chan struct{} + ws bool // true if a websocket connection // New style response handler respSub string // The wildcard subject @@ -672,6 +687,15 @@ func (m *Msg) Equal(msg *Msg) bool { return true } +// Size returns a message size in bytes. +func (m *Msg) Size() int { + if m.wsz != 0 { + return m.wsz + } + hdr, _ := m.headerBytes() + return len(m.Subject) + len(m.Reply) + len(hdr) + len(m.Data) +} + func (m *Msg) headerBytes() ([]byte, error) { var hdr []byte if len(m.Header) == 0 { @@ -834,21 +858,27 @@ func Secure(tls ...*tls.Config) Option { // If Secure is not already set this will set it as well. func RootCAs(file ...string) Option { return func(o *Options) error { - pool := x509.NewCertPool() - for _, f := range file { - rootPEM, err := os.ReadFile(f) - if err != nil || rootPEM == nil { - return fmt.Errorf("nats: error loading or parsing rootCA file: %w", err) - } - ok := pool.AppendCertsFromPEM(rootPEM) - if !ok { - return fmt.Errorf("nats: failed to parse root certificate from %q", f) + rootCAsCB := func() (*x509.CertPool, error) { + pool := x509.NewCertPool() + for _, f := range file { + rootPEM, err := os.ReadFile(f) + if err != nil || rootPEM == nil { + return nil, fmt.Errorf("nats: error loading or parsing rootCA file: %w", err) + } + ok := pool.AppendCertsFromPEM(rootPEM) + if !ok { + return nil, fmt.Errorf("nats: failed to parse root certificate from %q", f) + } } + return pool, nil } if o.TLSConfig == nil { o.TLSConfig = &tls.Config{MinVersion: tls.VersionTLS12} } - o.TLSConfig.RootCAs = pool + if _, err := rootCAsCB(); err != nil { + return err + } + o.RootCAsCB = rootCAsCB o.Secure = true return nil } @@ -858,18 +888,24 @@ func RootCAs(file ...string) Option { // If Secure is not already set this will set it as well. func ClientCert(certFile, keyFile string) Option { return func(o *Options) error { - cert, err := tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - return fmt.Errorf("nats: error loading client certificate: %w", err) - } - cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0]) - if err != nil { - return fmt.Errorf("nats: error parsing client certificate: %w", err) + tlsCertCB := func() (tls.Certificate, error) { + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return tls.Certificate{}, fmt.Errorf("nats: error loading client certificate: %w", err) + } + cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0]) + if err != nil { + return tls.Certificate{}, fmt.Errorf("nats: error parsing client certificate: %w", err) + } + return cert, nil } if o.TLSConfig == nil { o.TLSConfig = &tls.Config{MinVersion: tls.VersionTLS12} } - o.TLSConfig.Certificates = []tls.Certificate{cert} + if _, err := tlsCertCB(); err != nil { + return err + } + o.TLSCertCB = tlsCertCB o.Secure = true return nil } @@ -1969,11 +2005,23 @@ func (nc *Conn) makeTLSConn() error { } } // Allow the user to configure their own tls.Config structure. - var tlsCopy *tls.Config + tlsCopy := &tls.Config{} if nc.Opts.TLSConfig != nil { tlsCopy = util.CloneTLSConfig(nc.Opts.TLSConfig) - } else { - tlsCopy = &tls.Config{} + } + if nc.Opts.TLSCertCB != nil { + cert, err := nc.Opts.TLSCertCB() + if err != nil { + return err + } + tlsCopy.Certificates = []tls.Certificate{cert} + } + if nc.Opts.RootCAsCB != nil { + rootCAs, err := nc.Opts.RootCAsCB() + if err != nil { + return err + } + tlsCopy.RootCAs = rootCAs } // If its blank we will override it with the current host if tlsCopy.ServerName == _EMPTY_ { @@ -2181,7 +2229,7 @@ func (nc *Conn) processConnectInit() error { defer nc.conn.SetDeadline(time.Time{}) // Set our status to connecting. - nc.status = CONNECTING + nc.changeConnStatus(CONNECTING) // Process the INFO protocol received from the server err := nc.processExpectedInfo() @@ -2273,7 +2321,7 @@ func (nc *Conn) connect() (bool, error) { nc.initc = false } else if nc.Opts.RetryOnFailedConnect { nc.setup() - nc.status = RECONNECTING + nc.changeConnStatus(RECONNECTING) nc.bw.switchToPending() go nc.doReconnect(ErrNoServers) err = nil @@ -2466,6 +2514,9 @@ func (nc *Conn) sendConnect() error { // reading byte-by-byte here is ok. proto, err := nc.readProto() if err != nil { + if !nc.initc && nc.Opts.AsyncErrorCB != nil { + nc.ach.push(func() { nc.Opts.AsyncErrorCB(nc, nil, err) }) + } return err } @@ -2474,6 +2525,9 @@ func (nc *Conn) sendConnect() error { // Read the rest now... proto, err = nc.readProto() if err != nil { + if !nc.initc && nc.Opts.AsyncErrorCB != nil { + nc.ach.push(func() { nc.Opts.AsyncErrorCB(nc, nil, err) }) + } return err } } @@ -2507,7 +2561,7 @@ func (nc *Conn) sendConnect() error { } // This is where we are truly connected. - nc.status = CONNECTED + nc.changeConnStatus(CONNECTED) return nil } @@ -2682,7 +2736,7 @@ func (nc *Conn) doReconnect(err error) { if nc.ar { break } - nc.status = RECONNECTING + nc.changeConnStatus(RECONNECTING) continue } @@ -2700,7 +2754,7 @@ func (nc *Conn) doReconnect(err error) { // Now send off and clear pending buffer nc.err = nc.flushReconnectPendingItems() if nc.err != nil { - nc.status = RECONNECTING + nc.changeConnStatus(RECONNECTING) // Stop the ping timer (if set) nc.stopPingTimer() // Since processConnectInit() returned without error, the @@ -2753,7 +2807,7 @@ func (nc *Conn) processOpErr(err error) { if nc.Opts.AllowReconnect && nc.status == CONNECTED { // Set our new status - nc.status = RECONNECTING + nc.changeConnStatus(RECONNECTING) // Stop ping timer if set nc.stopPingTimer() if nc.conn != nil { @@ -2772,7 +2826,7 @@ func (nc *Conn) processOpErr(err error) { return } - nc.status = DISCONNECTED + nc.changeConnStatus(DISCONNECTED) nc.err = err nc.mu.Unlock() nc.close(CLOSED, true, nil) @@ -3049,7 +3103,7 @@ func (nc *Conn) processMsg(data []byte) { if nc.ps.ma.hdr > 0 { hbuf := msgPayload[:nc.ps.ma.hdr] msgPayload = msgPayload[nc.ps.ma.hdr:] - h, err = decodeHeadersMsg(hbuf) + h, err = DecodeHeadersMsg(hbuf) if err != nil { // We will pass the message through but send async error. nc.mu.Lock() @@ -3564,8 +3618,8 @@ const ( statusLen = 3 // e.g. 20x, 40x, 50x ) -// decodeHeadersMsg will decode and headers. -func decodeHeadersMsg(data []byte) (Header, error) { +// DecodeHeadersMsg will decode and headers. +func DecodeHeadersMsg(data []byte) (Header, error) { br := bufio.NewReaderSize(bytes.NewReader(data), 128) tp := textproto.NewReader(br) l, err := tp.ReadLine() @@ -5021,15 +5075,15 @@ func (nc *Conn) close(status Status, doCBs bool, err error) { nc.subs = nil nc.subsMu.Unlock() - nc.status = status + nc.changeConnStatus(status) // Perform appropriate callback if needed for a disconnect. if doCBs { if nc.conn != nil { - if nc.Opts.DisconnectedErrCB != nil { - nc.ach.push(func() { nc.Opts.DisconnectedErrCB(nc, err) }) - } else if nc.Opts.DisconnectedCB != nil { - nc.ach.push(func() { nc.Opts.DisconnectedCB(nc) }) + if disconnectedErrCB := nc.Opts.DisconnectedErrCB; disconnectedErrCB != nil { + nc.ach.push(func() { disconnectedErrCB(nc, err) }) + } else if disconnectedCB := nc.Opts.DisconnectedCB; disconnectedCB != nil { + nc.ach.push(func() { disconnectedCB(nc) }) } } if nc.Opts.ClosedCB != nil { @@ -5166,7 +5220,7 @@ func (nc *Conn) drainConnection() { // Flip State nc.mu.Lock() - nc.status = DRAINING_PUBS + nc.changeConnStatus(DRAINING_PUBS) nc.mu.Unlock() // Do publish drain via Flush() call. @@ -5201,7 +5255,7 @@ func (nc *Conn) Drain() error { nc.mu.Unlock() return nil } - nc.status = DRAINING_SUBS + nc.changeConnStatus(DRAINING_SUBS) go nc.drainConnection() nc.mu.Unlock() @@ -5411,6 +5465,68 @@ func (nc *Conn) GetClientID() (uint64, error) { return nc.info.CID, nil } +// StatusChanged returns a channel on which given list of connection status changes will be reported. +// If no statuses are provided, defaults will be used: CONNECTED, RECONNECTING, DISCONNECTED, CLOSED. +func (nc *Conn) StatusChanged(statuses ...Status) chan Status { + if len(statuses) == 0 { + statuses = []Status{CONNECTED, RECONNECTING, DISCONNECTED, CLOSED} + } + ch := make(chan Status) + for _, s := range statuses { + nc.registerStatusChangeListener(s, ch) + } + return ch +} + +// registerStatusChangeListener registers a channel waiting for a specific status change event. +// Status change events are non-blocking - if no receiver is waiting for the status change, +// it will not be sent on the channel. Closed channels are ignored. +func (nc *Conn) registerStatusChangeListener(status Status, ch chan Status) { + nc.mu.Lock() + defer nc.mu.Unlock() + if nc.statListeners == nil { + nc.statListeners = make(map[Status][]chan Status) + } + if _, ok := nc.statListeners[status]; !ok { + nc.statListeners[status] = make([]chan Status, 0) + } + nc.statListeners[status] = append(nc.statListeners[status], ch) +} + +// sendStatusEvent sends connection status event to all channels. +// If channel is closed, or there is no listener, sendStatusEvent +// will not block. Lock should be held entering. +func (nc *Conn) sendStatusEvent(s Status) { +Loop: + for i := 0; i < len(nc.statListeners[s]); i++ { + // make sure channel is not closed + select { + case <-nc.statListeners[s][i]: + // if chan is closed, remove it + nc.statListeners[s][i] = nc.statListeners[s][len(nc.statListeners[s])-1] + nc.statListeners[s] = nc.statListeners[s][:len(nc.statListeners[s])-1] + i-- + continue Loop + default: + } + // only send event if someone's listening + select { + case nc.statListeners[s][i] <- s: + default: + } + } +} + +// changeConnStatus changes connections status and sends events +// to all listeners. Lock should be held entering. +func (nc *Conn) changeConnStatus(status Status) { + if nc == nil { + return + } + nc.sendStatusEvent(status) + nc.status = status +} + // NkeyOptionFromSeed will load an nkey pair from a seed file. // It will return the NKey Option and will handle // signing of nonce challenges from the server. It will take diff --git a/vendor/github.com/nats-io/nats.go/object.go b/vendor/github.com/nats-io/nats.go/object.go index 133001fe1..6bce60575 100644 --- a/vendor/github.com/nats-io/nats.go/object.go +++ b/vendor/github.com/nats-io/nats.go/object.go @@ -29,6 +29,7 @@ import ( "sync" "time" + "github.com/nats-io/nats.go/internal/parser" "github.com/nats-io/nuid" ) @@ -368,14 +369,23 @@ func (obs *obs) Put(meta *ObjectMeta, r io.Reader, opts ...ObjectOpt) (*ObjectIn return perr } - purgePartial := func() { obs.js.purgeStream(obs.stream, &StreamPurgeRequest{Subject: chunkSubj}) } - // Create our own JS context to handle errors etc. - js, err := obs.js.nc.JetStream(PublishAsyncErrHandler(func(js JetStream, _ *Msg, err error) { setErr(err) })) + jetStream, err := obs.js.nc.JetStream(PublishAsyncErrHandler(func(js JetStream, _ *Msg, err error) { setErr(err) })) if err != nil { return nil, err } + defer jetStream.(*js).cleanupReplySub() + + purgePartial := func() { + // wait until all pubs are complete or up to default timeout before attempting purge + select { + case <-jetStream.PublishAsyncComplete(): + case <-time.After(obs.js.opts.wait): + } + obs.js.purgeStream(obs.stream, &StreamPurgeRequest{Subject: chunkSubj}) + } + m, h := NewMsg(chunkSubj), sha256.New() chunk, sent, total := make([]byte, meta.Opts.ChunkSize), 0, uint64(0) @@ -416,7 +426,7 @@ func (obs *obs) Put(meta *ObjectMeta, r io.Reader, opts ...ObjectOpt) (*ObjectIn h.Write(m.Data) // Send msg itself. - if _, err := js.PublishMsgAsync(m); err != nil { + if _, err := jetStream.PublishMsgAsync(m); err != nil { purgePartial() return nil, err } @@ -451,7 +461,7 @@ func (obs *obs) Put(meta *ObjectMeta, r io.Reader, opts ...ObjectOpt) (*ObjectIn } // Publish the meta message. - _, err = js.PublishMsgAsync(mm) + _, err = jetStream.PublishMsgAsync(mm) if err != nil { if r != nil { purgePartial() @@ -461,7 +471,7 @@ func (obs *obs) Put(meta *ObjectMeta, r io.Reader, opts ...ObjectOpt) (*ObjectIn // Wait for all to be processed. select { - case <-js.PublishAsyncComplete(): + case <-jetStream.PublishAsyncComplete(): if err := getErr(); err != nil { if r != nil { purgePartial() @@ -628,7 +638,7 @@ func (obs *obs) Get(name string, opts ...GetObjectOpt) (ObjectResult, error) { } } - tokens, err := getMetadataFields(m.Reply) + tokens, err := parser.GetMetadataFields(m.Reply) if err != nil { gotErr(m, err) return @@ -1207,7 +1217,7 @@ func (o *objResult) Read(p []byte) (n int, err error) { } } if o.err != nil { - return 0, err + return 0, o.err } if o.r == nil { return 0, io.EOF diff --git a/vendor/github.com/nats-io/nats.go/parser.go b/vendor/github.com/nats-io/nats.go/parser.go index a0f0cca2e..70204e60d 100644 --- a/vendor/github.com/nats-io/nats.go/parser.go +++ b/vendor/github.com/nats-io/nats.go/parser.go @@ -1,4 +1,4 @@ -// Copyright 2012-2122 The NATS Authors +// Copyright 2012-2023 The NATS 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 diff --git a/vendor/github.com/nats-io/nats.go/ws.go b/vendor/github.com/nats-io/nats.go/ws.go index c4919a3ae..2c2d421a8 100644 --- a/vendor/github.com/nats-io/nats.go/ws.go +++ b/vendor/github.com/nats-io/nats.go/ws.go @@ -1,4 +1,4 @@ -// Copyright 2021-2022 The NATS Authors +// Copyright 2021-2023 The NATS 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 @@ -16,7 +16,6 @@ package nats import ( "bufio" "bytes" - "compress/flate" "crypto/rand" "crypto/sha1" "encoding/base64" @@ -30,6 +29,8 @@ import ( "strings" "time" "unicode/utf8" + + "github.com/klauspost/compress/flate" ) type wsOpCode int @@ -448,8 +449,12 @@ func (w *websocketWriter) Write(p []byte) (int, error) { } else { w.compressor.Reset(buf) } - w.compressor.Write(p) - w.compressor.Close() + if n, err = w.compressor.Write(p); err != nil { + return n, err + } + if err = w.compressor.Flush(); err != nil { + return n, err + } b := buf.Bytes() p = b[:len(b)-4] } @@ -550,7 +555,7 @@ func wsFillFrameHeader(fh []byte, compressed bool, frameType wsOpCode, l int) (i func (nc *Conn) wsInitHandshake(u *url.URL) error { compress := nc.Opts.Compression - tlsRequired := u.Scheme == wsSchemeTLS || nc.Opts.Secure || nc.Opts.TLSConfig != nil + tlsRequired := u.Scheme == wsSchemeTLS || nc.Opts.Secure || nc.Opts.TLSConfig != nil || nc.Opts.TLSCertCB != nil || nc.Opts.RootCAsCB != nil // Do TLS here as needed. if tlsRequired { if err := nc.makeTLSConn(); err != nil { @@ -692,6 +697,9 @@ func (nc *Conn) wsEnqueueCloseMsgLocked(status int, payload string) { wr.cm = frame wr.cmDone = true nc.bw.flush() + if c := wr.compressor; c != nil { + c.Close() + } } func (nc *Conn) wsEnqueueControlMsg(needsLock bool, frameType wsOpCode, payload []byte) { diff --git a/vendor/github.com/prometheus/client_golang/prometheus/desc.go b/vendor/github.com/prometheus/client_golang/prometheus/desc.go index 12331542d..deedc2dfb 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/desc.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/desc.go @@ -18,12 +18,12 @@ import ( "sort" "strings" - "github.com/prometheus/client_golang/prometheus/internal" - "github.com/cespare/xxhash/v2" dto "github.com/prometheus/client_model/go" "github.com/prometheus/common/model" "google.golang.org/protobuf/proto" + + "github.com/prometheus/client_golang/prometheus/internal" ) // Desc is the descriptor used by every Prometheus Metric. It is essentially diff --git a/vendor/github.com/prometheus/client_golang/prometheus/histogram.go b/vendor/github.com/prometheus/client_golang/prometheus/histogram.go index 5b69965b2..8d818afe9 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/histogram.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/histogram.go @@ -401,7 +401,7 @@ type HistogramOpts struct { // Histogram by a Prometheus server with that feature enabled (requires // Prometheus v2.40+). Sparse buckets are exponential buckets covering // the whole float64 range (with the exception of the “zero” bucket, see - // SparseBucketsZeroThreshold below). From any one bucket to the next, + // NativeHistogramZeroThreshold below). From any one bucket to the next, // the width of the bucket grows by a constant // factor. NativeHistogramBucketFactor provides an upper bound for this // factor (exception see below). The smaller @@ -432,7 +432,7 @@ type HistogramOpts struct { // bucket. For best results, this should be close to a bucket // boundary. This is usually the case if picking a power of two. If // NativeHistogramZeroThreshold is left at zero, - // DefSparseBucketsZeroThreshold is used as the threshold. To configure + // DefNativeHistogramZeroThreshold is used as the threshold. To configure // a zero bucket with an actual threshold of zero (i.e. only // observations of precisely zero will go into the zero bucket), set // NativeHistogramZeroThreshold to the NativeHistogramZeroThresholdZero @@ -639,8 +639,8 @@ func (hc *histogramCounts) observe(v float64, bucket int, doSparse bool) { if frac == 0.5 { key-- } - div := 1 << -schema - key = (key + div - 1) / div + offset := (1 << -schema) - 1 + key = (key + offset) >> -schema } if isInf { key++ @@ -817,7 +817,7 @@ func (h *histogram) observe(v float64, bucket int) { } } -// limitSparsebuckets applies a strategy to limit the number of populated sparse +// limitBuckets applies a strategy to limit the number of populated sparse // buckets. It's generally best effort, and there are situations where the // number can go higher (if even the lowest resolution isn't enough to reduce // the number sufficiently, or if the provided counts aren't fully updated yet diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go index a4cc9810b..09b8d2fbe 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go @@ -37,6 +37,7 @@ import ( "fmt" "io" "net/http" + "strconv" "strings" "sync" "time" @@ -47,9 +48,10 @@ import ( ) const ( - contentTypeHeader = "Content-Type" - contentEncodingHeader = "Content-Encoding" - acceptEncodingHeader = "Accept-Encoding" + contentTypeHeader = "Content-Type" + contentEncodingHeader = "Content-Encoding" + acceptEncodingHeader = "Accept-Encoding" + processStartTimeHeader = "Process-Start-Time-Unix" ) var gzipPool = sync.Pool{ @@ -121,6 +123,9 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO } h := http.HandlerFunc(func(rsp http.ResponseWriter, req *http.Request) { + if !opts.ProcessStartTime.IsZero() { + rsp.Header().Set(processStartTimeHeader, strconv.FormatInt(opts.ProcessStartTime.Unix(), 10)) + } if inFlightSem != nil { select { case inFlightSem <- struct{}{}: // All good, carry on. @@ -366,6 +371,14 @@ type HandlerOpts struct { // (which changes the identity of the resulting series on the Prometheus // server). EnableOpenMetrics bool + // ProcessStartTime allows setting process start timevalue that will be exposed + // with "Process-Start-Time-Unix" response header along with the metrics + // payload. This allow callers to have efficient transformations to cumulative + // counters (e.g. OpenTelemetry) or generally _created timestamp estimation per + // scrape target. + // NOTE: This feature is experimental and not covered by OpenMetrics or Prometheus + // exposition format. + ProcessStartTime time.Time } // gzipAccepted returns whether the client will accept gzip-encoded content. diff --git a/vendor/github.com/prometheus/client_golang/prometheus/vec.go b/vendor/github.com/prometheus/client_golang/prometheus/vec.go index 386fb2d23..f0d0015a0 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/vec.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/vec.go @@ -20,6 +20,24 @@ import ( "github.com/prometheus/common/model" ) +var labelsPool = &sync.Pool{ + New: func() interface{} { + return make(Labels) + }, +} + +func getLabelsFromPool() Labels { + return labelsPool.Get().(Labels) +} + +func putLabelsToPool(labels Labels) { + for k := range labels { + delete(labels, k) + } + + labelsPool.Put(labels) +} + // MetricVec is a Collector to bundle metrics of the same name that differ in // their label values. MetricVec is not used directly but as a building block // for implementations of vectors of a given metric type, like GaugeVec, @@ -93,6 +111,8 @@ func (m *MetricVec) DeleteLabelValues(lvs ...string) bool { // there for pros and cons of the two methods. func (m *MetricVec) Delete(labels Labels) bool { labels = constrainLabels(m.desc, labels) + defer putLabelsToPool(labels) + h, err := m.hashLabels(labels) if err != nil { return false @@ -109,6 +129,8 @@ func (m *MetricVec) Delete(labels Labels) bool { // To match curried labels with DeletePartialMatch, it must be called on the base vector. func (m *MetricVec) DeletePartialMatch(labels Labels) int { labels = constrainLabels(m.desc, labels) + defer putLabelsToPool(labels) + return m.metricMap.deleteByLabels(labels, m.curry) } @@ -229,6 +251,8 @@ func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) { // for example GaugeVec. func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) { labels = constrainLabels(m.desc, labels) + defer putLabelsToPool(labels) + h, err := m.hashLabels(labels) if err != nil { return nil, err @@ -647,15 +671,16 @@ func inlineLabelValues(lvs []string, curry []curriedLabelValue) []string { } func constrainLabels(desc *Desc, labels Labels) Labels { - constrainedValues := make(Labels, len(labels)) + constrainedLabels := getLabelsFromPool() for l, v := range labels { if i, ok := indexOf(l, desc.variableLabels.labelNames()); ok { - constrainedValues[l] = desc.variableLabels[i].Constrain(v) - continue + v = desc.variableLabels[i].Constrain(v) } - constrainedValues[l] = v + + constrainedLabels[l] = v } - return constrainedValues + + return constrainedLabels } func constrainLabelValues(desc *Desc, lvs []string, curry []curriedLabelValue) []string { diff --git a/vendor/github.com/prometheus/procfs/Makefile.common b/vendor/github.com/prometheus/procfs/Makefile.common index e358db69c..b111d2562 100644 --- a/vendor/github.com/prometheus/procfs/Makefile.common +++ b/vendor/github.com/prometheus/procfs/Makefile.common @@ -61,7 +61,7 @@ PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_ SKIP_GOLANGCI_LINT := GOLANGCI_LINT := GOLANGCI_LINT_OPTS ?= -GOLANGCI_LINT_VERSION ?= v1.49.0 +GOLANGCI_LINT_VERSION ?= v1.51.2 # golangci-lint only supports linux, darwin and windows platforms on i386/amd64. # windows isn't included here because of the path separator being different. ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) @@ -91,6 +91,8 @@ BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS)) PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS)) TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS)) +SANITIZED_DOCKER_IMAGE_TAG := $(subst +,-,$(DOCKER_IMAGE_TAG)) + ifeq ($(GOHOSTARCH),amd64) ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux freebsd darwin windows)) # Only supported on amd64 @@ -205,7 +207,7 @@ common-tarball: promu .PHONY: common-docker $(BUILD_DOCKER_ARCHS) common-docker: $(BUILD_DOCKER_ARCHS) $(BUILD_DOCKER_ARCHS): common-docker-%: - docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" \ + docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" \ -f $(DOCKERFILE_PATH) \ --build-arg ARCH="$*" \ --build-arg OS="linux" \ @@ -214,19 +216,19 @@ $(BUILD_DOCKER_ARCHS): common-docker-%: .PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS) common-docker-publish: $(PUBLISH_DOCKER_ARCHS) $(PUBLISH_DOCKER_ARCHS): common-docker-publish-%: - docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" + docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" DOCKER_MAJOR_VERSION_TAG = $(firstword $(subst ., ,$(shell cat VERSION))) .PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS) common-docker-tag-latest: $(TAG_DOCKER_ARCHS) $(TAG_DOCKER_ARCHS): common-docker-tag-latest-%: - docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest" - docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)" + docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest" + docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)" .PHONY: common-docker-manifest common-docker-manifest: - DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(DOCKER_IMAGE_TAG)) - DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" + DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(SANITIZED_DOCKER_IMAGE_TAG)) + DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" .PHONY: promu promu: $(PROMU) diff --git a/vendor/github.com/prometheus/procfs/fs.go b/vendor/github.com/prometheus/procfs/fs.go index 0102ab0fd..60c551e02 100644 --- a/vendor/github.com/prometheus/procfs/fs.go +++ b/vendor/github.com/prometheus/procfs/fs.go @@ -21,6 +21,7 @@ import ( // kernel data structures. type FS struct { proc fs.FS + real bool } // DefaultMountPoint is the common mount point of the proc filesystem. @@ -39,5 +40,11 @@ func NewFS(mountPoint string) (FS, error) { if err != nil { return FS{}, err } - return FS{fs}, nil + + real, err := isRealProc(mountPoint) + if err != nil { + return FS{}, err + } + + return FS{fs, real}, nil } diff --git a/vendor/github.com/prometheus/procfs/fs_statfs_notype.go b/vendor/github.com/prometheus/procfs/fs_statfs_notype.go new file mode 100644 index 000000000..800576968 --- /dev/null +++ b/vendor/github.com/prometheus/procfs/fs_statfs_notype.go @@ -0,0 +1,23 @@ +// Copyright 2018 The Prometheus 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:build netbsd || openbsd || solaris || windows +// +build netbsd openbsd solaris windows + +package procfs + +// isRealProc returns true on architectures that don't have a Type argument +// in their Statfs_t struct +func isRealProc(mountPoint string) (bool, error) { + return true, nil +} diff --git a/vendor/github.com/prometheus/procfs/fs_statfs_type.go b/vendor/github.com/prometheus/procfs/fs_statfs_type.go new file mode 100644 index 000000000..6233217ad --- /dev/null +++ b/vendor/github.com/prometheus/procfs/fs_statfs_type.go @@ -0,0 +1,33 @@ +// Copyright 2018 The Prometheus 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:build !netbsd && !openbsd && !solaris && !windows +// +build !netbsd,!openbsd,!solaris,!windows + +package procfs + +import ( + "syscall" +) + +// isRealProc determines whether supplied mountpoint is really a proc filesystem. +func isRealProc(mountPoint string) (bool, error) { + stat := syscall.Statfs_t{} + err := syscall.Statfs(mountPoint, &stat) + if err != nil { + return false, err + } + + // 0x9fa0 is PROC_SUPER_MAGIC: https://elixir.bootlin.com/linux/v6.1/source/include/uapi/linux/magic.h#L87 + return stat.Type == 0x9fa0, nil +} diff --git a/vendor/github.com/prometheus/procfs/internal/util/parse.go b/vendor/github.com/prometheus/procfs/internal/util/parse.go index b030951fa..14272dc78 100644 --- a/vendor/github.com/prometheus/procfs/internal/util/parse.go +++ b/vendor/github.com/prometheus/procfs/internal/util/parse.go @@ -64,6 +64,21 @@ func ParsePInt64s(ss []string) ([]*int64, error) { return us, nil } +// Parses a uint64 from given hex in string. +func ParseHexUint64s(ss []string) ([]*uint64, error) { + us := make([]*uint64, 0, len(ss)) + for _, s := range ss { + u, err := strconv.ParseUint(s, 16, 64) + if err != nil { + return nil, err + } + + us = append(us, &u) + } + + return us, nil +} + // ReadUintFromFile reads a file and attempts to parse a uint64 from it. func ReadUintFromFile(path string) (uint64, error) { data, err := os.ReadFile(path) diff --git a/vendor/github.com/prometheus/procfs/mountstats.go b/vendor/github.com/prometheus/procfs/mountstats.go index 0c482c18c..7f68890cf 100644 --- a/vendor/github.com/prometheus/procfs/mountstats.go +++ b/vendor/github.com/prometheus/procfs/mountstats.go @@ -186,6 +186,8 @@ type NFSOperationStats struct { CumulativeTotalResponseMilliseconds uint64 // Duration from when a request was enqueued to when it was completely handled. CumulativeTotalRequestMilliseconds uint64 + // The average time from the point the client sends RPC requests until it receives the response. + AverageRTTMilliseconds float64 // The count of operations that complete with tk_status < 0. These statuses usually indicate error conditions. Errors uint64 } @@ -534,7 +536,6 @@ func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) { ns = append(ns, n) } - opStats := NFSOperationStats{ Operation: strings.TrimSuffix(ss[0], ":"), Requests: ns[0], @@ -546,6 +547,9 @@ func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) { CumulativeTotalResponseMilliseconds: ns[6], CumulativeTotalRequestMilliseconds: ns[7], } + if ns[0] != 0 { + opStats.AverageRTTMilliseconds = float64(ns[6]) / float64(ns[0]) + } if len(ns) > 8 { opStats.Errors = ns[8] diff --git a/vendor/github.com/prometheus/procfs/net_conntrackstat.go b/vendor/github.com/prometheus/procfs/net_conntrackstat.go index 8300daca0..64a0e9460 100644 --- a/vendor/github.com/prometheus/procfs/net_conntrackstat.go +++ b/vendor/github.com/prometheus/procfs/net_conntrackstat.go @@ -18,7 +18,6 @@ import ( "bytes" "fmt" "io" - "strconv" "strings" "github.com/prometheus/procfs/internal/util" @@ -28,9 +27,13 @@ import ( // and contains netfilter conntrack statistics at one CPU core. type ConntrackStatEntry struct { Entries uint64 + Searched uint64 Found uint64 + New uint64 Invalid uint64 Ignore uint64 + Delete uint64 + DeleteList uint64 Insert uint64 InsertFailed uint64 Drop uint64 @@ -81,73 +84,34 @@ func parseConntrackStat(r io.Reader) ([]ConntrackStatEntry, error) { // Parses a ConntrackStatEntry from given array of fields. func parseConntrackStatEntry(fields []string) (*ConntrackStatEntry, error) { - if len(fields) != 17 { - return nil, fmt.Errorf("invalid conntrackstat entry, missing fields") - } - entry := &ConntrackStatEntry{} - - entries, err := parseConntrackStatField(fields[0]) + entries, err := util.ParseHexUint64s(fields) if err != nil { - return nil, err + return nil, fmt.Errorf("invalid conntrackstat entry, couldn't parse fields: %s", err) } - entry.Entries = entries - - found, err := parseConntrackStatField(fields[2]) - if err != nil { - return nil, err + numEntries := len(entries) + if numEntries < 16 || numEntries > 17 { + return nil, fmt.Errorf("invalid conntrackstat entry, invalid number of fields: %d", numEntries) } - entry.Found = found - invalid, err := parseConntrackStatField(fields[4]) - if err != nil { - return nil, err + stats := &ConntrackStatEntry{ + Entries: *entries[0], + Searched: *entries[1], + Found: *entries[2], + New: *entries[3], + Invalid: *entries[4], + Ignore: *entries[5], + Delete: *entries[6], + DeleteList: *entries[7], + Insert: *entries[8], + InsertFailed: *entries[9], + Drop: *entries[10], + EarlyDrop: *entries[11], } - entry.Invalid = invalid - ignore, err := parseConntrackStatField(fields[5]) - if err != nil { - return nil, err + // Ignore missing search_restart on Linux < 2.6.35. + if numEntries == 17 { + stats.SearchRestart = *entries[16] } - entry.Ignore = ignore - insert, err := parseConntrackStatField(fields[8]) - if err != nil { - return nil, err - } - entry.Insert = insert - - insertFailed, err := parseConntrackStatField(fields[9]) - if err != nil { - return nil, err - } - entry.InsertFailed = insertFailed - - drop, err := parseConntrackStatField(fields[10]) - if err != nil { - return nil, err - } - entry.Drop = drop - - earlyDrop, err := parseConntrackStatField(fields[11]) - if err != nil { - return nil, err - } - entry.EarlyDrop = earlyDrop - - searchRestart, err := parseConntrackStatField(fields[16]) - if err != nil { - return nil, err - } - entry.SearchRestart = searchRestart - - return entry, nil -} - -// Parses a uint64 from given hex in string. -func parseConntrackStatField(field string) (uint64, error) { - val, err := strconv.ParseUint(field, 16, 64) - if err != nil { - return 0, fmt.Errorf("couldn't parse %q field: %w", field, err) - } - return val, err + return stats, nil } diff --git a/vendor/github.com/prometheus/procfs/net_softnet.go b/vendor/github.com/prometheus/procfs/net_softnet.go index 06b7b8f21..540cea52c 100644 --- a/vendor/github.com/prometheus/procfs/net_softnet.go +++ b/vendor/github.com/prometheus/procfs/net_softnet.go @@ -76,6 +76,7 @@ func parseSoftnet(r io.Reader) ([]SoftnetStat, error) { s := bufio.NewScanner(r) var stats []SoftnetStat + cpuIndex := 0 for s.Scan() { columns := strings.Fields(s.Text()) width := len(columns) @@ -127,9 +128,13 @@ func parseSoftnet(r io.Reader) ([]SoftnetStat, error) { softnetStat.SoftnetBacklogLen = us[0] softnetStat.Index = us[1] + } else { + // For older kernels, create the Index based on the scan line number. + softnetStat.Index = uint32(cpuIndex) } softnetStat.Width = width stats = append(stats, softnetStat) + cpuIndex++ } return stats, nil diff --git a/vendor/github.com/prometheus/procfs/net_wireless.go b/vendor/github.com/prometheus/procfs/net_wireless.go new file mode 100644 index 000000000..c80fb1542 --- /dev/null +++ b/vendor/github.com/prometheus/procfs/net_wireless.go @@ -0,0 +1,182 @@ +// Copyright 2023 The Prometheus 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 procfs + +import ( + "bufio" + "bytes" + "fmt" + "io" + "strconv" + "strings" + + "github.com/prometheus/procfs/internal/util" +) + +// Wireless models the content of /proc/net/wireless. +type Wireless struct { + Name string + + // Status is the current 4-digit hex value status of the interface. + Status uint64 + + // QualityLink is the link quality. + QualityLink int + + // QualityLevel is the signal gain (dBm). + QualityLevel int + + // QualityNoise is the signal noise baseline (dBm). + QualityNoise int + + // DiscardedNwid is the number of discarded packets with wrong nwid/essid. + DiscardedNwid int + + // DiscardedCrypt is the number of discarded packets with wrong code/decode (WEP). + DiscardedCrypt int + + // DiscardedFrag is the number of discarded packets that can't perform MAC reassembly. + DiscardedFrag int + + // DiscardedRetry is the number of discarded packets that reached max MAC retries. + DiscardedRetry int + + // DiscardedMisc is the number of discarded packets for other reasons. + DiscardedMisc int + + // MissedBeacon is the number of missed beacons/superframe. + MissedBeacon int +} + +// Wireless returns kernel wireless statistics. +func (fs FS) Wireless() ([]*Wireless, error) { + b, err := util.ReadFileNoStat(fs.proc.Path("net/wireless")) + if err != nil { + return nil, err + } + + m, err := parseWireless(bytes.NewReader(b)) + if err != nil { + return nil, fmt.Errorf("failed to parse wireless: %w", err) + } + + return m, nil +} + +// parseWireless parses the contents of /proc/net/wireless. +/* +Inter-| sta-| Quality | Discarded packets | Missed | WE +face | tus | link level noise | nwid crypt frag retry misc | beacon | 22 + eth1: 0000 5. -256. -10. 0 1 0 3 0 0 + eth2: 0000 5. -256. -20. 0 2 0 4 0 0 +*/ +func parseWireless(r io.Reader) ([]*Wireless, error) { + var ( + interfaces []*Wireless + scanner = bufio.NewScanner(r) + ) + + for n := 0; scanner.Scan(); n++ { + // Skip the 2 header lines. + if n < 2 { + continue + } + + line := scanner.Text() + + parts := strings.Split(line, ":") + if len(parts) != 2 { + return nil, fmt.Errorf("expected 2 parts after splitting line by ':', got %d for line %q", len(parts), line) + } + + name := strings.TrimSpace(parts[0]) + stats := strings.Fields(parts[1]) + + if len(stats) < 10 { + return nil, fmt.Errorf("invalid number of fields in line %d, expected at least 10, got %d: %q", n, len(stats), line) + } + + status, err := strconv.ParseUint(stats[0], 16, 16) + if err != nil { + return nil, fmt.Errorf("invalid status in line %d: %q", n, line) + } + + qlink, err := strconv.Atoi(strings.TrimSuffix(stats[1], ".")) + if err != nil { + return nil, fmt.Errorf("failed to parse Quality:link as integer %q: %w", qlink, err) + } + + qlevel, err := strconv.Atoi(strings.TrimSuffix(stats[2], ".")) + if err != nil { + return nil, fmt.Errorf("failed to parse Quality:level as integer %q: %w", qlevel, err) + } + + qnoise, err := strconv.Atoi(strings.TrimSuffix(stats[3], ".")) + if err != nil { + return nil, fmt.Errorf("failed to parse Quality:noise as integer %q: %w", qnoise, err) + } + + dnwid, err := strconv.Atoi(stats[4]) + if err != nil { + return nil, fmt.Errorf("failed to parse Discarded:nwid as integer %q: %w", dnwid, err) + } + + dcrypt, err := strconv.Atoi(stats[5]) + if err != nil { + return nil, fmt.Errorf("failed to parse Discarded:crypt as integer %q: %w", dcrypt, err) + } + + dfrag, err := strconv.Atoi(stats[6]) + if err != nil { + return nil, fmt.Errorf("failed to parse Discarded:frag as integer %q: %w", dfrag, err) + } + + dretry, err := strconv.Atoi(stats[7]) + if err != nil { + return nil, fmt.Errorf("failed to parse Discarded:retry as integer %q: %w", dretry, err) + } + + dmisc, err := strconv.Atoi(stats[8]) + if err != nil { + return nil, fmt.Errorf("failed to parse Discarded:misc as integer %q: %w", dmisc, err) + } + + mbeacon, err := strconv.Atoi(stats[9]) + if err != nil { + return nil, fmt.Errorf("failed to parse Missed:beacon as integer %q: %w", mbeacon, err) + } + + w := &Wireless{ + Name: name, + Status: status, + QualityLink: qlink, + QualityLevel: qlevel, + QualityNoise: qnoise, + DiscardedNwid: dnwid, + DiscardedCrypt: dcrypt, + DiscardedFrag: dfrag, + DiscardedRetry: dretry, + DiscardedMisc: dmisc, + MissedBeacon: mbeacon, + } + + interfaces = append(interfaces, w) + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("failed to scan /proc/net/wireless: %w", err) + } + + return interfaces, nil +} diff --git a/vendor/github.com/prometheus/procfs/netstat.go b/vendor/github.com/prometheus/procfs/netstat.go index 5cc40aef5..742dff453 100644 --- a/vendor/github.com/prometheus/procfs/netstat.go +++ b/vendor/github.com/prometheus/procfs/netstat.go @@ -15,7 +15,6 @@ package procfs import ( "bufio" - "io" "os" "path/filepath" "strconv" @@ -38,12 +37,7 @@ func (fs FS) NetStat() ([]NetStat, error) { var netStatsTotal []NetStat for _, filePath := range statFiles { - file, err := os.Open(filePath) - if err != nil { - return nil, err - } - - procNetstat, err := parseNetstat(file) + procNetstat, err := parseNetstat(filePath) if err != nil { return nil, err } @@ -56,14 +50,17 @@ func (fs FS) NetStat() ([]NetStat, error) { // parseNetstat parses the metrics from `/proc/net/stat/` file // and returns a NetStat structure. -func parseNetstat(r io.Reader) (NetStat, error) { - var ( - scanner = bufio.NewScanner(r) - netStat = NetStat{ - Stats: make(map[string][]uint64), - } - ) +func parseNetstat(filePath string) (NetStat, error) { + netStat := NetStat{ + Stats: make(map[string][]uint64), + } + file, err := os.Open(filePath) + if err != nil { + return netStat, err + } + defer file.Close() + scanner := bufio.NewScanner(file) scanner.Scan() // First string is always a header for stats diff --git a/vendor/github.com/prometheus/procfs/proc.go b/vendor/github.com/prometheus/procfs/proc.go index c30223af7..48f39dafd 100644 --- a/vendor/github.com/prometheus/procfs/proc.go +++ b/vendor/github.com/prometheus/procfs/proc.go @@ -21,7 +21,6 @@ import ( "strconv" "strings" - "github.com/prometheus/procfs/internal/fs" "github.com/prometheus/procfs/internal/util" ) @@ -30,7 +29,7 @@ type Proc struct { // The process ID. PID int - fs fs.FS + fs FS } // Procs represents a list of Proc structs. @@ -92,7 +91,7 @@ func (fs FS) Proc(pid int) (Proc, error) { if _, err := os.Stat(fs.proc.Path(strconv.Itoa(pid))); err != nil { return Proc{}, err } - return Proc{PID: pid, fs: fs.proc}, nil + return Proc{PID: pid, fs: fs}, nil } // AllProcs returns a list of all currently available processes. @@ -114,7 +113,7 @@ func (fs FS) AllProcs() (Procs, error) { if err != nil { continue } - p = append(p, Proc{PID: int(pid), fs: fs.proc}) + p = append(p, Proc{PID: int(pid), fs: fs}) } return p, nil @@ -237,6 +236,19 @@ func (p Proc) FileDescriptorTargets() ([]string, error) { // FileDescriptorsLen returns the number of currently open file descriptors of // a process. func (p Proc) FileDescriptorsLen() (int, error) { + // Use fast path if available (Linux v6.2): https://github.com/torvalds/linux/commit/f1f1f2569901 + if p.fs.real { + stat, err := os.Stat(p.path("fd")) + if err != nil { + return 0, err + } + + size := stat.Size() + if size > 0 { + return int(size), nil + } + } + fds, err := p.fileDescriptors() if err != nil { return 0, err @@ -285,7 +297,7 @@ func (p Proc) fileDescriptors() ([]string, error) { } func (p Proc) path(pa ...string) string { - return p.fs.Path(append([]string{strconv.Itoa(p.PID)}, pa...)...) + return p.fs.proc.Path(append([]string{strconv.Itoa(p.PID)}, pa...)...) } // FileDescriptorsInfo retrieves information about all file descriptors of diff --git a/vendor/github.com/prometheus/procfs/proc_stat.go b/vendor/github.com/prometheus/procfs/proc_stat.go index b278eb2c2..14b249f4f 100644 --- a/vendor/github.com/prometheus/procfs/proc_stat.go +++ b/vendor/github.com/prometheus/procfs/proc_stat.go @@ -18,7 +18,6 @@ import ( "fmt" "os" - "github.com/prometheus/procfs/internal/fs" "github.com/prometheus/procfs/internal/util" ) @@ -112,7 +111,7 @@ type ProcStat struct { // Aggregated block I/O delays, measured in clock ticks (centiseconds). DelayAcctBlkIOTicks uint64 - proc fs.FS + proc FS } // NewStat returns the current status information of the process. @@ -210,8 +209,7 @@ func (s ProcStat) ResidentMemory() int { // StartTime returns the unix timestamp of the process in seconds. func (s ProcStat) StartTime() (float64, error) { - fs := FS{proc: s.proc} - stat, err := fs.Stat() + stat, err := s.proc.Stat() if err != nil { return 0, err } diff --git a/vendor/github.com/prometheus/procfs/proc_status.go b/vendor/github.com/prometheus/procfs/proc_status.go index 3d8c06439..c055d075d 100644 --- a/vendor/github.com/prometheus/procfs/proc_status.go +++ b/vendor/github.com/prometheus/procfs/proc_status.go @@ -15,6 +15,7 @@ package procfs import ( "bytes" + "sort" "strconv" "strings" @@ -76,6 +77,9 @@ type ProcStatus struct { UIDs [4]string // GIDs of the process (Real, effective, saved set, and filesystem GIDs) GIDs [4]string + + // CpusAllowedList: List of cpu cores processes are allowed to run on. + CpusAllowedList []uint64 } // NewStatus returns the current status information of the process. @@ -161,10 +165,38 @@ func (s *ProcStatus) fillStatus(k string, vString string, vUint uint64, vUintByt s.VoluntaryCtxtSwitches = vUint case "nonvoluntary_ctxt_switches": s.NonVoluntaryCtxtSwitches = vUint + case "Cpus_allowed_list": + s.CpusAllowedList = calcCpusAllowedList(vString) } + } // TotalCtxtSwitches returns the total context switch. func (s ProcStatus) TotalCtxtSwitches() uint64 { return s.VoluntaryCtxtSwitches + s.NonVoluntaryCtxtSwitches } + +func calcCpusAllowedList(cpuString string) []uint64 { + s := strings.Split(cpuString, ",") + + var g []uint64 + + for _, cpu := range s { + // parse cpu ranges, example: 1-3=[1,2,3] + if l := strings.Split(strings.TrimSpace(cpu), "-"); len(l) > 1 { + startCPU, _ := strconv.ParseUint(l[0], 10, 64) + endCPU, _ := strconv.ParseUint(l[1], 10, 64) + + for i := startCPU; i <= endCPU; i++ { + g = append(g, i) + } + } else if len(l) == 1 { + cpu, _ := strconv.ParseUint(l[0], 10, 64) + g = append(g, cpu) + } + + } + + sort.Slice(g, func(i, j int) bool { return g[i] < g[j] }) + return g +} diff --git a/vendor/github.com/prometheus/procfs/thread.go b/vendor/github.com/prometheus/procfs/thread.go index f08bfc769..490c14708 100644 --- a/vendor/github.com/prometheus/procfs/thread.go +++ b/vendor/github.com/prometheus/procfs/thread.go @@ -54,7 +54,8 @@ func (fs FS) AllThreads(pid int) (Procs, error) { if err != nil { continue } - t = append(t, Proc{PID: int(tid), fs: fsi.FS(taskPath)}) + + t = append(t, Proc{PID: int(tid), fs: FS{fsi.FS(taskPath), fs.real}}) } return t, nil @@ -66,13 +67,13 @@ func (fs FS) Thread(pid, tid int) (Proc, error) { if _, err := os.Stat(taskPath); err != nil { return Proc{}, err } - return Proc{PID: tid, fs: fsi.FS(taskPath)}, nil + return Proc{PID: tid, fs: FS{fsi.FS(taskPath), fs.real}}, nil } // Thread returns a process for a given TID of Proc. func (proc Proc) Thread(tid int) (Proc, error) { - tfs := fsi.FS(proc.path("task")) - if _, err := os.Stat(tfs.Path(strconv.Itoa(tid))); err != nil { + tfs := FS{fsi.FS(proc.path("task")), proc.fs.real} + if _, err := os.Stat(tfs.proc.Path(strconv.Itoa(tid))); err != nil { return Proc{}, err } return Proc{PID: tid, fs: tfs}, nil diff --git a/vendor/github.com/r3labs/sse/v2/.gitignore b/vendor/github.com/r3labs/sse/v2/.gitignore new file mode 100644 index 000000000..d48c759d6 --- /dev/null +++ b/vendor/github.com/r3labs/sse/v2/.gitignore @@ -0,0 +1,2 @@ +.idea +.vscode \ No newline at end of file diff --git a/vendor/github.com/r3labs/sse/v2/.golangci.yml b/vendor/github.com/r3labs/sse/v2/.golangci.yml new file mode 100644 index 000000000..5a76e9a0f --- /dev/null +++ b/vendor/github.com/r3labs/sse/v2/.golangci.yml @@ -0,0 +1,15 @@ +linters: + enable-all: true + disable: + - gofmt + - gofumpt + - goimports + - golint # deprecated + - interfacer # deprecated + - maligned # deprecated + - scopelint # deprecated + - varnamelen + +linters-settings: + govet: + enable-all: true diff --git a/vendor/github.com/r3labs/sse/v2/CONTRIBUTING.md b/vendor/github.com/r3labs/sse/v2/CONTRIBUTING.md new file mode 100644 index 000000000..b9c7859d3 --- /dev/null +++ b/vendor/github.com/r3labs/sse/v2/CONTRIBUTING.md @@ -0,0 +1,80 @@ +# Contributing guidelines + +Looking to contribute something to this project? Here's how you can help: + +Please take a moment to review this document in order to make the contribution process easy and effective for everyone involved. + +Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue or assessing patches and features. + +We also have a [code of conduct](https://ernest.io/conduct). + +## Using the issue tracker + +The issue tracker is the preferred channel for [bug reports](#bug-reports), [features requests](#feature-requests) and [submitting pull requests](#pull-requests), but please respect the following restrictions: + +* Please **do not** use the issue tracker for personal support requests. + +* Please **do not** derail issues. Keep the discussion on topic and + respect the opinions of others. + + +## Bug reports + +A bug is a _demonstrable problem_ that is caused by the code in the repository. +Good bug reports are extremely helpful - thank you! + +Guidelines for bug reports: + +1. **Use the GitHub issue search** — check if the issue has already been + reported. + +2. **Check if the issue has been fixed** — try to reproduce it using the + latest `master` or `develop` branch in the repository. + +3. **Isolate the problem** — create a reduced test case and a live example. + +A good bug report shouldn't leave others needing to chase you up for more +information. Please try to be as detailed as possible in your report. What is +your environment? What steps will reproduce the issue? Which environment experience the problem? What would you expect to be the outcome? All these +details will help people to fix any potential bugs. + +Example: + +> Short and descriptive example bug report title +> +> A summary of the issue and the environment in which it occurs. If +> suitable, include the steps required to reproduce the bug. +> +> 1. This is the first step +> 2. This is the second step +> 3. Further steps, etc. +> +> `` - a link to the reduced test case +> +> Any other information you want to share that is relevant to the issue being +> reported. This might include the lines of code that you have identified as +> causing the bug, and potential solutions (and your opinions on their +> merits). + + +## Feature requests + +Feature requests are welcome. But take a moment to find out whether your idea +fits with the scope and aims of the project. It's up to *you* to make a strong +case to convince the project's developers of the merits of this feature. Please +provide as much detail and context as possible. + + +## Pull requests + +Good pull requests - patches, improvements, new features - are a fantastic +help. They should remain focused in scope and avoid containing unrelated +commits. + +[**Please ask first**](https://ernest.io/community) before embarking on any significant pull request (e.g. +implementing features, refactoring code, porting to a different language), +otherwise you risk spending a lot of time working on something that the +project's developers might not want to merge into the project. + +Please adhere to the coding conventions used throughout a project (indentation, +accurate comments, etc.) and any other requirements (such as test coverage). diff --git a/vendor/github.com/r3labs/sse/v2/LICENSE b/vendor/github.com/r3labs/sse/v2/LICENSE new file mode 100644 index 000000000..a612ad981 --- /dev/null +++ b/vendor/github.com/r3labs/sse/v2/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/vendor/github.com/r3labs/sse/v2/Makefile b/vendor/github.com/r3labs/sse/v2/Makefile new file mode 100644 index 000000000..a63b7001e --- /dev/null +++ b/vendor/github.com/r3labs/sse/v2/Makefile @@ -0,0 +1,20 @@ +install: + go install -v + +build: + go build -v ./... + +lint: + golint ./... + go vet ./... + +test: + go test -v ./... --cover + +deps: + go get -u gopkg.in/cenkalti/backoff.v1 + go get -u github.com/golang/lint/golint + go get -u github.com/stretchr/testify + +clean: + go clean diff --git a/vendor/github.com/r3labs/sse/v2/README.md b/vendor/github.com/r3labs/sse/v2/README.md new file mode 100644 index 000000000..c2201be69 --- /dev/null +++ b/vendor/github.com/r3labs/sse/v2/README.md @@ -0,0 +1,191 @@ +# SSE - Server Sent Events Client/Server Library for Go + +## Synopsis + +SSE is a client/server implementation for Server Sent Events for Golang. + +## Build status + +* Master: [![CircleCI Master](https://circleci.com/gh/r3labs/sse.svg?style=svg)](https://circleci.com/gh/r3labs/sse) + +## Quick start + +To install: +``` +go get github.com/r3labs/sse/v2 +``` + +To Test: + +```sh +$ make deps +$ make test +``` + +#### Example Server + +There are two parts of the server. It is comprised of the message scheduler and a http handler function. +The messaging system is started when running: + +```go +func main() { + server := sse.New() +} +``` + +To add a stream to this handler: + +```go +func main() { + server := sse.New() + server.CreateStream("messages") +} +``` + +This creates a new stream inside of the scheduler. Seeing as there are no consumers, publishing a message to this channel will do nothing. +Clients can connect to this stream once the http handler is started by specifying _stream_ as a url parameter, like so: + +``` +http://server/events?stream=messages +``` + + +In order to start the http server: + +```go +func main() { + server := sse.New() + + // Create a new Mux and set the handler + mux := http.NewServeMux() + mux.HandleFunc("/events", server.ServeHTTP) + + http.ListenAndServe(":8080", mux) +} +``` + +To publish messages to a stream: + +```go +func main() { + server := sse.New() + + // Publish a payload to the stream + server.Publish("messages", &sse.Event{ + Data: []byte("ping"), + }) +} +``` + +Please note there must be a stream with the name you specify and there must be subscribers to that stream + +A way to detect disconnected clients: + +```go +func main() { + server := sse.New() + + mux := http.NewServeMux() + mux.HandleFunc("/events", func(w http.ResponseWriter, r *http.Request) { + go func() { + // Received Browser Disconnection + <-r.Context().Done() + println("The client is disconnected here") + return + }() + + server.ServeHTTP(w, r) + }) + + http.ListenAndServe(":8080", mux) +} +``` + +#### Example Client + +The client exposes a way to connect to an SSE server. The client can also handle multiple events under the same url. + +To create a new client: + +```go +func main() { + client := sse.NewClient("http://server/events") +} +``` + +To subscribe to an event stream, please use the Subscribe function. This accepts the name of the stream and a handler function: + +```go +func main() { + client := sse.NewClient("http://server/events") + + client.Subscribe("messages", func(msg *sse.Event) { + // Got some data! + fmt.Println(msg.Data) + }) +} +``` + +Please note that this function will block the current thread. You can run this function in a go routine. + +If you wish to have events sent to a channel, you can use SubscribeChan: + +```go +func main() { + events := make(chan *sse.Event) + + client := sse.NewClient("http://server/events") + client.SubscribeChan("messages", events) +} +``` + +#### HTTP client parameters + +To add additional parameters to the http client, such as disabling ssl verification for self signed certs, you can override the http client or update its options: + +```go +func main() { + client := sse.NewClient("http://server/events") + client.Connection.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } +} +``` + +#### URL query parameters + +To set custom query parameters on the client or disable the stream parameter altogether: + +```go +func main() { + client := sse.NewClient("http://server/events?search=example") + + client.SubscribeRaw(func(msg *sse.Event) { + // Got some data! + fmt.Println(msg.Data) + }) +} +``` + + +## Contributing + +Please read through our +[contributing guidelines](CONTRIBUTING.md). +Included are directions for opening issues, coding standards, and notes on +development. + +Moreover, if your pull request contains patches or features, you must include +relevant unit tests. + +## Versioning + +For transparency into our release cycle and in striving to maintain backward +compatibility, this project is maintained under [the Semantic Versioning guidelines](http://semver.org/). + +## Copyright and License + +Code and documentation copyright since 2015 r3labs.io authors. + +Code released under +[the Mozilla Public License Version 2.0](LICENSE). diff --git a/vendor/github.com/r3labs/sse/v2/client.go b/vendor/github.com/r3labs/sse/v2/client.go new file mode 100644 index 000000000..61772b624 --- /dev/null +++ b/vendor/github.com/r3labs/sse/v2/client.go @@ -0,0 +1,390 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package sse + +import ( + "bytes" + "context" + "encoding/base64" + "errors" + "fmt" + "io" + "net/http" + "sync" + "sync/atomic" + "time" + + "gopkg.in/cenkalti/backoff.v1" +) + +var ( + headerID = []byte("id:") + headerData = []byte("data:") + headerEvent = []byte("event:") + headerRetry = []byte("retry:") +) + +func ClientMaxBufferSize(s int) func(c *Client) { + return func(c *Client) { + c.maxBufferSize = s + } +} + +// ConnCallback defines a function to be called on a particular connection event +type ConnCallback func(c *Client) + +// ResponseValidator validates a response +type ResponseValidator func(c *Client, resp *http.Response) error + +// Client handles an incoming server stream +type Client struct { + Retry time.Time + ReconnectStrategy backoff.BackOff + disconnectcb ConnCallback + connectedcb ConnCallback + subscribed map[chan *Event]chan struct{} + Headers map[string]string + ReconnectNotify backoff.Notify + ResponseValidator ResponseValidator + Connection *http.Client + URL string + LastEventID atomic.Value // []byte + maxBufferSize int + mu sync.Mutex + EncodingBase64 bool + Connected bool +} + +// NewClient creates a new client +func NewClient(url string, opts ...func(c *Client)) *Client { + c := &Client{ + URL: url, + Connection: &http.Client{}, + Headers: make(map[string]string), + subscribed: make(map[chan *Event]chan struct{}), + maxBufferSize: 1 << 16, + } + + for _, opt := range opts { + opt(c) + } + + return c +} + +// Subscribe to a data stream +func (c *Client) Subscribe(stream string, handler func(msg *Event)) error { + return c.SubscribeWithContext(context.Background(), stream, handler) +} + +// SubscribeWithContext to a data stream with context +func (c *Client) SubscribeWithContext(ctx context.Context, stream string, handler func(msg *Event)) error { + operation := func() error { + resp, err := c.request(ctx, stream) + if err != nil { + return err + } + if validator := c.ResponseValidator; validator != nil { + err = validator(c, resp) + if err != nil { + return err + } + } else if resp.StatusCode != 200 { + resp.Body.Close() + return fmt.Errorf("could not connect to stream: %s", http.StatusText(resp.StatusCode)) + } + defer resp.Body.Close() + + reader := NewEventStreamReader(resp.Body, c.maxBufferSize) + eventChan, errorChan := c.startReadLoop(reader) + + for { + select { + case err = <-errorChan: + return err + case msg := <-eventChan: + handler(msg) + } + } + } + + // Apply user specified reconnection strategy or default to standard NewExponentialBackOff() reconnection method + var err error + if c.ReconnectStrategy != nil { + err = backoff.RetryNotify(operation, c.ReconnectStrategy, c.ReconnectNotify) + } else { + err = backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), c.ReconnectNotify) + } + return err +} + +// SubscribeChan sends all events to the provided channel +func (c *Client) SubscribeChan(stream string, ch chan *Event) error { + return c.SubscribeChanWithContext(context.Background(), stream, ch) +} + +// SubscribeChanWithContext sends all events to the provided channel with context +func (c *Client) SubscribeChanWithContext(ctx context.Context, stream string, ch chan *Event) error { + var connected bool + errch := make(chan error) + c.mu.Lock() + c.subscribed[ch] = make(chan struct{}) + c.mu.Unlock() + + operation := func() error { + resp, err := c.request(ctx, stream) + if err != nil { + return err + } + if validator := c.ResponseValidator; validator != nil { + err = validator(c, resp) + if err != nil { + return err + } + } else if resp.StatusCode != 200 { + resp.Body.Close() + return fmt.Errorf("could not connect to stream: %s", http.StatusText(resp.StatusCode)) + } + defer resp.Body.Close() + + if !connected { + // Notify connect + errch <- nil + connected = true + } + + reader := NewEventStreamReader(resp.Body, c.maxBufferSize) + eventChan, errorChan := c.startReadLoop(reader) + + for { + var msg *Event + // Wait for message to arrive or exit + select { + case <-c.subscribed[ch]: + return nil + case err = <-errorChan: + return err + case msg = <-eventChan: + } + + // Wait for message to be sent or exit + if msg != nil { + select { + case <-c.subscribed[ch]: + return nil + case ch <- msg: + // message sent + } + } + } + } + + go func() { + defer c.cleanup(ch) + // Apply user specified reconnection strategy or default to standard NewExponentialBackOff() reconnection method + var err error + if c.ReconnectStrategy != nil { + err = backoff.RetryNotify(operation, c.ReconnectStrategy, c.ReconnectNotify) + } else { + err = backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), c.ReconnectNotify) + } + + // channel closed once connected + if err != nil && !connected { + errch <- err + } + }() + err := <-errch + close(errch) + return err +} + +func (c *Client) startReadLoop(reader *EventStreamReader) (chan *Event, chan error) { + outCh := make(chan *Event) + erChan := make(chan error) + go c.readLoop(reader, outCh, erChan) + return outCh, erChan +} + +func (c *Client) readLoop(reader *EventStreamReader, outCh chan *Event, erChan chan error) { + for { + // Read each new line and process the type of event + event, err := reader.ReadEvent() + if err != nil { + if err == io.EOF { + erChan <- nil + return + } + // run user specified disconnect function + if c.disconnectcb != nil { + c.Connected = false + c.disconnectcb(c) + } + erChan <- err + return + } + + if !c.Connected && c.connectedcb != nil { + c.Connected = true + c.connectedcb(c) + } + + // If we get an error, ignore it. + var msg *Event + if msg, err = c.processEvent(event); err == nil { + if len(msg.ID) > 0 { + c.LastEventID.Store(msg.ID) + } else { + msg.ID, _ = c.LastEventID.Load().([]byte) + } + + // Send downstream if the event has something useful + if msg.hasContent() { + outCh <- msg + } + } + } +} + +// SubscribeRaw to an sse endpoint +func (c *Client) SubscribeRaw(handler func(msg *Event)) error { + return c.Subscribe("", handler) +} + +// SubscribeRawWithContext to an sse endpoint with context +func (c *Client) SubscribeRawWithContext(ctx context.Context, handler func(msg *Event)) error { + return c.SubscribeWithContext(ctx, "", handler) +} + +// SubscribeChanRaw sends all events to the provided channel +func (c *Client) SubscribeChanRaw(ch chan *Event) error { + return c.SubscribeChan("", ch) +} + +// SubscribeChanRawWithContext sends all events to the provided channel with context +func (c *Client) SubscribeChanRawWithContext(ctx context.Context, ch chan *Event) error { + return c.SubscribeChanWithContext(ctx, "", ch) +} + +// Unsubscribe unsubscribes a channel +func (c *Client) Unsubscribe(ch chan *Event) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.subscribed[ch] != nil { + c.subscribed[ch] <- struct{}{} + } +} + +// OnDisconnect specifies the function to run when the connection disconnects +func (c *Client) OnDisconnect(fn ConnCallback) { + c.disconnectcb = fn +} + +// OnConnect specifies the function to run when the connection is successful +func (c *Client) OnConnect(fn ConnCallback) { + c.connectedcb = fn +} + +func (c *Client) request(ctx context.Context, stream string) (*http.Response, error) { + req, err := http.NewRequest("GET", c.URL, nil) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + + // Setup request, specify stream to connect to + if stream != "" { + query := req.URL.Query() + query.Add("stream", stream) + req.URL.RawQuery = query.Encode() + } + + req.Header.Set("Cache-Control", "no-cache") + req.Header.Set("Accept", "text/event-stream") + req.Header.Set("Connection", "keep-alive") + + lastID, exists := c.LastEventID.Load().([]byte) + if exists && lastID != nil { + req.Header.Set("Last-Event-ID", string(lastID)) + } + + // Add user specified headers + for k, v := range c.Headers { + req.Header.Set(k, v) + } + + return c.Connection.Do(req) +} + +func (c *Client) processEvent(msg []byte) (event *Event, err error) { + var e Event + + if len(msg) < 1 { + return nil, errors.New("event message was empty") + } + + // Normalize the crlf to lf to make it easier to split the lines. + // Split the line by "\n" or "\r", per the spec. + for _, line := range bytes.FieldsFunc(msg, func(r rune) bool { return r == '\n' || r == '\r' }) { + switch { + case bytes.HasPrefix(line, headerID): + e.ID = append([]byte(nil), trimHeader(len(headerID), line)...) + case bytes.HasPrefix(line, headerData): + // The spec allows for multiple data fields per event, concatenated them with "\n". + e.Data = append(e.Data[:], append(trimHeader(len(headerData), line), byte('\n'))...) + // The spec says that a line that simply contains the string "data" should be treated as a data field with an empty body. + case bytes.Equal(line, bytes.TrimSuffix(headerData, []byte(":"))): + e.Data = append(e.Data, byte('\n')) + case bytes.HasPrefix(line, headerEvent): + e.Event = append([]byte(nil), trimHeader(len(headerEvent), line)...) + case bytes.HasPrefix(line, headerRetry): + e.Retry = append([]byte(nil), trimHeader(len(headerRetry), line)...) + default: + // Ignore any garbage that doesn't match what we're looking for. + } + } + + // Trim the last "\n" per the spec. + e.Data = bytes.TrimSuffix(e.Data, []byte("\n")) + + if c.EncodingBase64 { + buf := make([]byte, base64.StdEncoding.DecodedLen(len(e.Data))) + + n, err := base64.StdEncoding.Decode(buf, e.Data) + if err != nil { + err = fmt.Errorf("failed to decode event message: %s", err) + } + e.Data = buf[:n] + } + return &e, err +} + +func (c *Client) cleanup(ch chan *Event) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.subscribed[ch] != nil { + close(c.subscribed[ch]) + delete(c.subscribed, ch) + } +} + +func trimHeader(size int, data []byte) []byte { + if data == nil || len(data) < size { + return data + } + + data = data[size:] + // Remove optional leading whitespace + if len(data) > 0 && data[0] == 32 { + data = data[1:] + } + // Remove trailing new line + if len(data) > 0 && data[len(data)-1] == 10 { + data = data[:len(data)-1] + } + return data +} diff --git a/vendor/github.com/r3labs/sse/v2/event.go b/vendor/github.com/r3labs/sse/v2/event.go new file mode 100644 index 000000000..125803878 --- /dev/null +++ b/vendor/github.com/r3labs/sse/v2/event.go @@ -0,0 +1,114 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package sse + +import ( + "bufio" + "bytes" + "context" + "io" + "time" +) + +// Event holds all of the event source fields +type Event struct { + timestamp time.Time + ID []byte + Data []byte + Event []byte + Retry []byte + Comment []byte +} + +func (e *Event) hasContent() bool { + return len(e.ID) > 0 || len(e.Data) > 0 || len(e.Event) > 0 || len(e.Retry) > 0 +} + +// EventStreamReader scans an io.Reader looking for EventStream messages. +type EventStreamReader struct { + scanner *bufio.Scanner +} + +// NewEventStreamReader creates an instance of EventStreamReader. +func NewEventStreamReader(eventStream io.Reader, maxBufferSize int) *EventStreamReader { + scanner := bufio.NewScanner(eventStream) + initBufferSize := minPosInt(4096, maxBufferSize) + scanner.Buffer(make([]byte, initBufferSize), maxBufferSize) + + split := func(data []byte, atEOF bool) (int, []byte, error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + + // We have a full event payload to parse. + if i, nlen := containsDoubleNewline(data); i >= 0 { + return i + nlen, data[0:i], nil + } + // If we're at EOF, we have all of the data. + if atEOF { + return len(data), data, nil + } + // Request more data. + return 0, nil, nil + } + // Set the split function for the scanning operation. + scanner.Split(split) + + return &EventStreamReader{ + scanner: scanner, + } +} + +// Returns a tuple containing the index of a double newline, and the number of bytes +// represented by that sequence. If no double newline is present, the first value +// will be negative. +func containsDoubleNewline(data []byte) (int, int) { + // Search for each potentially valid sequence of newline characters + crcr := bytes.Index(data, []byte("\r\r")) + lflf := bytes.Index(data, []byte("\n\n")) + crlflf := bytes.Index(data, []byte("\r\n\n")) + lfcrlf := bytes.Index(data, []byte("\n\r\n")) + crlfcrlf := bytes.Index(data, []byte("\r\n\r\n")) + // Find the earliest position of a double newline combination + minPos := minPosInt(crcr, minPosInt(lflf, minPosInt(crlflf, minPosInt(lfcrlf, crlfcrlf)))) + // Detemine the length of the sequence + nlen := 2 + if minPos == crlfcrlf { + nlen = 4 + } else if minPos == crlflf || minPos == lfcrlf { + nlen = 3 + } + return minPos, nlen +} + +// Returns the minimum non-negative value out of the two values. If both +// are negative, a negative value is returned. +func minPosInt(a, b int) int { + if a < 0 { + return b + } + if b < 0 { + return a + } + if a > b { + return b + } + return a +} + +// ReadEvent scans the EventStream for events. +func (e *EventStreamReader) ReadEvent() ([]byte, error) { + if e.scanner.Scan() { + event := e.scanner.Bytes() + return event, nil + } + if err := e.scanner.Err(); err != nil { + if err == context.Canceled { + return nil, io.EOF + } + return nil, err + } + return nil, io.EOF +} diff --git a/vendor/github.com/r3labs/sse/v2/event_log.go b/vendor/github.com/r3labs/sse/v2/event_log.go new file mode 100644 index 000000000..aa17dad05 --- /dev/null +++ b/vendor/github.com/r3labs/sse/v2/event_log.go @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package sse + +import ( + "strconv" + "time" +) + +// EventLog holds all of previous events +type EventLog []*Event + +// Add event to eventlog +func (e *EventLog) Add(ev *Event) { + if !ev.hasContent() { + return + } + + ev.ID = []byte(e.currentindex()) + ev.timestamp = time.Now() + *e = append(*e, ev) +} + +// Clear events from eventlog +func (e *EventLog) Clear() { + *e = nil +} + +// Replay events to a subscriber +func (e *EventLog) Replay(s *Subscriber) { + for i := 0; i < len(*e); i++ { + id, _ := strconv.Atoi(string((*e)[i].ID)) + if id >= s.eventid { + s.connection <- (*e)[i] + } + } +} + +func (e *EventLog) currentindex() string { + return strconv.Itoa(len(*e)) +} diff --git a/vendor/github.com/r3labs/sse/v2/http.go b/vendor/github.com/r3labs/sse/v2/http.go new file mode 100644 index 000000000..c7a2b434a --- /dev/null +++ b/vendor/github.com/r3labs/sse/v2/http.go @@ -0,0 +1,120 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package sse + +import ( + "bytes" + "fmt" + "net/http" + "strconv" + "time" +) + +// ServeHTTP serves new connections with events for a given stream ... +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + flusher, err := w.(http.Flusher) + if !err { + http.Error(w, "Streaming unsupported!", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Connection", "keep-alive") + + for k, v := range s.Headers { + w.Header().Set(k, v) + } + + // Get the StreamID from the URL + streamID := r.URL.Query().Get("stream") + if streamID == "" { + http.Error(w, "Please specify a stream!", http.StatusInternalServerError) + return + } + + stream := s.getStream(streamID) + + if stream == nil { + if !s.AutoStream { + http.Error(w, "Stream not found!", http.StatusInternalServerError) + return + } + + stream = s.CreateStream(streamID) + } + + eventid := 0 + if id := r.Header.Get("Last-Event-ID"); id != "" { + var err error + eventid, err = strconv.Atoi(id) + if err != nil { + http.Error(w, "Last-Event-ID must be a number!", http.StatusBadRequest) + return + } + } + + // Create the stream subscriber + sub := stream.addSubscriber(eventid, r.URL) + + go func() { + <-r.Context().Done() + + sub.close() + + if s.AutoStream && !s.AutoReplay && stream.getSubscriberCount() == 0 { + s.RemoveStream(streamID) + } + }() + + w.WriteHeader(http.StatusOK) + flusher.Flush() + + // Push events to client + for ev := range sub.connection { + // If the data buffer is an empty string abort. + if len(ev.Data) == 0 && len(ev.Comment) == 0 { + break + } + + // if the event has expired, dont send it + if s.EventTTL != 0 && time.Now().After(ev.timestamp.Add(s.EventTTL)) { + continue + } + + if len(ev.Data) > 0 { + fmt.Fprintf(w, "id: %s\n", ev.ID) + + if s.SplitData { + sd := bytes.Split(ev.Data, []byte("\n")) + for i := range sd { + fmt.Fprintf(w, "data: %s\n", sd[i]) + } + } else { + if bytes.HasPrefix(ev.Data, []byte(":")) { + fmt.Fprintf(w, "%s\n", ev.Data) + } else { + fmt.Fprintf(w, "data: %s\n", ev.Data) + } + } + + if len(ev.Event) > 0 { + fmt.Fprintf(w, "event: %s\n", ev.Event) + } + + if len(ev.Retry) > 0 { + fmt.Fprintf(w, "retry: %s\n", ev.Retry) + } + } + + if len(ev.Comment) > 0 { + fmt.Fprintf(w, ": %s\n", ev.Comment) + } + + fmt.Fprint(w, "\n") + + flusher.Flush() + } +} diff --git a/vendor/github.com/r3labs/sse/v2/server.go b/vendor/github.com/r3labs/sse/v2/server.go new file mode 100644 index 000000000..d1b27af32 --- /dev/null +++ b/vendor/github.com/r3labs/sse/v2/server.go @@ -0,0 +1,156 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package sse + +import ( + "encoding/base64" + "sync" + "time" +) + +// DefaultBufferSize size of the queue that holds the streams messages. +const DefaultBufferSize = 1024 + +// Server Is our main struct +type Server struct { + // Extra headers adding to the HTTP response to each client + Headers map[string]string + // Sets a ttl that prevents old events from being transmitted + EventTTL time.Duration + // Specifies the size of the message buffer for each stream + BufferSize int + // Encodes all data as base64 + EncodeBase64 bool + // Splits an events data into multiple data: entries + SplitData bool + // Enables creation of a stream when a client connects + AutoStream bool + // Enables automatic replay for each new subscriber that connects + AutoReplay bool + + // Specifies the function to run when client subscribe or un-subscribe + OnSubscribe func(streamID string, sub *Subscriber) + OnUnsubscribe func(streamID string, sub *Subscriber) + + streams map[string]*Stream + muStreams sync.RWMutex +} + +// New will create a server and setup defaults +func New() *Server { + return &Server{ + BufferSize: DefaultBufferSize, + AutoStream: false, + AutoReplay: true, + streams: make(map[string]*Stream), + Headers: map[string]string{}, + } +} + +// NewWithCallback will create a server and setup defaults with callback function +func NewWithCallback(onSubscribe, onUnsubscribe func(streamID string, sub *Subscriber)) *Server { + return &Server{ + BufferSize: DefaultBufferSize, + AutoStream: false, + AutoReplay: true, + streams: make(map[string]*Stream), + Headers: map[string]string{}, + OnSubscribe: onSubscribe, + OnUnsubscribe: onUnsubscribe, + } +} + +// Close shuts down the server, closes all of the streams and connections +func (s *Server) Close() { + s.muStreams.Lock() + defer s.muStreams.Unlock() + + for id := range s.streams { + s.streams[id].close() + delete(s.streams, id) + } +} + +// CreateStream will create a new stream and register it +func (s *Server) CreateStream(id string) *Stream { + s.muStreams.Lock() + defer s.muStreams.Unlock() + + if s.streams[id] != nil { + return s.streams[id] + } + + str := newStream(id, s.BufferSize, s.AutoReplay, s.AutoStream, s.OnSubscribe, s.OnUnsubscribe) + str.run() + + s.streams[id] = str + + return str +} + +// RemoveStream will remove a stream +func (s *Server) RemoveStream(id string) { + s.muStreams.Lock() + defer s.muStreams.Unlock() + + if s.streams[id] != nil { + s.streams[id].close() + delete(s.streams, id) + } +} + +// StreamExists checks whether a stream by a given id exists +func (s *Server) StreamExists(id string) bool { + return s.getStream(id) != nil +} + +// Publish sends a mesage to every client in a streamID. +// If the stream's buffer is full, it blocks until the message is sent out to +// all subscribers (but not necessarily arrived the clients), or when the +// stream is closed. +func (s *Server) Publish(id string, event *Event) { + stream := s.getStream(id) + if stream == nil { + return + } + + select { + case <-stream.quit: + case stream.event <- s.process(event): + } +} + +// TryPublish is the same as Publish except that when the operation would cause +// the call to be blocked, it simply drops the message and returns false. +// Together with a small BufferSize, it can be useful when publishing the +// latest message ASAP is more important than reliable delivery. +func (s *Server) TryPublish(id string, event *Event) bool { + stream := s.getStream(id) + if stream == nil { + return false + } + + select { + case stream.event <- s.process(event): + return true + default: + return false + } +} + +func (s *Server) getStream(id string) *Stream { + s.muStreams.RLock() + defer s.muStreams.RUnlock() + return s.streams[id] +} + +func (s *Server) process(event *Event) *Event { + if s.EncodeBase64 { + output := make([]byte, base64.StdEncoding.EncodedLen(len(event.Data))) + base64.StdEncoding.Encode(output, event.Data) + event.Data = output + } + return event +} diff --git a/vendor/github.com/r3labs/sse/v2/stream.go b/vendor/github.com/r3labs/sse/v2/stream.go new file mode 100644 index 000000000..bfbcb9b52 --- /dev/null +++ b/vendor/github.com/r3labs/sse/v2/stream.go @@ -0,0 +1,153 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package sse + +import ( + "net/url" + "sync" + "sync/atomic" +) + +// Stream ... +type Stream struct { + ID string + event chan *Event + quit chan struct{} + quitOnce sync.Once + register chan *Subscriber + deregister chan *Subscriber + subscribers []*Subscriber + Eventlog EventLog + subscriberCount int32 + // Enables replaying of eventlog to newly added subscribers + AutoReplay bool + isAutoStream bool + + // Specifies the function to run when client subscribe or un-subscribe + OnSubscribe func(streamID string, sub *Subscriber) + OnUnsubscribe func(streamID string, sub *Subscriber) +} + +// newStream returns a new stream +func newStream(id string, buffSize int, replay, isAutoStream bool, onSubscribe, onUnsubscribe func(string, *Subscriber)) *Stream { + return &Stream{ + ID: id, + AutoReplay: replay, + subscribers: make([]*Subscriber, 0), + isAutoStream: isAutoStream, + register: make(chan *Subscriber), + deregister: make(chan *Subscriber), + event: make(chan *Event, buffSize), + quit: make(chan struct{}), + Eventlog: make(EventLog, 0), + OnSubscribe: onSubscribe, + OnUnsubscribe: onUnsubscribe, + } +} + +func (str *Stream) run() { + go func(str *Stream) { + for { + select { + // Add new subscriber + case subscriber := <-str.register: + str.subscribers = append(str.subscribers, subscriber) + if str.AutoReplay { + str.Eventlog.Replay(subscriber) + } + + // Remove closed subscriber + case subscriber := <-str.deregister: + i := str.getSubIndex(subscriber) + if i != -1 { + str.removeSubscriber(i) + } + + if str.OnUnsubscribe != nil { + go str.OnUnsubscribe(str.ID, subscriber) + } + + // Publish event to subscribers + case event := <-str.event: + if str.AutoReplay { + str.Eventlog.Add(event) + } + for i := range str.subscribers { + str.subscribers[i].connection <- event + } + + // Shutdown if the server closes + case <-str.quit: + // remove connections + str.removeAllSubscribers() + return + } + } + }(str) +} + +func (str *Stream) close() { + str.quitOnce.Do(func() { + close(str.quit) + }) +} + +func (str *Stream) getSubIndex(sub *Subscriber) int { + for i := range str.subscribers { + if str.subscribers[i] == sub { + return i + } + } + return -1 +} + +// addSubscriber will create a new subscriber on a stream +func (str *Stream) addSubscriber(eventid int, url *url.URL) *Subscriber { + atomic.AddInt32(&str.subscriberCount, 1) + sub := &Subscriber{ + eventid: eventid, + quit: str.deregister, + connection: make(chan *Event, 64), + URL: url, + } + + if str.isAutoStream { + sub.removed = make(chan struct{}, 1) + } + + str.register <- sub + + if str.OnSubscribe != nil { + go str.OnSubscribe(str.ID, sub) + } + + return sub +} + +func (str *Stream) removeSubscriber(i int) { + atomic.AddInt32(&str.subscriberCount, -1) + close(str.subscribers[i].connection) + if str.subscribers[i].removed != nil { + str.subscribers[i].removed <- struct{}{} + close(str.subscribers[i].removed) + } + str.subscribers = append(str.subscribers[:i], str.subscribers[i+1:]...) +} + +func (str *Stream) removeAllSubscribers() { + for i := 0; i < len(str.subscribers); i++ { + close(str.subscribers[i].connection) + if str.subscribers[i].removed != nil { + str.subscribers[i].removed <- struct{}{} + close(str.subscribers[i].removed) + } + } + atomic.StoreInt32(&str.subscriberCount, 0) + str.subscribers = str.subscribers[:0] +} + +func (str *Stream) getSubscriberCount() int { + return int(atomic.LoadInt32(&str.subscriberCount)) +} diff --git a/vendor/github.com/r3labs/sse/v2/subscriber.go b/vendor/github.com/r3labs/sse/v2/subscriber.go new file mode 100644 index 000000000..4b54c204f --- /dev/null +++ b/vendor/github.com/r3labs/sse/v2/subscriber.go @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package sse + +import "net/url" + +// Subscriber ... +type Subscriber struct { + quit chan *Subscriber + connection chan *Event + removed chan struct{} + eventid int + URL *url.URL +} + +// Close will let the stream know that the clients connection has terminated +func (s *Subscriber) close() { + s.quit <- s + if s.removed != nil { + <-s.removed + } +} diff --git a/vendor/github.com/riandyrn/otelchi/LICENSE b/vendor/github.com/riandyrn/otelchi/LICENSE new file mode 100644 index 000000000..3e2e03d70 --- /dev/null +++ b/vendor/github.com/riandyrn/otelchi/LICENSE @@ -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 [2021] [Riandy Rahman Nugraha] + + 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. diff --git a/vendor/github.com/riandyrn/otelchi/Makefile b/vendor/github.com/riandyrn/otelchi/Makefile new file mode 100644 index 000000000..93b3329a3 --- /dev/null +++ b/vendor/github.com/riandyrn/otelchi/Makefile @@ -0,0 +1,4 @@ +.PHONY: * + +test: + go test -v ./... \ No newline at end of file diff --git a/vendor/github.com/riandyrn/otelchi/README.md b/vendor/github.com/riandyrn/otelchi/README.md new file mode 100644 index 000000000..ba08bb7a5 --- /dev/null +++ b/vendor/github.com/riandyrn/otelchi/README.md @@ -0,0 +1,27 @@ +# otelchi + +[![ci](https://github.com/riandyrn/otelchi/actions/workflows/ci.yaml/badge.svg)](https://github.com/riandyrn/otelchi/actions/workflows/ci.yaml) +[![Go Report Card](https://goreportcard.com/badge/github.com/riandyrn/otelchi)](https://goreportcard.com/report/github.com/riandyrn/otelchi) +[![Documentation](https://godoc.org/github.com/riandyrn/otelchi?status.svg)](https://pkg.go.dev/mod/github.com/riandyrn/otelchi) + +OpenTelemetry instrumentation for [go-chi/chi](https://github.com/go-chi/chi). + +Essentialy this is adaptation from [otelmux](https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/github.com/gorilla/mux/otelmux) but instead using `gorilla/mux`, we use `go-chi/chi`. + +Currently it could only instrument traces. + +Contributions are welcomed! + +## Install + +```bash +$ go get github.com/riandyrn/otelchi +``` + +## Examples + +See [examples](./examples) for details. + +## Why Port This? + +I was planning to make this project as part of Open Telemetry Go instrumentation project. However based on [this comment](https://github.com/open-telemetry/opentelemetry-go-contrib/pull/986#issuecomment-941280855) they no longer accept new instrumentation. This is why I maintain this project here. \ No newline at end of file diff --git a/vendor/github.com/riandyrn/otelchi/config.go b/vendor/github.com/riandyrn/otelchi/config.go new file mode 100644 index 000000000..2570b1848 --- /dev/null +++ b/vendor/github.com/riandyrn/otelchi/config.go @@ -0,0 +1,82 @@ +package otelchi + +import ( + "net/http" + + "github.com/go-chi/chi/v5" + "go.opentelemetry.io/otel/propagation" + oteltrace "go.opentelemetry.io/otel/trace" +) + +// config is used to configure the mux middleware. +type config struct { + TracerProvider oteltrace.TracerProvider + Propagators propagation.TextMapPropagator + ChiRoutes chi.Routes + RequestMethodInSpanName bool + Filter func(r *http.Request) bool +} + +// Option specifies instrumentation configuration options. +type Option interface { + apply(*config) +} + +type optionFunc func(*config) + +func (o optionFunc) apply(c *config) { + o(c) +} + +// WithPropagators specifies propagators to use for extracting +// information from the HTTP requests. If none are specified, global +// ones will be used. +func WithPropagators(propagators propagation.TextMapPropagator) Option { + return optionFunc(func(cfg *config) { + cfg.Propagators = propagators + }) +} + +// WithTracerProvider specifies a tracer provider to use for creating a tracer. +// If none is specified, the global provider is used. +func WithTracerProvider(provider oteltrace.TracerProvider) Option { + return optionFunc(func(cfg *config) { + cfg.TracerProvider = provider + }) +} + +// WithChiRoutes specified the routes that being used by application. Its main +// purpose is to provide route pattern as span name during span creation. If this +// option is not set, by default the span will be given name at the end of span +// execution. For some people, this behavior is not desirable since they want +// to override the span name on underlying handler. By setting this option, it +// is possible for them to override the span name. +func WithChiRoutes(routes chi.Routes) Option { + return optionFunc(func(cfg *config) { + cfg.ChiRoutes = routes + }) +} + +// WithRequestMethodInSpanName is used for adding http request method to span name. +// While this is not necessary for vendors that properly implemented the tracing +// specs (e.g Jaeger, AWS X-Ray, etc...), but for other vendors such as Elastic +// and New Relic this might be helpful. +// +// See following threads for details: +// +// - https://github.com/riandyrn/otelchi/pull/3#issuecomment-1005883910 +// - https://github.com/riandyrn/otelchi/issues/6#issuecomment-1034461912 +func WithRequestMethodInSpanName(isActive bool) Option { + return optionFunc(func(cfg *config) { + cfg.RequestMethodInSpanName = isActive + }) +} + +// WithFilter is used for filtering request that should not be traced. +// This is useful for filtering health check request, etc. +// A Filter must return true if the request should be traced. +func WithFilter(filter func(r *http.Request) bool) Option { + return optionFunc(func(cfg *config) { + cfg.Filter = filter + }) +} diff --git a/vendor/github.com/riandyrn/otelchi/middleware.go b/vendor/github.com/riandyrn/otelchi/middleware.go new file mode 100644 index 000000000..7838ac3b7 --- /dev/null +++ b/vendor/github.com/riandyrn/otelchi/middleware.go @@ -0,0 +1,181 @@ +package otelchi + +import ( + "net/http" + "sync" + + "github.com/felixge/httpsnoop" + "github.com/go-chi/chi/v5" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" + + otelcontrib "go.opentelemetry.io/contrib" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" + oteltrace "go.opentelemetry.io/otel/trace" +) + +const ( + tracerName = "github.com/riandyrn/otelchi" +) + +// Middleware sets up a handler to start tracing the incoming +// requests. The serverName parameter should describe the name of the +// (virtual) server handling the request. +func Middleware(serverName string, opts ...Option) func(next http.Handler) http.Handler { + cfg := config{} + for _, opt := range opts { + opt.apply(&cfg) + } + if cfg.TracerProvider == nil { + cfg.TracerProvider = otel.GetTracerProvider() + } + tracer := cfg.TracerProvider.Tracer( + tracerName, + oteltrace.WithInstrumentationVersion(otelcontrib.SemVersion()), + ) + if cfg.Propagators == nil { + cfg.Propagators = otel.GetTextMapPropagator() + } + return func(handler http.Handler) http.Handler { + return traceware{ + serverName: serverName, + tracer: tracer, + propagators: cfg.Propagators, + handler: handler, + chiRoutes: cfg.ChiRoutes, + reqMethodInSpanName: cfg.RequestMethodInSpanName, + filter: cfg.Filter, + } + } +} + +type traceware struct { + serverName string + tracer oteltrace.Tracer + propagators propagation.TextMapPropagator + handler http.Handler + chiRoutes chi.Routes + reqMethodInSpanName bool + filter func(r *http.Request) bool +} + +type recordingResponseWriter struct { + writer http.ResponseWriter + written bool + status int +} + +var rrwPool = &sync.Pool{ + New: func() interface{} { + return &recordingResponseWriter{} + }, +} + +func getRRW(writer http.ResponseWriter) *recordingResponseWriter { + rrw := rrwPool.Get().(*recordingResponseWriter) + rrw.written = false + rrw.status = 0 + rrw.writer = httpsnoop.Wrap(writer, httpsnoop.Hooks{ + Write: func(next httpsnoop.WriteFunc) httpsnoop.WriteFunc { + return func(b []byte) (int, error) { + if !rrw.written { + rrw.written = true + rrw.status = http.StatusOK + } + return next(b) + } + }, + WriteHeader: func(next httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc { + return func(statusCode int) { + if !rrw.written { + rrw.written = true + rrw.status = statusCode + } + next(statusCode) + } + }, + }) + return rrw +} + +func putRRW(rrw *recordingResponseWriter) { + rrw.writer = nil + rrwPool.Put(rrw) +} + +// ServeHTTP implements the http.Handler interface. It does the actual +// tracing of the request. +func (tw traceware) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // skip if filter returns false + if tw.filter != nil && !tw.filter(r) { + tw.handler.ServeHTTP(w, r) + return + } + + // extract tracing header using propagator + ctx := tw.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header)) + // create span, based on specification, we need to set already known attributes + // when creating the span, the only thing missing here is HTTP route pattern since + // in go-chi/chi route pattern could only be extracted once the request is executed + // check here for details: + // + // https://github.com/go-chi/chi/issues/150#issuecomment-278850733 + // + // if we have access to chi routes, we could extract the route pattern beforehand. + spanName := "" + routePattern := "" + if tw.chiRoutes != nil { + rctx := chi.NewRouteContext() + if tw.chiRoutes.Match(rctx, r.Method, r.URL.Path) { + routePattern = rctx.RoutePattern() + spanName = addPrefixToSpanName(tw.reqMethodInSpanName, r.Method, routePattern) + } + } + + ctx, span := tw.tracer.Start( + ctx, spanName, + oteltrace.WithAttributes(semconv.NetAttributesFromHTTPRequest("tcp", r)...), + oteltrace.WithAttributes(semconv.EndUserAttributesFromHTTPRequest(r)...), + oteltrace.WithAttributes(semconv.HTTPServerAttributesFromHTTPRequest(tw.serverName, routePattern, r)...), + oteltrace.WithSpanKind(oteltrace.SpanKindServer), + ) + defer span.End() + + // get recording response writer + rrw := getRRW(w) + defer putRRW(rrw) + + // execute next http handler + r = r.WithContext(ctx) + tw.handler.ServeHTTP(rrw.writer, r) + + // set span name & http route attribute if necessary + if len(routePattern) == 0 { + routePattern = chi.RouteContext(r.Context()).RoutePattern() + span.SetAttributes(semconv.HTTPRouteKey.String(routePattern)) + + spanName = addPrefixToSpanName(tw.reqMethodInSpanName, r.Method, routePattern) + span.SetName(spanName) + } + + // set status code attribute + span.SetAttributes(semconv.HTTPStatusCodeKey.Int(rrw.status)) + + // set span status + spanStatus, spanMessage := semconv.SpanStatusFromHTTPStatusCode(rrw.status) + span.SetStatus(spanStatus, spanMessage) +} + +func addPrefixToSpanName(shouldAdd bool, prefix, spanName string) string { + // in chi v5.0.8, the root route will be returned has an empty string + // (see github.com/go-chi/chi/v5@v5.0.8/context.go:126) + if spanName == "" { + spanName = "/" + } + + if shouldAdd && len(spanName) > 0 { + spanName = prefix + " " + spanName + } + return spanName +} diff --git a/vendor/github.com/rs/zerolog/array.go b/vendor/github.com/rs/zerolog/array.go index c75c05200..99612ee9f 100644 --- a/vendor/github.com/rs/zerolog/array.go +++ b/vendor/github.com/rs/zerolog/array.go @@ -57,7 +57,7 @@ func (a *Array) write(dst []byte) []byte { } // Object marshals an object that implement the LogObjectMarshaler -// interface and append append it to the array. +// interface and appends it to the array. func (a *Array) Object(obj LogObjectMarshaler) *Array { e := Dict() obj.MarshalZerologObject(e) @@ -67,19 +67,19 @@ func (a *Array) Object(obj LogObjectMarshaler) *Array { return a } -// Str append append the val as a string to the array. +// Str appends the val as a string to the array. func (a *Array) Str(val string) *Array { a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), val) return a } -// Bytes append append the val as a string to the array. +// Bytes appends the val as a string to the array. func (a *Array) Bytes(val []byte) *Array { a.buf = enc.AppendBytes(enc.AppendArrayDelim(a.buf), val) return a } -// Hex append append the val as a hex string to the array. +// Hex appends the val as a hex string to the array. func (a *Array) Hex(val []byte) *Array { a.buf = enc.AppendHex(enc.AppendArrayDelim(a.buf), val) return a @@ -115,97 +115,97 @@ func (a *Array) Err(err error) *Array { return a } -// Bool append append the val as a bool to the array. +// Bool appends the val as a bool to the array. func (a *Array) Bool(b bool) *Array { a.buf = enc.AppendBool(enc.AppendArrayDelim(a.buf), b) return a } -// Int append append i as a int to the array. +// Int appends i as a int to the array. func (a *Array) Int(i int) *Array { a.buf = enc.AppendInt(enc.AppendArrayDelim(a.buf), i) return a } -// Int8 append append i as a int8 to the array. +// Int8 appends i as a int8 to the array. func (a *Array) Int8(i int8) *Array { a.buf = enc.AppendInt8(enc.AppendArrayDelim(a.buf), i) return a } -// Int16 append append i as a int16 to the array. +// Int16 appends i as a int16 to the array. func (a *Array) Int16(i int16) *Array { a.buf = enc.AppendInt16(enc.AppendArrayDelim(a.buf), i) return a } -// Int32 append append i as a int32 to the array. +// Int32 appends i as a int32 to the array. func (a *Array) Int32(i int32) *Array { a.buf = enc.AppendInt32(enc.AppendArrayDelim(a.buf), i) return a } -// Int64 append append i as a int64 to the array. +// Int64 appends i as a int64 to the array. func (a *Array) Int64(i int64) *Array { a.buf = enc.AppendInt64(enc.AppendArrayDelim(a.buf), i) return a } -// Uint append append i as a uint to the array. +// Uint appends i as a uint to the array. func (a *Array) Uint(i uint) *Array { a.buf = enc.AppendUint(enc.AppendArrayDelim(a.buf), i) return a } -// Uint8 append append i as a uint8 to the array. +// Uint8 appends i as a uint8 to the array. func (a *Array) Uint8(i uint8) *Array { a.buf = enc.AppendUint8(enc.AppendArrayDelim(a.buf), i) return a } -// Uint16 append append i as a uint16 to the array. +// Uint16 appends i as a uint16 to the array. func (a *Array) Uint16(i uint16) *Array { a.buf = enc.AppendUint16(enc.AppendArrayDelim(a.buf), i) return a } -// Uint32 append append i as a uint32 to the array. +// Uint32 appends i as a uint32 to the array. func (a *Array) Uint32(i uint32) *Array { a.buf = enc.AppendUint32(enc.AppendArrayDelim(a.buf), i) return a } -// Uint64 append append i as a uint64 to the array. +// Uint64 appends i as a uint64 to the array. func (a *Array) Uint64(i uint64) *Array { a.buf = enc.AppendUint64(enc.AppendArrayDelim(a.buf), i) return a } -// Float32 append append f as a float32 to the array. +// Float32 appends f as a float32 to the array. func (a *Array) Float32(f float32) *Array { a.buf = enc.AppendFloat32(enc.AppendArrayDelim(a.buf), f) return a } -// Float64 append append f as a float64 to the array. +// Float64 appends f as a float64 to the array. func (a *Array) Float64(f float64) *Array { a.buf = enc.AppendFloat64(enc.AppendArrayDelim(a.buf), f) return a } -// Time append append t formatted as string using zerolog.TimeFieldFormat. +// Time appends t formatted as string using zerolog.TimeFieldFormat. func (a *Array) Time(t time.Time) *Array { a.buf = enc.AppendTime(enc.AppendArrayDelim(a.buf), t, TimeFieldFormat) return a } -// Dur append append d to the array. +// Dur appends d to the array. func (a *Array) Dur(d time.Duration) *Array { a.buf = enc.AppendDuration(enc.AppendArrayDelim(a.buf), d, DurationFieldUnit, DurationFieldInteger) return a } -// Interface append append i marshaled using reflection. +// Interface appends i marshaled using reflection. func (a *Array) Interface(i interface{}) *Array { if obj, ok := i.(LogObjectMarshaler); ok { return a.Object(obj) diff --git a/vendor/github.com/rs/zerolog/log.go b/vendor/github.com/rs/zerolog/log.go index c8dbc4251..0b51676c7 100644 --- a/vendor/github.com/rs/zerolog/log.go +++ b/vendor/github.com/rs/zerolog/log.go @@ -161,24 +161,24 @@ func (l Level) String() string { // ParseLevel converts a level string into a zerolog Level value. // returns an error if the input string does not match known values. func ParseLevel(levelStr string) (Level, error) { - switch strings.ToLower(levelStr) { - case LevelFieldMarshalFunc(TraceLevel): + switch { + case strings.EqualFold(levelStr, LevelFieldMarshalFunc(TraceLevel)): return TraceLevel, nil - case LevelFieldMarshalFunc(DebugLevel): + case strings.EqualFold(levelStr, LevelFieldMarshalFunc(DebugLevel)): return DebugLevel, nil - case LevelFieldMarshalFunc(InfoLevel): + case strings.EqualFold(levelStr, LevelFieldMarshalFunc(InfoLevel)): return InfoLevel, nil - case LevelFieldMarshalFunc(WarnLevel): + case strings.EqualFold(levelStr, LevelFieldMarshalFunc(WarnLevel)): return WarnLevel, nil - case LevelFieldMarshalFunc(ErrorLevel): + case strings.EqualFold(levelStr, LevelFieldMarshalFunc(ErrorLevel)): return ErrorLevel, nil - case LevelFieldMarshalFunc(FatalLevel): + case strings.EqualFold(levelStr, LevelFieldMarshalFunc(FatalLevel)): return FatalLevel, nil - case LevelFieldMarshalFunc(PanicLevel): + case strings.EqualFold(levelStr, LevelFieldMarshalFunc(PanicLevel)): return PanicLevel, nil - case LevelFieldMarshalFunc(Disabled): + case strings.EqualFold(levelStr, LevelFieldMarshalFunc(Disabled)): return Disabled, nil - case LevelFieldMarshalFunc(NoLevel): + case strings.EqualFold(levelStr, LevelFieldMarshalFunc(NoLevel)): return NoLevel, nil } i, err := strconv.Atoi(levelStr) diff --git a/vendor/github.com/stretchr/testify/assert/assertion_compare.go b/vendor/github.com/stretchr/testify/assert/assertion_compare.go index 95d8e59da..b774da88d 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_compare.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_compare.go @@ -352,9 +352,9 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { // Greater asserts that the first element is greater than the second // -// assert.Greater(t, 2, 1) -// assert.Greater(t, float64(2), float64(1)) -// assert.Greater(t, "b", "a") +// assert.Greater(t, 2, 1) +// assert.Greater(t, float64(2), float64(1)) +// assert.Greater(t, "b", "a") func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -364,10 +364,10 @@ func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface // GreaterOrEqual asserts that the first element is greater than or equal to the second // -// assert.GreaterOrEqual(t, 2, 1) -// assert.GreaterOrEqual(t, 2, 2) -// assert.GreaterOrEqual(t, "b", "a") -// assert.GreaterOrEqual(t, "b", "b") +// assert.GreaterOrEqual(t, 2, 1) +// assert.GreaterOrEqual(t, 2, 2) +// assert.GreaterOrEqual(t, "b", "a") +// assert.GreaterOrEqual(t, "b", "b") func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -377,9 +377,9 @@ func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...in // Less asserts that the first element is less than the second // -// assert.Less(t, 1, 2) -// assert.Less(t, float64(1), float64(2)) -// assert.Less(t, "a", "b") +// assert.Less(t, 1, 2) +// assert.Less(t, float64(1), float64(2)) +// assert.Less(t, "a", "b") func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -389,10 +389,10 @@ func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) // LessOrEqual asserts that the first element is less than or equal to the second // -// assert.LessOrEqual(t, 1, 2) -// assert.LessOrEqual(t, 2, 2) -// assert.LessOrEqual(t, "a", "b") -// assert.LessOrEqual(t, "b", "b") +// assert.LessOrEqual(t, 1, 2) +// assert.LessOrEqual(t, 2, 2) +// assert.LessOrEqual(t, "a", "b") +// assert.LessOrEqual(t, "b", "b") func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -402,8 +402,8 @@ func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...inter // Positive asserts that the specified element is positive // -// assert.Positive(t, 1) -// assert.Positive(t, 1.23) +// assert.Positive(t, 1) +// assert.Positive(t, 1.23) func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -414,8 +414,8 @@ func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool { // Negative asserts that the specified element is negative // -// assert.Negative(t, -1) -// assert.Negative(t, -1.23) +// assert.Negative(t, -1) +// assert.Negative(t, -1.23) func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go b/vendor/github.com/stretchr/testify/assert/assertion_format.go index 7880b8f94..84dbd6c79 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_format.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go @@ -22,9 +22,9 @@ func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bo // Containsf asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. // -// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted") -// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") -// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") +// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted") +// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") +// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -56,7 +56,7 @@ func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string // Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // -// assert.Emptyf(t, obj, "error message %s", "formatted") +// assert.Emptyf(t, obj, "error message %s", "formatted") func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -66,7 +66,7 @@ func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) boo // Equalf asserts that two objects are equal. // -// assert.Equalf(t, 123, 123, "error message %s", "formatted") +// assert.Equalf(t, 123, 123, "error message %s", "formatted") // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality @@ -81,8 +81,8 @@ func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, ar // EqualErrorf asserts that a function returned an error (i.e. not `nil`) // and that it is equal to the provided error. // -// actualObj, err := SomeFunction() -// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") +// actualObj, err := SomeFunction() +// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -90,10 +90,27 @@ func EqualErrorf(t TestingT, theError error, errString string, msg string, args return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...) } +// EqualExportedValuesf asserts that the types of two objects are equal and their public +// fields are also equal. This is useful for comparing structs that have private fields +// that could potentially differ. +// +// type S struct { +// Exported int +// notExported int +// } +// assert.EqualExportedValuesf(t, S{1, 2}, S{1, 3}, "error message %s", "formatted") => true +// assert.EqualExportedValuesf(t, S{1, 2}, S{2, 3}, "error message %s", "formatted") => false +func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return EqualExportedValues(t, expected, actual, append([]interface{}{msg}, args...)...) +} + // EqualValuesf asserts that two objects are equal or convertable to the same types // and equal. // -// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted") +// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted") func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -103,10 +120,10 @@ func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg stri // Errorf asserts that a function returned an error (i.e. not `nil`). // -// actualObj, err := SomeFunction() -// if assert.Errorf(t, err, "error message %s", "formatted") { -// assert.Equal(t, expectedErrorf, err) -// } +// actualObj, err := SomeFunction() +// if assert.Errorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } func Errorf(t TestingT, err error, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -126,8 +143,8 @@ func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...int // ErrorContainsf asserts that a function returned an error (i.e. not `nil`) // and that the error contains the specified substring. // -// actualObj, err := SomeFunction() -// assert.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted") +// actualObj, err := SomeFunction() +// assert.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted") func ErrorContainsf(t TestingT, theError error, contains string, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -147,7 +164,7 @@ func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface // Eventuallyf asserts that given condition will be met in waitFor time, // periodically checking target function each tick. // -// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -155,9 +172,34 @@ func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick return Eventually(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...) } +// EventuallyWithTf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. In contrast to Eventually, +// it supplies a CollectT to the condition function, so that the condition +// function can use the CollectT to call other assertions. +// The condition is considered "met" if no errors are raised in a tick. +// The supplied CollectT collects all errors from one tick (if there are any). +// If the condition is not met before waitFor, the collected errors of +// the last tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// assert.EventuallyWithTf(t, func(c *assert.CollectT, "error message %s", "formatted") { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +func EventuallyWithTf(t TestingT, condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return EventuallyWithT(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...) +} + // Exactlyf asserts that two objects are equal in value and type. // -// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted") +// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted") func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -183,7 +225,7 @@ func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{} // Falsef asserts that the specified value is false. // -// assert.Falsef(t, myBool, "error message %s", "formatted") +// assert.Falsef(t, myBool, "error message %s", "formatted") func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -202,9 +244,9 @@ func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool // Greaterf asserts that the first element is greater than the second // -// assert.Greaterf(t, 2, 1, "error message %s", "formatted") -// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted") -// assert.Greaterf(t, "b", "a", "error message %s", "formatted") +// assert.Greaterf(t, 2, 1, "error message %s", "formatted") +// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted") +// assert.Greaterf(t, "b", "a", "error message %s", "formatted") func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -214,10 +256,10 @@ func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...in // GreaterOrEqualf asserts that the first element is greater than or equal to the second // -// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted") -// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted") -// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted") -// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted") +// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted") +// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted") +// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted") +// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted") func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -228,7 +270,7 @@ func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, arg // HTTPBodyContainsf asserts that a specified handler returns a // body that contains a string. // -// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { @@ -241,7 +283,7 @@ func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url // HTTPBodyNotContainsf asserts that a specified handler returns a // body that does not contain a string. // -// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { @@ -253,7 +295,7 @@ func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, u // HTTPErrorf asserts that a specified handler returns an error status code. // -// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { @@ -265,7 +307,7 @@ func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, // HTTPRedirectf asserts that a specified handler returns a redirect status code. // -// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { @@ -277,7 +319,7 @@ func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url stri // HTTPStatusCodef asserts that a specified handler returns a specified status code. // -// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") +// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) bool { @@ -289,7 +331,7 @@ func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url st // HTTPSuccessf asserts that a specified handler returns a success status code. // -// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { @@ -301,7 +343,7 @@ func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url strin // Implementsf asserts that an object is implemented by the specified interface. // -// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted") func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -311,7 +353,7 @@ func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, ms // InDeltaf asserts that the two numerals are within delta of each other. // -// assert.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted") +// assert.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted") func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -353,9 +395,9 @@ func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsil // IsDecreasingf asserts that the collection is decreasing // -// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted") -// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted") -// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted") +// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted") +// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted") +// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted") func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -365,9 +407,9 @@ func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface // IsIncreasingf asserts that the collection is increasing // -// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted") -// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted") -// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted") +// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted") +// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted") +// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted") func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -377,9 +419,9 @@ func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface // IsNonDecreasingf asserts that the collection is not decreasing // -// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted") -// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted") -// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted") +// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted") +// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted") +// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted") func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -389,9 +431,9 @@ func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interf // IsNonIncreasingf asserts that the collection is not increasing // -// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted") -// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted") -// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted") +// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted") +// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted") +// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted") func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -409,7 +451,7 @@ func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg strin // JSONEqf asserts that two JSON strings are equivalent. // -// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -420,7 +462,7 @@ func JSONEqf(t TestingT, expected string, actual string, msg string, args ...int // Lenf asserts that the specified object has specific length. // Lenf also fails if the object has a type that len() not accept. // -// assert.Lenf(t, mySlice, 3, "error message %s", "formatted") +// assert.Lenf(t, mySlice, 3, "error message %s", "formatted") func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -430,9 +472,9 @@ func Lenf(t TestingT, object interface{}, length int, msg string, args ...interf // Lessf asserts that the first element is less than the second // -// assert.Lessf(t, 1, 2, "error message %s", "formatted") -// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted") -// assert.Lessf(t, "a", "b", "error message %s", "formatted") +// assert.Lessf(t, 1, 2, "error message %s", "formatted") +// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted") +// assert.Lessf(t, "a", "b", "error message %s", "formatted") func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -442,10 +484,10 @@ func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...inter // LessOrEqualf asserts that the first element is less than or equal to the second // -// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted") -// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted") -// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted") -// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted") +// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted") +// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted") +// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted") +// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted") func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -455,8 +497,8 @@ func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args . // Negativef asserts that the specified element is negative // -// assert.Negativef(t, -1, "error message %s", "formatted") -// assert.Negativef(t, -1.23, "error message %s", "formatted") +// assert.Negativef(t, -1, "error message %s", "formatted") +// assert.Negativef(t, -1.23, "error message %s", "formatted") func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -467,7 +509,7 @@ func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) bool // Neverf asserts that the given condition doesn't satisfy in waitFor time, // periodically checking the target function each tick. // -// assert.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +// assert.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -477,7 +519,7 @@ func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time. // Nilf asserts that the specified object is nil. // -// assert.Nilf(t, err, "error message %s", "formatted") +// assert.Nilf(t, err, "error message %s", "formatted") func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -496,10 +538,10 @@ func NoDirExistsf(t TestingT, path string, msg string, args ...interface{}) bool // NoErrorf asserts that a function returned no error (i.e. `nil`). // -// actualObj, err := SomeFunction() -// if assert.NoErrorf(t, err, "error message %s", "formatted") { -// assert.Equal(t, expectedObj, actualObj) -// } +// actualObj, err := SomeFunction() +// if assert.NoErrorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -519,9 +561,9 @@ func NoFileExistsf(t TestingT, path string, msg string, args ...interface{}) boo // NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. // -// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") -// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") -// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -532,9 +574,9 @@ func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, a // NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // -// if assert.NotEmptyf(t, obj, "error message %s", "formatted") { -// assert.Equal(t, "two", obj[1]) -// } +// if assert.NotEmptyf(t, obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -544,7 +586,7 @@ func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) // NotEqualf asserts that the specified values are NOT equal. // -// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted") +// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted") // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). @@ -557,7 +599,7 @@ func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, // NotEqualValuesf asserts that two objects are not equal even when converted to the same type // -// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted") +// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted") func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -576,7 +618,7 @@ func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interf // NotNilf asserts that the specified object is not nil. // -// assert.NotNilf(t, err, "error message %s", "formatted") +// assert.NotNilf(t, err, "error message %s", "formatted") func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -586,7 +628,7 @@ func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bo // NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. // -// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") +// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -596,8 +638,8 @@ func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bo // NotRegexpf asserts that a specified regexp does not match a string. // -// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") -// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") +// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") +// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -607,7 +649,7 @@ func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args .. // NotSamef asserts that two pointers do not reference the same object. // -// assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted") +// assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted") // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -621,7 +663,7 @@ func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, // NotSubsetf asserts that the specified list(array, slice...) contains not all // elements given in the specified subset(array, slice...). // -// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") +// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -639,7 +681,7 @@ func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool { // Panicsf asserts that the code inside the specified PanicTestFunc panics. // -// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") +// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -651,7 +693,7 @@ func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool // panics, and that the recovered panic value is an error that satisfies the // EqualError comparison. // -// assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +// assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") func PanicsWithErrorf(t TestingT, errString string, f PanicTestFunc, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -662,7 +704,7 @@ func PanicsWithErrorf(t TestingT, errString string, f PanicTestFunc, msg string, // PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that // the recovered panic value equals the expected panic value. // -// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -672,8 +714,8 @@ func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg str // Positivef asserts that the specified element is positive // -// assert.Positivef(t, 1, "error message %s", "formatted") -// assert.Positivef(t, 1.23, "error message %s", "formatted") +// assert.Positivef(t, 1, "error message %s", "formatted") +// assert.Positivef(t, 1.23, "error message %s", "formatted") func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -683,8 +725,8 @@ func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool // Regexpf asserts that a specified regexp matches a string. // -// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") -// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") +// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") +// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -694,7 +736,7 @@ func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...in // Samef asserts that two pointers reference the same object. // -// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted") +// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted") // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -708,7 +750,7 @@ func Samef(t TestingT, expected interface{}, actual interface{}, msg string, arg // Subsetf asserts that the specified list(array, slice...) contains all // elements given in the specified subset(array, slice...). // -// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") +// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -718,7 +760,7 @@ func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args // Truef asserts that the specified value is true. // -// assert.Truef(t, myBool, "error message %s", "formatted") +// assert.Truef(t, myBool, "error message %s", "formatted") func Truef(t TestingT, value bool, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -728,7 +770,7 @@ func Truef(t TestingT, value bool, msg string, args ...interface{}) bool { // WithinDurationf asserts that the two times are within duration delta of each other. // -// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -738,7 +780,7 @@ func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta tim // WithinRangef asserts that a time is within a time range (inclusive). // -// assert.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") +// assert.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") func WithinRangef(t TestingT, actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go index 339515b8b..b1d94aec5 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_forward.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go @@ -30,9 +30,9 @@ func (a *Assertions) Conditionf(comp Comparison, msg string, args ...interface{} // Contains asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. // -// a.Contains("Hello World", "World") -// a.Contains(["Hello", "World"], "World") -// a.Contains({"Hello": "World"}, "Hello") +// a.Contains("Hello World", "World") +// a.Contains(["Hello", "World"], "World") +// a.Contains({"Hello": "World"}, "Hello") func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -43,9 +43,9 @@ func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs .. // Containsf asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. // -// a.Containsf("Hello World", "World", "error message %s", "formatted") -// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted") -// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted") +// a.Containsf("Hello World", "World", "error message %s", "formatted") +// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted") +// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted") func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -98,7 +98,7 @@ func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg st // Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // -// a.Empty(obj) +// a.Empty(obj) func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -109,7 +109,7 @@ func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool { // Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // -// a.Emptyf(obj, "error message %s", "formatted") +// a.Emptyf(obj, "error message %s", "formatted") func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -119,7 +119,7 @@ func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) // Equal asserts that two objects are equal. // -// a.Equal(123, 123) +// a.Equal(123, 123) // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality @@ -134,8 +134,8 @@ func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs // EqualError asserts that a function returned an error (i.e. not `nil`) // and that it is equal to the provided error. // -// actualObj, err := SomeFunction() -// a.EqualError(err, expectedErrorString) +// actualObj, err := SomeFunction() +// a.EqualError(err, expectedErrorString) func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -146,8 +146,8 @@ func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ... // EqualErrorf asserts that a function returned an error (i.e. not `nil`) // and that it is equal to the provided error. // -// actualObj, err := SomeFunction() -// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted") +// actualObj, err := SomeFunction() +// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted") func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -155,10 +155,44 @@ func (a *Assertions) EqualErrorf(theError error, errString string, msg string, a return EqualErrorf(a.t, theError, errString, msg, args...) } +// EqualExportedValues asserts that the types of two objects are equal and their public +// fields are also equal. This is useful for comparing structs that have private fields +// that could potentially differ. +// +// type S struct { +// Exported int +// notExported int +// } +// a.EqualExportedValues(S{1, 2}, S{1, 3}) => true +// a.EqualExportedValues(S{1, 2}, S{2, 3}) => false +func (a *Assertions) EqualExportedValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return EqualExportedValues(a.t, expected, actual, msgAndArgs...) +} + +// EqualExportedValuesf asserts that the types of two objects are equal and their public +// fields are also equal. This is useful for comparing structs that have private fields +// that could potentially differ. +// +// type S struct { +// Exported int +// notExported int +// } +// a.EqualExportedValuesf(S{1, 2}, S{1, 3}, "error message %s", "formatted") => true +// a.EqualExportedValuesf(S{1, 2}, S{2, 3}, "error message %s", "formatted") => false +func (a *Assertions) EqualExportedValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return EqualExportedValuesf(a.t, expected, actual, msg, args...) +} + // EqualValues asserts that two objects are equal or convertable to the same types // and equal. // -// a.EqualValues(uint32(123), int32(123)) +// a.EqualValues(uint32(123), int32(123)) func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -169,7 +203,7 @@ func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAn // EqualValuesf asserts that two objects are equal or convertable to the same types // and equal. // -// a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted") +// a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted") func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -179,7 +213,7 @@ func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg // Equalf asserts that two objects are equal. // -// a.Equalf(123, 123, "error message %s", "formatted") +// a.Equalf(123, 123, "error message %s", "formatted") // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality @@ -193,10 +227,10 @@ func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string // Error asserts that a function returned an error (i.e. not `nil`). // -// actualObj, err := SomeFunction() -// if a.Error(err) { -// assert.Equal(t, expectedError, err) -// } +// actualObj, err := SomeFunction() +// if a.Error(err) { +// assert.Equal(t, expectedError, err) +// } func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -225,8 +259,8 @@ func (a *Assertions) ErrorAsf(err error, target interface{}, msg string, args .. // ErrorContains asserts that a function returned an error (i.e. not `nil`) // and that the error contains the specified substring. // -// actualObj, err := SomeFunction() -// a.ErrorContains(err, expectedErrorSubString) +// actualObj, err := SomeFunction() +// a.ErrorContains(err, expectedErrorSubString) func (a *Assertions) ErrorContains(theError error, contains string, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -237,8 +271,8 @@ func (a *Assertions) ErrorContains(theError error, contains string, msgAndArgs . // ErrorContainsf asserts that a function returned an error (i.e. not `nil`) // and that the error contains the specified substring. // -// actualObj, err := SomeFunction() -// a.ErrorContainsf(err, expectedErrorSubString, "error message %s", "formatted") +// actualObj, err := SomeFunction() +// a.ErrorContainsf(err, expectedErrorSubString, "error message %s", "formatted") func (a *Assertions) ErrorContainsf(theError error, contains string, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -266,10 +300,10 @@ func (a *Assertions) ErrorIsf(err error, target error, msg string, args ...inter // Errorf asserts that a function returned an error (i.e. not `nil`). // -// actualObj, err := SomeFunction() -// if a.Errorf(err, "error message %s", "formatted") { -// assert.Equal(t, expectedErrorf, err) -// } +// actualObj, err := SomeFunction() +// if a.Errorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } func (a *Assertions) Errorf(err error, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -280,7 +314,7 @@ func (a *Assertions) Errorf(err error, msg string, args ...interface{}) bool { // Eventually asserts that given condition will be met in waitFor time, // periodically checking target function each tick. // -// a.Eventually(func() bool { return true; }, time.Second, 10*time.Millisecond) +// a.Eventually(func() bool { return true; }, time.Second, 10*time.Millisecond) func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -288,10 +322,60 @@ func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, ti return Eventually(a.t, condition, waitFor, tick, msgAndArgs...) } +// EventuallyWithT asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. In contrast to Eventually, +// it supplies a CollectT to the condition function, so that the condition +// function can use the CollectT to call other assertions. +// The condition is considered "met" if no errors are raised in a tick. +// The supplied CollectT collects all errors from one tick (if there are any). +// If the condition is not met before waitFor, the collected errors of +// the last tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// a.EventuallyWithT(func(c *assert.CollectT) { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +func (a *Assertions) EventuallyWithT(condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return EventuallyWithT(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// EventuallyWithTf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. In contrast to Eventually, +// it supplies a CollectT to the condition function, so that the condition +// function can use the CollectT to call other assertions. +// The condition is considered "met" if no errors are raised in a tick. +// The supplied CollectT collects all errors from one tick (if there are any). +// If the condition is not met before waitFor, the collected errors of +// the last tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// a.EventuallyWithTf(func(c *assert.CollectT, "error message %s", "formatted") { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +func (a *Assertions) EventuallyWithTf(condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return EventuallyWithTf(a.t, condition, waitFor, tick, msg, args...) +} + // Eventuallyf asserts that given condition will be met in waitFor time, // periodically checking target function each tick. // -// a.Eventuallyf(func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +// a.Eventuallyf(func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") func (a *Assertions) Eventuallyf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -301,7 +385,7 @@ func (a *Assertions) Eventuallyf(condition func() bool, waitFor time.Duration, t // Exactly asserts that two objects are equal in value and type. // -// a.Exactly(int32(123), int64(123)) +// a.Exactly(int32(123), int64(123)) func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -311,7 +395,7 @@ func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArg // Exactlyf asserts that two objects are equal in value and type. // -// a.Exactlyf(int32(123), int64(123), "error message %s", "formatted") +// a.Exactlyf(int32(123), int64(123), "error message %s", "formatted") func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -353,7 +437,7 @@ func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{ // False asserts that the specified value is false. // -// a.False(myBool) +// a.False(myBool) func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -363,7 +447,7 @@ func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool { // Falsef asserts that the specified value is false. // -// a.Falsef(myBool, "error message %s", "formatted") +// a.Falsef(myBool, "error message %s", "formatted") func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -391,9 +475,9 @@ func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) b // Greater asserts that the first element is greater than the second // -// a.Greater(2, 1) -// a.Greater(float64(2), float64(1)) -// a.Greater("b", "a") +// a.Greater(2, 1) +// a.Greater(float64(2), float64(1)) +// a.Greater("b", "a") func (a *Assertions) Greater(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -403,10 +487,10 @@ func (a *Assertions) Greater(e1 interface{}, e2 interface{}, msgAndArgs ...inter // GreaterOrEqual asserts that the first element is greater than or equal to the second // -// a.GreaterOrEqual(2, 1) -// a.GreaterOrEqual(2, 2) -// a.GreaterOrEqual("b", "a") -// a.GreaterOrEqual("b", "b") +// a.GreaterOrEqual(2, 1) +// a.GreaterOrEqual(2, 2) +// a.GreaterOrEqual("b", "a") +// a.GreaterOrEqual("b", "b") func (a *Assertions) GreaterOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -416,10 +500,10 @@ func (a *Assertions) GreaterOrEqual(e1 interface{}, e2 interface{}, msgAndArgs . // GreaterOrEqualf asserts that the first element is greater than or equal to the second // -// a.GreaterOrEqualf(2, 1, "error message %s", "formatted") -// a.GreaterOrEqualf(2, 2, "error message %s", "formatted") -// a.GreaterOrEqualf("b", "a", "error message %s", "formatted") -// a.GreaterOrEqualf("b", "b", "error message %s", "formatted") +// a.GreaterOrEqualf(2, 1, "error message %s", "formatted") +// a.GreaterOrEqualf(2, 2, "error message %s", "formatted") +// a.GreaterOrEqualf("b", "a", "error message %s", "formatted") +// a.GreaterOrEqualf("b", "b", "error message %s", "formatted") func (a *Assertions) GreaterOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -429,9 +513,9 @@ func (a *Assertions) GreaterOrEqualf(e1 interface{}, e2 interface{}, msg string, // Greaterf asserts that the first element is greater than the second // -// a.Greaterf(2, 1, "error message %s", "formatted") -// a.Greaterf(float64(2), float64(1), "error message %s", "formatted") -// a.Greaterf("b", "a", "error message %s", "formatted") +// a.Greaterf(2, 1, "error message %s", "formatted") +// a.Greaterf(float64(2), float64(1), "error message %s", "formatted") +// a.Greaterf("b", "a", "error message %s", "formatted") func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -442,7 +526,7 @@ func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args . // HTTPBodyContains asserts that a specified handler returns a // body that contains a string. // -// a.HTTPBodyContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// a.HTTPBodyContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { @@ -455,7 +539,7 @@ func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, u // HTTPBodyContainsf asserts that a specified handler returns a // body that contains a string. // -// a.HTTPBodyContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// a.HTTPBodyContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { @@ -468,7 +552,7 @@ func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, // HTTPBodyNotContains asserts that a specified handler returns a // body that does not contain a string. // -// a.HTTPBodyNotContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// a.HTTPBodyNotContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { @@ -481,7 +565,7 @@ func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string // HTTPBodyNotContainsf asserts that a specified handler returns a // body that does not contain a string. // -// a.HTTPBodyNotContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// a.HTTPBodyNotContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { @@ -493,7 +577,7 @@ func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method strin // HTTPError asserts that a specified handler returns an error status code. // -// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool { @@ -505,7 +589,7 @@ func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url stri // HTTPErrorf asserts that a specified handler returns an error status code. // -// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { @@ -517,7 +601,7 @@ func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url str // HTTPRedirect asserts that a specified handler returns a redirect status code. // -// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool { @@ -529,7 +613,7 @@ func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url s // HTTPRedirectf asserts that a specified handler returns a redirect status code. // -// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { @@ -541,7 +625,7 @@ func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url // HTTPStatusCode asserts that a specified handler returns a specified status code. // -// a.HTTPStatusCode(myHandler, "GET", "/notImplemented", nil, 501) +// a.HTTPStatusCode(myHandler, "GET", "/notImplemented", nil, 501) // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPStatusCode(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) bool { @@ -553,7 +637,7 @@ func (a *Assertions) HTTPStatusCode(handler http.HandlerFunc, method string, url // HTTPStatusCodef asserts that a specified handler returns a specified status code. // -// a.HTTPStatusCodef(myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") +// a.HTTPStatusCodef(myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPStatusCodef(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) bool { @@ -565,7 +649,7 @@ func (a *Assertions) HTTPStatusCodef(handler http.HandlerFunc, method string, ur // HTTPSuccess asserts that a specified handler returns a success status code. // -// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) +// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool { @@ -577,7 +661,7 @@ func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url st // HTTPSuccessf asserts that a specified handler returns a success status code. // -// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { @@ -589,7 +673,7 @@ func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url s // Implements asserts that an object is implemented by the specified interface. // -// a.Implements((*MyInterface)(nil), new(MyObject)) +// a.Implements((*MyInterface)(nil), new(MyObject)) func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -599,7 +683,7 @@ func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, // Implementsf asserts that an object is implemented by the specified interface. // -// a.Implementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +// a.Implementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted") func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -609,7 +693,7 @@ func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{} // InDelta asserts that the two numerals are within delta of each other. // -// a.InDelta(math.Pi, 22/7.0, 0.01) +// a.InDelta(math.Pi, 22/7.0, 0.01) func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -651,7 +735,7 @@ func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, del // InDeltaf asserts that the two numerals are within delta of each other. // -// a.InDeltaf(math.Pi, 22/7.0, 0.01, "error message %s", "formatted") +// a.InDeltaf(math.Pi, 22/7.0, 0.01, "error message %s", "formatted") func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -693,9 +777,9 @@ func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilo // IsDecreasing asserts that the collection is decreasing // -// a.IsDecreasing([]int{2, 1, 0}) -// a.IsDecreasing([]float{2, 1}) -// a.IsDecreasing([]string{"b", "a"}) +// a.IsDecreasing([]int{2, 1, 0}) +// a.IsDecreasing([]float{2, 1}) +// a.IsDecreasing([]string{"b", "a"}) func (a *Assertions) IsDecreasing(object interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -705,9 +789,9 @@ func (a *Assertions) IsDecreasing(object interface{}, msgAndArgs ...interface{}) // IsDecreasingf asserts that the collection is decreasing // -// a.IsDecreasingf([]int{2, 1, 0}, "error message %s", "formatted") -// a.IsDecreasingf([]float{2, 1}, "error message %s", "formatted") -// a.IsDecreasingf([]string{"b", "a"}, "error message %s", "formatted") +// a.IsDecreasingf([]int{2, 1, 0}, "error message %s", "formatted") +// a.IsDecreasingf([]float{2, 1}, "error message %s", "formatted") +// a.IsDecreasingf([]string{"b", "a"}, "error message %s", "formatted") func (a *Assertions) IsDecreasingf(object interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -717,9 +801,9 @@ func (a *Assertions) IsDecreasingf(object interface{}, msg string, args ...inter // IsIncreasing asserts that the collection is increasing // -// a.IsIncreasing([]int{1, 2, 3}) -// a.IsIncreasing([]float{1, 2}) -// a.IsIncreasing([]string{"a", "b"}) +// a.IsIncreasing([]int{1, 2, 3}) +// a.IsIncreasing([]float{1, 2}) +// a.IsIncreasing([]string{"a", "b"}) func (a *Assertions) IsIncreasing(object interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -729,9 +813,9 @@ func (a *Assertions) IsIncreasing(object interface{}, msgAndArgs ...interface{}) // IsIncreasingf asserts that the collection is increasing // -// a.IsIncreasingf([]int{1, 2, 3}, "error message %s", "formatted") -// a.IsIncreasingf([]float{1, 2}, "error message %s", "formatted") -// a.IsIncreasingf([]string{"a", "b"}, "error message %s", "formatted") +// a.IsIncreasingf([]int{1, 2, 3}, "error message %s", "formatted") +// a.IsIncreasingf([]float{1, 2}, "error message %s", "formatted") +// a.IsIncreasingf([]string{"a", "b"}, "error message %s", "formatted") func (a *Assertions) IsIncreasingf(object interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -741,9 +825,9 @@ func (a *Assertions) IsIncreasingf(object interface{}, msg string, args ...inter // IsNonDecreasing asserts that the collection is not decreasing // -// a.IsNonDecreasing([]int{1, 1, 2}) -// a.IsNonDecreasing([]float{1, 2}) -// a.IsNonDecreasing([]string{"a", "b"}) +// a.IsNonDecreasing([]int{1, 1, 2}) +// a.IsNonDecreasing([]float{1, 2}) +// a.IsNonDecreasing([]string{"a", "b"}) func (a *Assertions) IsNonDecreasing(object interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -753,9 +837,9 @@ func (a *Assertions) IsNonDecreasing(object interface{}, msgAndArgs ...interface // IsNonDecreasingf asserts that the collection is not decreasing // -// a.IsNonDecreasingf([]int{1, 1, 2}, "error message %s", "formatted") -// a.IsNonDecreasingf([]float{1, 2}, "error message %s", "formatted") -// a.IsNonDecreasingf([]string{"a", "b"}, "error message %s", "formatted") +// a.IsNonDecreasingf([]int{1, 1, 2}, "error message %s", "formatted") +// a.IsNonDecreasingf([]float{1, 2}, "error message %s", "formatted") +// a.IsNonDecreasingf([]string{"a", "b"}, "error message %s", "formatted") func (a *Assertions) IsNonDecreasingf(object interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -765,9 +849,9 @@ func (a *Assertions) IsNonDecreasingf(object interface{}, msg string, args ...in // IsNonIncreasing asserts that the collection is not increasing // -// a.IsNonIncreasing([]int{2, 1, 1}) -// a.IsNonIncreasing([]float{2, 1}) -// a.IsNonIncreasing([]string{"b", "a"}) +// a.IsNonIncreasing([]int{2, 1, 1}) +// a.IsNonIncreasing([]float{2, 1}) +// a.IsNonIncreasing([]string{"b", "a"}) func (a *Assertions) IsNonIncreasing(object interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -777,9 +861,9 @@ func (a *Assertions) IsNonIncreasing(object interface{}, msgAndArgs ...interface // IsNonIncreasingf asserts that the collection is not increasing // -// a.IsNonIncreasingf([]int{2, 1, 1}, "error message %s", "formatted") -// a.IsNonIncreasingf([]float{2, 1}, "error message %s", "formatted") -// a.IsNonIncreasingf([]string{"b", "a"}, "error message %s", "formatted") +// a.IsNonIncreasingf([]int{2, 1, 1}, "error message %s", "formatted") +// a.IsNonIncreasingf([]float{2, 1}, "error message %s", "formatted") +// a.IsNonIncreasingf([]string{"b", "a"}, "error message %s", "formatted") func (a *Assertions) IsNonIncreasingf(object interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -805,7 +889,7 @@ func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg s // JSONEq asserts that two JSON strings are equivalent. // -// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -815,7 +899,7 @@ func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interf // JSONEqf asserts that two JSON strings are equivalent. // -// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -826,7 +910,7 @@ func (a *Assertions) JSONEqf(expected string, actual string, msg string, args .. // Len asserts that the specified object has specific length. // Len also fails if the object has a type that len() not accept. // -// a.Len(mySlice, 3) +// a.Len(mySlice, 3) func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -837,7 +921,7 @@ func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface // Lenf asserts that the specified object has specific length. // Lenf also fails if the object has a type that len() not accept. // -// a.Lenf(mySlice, 3, "error message %s", "formatted") +// a.Lenf(mySlice, 3, "error message %s", "formatted") func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -847,9 +931,9 @@ func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...in // Less asserts that the first element is less than the second // -// a.Less(1, 2) -// a.Less(float64(1), float64(2)) -// a.Less("a", "b") +// a.Less(1, 2) +// a.Less(float64(1), float64(2)) +// a.Less("a", "b") func (a *Assertions) Less(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -859,10 +943,10 @@ func (a *Assertions) Less(e1 interface{}, e2 interface{}, msgAndArgs ...interfac // LessOrEqual asserts that the first element is less than or equal to the second // -// a.LessOrEqual(1, 2) -// a.LessOrEqual(2, 2) -// a.LessOrEqual("a", "b") -// a.LessOrEqual("b", "b") +// a.LessOrEqual(1, 2) +// a.LessOrEqual(2, 2) +// a.LessOrEqual("a", "b") +// a.LessOrEqual("b", "b") func (a *Assertions) LessOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -872,10 +956,10 @@ func (a *Assertions) LessOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...i // LessOrEqualf asserts that the first element is less than or equal to the second // -// a.LessOrEqualf(1, 2, "error message %s", "formatted") -// a.LessOrEqualf(2, 2, "error message %s", "formatted") -// a.LessOrEqualf("a", "b", "error message %s", "formatted") -// a.LessOrEqualf("b", "b", "error message %s", "formatted") +// a.LessOrEqualf(1, 2, "error message %s", "formatted") +// a.LessOrEqualf(2, 2, "error message %s", "formatted") +// a.LessOrEqualf("a", "b", "error message %s", "formatted") +// a.LessOrEqualf("b", "b", "error message %s", "formatted") func (a *Assertions) LessOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -885,9 +969,9 @@ func (a *Assertions) LessOrEqualf(e1 interface{}, e2 interface{}, msg string, ar // Lessf asserts that the first element is less than the second // -// a.Lessf(1, 2, "error message %s", "formatted") -// a.Lessf(float64(1), float64(2), "error message %s", "formatted") -// a.Lessf("a", "b", "error message %s", "formatted") +// a.Lessf(1, 2, "error message %s", "formatted") +// a.Lessf(float64(1), float64(2), "error message %s", "formatted") +// a.Lessf("a", "b", "error message %s", "formatted") func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -897,8 +981,8 @@ func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...i // Negative asserts that the specified element is negative // -// a.Negative(-1) -// a.Negative(-1.23) +// a.Negative(-1) +// a.Negative(-1.23) func (a *Assertions) Negative(e interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -908,8 +992,8 @@ func (a *Assertions) Negative(e interface{}, msgAndArgs ...interface{}) bool { // Negativef asserts that the specified element is negative // -// a.Negativef(-1, "error message %s", "formatted") -// a.Negativef(-1.23, "error message %s", "formatted") +// a.Negativef(-1, "error message %s", "formatted") +// a.Negativef(-1.23, "error message %s", "formatted") func (a *Assertions) Negativef(e interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -920,7 +1004,7 @@ func (a *Assertions) Negativef(e interface{}, msg string, args ...interface{}) b // Never asserts that the given condition doesn't satisfy in waitFor time, // periodically checking the target function each tick. // -// a.Never(func() bool { return false; }, time.Second, 10*time.Millisecond) +// a.Never(func() bool { return false; }, time.Second, 10*time.Millisecond) func (a *Assertions) Never(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -931,7 +1015,7 @@ func (a *Assertions) Never(condition func() bool, waitFor time.Duration, tick ti // Neverf asserts that the given condition doesn't satisfy in waitFor time, // periodically checking the target function each tick. // -// a.Neverf(func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +// a.Neverf(func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") func (a *Assertions) Neverf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -941,7 +1025,7 @@ func (a *Assertions) Neverf(condition func() bool, waitFor time.Duration, tick t // Nil asserts that the specified object is nil. // -// a.Nil(err) +// a.Nil(err) func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -951,7 +1035,7 @@ func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool { // Nilf asserts that the specified object is nil. // -// a.Nilf(err, "error message %s", "formatted") +// a.Nilf(err, "error message %s", "formatted") func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -979,10 +1063,10 @@ func (a *Assertions) NoDirExistsf(path string, msg string, args ...interface{}) // NoError asserts that a function returned no error (i.e. `nil`). // -// actualObj, err := SomeFunction() -// if a.NoError(err) { -// assert.Equal(t, expectedObj, actualObj) -// } +// actualObj, err := SomeFunction() +// if a.NoError(err) { +// assert.Equal(t, expectedObj, actualObj) +// } func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -992,10 +1076,10 @@ func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool { // NoErrorf asserts that a function returned no error (i.e. `nil`). // -// actualObj, err := SomeFunction() -// if a.NoErrorf(err, "error message %s", "formatted") { -// assert.Equal(t, expectedObj, actualObj) -// } +// actualObj, err := SomeFunction() +// if a.NoErrorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1024,9 +1108,9 @@ func (a *Assertions) NoFileExistsf(path string, msg string, args ...interface{}) // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. // -// a.NotContains("Hello World", "Earth") -// a.NotContains(["Hello", "World"], "Earth") -// a.NotContains({"Hello": "World"}, "Earth") +// a.NotContains("Hello World", "Earth") +// a.NotContains(["Hello", "World"], "Earth") +// a.NotContains({"Hello": "World"}, "Earth") func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1037,9 +1121,9 @@ func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs // NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. // -// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted") -// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted") -// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted") +// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted") +// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted") +// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted") func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1050,9 +1134,9 @@ func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg strin // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // -// if a.NotEmpty(obj) { -// assert.Equal(t, "two", obj[1]) -// } +// if a.NotEmpty(obj) { +// assert.Equal(t, "two", obj[1]) +// } func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1063,9 +1147,9 @@ func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) boo // NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // -// if a.NotEmptyf(obj, "error message %s", "formatted") { -// assert.Equal(t, "two", obj[1]) -// } +// if a.NotEmptyf(obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1075,7 +1159,7 @@ func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface // NotEqual asserts that the specified values are NOT equal. // -// a.NotEqual(obj1, obj2) +// a.NotEqual(obj1, obj2) // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). @@ -1088,7 +1172,7 @@ func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndAr // NotEqualValues asserts that two objects are not equal even when converted to the same type // -// a.NotEqualValues(obj1, obj2) +// a.NotEqualValues(obj1, obj2) func (a *Assertions) NotEqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1098,7 +1182,7 @@ func (a *Assertions) NotEqualValues(expected interface{}, actual interface{}, ms // NotEqualValuesf asserts that two objects are not equal even when converted to the same type // -// a.NotEqualValuesf(obj1, obj2, "error message %s", "formatted") +// a.NotEqualValuesf(obj1, obj2, "error message %s", "formatted") func (a *Assertions) NotEqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1108,7 +1192,7 @@ func (a *Assertions) NotEqualValuesf(expected interface{}, actual interface{}, m // NotEqualf asserts that the specified values are NOT equal. // -// a.NotEqualf(obj1, obj2, "error message %s", "formatted") +// a.NotEqualf(obj1, obj2, "error message %s", "formatted") // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). @@ -1139,7 +1223,7 @@ func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...in // NotNil asserts that the specified object is not nil. // -// a.NotNil(err) +// a.NotNil(err) func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1149,7 +1233,7 @@ func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool // NotNilf asserts that the specified object is not nil. // -// a.NotNilf(err, "error message %s", "formatted") +// a.NotNilf(err, "error message %s", "formatted") func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1159,7 +1243,7 @@ func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{} // NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. // -// a.NotPanics(func(){ RemainCalm() }) +// a.NotPanics(func(){ RemainCalm() }) func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1169,7 +1253,7 @@ func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool // NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. // -// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted") +// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted") func (a *Assertions) NotPanicsf(f PanicTestFunc, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1179,8 +1263,8 @@ func (a *Assertions) NotPanicsf(f PanicTestFunc, msg string, args ...interface{} // NotRegexp asserts that a specified regexp does not match a string. // -// a.NotRegexp(regexp.MustCompile("starts"), "it's starting") -// a.NotRegexp("^start", "it's not starting") +// a.NotRegexp(regexp.MustCompile("starts"), "it's starting") +// a.NotRegexp("^start", "it's not starting") func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1190,8 +1274,8 @@ func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...in // NotRegexpf asserts that a specified regexp does not match a string. // -// a.NotRegexpf(regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") -// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") +// a.NotRegexpf(regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") +// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1201,7 +1285,7 @@ func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, arg // NotSame asserts that two pointers do not reference the same object. // -// a.NotSame(ptr1, ptr2) +// a.NotSame(ptr1, ptr2) // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1214,7 +1298,7 @@ func (a *Assertions) NotSame(expected interface{}, actual interface{}, msgAndArg // NotSamef asserts that two pointers do not reference the same object. // -// a.NotSamef(ptr1, ptr2, "error message %s", "formatted") +// a.NotSamef(ptr1, ptr2, "error message %s", "formatted") // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1228,7 +1312,7 @@ func (a *Assertions) NotSamef(expected interface{}, actual interface{}, msg stri // NotSubset asserts that the specified list(array, slice...) contains not all // elements given in the specified subset(array, slice...). // -// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") +// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1239,7 +1323,7 @@ func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs // NotSubsetf asserts that the specified list(array, slice...) contains not all // elements given in the specified subset(array, slice...). // -// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") +// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1265,7 +1349,7 @@ func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) bo // Panics asserts that the code inside the specified PanicTestFunc panics. // -// a.Panics(func(){ GoCrazy() }) +// a.Panics(func(){ GoCrazy() }) func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1277,7 +1361,7 @@ func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool { // panics, and that the recovered panic value is an error that satisfies the // EqualError comparison. // -// a.PanicsWithError("crazy error", func(){ GoCrazy() }) +// a.PanicsWithError("crazy error", func(){ GoCrazy() }) func (a *Assertions) PanicsWithError(errString string, f PanicTestFunc, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1289,7 +1373,7 @@ func (a *Assertions) PanicsWithError(errString string, f PanicTestFunc, msgAndAr // panics, and that the recovered panic value is an error that satisfies the // EqualError comparison. // -// a.PanicsWithErrorf("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +// a.PanicsWithErrorf("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") func (a *Assertions) PanicsWithErrorf(errString string, f PanicTestFunc, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1300,7 +1384,7 @@ func (a *Assertions) PanicsWithErrorf(errString string, f PanicTestFunc, msg str // PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that // the recovered panic value equals the expected panic value. // -// a.PanicsWithValue("crazy error", func(){ GoCrazy() }) +// a.PanicsWithValue("crazy error", func(){ GoCrazy() }) func (a *Assertions) PanicsWithValue(expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1311,7 +1395,7 @@ func (a *Assertions) PanicsWithValue(expected interface{}, f PanicTestFunc, msgA // PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that // the recovered panic value equals the expected panic value. // -// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") func (a *Assertions) PanicsWithValuef(expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1321,7 +1405,7 @@ func (a *Assertions) PanicsWithValuef(expected interface{}, f PanicTestFunc, msg // Panicsf asserts that the code inside the specified PanicTestFunc panics. // -// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted") +// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted") func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1331,8 +1415,8 @@ func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) b // Positive asserts that the specified element is positive // -// a.Positive(1) -// a.Positive(1.23) +// a.Positive(1) +// a.Positive(1.23) func (a *Assertions) Positive(e interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1342,8 +1426,8 @@ func (a *Assertions) Positive(e interface{}, msgAndArgs ...interface{}) bool { // Positivef asserts that the specified element is positive // -// a.Positivef(1, "error message %s", "formatted") -// a.Positivef(1.23, "error message %s", "formatted") +// a.Positivef(1, "error message %s", "formatted") +// a.Positivef(1.23, "error message %s", "formatted") func (a *Assertions) Positivef(e interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1353,8 +1437,8 @@ func (a *Assertions) Positivef(e interface{}, msg string, args ...interface{}) b // Regexp asserts that a specified regexp matches a string. // -// a.Regexp(regexp.MustCompile("start"), "it's starting") -// a.Regexp("start...$", "it's not starting") +// a.Regexp(regexp.MustCompile("start"), "it's starting") +// a.Regexp("start...$", "it's not starting") func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1364,8 +1448,8 @@ func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...inter // Regexpf asserts that a specified regexp matches a string. // -// a.Regexpf(regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") -// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") +// a.Regexpf(regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") +// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1375,7 +1459,7 @@ func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args . // Same asserts that two pointers reference the same object. // -// a.Same(ptr1, ptr2) +// a.Same(ptr1, ptr2) // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1388,7 +1472,7 @@ func (a *Assertions) Same(expected interface{}, actual interface{}, msgAndArgs . // Samef asserts that two pointers reference the same object. // -// a.Samef(ptr1, ptr2, "error message %s", "formatted") +// a.Samef(ptr1, ptr2, "error message %s", "formatted") // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1402,7 +1486,7 @@ func (a *Assertions) Samef(expected interface{}, actual interface{}, msg string, // Subset asserts that the specified list(array, slice...) contains all // elements given in the specified subset(array, slice...). // -// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") +// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1413,7 +1497,7 @@ func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ... // Subsetf asserts that the specified list(array, slice...) contains all // elements given in the specified subset(array, slice...). // -// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") +// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1423,7 +1507,7 @@ func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, a // True asserts that the specified value is true. // -// a.True(myBool) +// a.True(myBool) func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1433,7 +1517,7 @@ func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool { // Truef asserts that the specified value is true. // -// a.Truef(myBool, "error message %s", "formatted") +// a.Truef(myBool, "error message %s", "formatted") func (a *Assertions) Truef(value bool, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1443,7 +1527,7 @@ func (a *Assertions) Truef(value bool, msg string, args ...interface{}) bool { // WithinDuration asserts that the two times are within duration delta of each other. // -// a.WithinDuration(time.Now(), time.Now(), 10*time.Second) +// a.WithinDuration(time.Now(), time.Now(), 10*time.Second) func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1453,7 +1537,7 @@ func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta // WithinDurationf asserts that the two times are within duration delta of each other. // -// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1463,7 +1547,7 @@ func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta // WithinRange asserts that a time is within a time range (inclusive). // -// a.WithinRange(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) +// a.WithinRange(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) func (a *Assertions) WithinRange(actual time.Time, start time.Time, end time.Time, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1473,7 +1557,7 @@ func (a *Assertions) WithinRange(actual time.Time, start time.Time, end time.Tim // WithinRangef asserts that a time is within a time range (inclusive). // -// a.WithinRangef(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") +// a.WithinRangef(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") func (a *Assertions) WithinRangef(actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() diff --git a/vendor/github.com/stretchr/testify/assert/assertion_order.go b/vendor/github.com/stretchr/testify/assert/assertion_order.go index 759448783..00df62a05 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_order.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_order.go @@ -46,36 +46,36 @@ func isOrdered(t TestingT, object interface{}, allowedComparesResults []CompareT // IsIncreasing asserts that the collection is increasing // -// assert.IsIncreasing(t, []int{1, 2, 3}) -// assert.IsIncreasing(t, []float{1, 2}) -// assert.IsIncreasing(t, []string{"a", "b"}) +// assert.IsIncreasing(t, []int{1, 2, 3}) +// assert.IsIncreasing(t, []float{1, 2}) +// assert.IsIncreasing(t, []string{"a", "b"}) func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { return isOrdered(t, object, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...) } // IsNonIncreasing asserts that the collection is not increasing // -// assert.IsNonIncreasing(t, []int{2, 1, 1}) -// assert.IsNonIncreasing(t, []float{2, 1}) -// assert.IsNonIncreasing(t, []string{"b", "a"}) +// assert.IsNonIncreasing(t, []int{2, 1, 1}) +// assert.IsNonIncreasing(t, []float{2, 1}) +// assert.IsNonIncreasing(t, []string{"b", "a"}) func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { return isOrdered(t, object, []CompareType{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...) } // IsDecreasing asserts that the collection is decreasing // -// assert.IsDecreasing(t, []int{2, 1, 0}) -// assert.IsDecreasing(t, []float{2, 1}) -// assert.IsDecreasing(t, []string{"b", "a"}) +// assert.IsDecreasing(t, []int{2, 1, 0}) +// assert.IsDecreasing(t, []float{2, 1}) +// assert.IsDecreasing(t, []string{"b", "a"}) func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { return isOrdered(t, object, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...) } // IsNonDecreasing asserts that the collection is not decreasing // -// assert.IsNonDecreasing(t, []int{1, 1, 2}) -// assert.IsNonDecreasing(t, []float{1, 2}) -// assert.IsNonDecreasing(t, []string{"a", "b"}) +// assert.IsNonDecreasing(t, []int{1, 1, 2}) +// assert.IsNonDecreasing(t, []float{1, 2}) +// assert.IsNonDecreasing(t, []string{"a", "b"}) func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { return isOrdered(t, object, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...) } diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go index 2924cf3a1..a55d1bba9 100644 --- a/vendor/github.com/stretchr/testify/assert/assertions.go +++ b/vendor/github.com/stretchr/testify/assert/assertions.go @@ -75,6 +75,77 @@ func ObjectsAreEqual(expected, actual interface{}) bool { return bytes.Equal(exp, act) } +// copyExportedFields iterates downward through nested data structures and creates a copy +// that only contains the exported struct fields. +func copyExportedFields(expected interface{}) interface{} { + if isNil(expected) { + return expected + } + + expectedType := reflect.TypeOf(expected) + expectedKind := expectedType.Kind() + expectedValue := reflect.ValueOf(expected) + + switch expectedKind { + case reflect.Struct: + result := reflect.New(expectedType).Elem() + for i := 0; i < expectedType.NumField(); i++ { + field := expectedType.Field(i) + isExported := field.IsExported() + if isExported { + fieldValue := expectedValue.Field(i) + if isNil(fieldValue) || isNil(fieldValue.Interface()) { + continue + } + newValue := copyExportedFields(fieldValue.Interface()) + result.Field(i).Set(reflect.ValueOf(newValue)) + } + } + return result.Interface() + + case reflect.Ptr: + result := reflect.New(expectedType.Elem()) + unexportedRemoved := copyExportedFields(expectedValue.Elem().Interface()) + result.Elem().Set(reflect.ValueOf(unexportedRemoved)) + return result.Interface() + + case reflect.Array, reflect.Slice: + result := reflect.MakeSlice(expectedType, expectedValue.Len(), expectedValue.Len()) + for i := 0; i < expectedValue.Len(); i++ { + index := expectedValue.Index(i) + if isNil(index) { + continue + } + unexportedRemoved := copyExportedFields(index.Interface()) + result.Index(i).Set(reflect.ValueOf(unexportedRemoved)) + } + return result.Interface() + + case reflect.Map: + result := reflect.MakeMap(expectedType) + for _, k := range expectedValue.MapKeys() { + index := expectedValue.MapIndex(k) + unexportedRemoved := copyExportedFields(index.Interface()) + result.SetMapIndex(k, reflect.ValueOf(unexportedRemoved)) + } + return result.Interface() + + default: + return expected + } +} + +// ObjectsExportedFieldsAreEqual determines if the exported (public) fields of two objects are +// considered equal. This comparison of only exported fields is applied recursively to nested data +// structures. +// +// This function does no assertion of any kind. +func ObjectsExportedFieldsAreEqual(expected, actual interface{}) bool { + expectedCleaned := copyExportedFields(expected) + actualCleaned := copyExportedFields(actual) + return ObjectsAreEqualValues(expectedCleaned, actualCleaned) +} + // ObjectsAreEqualValues gets whether two objects are equal, or if their // values are equal. func ObjectsAreEqualValues(expected, actual interface{}) bool { @@ -271,7 +342,7 @@ type labeledContent struct { // labeledOutput returns a string consisting of the provided labeledContent. Each labeled output is appended in the following manner: // -// \t{{label}}:{{align_spaces}}\t{{content}}\n +// \t{{label}}:{{align_spaces}}\t{{content}}\n // // The initial carriage return is required to undo/erase any padding added by testing.T.Errorf. The "\t{{label}}:" is for the label. // If a label is shorter than the longest label provided, padding spaces are added to make all the labels match in length. Once this @@ -294,7 +365,7 @@ func labeledOutput(content ...labeledContent) string { // Implements asserts that an object is implemented by the specified interface. // -// assert.Implements(t, (*MyInterface)(nil), new(MyObject)) +// assert.Implements(t, (*MyInterface)(nil), new(MyObject)) func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -326,7 +397,7 @@ func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs // Equal asserts that two objects are equal. // -// assert.Equal(t, 123, 123) +// assert.Equal(t, 123, 123) // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality @@ -367,7 +438,7 @@ func validateEqualArgs(expected, actual interface{}) error { // Same asserts that two pointers reference the same object. // -// assert.Same(t, ptr1, ptr2) +// assert.Same(t, ptr1, ptr2) // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -387,7 +458,7 @@ func Same(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) b // NotSame asserts that two pointers do not reference the same object. // -// assert.NotSame(t, ptr1, ptr2) +// assert.NotSame(t, ptr1, ptr2) // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -455,7 +526,7 @@ func truncatingFormat(data interface{}) string { // EqualValues asserts that two objects are equal or convertable to the same types // and equal. // -// assert.EqualValues(t, uint32(123), int32(123)) +// assert.EqualValues(t, uint32(123), int32(123)) func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -473,9 +544,53 @@ func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interfa } +// EqualExportedValues asserts that the types of two objects are equal and their public +// fields are also equal. This is useful for comparing structs that have private fields +// that could potentially differ. +// +// type S struct { +// Exported int +// notExported int +// } +// assert.EqualExportedValues(t, S{1, 2}, S{1, 3}) => true +// assert.EqualExportedValues(t, S{1, 2}, S{2, 3}) => false +func EqualExportedValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + aType := reflect.TypeOf(expected) + bType := reflect.TypeOf(actual) + + if aType != bType { + return Fail(t, fmt.Sprintf("Types expected to match exactly\n\t%v != %v", aType, bType), msgAndArgs...) + } + + if aType.Kind() != reflect.Struct { + return Fail(t, fmt.Sprintf("Types expected to both be struct \n\t%v != %v", aType.Kind(), reflect.Struct), msgAndArgs...) + } + + if bType.Kind() != reflect.Struct { + return Fail(t, fmt.Sprintf("Types expected to both be struct \n\t%v != %v", bType.Kind(), reflect.Struct), msgAndArgs...) + } + + expected = copyExportedFields(expected) + actual = copyExportedFields(actual) + + if !ObjectsAreEqualValues(expected, actual) { + diff := diff(expected, actual) + expected, actual = formatUnequalValues(expected, actual) + return Fail(t, fmt.Sprintf("Not equal (comparing only exported fields): \n"+ + "expected: %s\n"+ + "actual : %s%s", expected, actual, diff), msgAndArgs...) + } + + return true +} + // Exactly asserts that two objects are equal in value and type. // -// assert.Exactly(t, int32(123), int64(123)) +// assert.Exactly(t, int32(123), int64(123)) func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -494,7 +609,7 @@ func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} // NotNil asserts that the specified object is not nil. // -// assert.NotNil(t, err) +// assert.NotNil(t, err) func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { if !isNil(object) { return true @@ -540,7 +655,7 @@ func isNil(object interface{}) bool { // Nil asserts that the specified object is nil. // -// assert.Nil(t, err) +// assert.Nil(t, err) func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { if isNil(object) { return true @@ -583,7 +698,7 @@ func isEmpty(object interface{}) bool { // Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // -// assert.Empty(t, obj) +// assert.Empty(t, obj) func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { pass := isEmpty(object) if !pass { @@ -600,9 +715,9 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // -// if assert.NotEmpty(t, obj) { -// assert.Equal(t, "two", obj[1]) -// } +// if assert.NotEmpty(t, obj) { +// assert.Equal(t, "two", obj[1]) +// } func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { pass := !isEmpty(object) if !pass { @@ -631,7 +746,7 @@ func getLen(x interface{}) (ok bool, length int) { // Len asserts that the specified object has specific length. // Len also fails if the object has a type that len() not accept. // -// assert.Len(t, mySlice, 3) +// assert.Len(t, mySlice, 3) func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -649,7 +764,7 @@ func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) // True asserts that the specified value is true. // -// assert.True(t, myBool) +// assert.True(t, myBool) func True(t TestingT, value bool, msgAndArgs ...interface{}) bool { if !value { if h, ok := t.(tHelper); ok { @@ -664,7 +779,7 @@ func True(t TestingT, value bool, msgAndArgs ...interface{}) bool { // False asserts that the specified value is false. // -// assert.False(t, myBool) +// assert.False(t, myBool) func False(t TestingT, value bool, msgAndArgs ...interface{}) bool { if value { if h, ok := t.(tHelper); ok { @@ -679,7 +794,7 @@ func False(t TestingT, value bool, msgAndArgs ...interface{}) bool { // NotEqual asserts that the specified values are NOT equal. // -// assert.NotEqual(t, obj1, obj2) +// assert.NotEqual(t, obj1, obj2) // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). @@ -702,7 +817,7 @@ func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{ // NotEqualValues asserts that two objects are not equal even when converted to the same type // -// assert.NotEqualValues(t, obj1, obj2) +// assert.NotEqualValues(t, obj1, obj2) func NotEqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -761,9 +876,9 @@ func containsElement(list interface{}, element interface{}) (ok, found bool) { // Contains asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. // -// assert.Contains(t, "Hello World", "World") -// assert.Contains(t, ["Hello", "World"], "World") -// assert.Contains(t, {"Hello": "World"}, "Hello") +// assert.Contains(t, "Hello World", "World") +// assert.Contains(t, ["Hello", "World"], "World") +// assert.Contains(t, {"Hello": "World"}, "Hello") func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -784,9 +899,9 @@ func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bo // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. // -// assert.NotContains(t, "Hello World", "Earth") -// assert.NotContains(t, ["Hello", "World"], "Earth") -// assert.NotContains(t, {"Hello": "World"}, "Earth") +// assert.NotContains(t, "Hello World", "Earth") +// assert.NotContains(t, ["Hello", "World"], "Earth") +// assert.NotContains(t, {"Hello": "World"}, "Earth") func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -794,10 +909,10 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) ok, found := containsElement(s, contains) if !ok { - return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", s), msgAndArgs...) + return Fail(t, fmt.Sprintf("%#v could not be applied builtin len()", s), msgAndArgs...) } if found { - return Fail(t, fmt.Sprintf("\"%s\" should not contain \"%s\"", s, contains), msgAndArgs...) + return Fail(t, fmt.Sprintf("%#v should not contain %#v", s, contains), msgAndArgs...) } return true @@ -807,7 +922,7 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) // Subset asserts that the specified list(array, slice...) contains all // elements given in the specified subset(array, slice...). // -// assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") +// assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) { if h, ok := t.(tHelper); ok { h.Helper() @@ -863,7 +978,7 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok // NotSubset asserts that the specified list(array, slice...) contains not all // elements given in the specified subset(array, slice...). // -// assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") +// assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1048,7 +1163,7 @@ func didPanic(f PanicTestFunc) (didPanic bool, message interface{}, stack string // Panics asserts that the code inside the specified PanicTestFunc panics. // -// assert.Panics(t, func(){ GoCrazy() }) +// assert.Panics(t, func(){ GoCrazy() }) func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -1064,7 +1179,7 @@ func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { // PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that // the recovered panic value equals the expected panic value. // -// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) +// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) func PanicsWithValue(t TestingT, expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -1085,7 +1200,7 @@ func PanicsWithValue(t TestingT, expected interface{}, f PanicTestFunc, msgAndAr // panics, and that the recovered panic value is an error that satisfies the // EqualError comparison. // -// assert.PanicsWithError(t, "crazy error", func(){ GoCrazy() }) +// assert.PanicsWithError(t, "crazy error", func(){ GoCrazy() }) func PanicsWithError(t TestingT, errString string, f PanicTestFunc, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -1105,7 +1220,7 @@ func PanicsWithError(t TestingT, errString string, f PanicTestFunc, msgAndArgs . // NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. // -// assert.NotPanics(t, func(){ RemainCalm() }) +// assert.NotPanics(t, func(){ RemainCalm() }) func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -1120,7 +1235,7 @@ func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { // WithinDuration asserts that the two times are within duration delta of each other. // -// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) +// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -1136,7 +1251,7 @@ func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration, // WithinRange asserts that a time is within a time range (inclusive). // -// assert.WithinRange(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) +// assert.WithinRange(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) func WithinRange(t TestingT, actual, start, end time.Time, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -1195,7 +1310,7 @@ func toFloat(x interface{}) (float64, bool) { // InDelta asserts that the two numerals are within delta of each other. // -// assert.InDelta(t, math.Pi, 22/7.0, 0.01) +// assert.InDelta(t, math.Pi, 22/7.0, 0.01) func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -1368,10 +1483,10 @@ func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, m // NoError asserts that a function returned no error (i.e. `nil`). // -// actualObj, err := SomeFunction() -// if assert.NoError(t, err) { -// assert.Equal(t, expectedObj, actualObj) -// } +// actualObj, err := SomeFunction() +// if assert.NoError(t, err) { +// assert.Equal(t, expectedObj, actualObj) +// } func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool { if err != nil { if h, ok := t.(tHelper); ok { @@ -1385,10 +1500,10 @@ func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool { // Error asserts that a function returned an error (i.e. not `nil`). // -// actualObj, err := SomeFunction() -// if assert.Error(t, err) { -// assert.Equal(t, expectedError, err) -// } +// actualObj, err := SomeFunction() +// if assert.Error(t, err) { +// assert.Equal(t, expectedError, err) +// } func Error(t TestingT, err error, msgAndArgs ...interface{}) bool { if err == nil { if h, ok := t.(tHelper); ok { @@ -1403,8 +1518,8 @@ func Error(t TestingT, err error, msgAndArgs ...interface{}) bool { // EqualError asserts that a function returned an error (i.e. not `nil`) // and that it is equal to the provided error. // -// actualObj, err := SomeFunction() -// assert.EqualError(t, err, expectedErrorString) +// actualObj, err := SomeFunction() +// assert.EqualError(t, err, expectedErrorString) func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -1426,8 +1541,8 @@ func EqualError(t TestingT, theError error, errString string, msgAndArgs ...inte // ErrorContains asserts that a function returned an error (i.e. not `nil`) // and that the error contains the specified substring. // -// actualObj, err := SomeFunction() -// assert.ErrorContains(t, err, expectedErrorSubString) +// actualObj, err := SomeFunction() +// assert.ErrorContains(t, err, expectedErrorSubString) func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -1460,8 +1575,8 @@ func matchRegexp(rx interface{}, str interface{}) bool { // Regexp asserts that a specified regexp matches a string. // -// assert.Regexp(t, regexp.MustCompile("start"), "it's starting") -// assert.Regexp(t, "start...$", "it's not starting") +// assert.Regexp(t, regexp.MustCompile("start"), "it's starting") +// assert.Regexp(t, "start...$", "it's not starting") func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -1478,8 +1593,8 @@ func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface // NotRegexp asserts that a specified regexp does not match a string. // -// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") -// assert.NotRegexp(t, "^start", "it's not starting") +// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") +// assert.NotRegexp(t, "^start", "it's not starting") func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -1591,7 +1706,7 @@ func NoDirExists(t TestingT, path string, msgAndArgs ...interface{}) bool { // JSONEq asserts that two JSON strings are equivalent. // -// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -1714,7 +1829,7 @@ type tHelper interface { // Eventually asserts that given condition will be met in waitFor time, // periodically checking target function each tick. // -// assert.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond) +// assert.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond) func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -1744,10 +1859,93 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t } } +// CollectT implements the TestingT interface and collects all errors. +type CollectT struct { + errors []error +} + +// Errorf collects the error. +func (c *CollectT) Errorf(format string, args ...interface{}) { + c.errors = append(c.errors, fmt.Errorf(format, args...)) +} + +// FailNow panics. +func (c *CollectT) FailNow() { + panic("Assertion failed") +} + +// Reset clears the collected errors. +func (c *CollectT) Reset() { + c.errors = nil +} + +// Copy copies the collected errors to the supplied t. +func (c *CollectT) Copy(t TestingT) { + if tt, ok := t.(tHelper); ok { + tt.Helper() + } + for _, err := range c.errors { + t.Errorf("%v", err) + } +} + +// EventuallyWithT asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. In contrast to Eventually, +// it supplies a CollectT to the condition function, so that the condition +// function can use the CollectT to call other assertions. +// The condition is considered "met" if no errors are raised in a tick. +// The supplied CollectT collects all errors from one tick (if there are any). +// If the condition is not met before waitFor, the collected errors of +// the last tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// assert.EventuallyWithT(t, func(c *assert.CollectT) { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + collect := new(CollectT) + ch := make(chan bool, 1) + + timer := time.NewTimer(waitFor) + defer timer.Stop() + + ticker := time.NewTicker(tick) + defer ticker.Stop() + + for tick := ticker.C; ; { + select { + case <-timer.C: + collect.Copy(t) + return Fail(t, "Condition never satisfied", msgAndArgs...) + case <-tick: + tick = nil + collect.Reset() + go func() { + condition(collect) + ch <- len(collect.errors) == 0 + }() + case v := <-ch: + if v { + return true + } + tick = ticker.C + } + } +} + // Never asserts that the given condition doesn't satisfy in waitFor time, // periodically checking the target function each tick. // -// assert.Never(t, func() bool { return false; }, time.Second, 10*time.Millisecond) +// assert.Never(t, func() bool { return false; }, time.Second, 10*time.Millisecond) func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/vendor/github.com/stretchr/testify/assert/doc.go b/vendor/github.com/stretchr/testify/assert/doc.go index c9dccc4d6..4953981d3 100644 --- a/vendor/github.com/stretchr/testify/assert/doc.go +++ b/vendor/github.com/stretchr/testify/assert/doc.go @@ -1,39 +1,40 @@ // Package assert provides a set of comprehensive testing tools for use with the normal Go testing system. // -// Example Usage +// # Example Usage // // The following is a complete example using assert in a standard test function: -// import ( -// "testing" -// "github.com/stretchr/testify/assert" -// ) // -// func TestSomething(t *testing.T) { +// import ( +// "testing" +// "github.com/stretchr/testify/assert" +// ) // -// var a string = "Hello" -// var b string = "Hello" +// func TestSomething(t *testing.T) { // -// assert.Equal(t, a, b, "The two words should be the same.") +// var a string = "Hello" +// var b string = "Hello" // -// } +// assert.Equal(t, a, b, "The two words should be the same.") +// +// } // // if you assert many times, use the format below: // -// import ( -// "testing" -// "github.com/stretchr/testify/assert" -// ) +// import ( +// "testing" +// "github.com/stretchr/testify/assert" +// ) // -// func TestSomething(t *testing.T) { -// assert := assert.New(t) +// func TestSomething(t *testing.T) { +// assert := assert.New(t) // -// var a string = "Hello" -// var b string = "Hello" +// var a string = "Hello" +// var b string = "Hello" // -// assert.Equal(a, b, "The two words should be the same.") -// } +// assert.Equal(a, b, "The two words should be the same.") +// } // -// Assertions +// # Assertions // // Assertions allow you to easily write test code, and are global funcs in the `assert` package. // All assertion functions take, as the first argument, the `*testing.T` object provided by the diff --git a/vendor/github.com/stretchr/testify/assert/http_assertions.go b/vendor/github.com/stretchr/testify/assert/http_assertions.go index 4ed341dd2..d8038c28a 100644 --- a/vendor/github.com/stretchr/testify/assert/http_assertions.go +++ b/vendor/github.com/stretchr/testify/assert/http_assertions.go @@ -23,7 +23,7 @@ func httpCode(handler http.HandlerFunc, method, url string, values url.Values) ( // HTTPSuccess asserts that a specified handler returns a success status code. // -// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) +// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) // // Returns whether the assertion was successful (true) or not (false). func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { @@ -45,7 +45,7 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, value // HTTPRedirect asserts that a specified handler returns a redirect status code. // -// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { @@ -67,7 +67,7 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, valu // HTTPError asserts that a specified handler returns an error status code. // -// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { @@ -89,7 +89,7 @@ func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values // HTTPStatusCode asserts that a specified handler returns a specified status code. // -// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501) +// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501) // // Returns whether the assertion was successful (true) or not (false). func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) bool { @@ -124,7 +124,7 @@ func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) s // HTTPBodyContains asserts that a specified handler returns a // body that contains a string. // -// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { @@ -144,7 +144,7 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, // HTTPBodyNotContains asserts that a specified handler returns a // body that does not contain a string. // -// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { diff --git a/vendor/github.com/stretchr/testify/mock/doc.go b/vendor/github.com/stretchr/testify/mock/doc.go index 7324128ef..d6b3c844c 100644 --- a/vendor/github.com/stretchr/testify/mock/doc.go +++ b/vendor/github.com/stretchr/testify/mock/doc.go @@ -1,17 +1,17 @@ // Package mock provides a system by which it is possible to mock your objects // and verify calls are happening as expected. // -// Example Usage +// # Example Usage // // The mock package provides an object, Mock, that tracks activity on another object. It is usually // embedded into a test object as shown below: // -// type MyTestObject struct { -// // add a Mock object instance -// mock.Mock +// type MyTestObject struct { +// // add a Mock object instance +// mock.Mock // -// // other fields go here as normal -// } +// // other fields go here as normal +// } // // When implementing the methods of an interface, you wire your functions up // to call the Mock.Called(args...) method, and return the appropriate values. @@ -19,25 +19,25 @@ // For example, to mock a method that saves the name and age of a person and returns // the year of their birth or an error, you might write this: // -// func (o *MyTestObject) SavePersonDetails(firstname, lastname string, age int) (int, error) { -// args := o.Called(firstname, lastname, age) -// return args.Int(0), args.Error(1) -// } +// func (o *MyTestObject) SavePersonDetails(firstname, lastname string, age int) (int, error) { +// args := o.Called(firstname, lastname, age) +// return args.Int(0), args.Error(1) +// } // // The Int, Error and Bool methods are examples of strongly typed getters that take the argument // index position. Given this argument list: // -// (12, true, "Something") +// (12, true, "Something") // // You could read them out strongly typed like this: // -// args.Int(0) -// args.Bool(1) -// args.String(2) +// args.Int(0) +// args.Bool(1) +// args.String(2) // // For objects of your own type, use the generic Arguments.Get(index) method and make a type assertion: // -// return args.Get(0).(*MyObject), args.Get(1).(*AnotherObjectOfMine) +// return args.Get(0).(*MyObject), args.Get(1).(*AnotherObjectOfMine) // // This may cause a panic if the object you are getting is nil (the type assertion will fail), in those // cases you should check for nil first. diff --git a/vendor/github.com/stretchr/testify/mock/mock.go b/vendor/github.com/stretchr/testify/mock/mock.go index e6ff8dfeb..f4b42e44f 100644 --- a/vendor/github.com/stretchr/testify/mock/mock.go +++ b/vendor/github.com/stretchr/testify/mock/mock.go @@ -3,6 +3,7 @@ package mock import ( "errors" "fmt" + "path" "reflect" "regexp" "runtime" @@ -13,6 +14,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/pmezard/go-difflib/difflib" "github.com/stretchr/objx" + "github.com/stretchr/testify/assert" ) @@ -99,7 +101,7 @@ func (c *Call) unlock() { // Return specifies the return arguments for the expectation. // -// Mock.On("DoSomething").Return(errors.New("failed")) +// Mock.On("DoSomething").Return(errors.New("failed")) func (c *Call) Return(returnArguments ...interface{}) *Call { c.lock() defer c.unlock() @@ -111,7 +113,7 @@ func (c *Call) Return(returnArguments ...interface{}) *Call { // Panic specifies if the functon call should fail and the panic message // -// Mock.On("DoSomething").Panic("test panic") +// Mock.On("DoSomething").Panic("test panic") func (c *Call) Panic(msg string) *Call { c.lock() defer c.unlock() @@ -123,14 +125,14 @@ func (c *Call) Panic(msg string) *Call { // Once indicates that that the mock should only return the value once. // -// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Once() +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Once() func (c *Call) Once() *Call { return c.Times(1) } // Twice indicates that that the mock should only return the value twice. // -// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Twice() +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Twice() func (c *Call) Twice() *Call { return c.Times(2) } @@ -138,7 +140,7 @@ func (c *Call) Twice() *Call { // Times indicates that that the mock should only return the indicated number // of times. // -// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Times(5) +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Times(5) func (c *Call) Times(i int) *Call { c.lock() defer c.unlock() @@ -149,7 +151,7 @@ func (c *Call) Times(i int) *Call { // WaitUntil sets the channel that will block the mock's return until its closed // or a message is received. // -// Mock.On("MyMethod", arg1, arg2).WaitUntil(time.After(time.Second)) +// Mock.On("MyMethod", arg1, arg2).WaitUntil(time.After(time.Second)) func (c *Call) WaitUntil(w <-chan time.Time) *Call { c.lock() defer c.unlock() @@ -159,7 +161,7 @@ func (c *Call) WaitUntil(w <-chan time.Time) *Call { // After sets how long to block until the call returns // -// Mock.On("MyMethod", arg1, arg2).After(time.Second) +// Mock.On("MyMethod", arg1, arg2).After(time.Second) func (c *Call) After(d time.Duration) *Call { c.lock() defer c.unlock() @@ -171,10 +173,10 @@ func (c *Call) After(d time.Duration) *Call { // mocking a method (such as an unmarshaler) that takes a pointer to a struct and // sets properties in such struct // -// Mock.On("Unmarshal", AnythingOfType("*map[string]interface{}")).Return().Run(func(args Arguments) { -// arg := args.Get(0).(*map[string]interface{}) -// arg["foo"] = "bar" -// }) +// Mock.On("Unmarshal", AnythingOfType("*map[string]interface{}")).Return().Run(func(args Arguments) { +// arg := args.Get(0).(*map[string]interface{}) +// arg["foo"] = "bar" +// }) func (c *Call) Run(fn func(args Arguments)) *Call { c.lock() defer c.unlock() @@ -194,16 +196,18 @@ func (c *Call) Maybe() *Call { // On chains a new expectation description onto the mocked interface. This // allows syntax like. // -// Mock. -// On("MyMethod", 1).Return(nil). -// On("MyOtherMethod", 'a', 'b', 'c').Return(errors.New("Some Error")) +// Mock. +// On("MyMethod", 1).Return(nil). +// On("MyOtherMethod", 'a', 'b', 'c').Return(errors.New("Some Error")) +// //go:noinline func (c *Call) On(methodName string, arguments ...interface{}) *Call { return c.Parent.On(methodName, arguments...) } // Unset removes a mock handler from being called. -// test.On("func", mock.Anything).Unset() +// +// test.On("func", mock.Anything).Unset() func (c *Call) Unset() *Call { var unlockOnce sync.Once @@ -249,9 +253,9 @@ func (c *Call) Unset() *Call { // calls have been called as expected. The referenced calls may be from the // same mock instance and/or other mock instances. // -// Mock.On("Do").Return(nil).Notbefore( -// Mock.On("Init").Return(nil) -// ) +// Mock.On("Do").Return(nil).Notbefore( +// Mock.On("Init").Return(nil) +// ) func (c *Call) NotBefore(calls ...*Call) *Call { c.lock() defer c.unlock() @@ -334,7 +338,7 @@ func (m *Mock) fail(format string, args ...interface{}) { // On starts a description of an expectation of the specified method // being called. // -// Mock.On("MyMethod", arg1, arg2) +// Mock.On("MyMethod", arg1, arg2) func (m *Mock) On(methodName string, arguments ...interface{}) *Call { for _, arg := range arguments { if v := reflect.ValueOf(arg); v.Kind() == reflect.Func { @@ -424,6 +428,10 @@ func callString(method string, arguments Arguments, includeArgumentValues bool) if includeArgumentValues { var argVals []string for argIndex, arg := range arguments { + if _, ok := arg.(*FunctionalOptionsArgument); ok { + argVals = append(argVals, fmt.Sprintf("%d: %s", argIndex, arg)) + continue + } argVals = append(argVals, fmt.Sprintf("%d: %#v", argIndex, arg)) } argValsString = fmt.Sprintf("\n\t\t%s", strings.Join(argVals, "\n\t\t")) @@ -758,6 +766,7 @@ type AnythingOfTypeArgument string // name of the type to check for. Used in Diff and Assert. // // For example: +// // Assert(t, AnythingOfType("string"), AnythingOfType("int")) func AnythingOfType(t string) AnythingOfTypeArgument { return AnythingOfTypeArgument(t) @@ -780,6 +789,34 @@ func IsType(t interface{}) *IsTypeArgument { return &IsTypeArgument{t: t} } +// FunctionalOptionsArgument is a struct that contains the type and value of an functional option argument +// for use when type checking. +type FunctionalOptionsArgument struct { + value interface{} +} + +// String returns the string representation of FunctionalOptionsArgument +func (f *FunctionalOptionsArgument) String() string { + var name string + tValue := reflect.ValueOf(f.value) + if tValue.Len() > 0 { + name = "[]" + reflect.TypeOf(tValue.Index(0).Interface()).String() + } + + return strings.Replace(fmt.Sprintf("%#v", f.value), "[]interface {}", name, 1) +} + +// FunctionalOptions returns an FunctionalOptionsArgument object containing the functional option type +// and the values to check of +// +// For example: +// Assert(t, FunctionalOptions("[]foo.FunctionalOption", foo.Opt1(), foo.Opt2())) +func FunctionalOptions(value ...interface{}) *FunctionalOptionsArgument { + return &FunctionalOptionsArgument{ + value: value, + } +} + // argumentMatcher performs custom argument matching, returning whether or // not the argument is matched by the expectation fixture function. type argumentMatcher struct { @@ -926,6 +963,29 @@ func (args Arguments) Diff(objects []interface{}) (string, int) { differences++ output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, reflect.TypeOf(t).Name(), reflect.TypeOf(actual).Name(), actualFmt) } + } else if reflect.TypeOf(expected) == reflect.TypeOf((*FunctionalOptionsArgument)(nil)) { + t := expected.(*FunctionalOptionsArgument).value + + var name string + tValue := reflect.ValueOf(t) + if tValue.Len() > 0 { + name = "[]" + reflect.TypeOf(tValue.Index(0).Interface()).String() + } + + tName := reflect.TypeOf(t).Name() + if name != reflect.TypeOf(actual).String() && tValue.Len() != 0 { + differences++ + output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, tName, reflect.TypeOf(actual).Name(), actualFmt) + } else { + if ef, af := assertOpts(t, actual); ef == "" && af == "" { + // match + output = fmt.Sprintf("%s\t%d: PASS: %s == %s\n", output, i, tName, tName) + } else { + // not match + differences++ + output = fmt.Sprintf("%s\t%d: FAIL: %s != %s\n", output, i, af, ef) + } + } } else { // normal checking @@ -1102,3 +1162,65 @@ var spewConfig = spew.ConfigState{ type tHelper interface { Helper() } + +func assertOpts(expected, actual interface{}) (expectedFmt, actualFmt string) { + expectedOpts := reflect.ValueOf(expected) + actualOpts := reflect.ValueOf(actual) + var expectedNames []string + for i := 0; i < expectedOpts.Len(); i++ { + expectedNames = append(expectedNames, funcName(expectedOpts.Index(i).Interface())) + } + var actualNames []string + for i := 0; i < actualOpts.Len(); i++ { + actualNames = append(actualNames, funcName(actualOpts.Index(i).Interface())) + } + if !assert.ObjectsAreEqual(expectedNames, actualNames) { + expectedFmt = fmt.Sprintf("%v", expectedNames) + actualFmt = fmt.Sprintf("%v", actualNames) + return + } + + for i := 0; i < expectedOpts.Len(); i++ { + expectedOpt := expectedOpts.Index(i).Interface() + actualOpt := actualOpts.Index(i).Interface() + + expectedFunc := expectedNames[i] + actualFunc := actualNames[i] + if expectedFunc != actualFunc { + expectedFmt = expectedFunc + actualFmt = actualFunc + return + } + + ot := reflect.TypeOf(expectedOpt) + var expectedValues []reflect.Value + var actualValues []reflect.Value + if ot.NumIn() == 0 { + return + } + + for i := 0; i < ot.NumIn(); i++ { + vt := ot.In(i).Elem() + expectedValues = append(expectedValues, reflect.New(vt)) + actualValues = append(actualValues, reflect.New(vt)) + } + + reflect.ValueOf(expectedOpt).Call(expectedValues) + reflect.ValueOf(actualOpt).Call(actualValues) + + for i := 0; i < ot.NumIn(); i++ { + if !assert.ObjectsAreEqual(expectedValues[i].Interface(), actualValues[i].Interface()) { + expectedFmt = fmt.Sprintf("%s %+v", expectedNames[i], expectedValues[i].Interface()) + actualFmt = fmt.Sprintf("%s %+v", expectedNames[i], actualValues[i].Interface()) + return + } + } + } + + return "", "" +} + +func funcName(opt interface{}) string { + n := runtime.FuncForPC(reflect.ValueOf(opt).Pointer()).Name() + return strings.TrimSuffix(path.Base(n), path.Ext(n)) +} diff --git a/vendor/github.com/stretchr/testify/require/doc.go b/vendor/github.com/stretchr/testify/require/doc.go index 169de3922..968434724 100644 --- a/vendor/github.com/stretchr/testify/require/doc.go +++ b/vendor/github.com/stretchr/testify/require/doc.go @@ -1,24 +1,25 @@ // Package require implements the same assertions as the `assert` package but // stops test execution when a test fails. // -// Example Usage +// # Example Usage // // The following is a complete example using require in a standard test function: -// import ( -// "testing" -// "github.com/stretchr/testify/require" -// ) // -// func TestSomething(t *testing.T) { +// import ( +// "testing" +// "github.com/stretchr/testify/require" +// ) // -// var a string = "Hello" -// var b string = "Hello" +// func TestSomething(t *testing.T) { // -// require.Equal(t, a, b, "The two words should be the same.") +// var a string = "Hello" +// var b string = "Hello" // -// } +// require.Equal(t, a, b, "The two words should be the same.") // -// Assertions +// } +// +// # Assertions // // The `require` package have same global functions as in the `assert` package, // but instead of returning a boolean result they call `t.FailNow()`. diff --git a/vendor/github.com/stretchr/testify/require/require.go b/vendor/github.com/stretchr/testify/require/require.go index 880853f5a..63f852147 100644 --- a/vendor/github.com/stretchr/testify/require/require.go +++ b/vendor/github.com/stretchr/testify/require/require.go @@ -37,9 +37,9 @@ func Conditionf(t TestingT, comp assert.Comparison, msg string, args ...interfac // Contains asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. // -// assert.Contains(t, "Hello World", "World") -// assert.Contains(t, ["Hello", "World"], "World") -// assert.Contains(t, {"Hello": "World"}, "Hello") +// assert.Contains(t, "Hello World", "World") +// assert.Contains(t, ["Hello", "World"], "World") +// assert.Contains(t, {"Hello": "World"}, "Hello") func Contains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -53,9 +53,9 @@ func Contains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...int // Containsf asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. // -// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted") -// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") -// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") +// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted") +// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") +// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -123,7 +123,7 @@ func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string // Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // -// assert.Empty(t, obj) +// assert.Empty(t, obj) func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -137,7 +137,7 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { // Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // -// assert.Emptyf(t, obj, "error message %s", "formatted") +// assert.Emptyf(t, obj, "error message %s", "formatted") func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -150,7 +150,7 @@ func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) { // Equal asserts that two objects are equal. // -// assert.Equal(t, 123, 123) +// assert.Equal(t, 123, 123) // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality @@ -168,8 +168,8 @@ func Equal(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...i // EqualError asserts that a function returned an error (i.e. not `nil`) // and that it is equal to the provided error. // -// actualObj, err := SomeFunction() -// assert.EqualError(t, err, expectedErrorString) +// actualObj, err := SomeFunction() +// assert.EqualError(t, err, expectedErrorString) func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -183,8 +183,8 @@ func EqualError(t TestingT, theError error, errString string, msgAndArgs ...inte // EqualErrorf asserts that a function returned an error (i.e. not `nil`) // and that it is equal to the provided error. // -// actualObj, err := SomeFunction() -// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") +// actualObj, err := SomeFunction() +// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -195,10 +195,50 @@ func EqualErrorf(t TestingT, theError error, errString string, msg string, args t.FailNow() } +// EqualExportedValues asserts that the types of two objects are equal and their public +// fields are also equal. This is useful for comparing structs that have private fields +// that could potentially differ. +// +// type S struct { +// Exported int +// notExported int +// } +// assert.EqualExportedValues(t, S{1, 2}, S{1, 3}) => true +// assert.EqualExportedValues(t, S{1, 2}, S{2, 3}) => false +func EqualExportedValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualExportedValues(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// EqualExportedValuesf asserts that the types of two objects are equal and their public +// fields are also equal. This is useful for comparing structs that have private fields +// that could potentially differ. +// +// type S struct { +// Exported int +// notExported int +// } +// assert.EqualExportedValuesf(t, S{1, 2}, S{1, 3}, "error message %s", "formatted") => true +// assert.EqualExportedValuesf(t, S{1, 2}, S{2, 3}, "error message %s", "formatted") => false +func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualExportedValuesf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + // EqualValues asserts that two objects are equal or convertable to the same types // and equal. // -// assert.EqualValues(t, uint32(123), int32(123)) +// assert.EqualValues(t, uint32(123), int32(123)) func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -212,7 +252,7 @@ func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArg // EqualValuesf asserts that two objects are equal or convertable to the same types // and equal. // -// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted") +// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted") func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -225,7 +265,7 @@ func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg stri // Equalf asserts that two objects are equal. // -// assert.Equalf(t, 123, 123, "error message %s", "formatted") +// assert.Equalf(t, 123, 123, "error message %s", "formatted") // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality @@ -242,10 +282,10 @@ func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, ar // Error asserts that a function returned an error (i.e. not `nil`). // -// actualObj, err := SomeFunction() -// if assert.Error(t, err) { -// assert.Equal(t, expectedError, err) -// } +// actualObj, err := SomeFunction() +// if assert.Error(t, err) { +// assert.Equal(t, expectedError, err) +// } func Error(t TestingT, err error, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -283,8 +323,8 @@ func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...int // ErrorContains asserts that a function returned an error (i.e. not `nil`) // and that the error contains the specified substring. // -// actualObj, err := SomeFunction() -// assert.ErrorContains(t, err, expectedErrorSubString) +// actualObj, err := SomeFunction() +// assert.ErrorContains(t, err, expectedErrorSubString) func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -298,8 +338,8 @@ func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...in // ErrorContainsf asserts that a function returned an error (i.e. not `nil`) // and that the error contains the specified substring. // -// actualObj, err := SomeFunction() -// assert.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted") +// actualObj, err := SomeFunction() +// assert.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted") func ErrorContainsf(t TestingT, theError error, contains string, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -336,10 +376,10 @@ func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface // Errorf asserts that a function returned an error (i.e. not `nil`). // -// actualObj, err := SomeFunction() -// if assert.Errorf(t, err, "error message %s", "formatted") { -// assert.Equal(t, expectedErrorf, err) -// } +// actualObj, err := SomeFunction() +// if assert.Errorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } func Errorf(t TestingT, err error, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -353,7 +393,7 @@ func Errorf(t TestingT, err error, msg string, args ...interface{}) { // Eventually asserts that given condition will be met in waitFor time, // periodically checking target function each tick. // -// assert.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond) +// assert.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond) func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -364,10 +404,66 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t t.FailNow() } +// EventuallyWithT asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. In contrast to Eventually, +// it supplies a CollectT to the condition function, so that the condition +// function can use the CollectT to call other assertions. +// The condition is considered "met" if no errors are raised in a tick. +// The supplied CollectT collects all errors from one tick (if there are any). +// If the condition is not met before waitFor, the collected errors of +// the last tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// assert.EventuallyWithT(t, func(c *assert.CollectT) { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +func EventuallyWithT(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EventuallyWithT(t, condition, waitFor, tick, msgAndArgs...) { + return + } + t.FailNow() +} + +// EventuallyWithTf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. In contrast to Eventually, +// it supplies a CollectT to the condition function, so that the condition +// function can use the CollectT to call other assertions. +// The condition is considered "met" if no errors are raised in a tick. +// The supplied CollectT collects all errors from one tick (if there are any). +// If the condition is not met before waitFor, the collected errors of +// the last tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// assert.EventuallyWithTf(t, func(c *assert.CollectT, "error message %s", "formatted") { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +func EventuallyWithTf(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EventuallyWithTf(t, condition, waitFor, tick, msg, args...) { + return + } + t.FailNow() +} + // Eventuallyf asserts that given condition will be met in waitFor time, // periodically checking target function each tick. // -// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -380,7 +476,7 @@ func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick // Exactly asserts that two objects are equal in value and type. // -// assert.Exactly(t, int32(123), int64(123)) +// assert.Exactly(t, int32(123), int64(123)) func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -393,7 +489,7 @@ func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs .. // Exactlyf asserts that two objects are equal in value and type. // -// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted") +// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted") func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -450,7 +546,7 @@ func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) { // False asserts that the specified value is false. // -// assert.False(t, myBool) +// assert.False(t, myBool) func False(t TestingT, value bool, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -463,7 +559,7 @@ func False(t TestingT, value bool, msgAndArgs ...interface{}) { // Falsef asserts that the specified value is false. // -// assert.Falsef(t, myBool, "error message %s", "formatted") +// assert.Falsef(t, myBool, "error message %s", "formatted") func Falsef(t TestingT, value bool, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -500,9 +596,9 @@ func FileExistsf(t TestingT, path string, msg string, args ...interface{}) { // Greater asserts that the first element is greater than the second // -// assert.Greater(t, 2, 1) -// assert.Greater(t, float64(2), float64(1)) -// assert.Greater(t, "b", "a") +// assert.Greater(t, 2, 1) +// assert.Greater(t, float64(2), float64(1)) +// assert.Greater(t, "b", "a") func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -515,10 +611,10 @@ func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface // GreaterOrEqual asserts that the first element is greater than or equal to the second // -// assert.GreaterOrEqual(t, 2, 1) -// assert.GreaterOrEqual(t, 2, 2) -// assert.GreaterOrEqual(t, "b", "a") -// assert.GreaterOrEqual(t, "b", "b") +// assert.GreaterOrEqual(t, 2, 1) +// assert.GreaterOrEqual(t, 2, 2) +// assert.GreaterOrEqual(t, "b", "a") +// assert.GreaterOrEqual(t, "b", "b") func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -531,10 +627,10 @@ func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...in // GreaterOrEqualf asserts that the first element is greater than or equal to the second // -// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted") -// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted") -// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted") -// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted") +// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted") +// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted") +// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted") +// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted") func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -547,9 +643,9 @@ func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, arg // Greaterf asserts that the first element is greater than the second // -// assert.Greaterf(t, 2, 1, "error message %s", "formatted") -// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted") -// assert.Greaterf(t, "b", "a", "error message %s", "formatted") +// assert.Greaterf(t, 2, 1, "error message %s", "formatted") +// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted") +// assert.Greaterf(t, "b", "a", "error message %s", "formatted") func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -563,7 +659,7 @@ func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...in // HTTPBodyContains asserts that a specified handler returns a // body that contains a string. // -// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { @@ -579,7 +675,7 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url s // HTTPBodyContainsf asserts that a specified handler returns a // body that contains a string. // -// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { @@ -595,7 +691,7 @@ func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url // HTTPBodyNotContains asserts that a specified handler returns a // body that does not contain a string. // -// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { @@ -611,7 +707,7 @@ func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, ur // HTTPBodyNotContainsf asserts that a specified handler returns a // body that does not contain a string. // -// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { @@ -626,7 +722,7 @@ func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, u // HTTPError asserts that a specified handler returns an error status code. // -// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func HTTPError(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { @@ -641,7 +737,7 @@ func HTTPError(t TestingT, handler http.HandlerFunc, method string, url string, // HTTPErrorf asserts that a specified handler returns an error status code. // -// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { @@ -656,7 +752,7 @@ func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, // HTTPRedirect asserts that a specified handler returns a redirect status code. // -// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func HTTPRedirect(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { @@ -671,7 +767,7 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method string, url strin // HTTPRedirectf asserts that a specified handler returns a redirect status code. // -// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { @@ -686,7 +782,7 @@ func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url stri // HTTPStatusCode asserts that a specified handler returns a specified status code. // -// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501) +// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501) // // Returns whether the assertion was successful (true) or not (false). func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) { @@ -701,7 +797,7 @@ func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method string, url str // HTTPStatusCodef asserts that a specified handler returns a specified status code. // -// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") +// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) { @@ -716,7 +812,7 @@ func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url st // HTTPSuccess asserts that a specified handler returns a success status code. // -// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) +// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) // // Returns whether the assertion was successful (true) or not (false). func HTTPSuccess(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { @@ -731,7 +827,7 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method string, url string // HTTPSuccessf asserts that a specified handler returns a success status code. // -// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { @@ -746,7 +842,7 @@ func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url strin // Implements asserts that an object is implemented by the specified interface. // -// assert.Implements(t, (*MyInterface)(nil), new(MyObject)) +// assert.Implements(t, (*MyInterface)(nil), new(MyObject)) func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -759,7 +855,7 @@ func Implements(t TestingT, interfaceObject interface{}, object interface{}, msg // Implementsf asserts that an object is implemented by the specified interface. // -// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted") func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -772,7 +868,7 @@ func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, ms // InDelta asserts that the two numerals are within delta of each other. // -// assert.InDelta(t, math.Pi, 22/7.0, 0.01) +// assert.InDelta(t, math.Pi, 22/7.0, 0.01) func InDelta(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -829,7 +925,7 @@ func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta f // InDeltaf asserts that the two numerals are within delta of each other. // -// assert.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted") +// assert.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted") func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -886,9 +982,9 @@ func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon fl // IsDecreasing asserts that the collection is decreasing // -// assert.IsDecreasing(t, []int{2, 1, 0}) -// assert.IsDecreasing(t, []float{2, 1}) -// assert.IsDecreasing(t, []string{"b", "a"}) +// assert.IsDecreasing(t, []int{2, 1, 0}) +// assert.IsDecreasing(t, []float{2, 1}) +// assert.IsDecreasing(t, []string{"b", "a"}) func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -901,9 +997,9 @@ func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { // IsDecreasingf asserts that the collection is decreasing // -// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted") -// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted") -// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted") +// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted") +// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted") +// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted") func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -916,9 +1012,9 @@ func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface // IsIncreasing asserts that the collection is increasing // -// assert.IsIncreasing(t, []int{1, 2, 3}) -// assert.IsIncreasing(t, []float{1, 2}) -// assert.IsIncreasing(t, []string{"a", "b"}) +// assert.IsIncreasing(t, []int{1, 2, 3}) +// assert.IsIncreasing(t, []float{1, 2}) +// assert.IsIncreasing(t, []string{"a", "b"}) func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -931,9 +1027,9 @@ func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { // IsIncreasingf asserts that the collection is increasing // -// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted") -// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted") -// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted") +// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted") +// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted") +// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted") func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -946,9 +1042,9 @@ func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface // IsNonDecreasing asserts that the collection is not decreasing // -// assert.IsNonDecreasing(t, []int{1, 1, 2}) -// assert.IsNonDecreasing(t, []float{1, 2}) -// assert.IsNonDecreasing(t, []string{"a", "b"}) +// assert.IsNonDecreasing(t, []int{1, 1, 2}) +// assert.IsNonDecreasing(t, []float{1, 2}) +// assert.IsNonDecreasing(t, []string{"a", "b"}) func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -961,9 +1057,9 @@ func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) // IsNonDecreasingf asserts that the collection is not decreasing // -// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted") -// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted") -// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted") +// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted") +// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted") +// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted") func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -976,9 +1072,9 @@ func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interf // IsNonIncreasing asserts that the collection is not increasing // -// assert.IsNonIncreasing(t, []int{2, 1, 1}) -// assert.IsNonIncreasing(t, []float{2, 1}) -// assert.IsNonIncreasing(t, []string{"b", "a"}) +// assert.IsNonIncreasing(t, []int{2, 1, 1}) +// assert.IsNonIncreasing(t, []float{2, 1}) +// assert.IsNonIncreasing(t, []string{"b", "a"}) func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -991,9 +1087,9 @@ func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) // IsNonIncreasingf asserts that the collection is not increasing // -// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted") -// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted") -// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted") +// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted") +// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted") +// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted") func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1028,7 +1124,7 @@ func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg strin // JSONEq asserts that two JSON strings are equivalent. // -// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1041,7 +1137,7 @@ func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{ // JSONEqf asserts that two JSON strings are equivalent. // -// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1055,7 +1151,7 @@ func JSONEqf(t TestingT, expected string, actual string, msg string, args ...int // Len asserts that the specified object has specific length. // Len also fails if the object has a type that len() not accept. // -// assert.Len(t, mySlice, 3) +// assert.Len(t, mySlice, 3) func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1069,7 +1165,7 @@ func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) // Lenf asserts that the specified object has specific length. // Lenf also fails if the object has a type that len() not accept. // -// assert.Lenf(t, mySlice, 3, "error message %s", "formatted") +// assert.Lenf(t, mySlice, 3, "error message %s", "formatted") func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1082,9 +1178,9 @@ func Lenf(t TestingT, object interface{}, length int, msg string, args ...interf // Less asserts that the first element is less than the second // -// assert.Less(t, 1, 2) -// assert.Less(t, float64(1), float64(2)) -// assert.Less(t, "a", "b") +// assert.Less(t, 1, 2) +// assert.Less(t, float64(1), float64(2)) +// assert.Less(t, "a", "b") func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1097,10 +1193,10 @@ func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) // LessOrEqual asserts that the first element is less than or equal to the second // -// assert.LessOrEqual(t, 1, 2) -// assert.LessOrEqual(t, 2, 2) -// assert.LessOrEqual(t, "a", "b") -// assert.LessOrEqual(t, "b", "b") +// assert.LessOrEqual(t, 1, 2) +// assert.LessOrEqual(t, 2, 2) +// assert.LessOrEqual(t, "a", "b") +// assert.LessOrEqual(t, "b", "b") func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1113,10 +1209,10 @@ func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...inter // LessOrEqualf asserts that the first element is less than or equal to the second // -// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted") -// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted") -// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted") -// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted") +// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted") +// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted") +// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted") +// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted") func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1129,9 +1225,9 @@ func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args . // Lessf asserts that the first element is less than the second // -// assert.Lessf(t, 1, 2, "error message %s", "formatted") -// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted") -// assert.Lessf(t, "a", "b", "error message %s", "formatted") +// assert.Lessf(t, 1, 2, "error message %s", "formatted") +// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted") +// assert.Lessf(t, "a", "b", "error message %s", "formatted") func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1144,8 +1240,8 @@ func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...inter // Negative asserts that the specified element is negative // -// assert.Negative(t, -1) -// assert.Negative(t, -1.23) +// assert.Negative(t, -1) +// assert.Negative(t, -1.23) func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1158,8 +1254,8 @@ func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) { // Negativef asserts that the specified element is negative // -// assert.Negativef(t, -1, "error message %s", "formatted") -// assert.Negativef(t, -1.23, "error message %s", "formatted") +// assert.Negativef(t, -1, "error message %s", "formatted") +// assert.Negativef(t, -1.23, "error message %s", "formatted") func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1173,7 +1269,7 @@ func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) { // Never asserts that the given condition doesn't satisfy in waitFor time, // periodically checking the target function each tick. // -// assert.Never(t, func() bool { return false; }, time.Second, 10*time.Millisecond) +// assert.Never(t, func() bool { return false; }, time.Second, 10*time.Millisecond) func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1187,7 +1283,7 @@ func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.D // Neverf asserts that the given condition doesn't satisfy in waitFor time, // periodically checking the target function each tick. // -// assert.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +// assert.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1200,7 +1296,7 @@ func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time. // Nil asserts that the specified object is nil. // -// assert.Nil(t, err) +// assert.Nil(t, err) func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1213,7 +1309,7 @@ func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) { // Nilf asserts that the specified object is nil. // -// assert.Nilf(t, err, "error message %s", "formatted") +// assert.Nilf(t, err, "error message %s", "formatted") func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1250,10 +1346,10 @@ func NoDirExistsf(t TestingT, path string, msg string, args ...interface{}) { // NoError asserts that a function returned no error (i.e. `nil`). // -// actualObj, err := SomeFunction() -// if assert.NoError(t, err) { -// assert.Equal(t, expectedObj, actualObj) -// } +// actualObj, err := SomeFunction() +// if assert.NoError(t, err) { +// assert.Equal(t, expectedObj, actualObj) +// } func NoError(t TestingT, err error, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1266,10 +1362,10 @@ func NoError(t TestingT, err error, msgAndArgs ...interface{}) { // NoErrorf asserts that a function returned no error (i.e. `nil`). // -// actualObj, err := SomeFunction() -// if assert.NoErrorf(t, err, "error message %s", "formatted") { -// assert.Equal(t, expectedObj, actualObj) -// } +// actualObj, err := SomeFunction() +// if assert.NoErrorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } func NoErrorf(t TestingT, err error, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1307,9 +1403,9 @@ func NoFileExistsf(t TestingT, path string, msg string, args ...interface{}) { // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. // -// assert.NotContains(t, "Hello World", "Earth") -// assert.NotContains(t, ["Hello", "World"], "Earth") -// assert.NotContains(t, {"Hello": "World"}, "Earth") +// assert.NotContains(t, "Hello World", "Earth") +// assert.NotContains(t, ["Hello", "World"], "Earth") +// assert.NotContains(t, {"Hello": "World"}, "Earth") func NotContains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1323,9 +1419,9 @@ func NotContains(t TestingT, s interface{}, contains interface{}, msgAndArgs ... // NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. // -// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") -// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") -// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1339,9 +1435,9 @@ func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, a // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // -// if assert.NotEmpty(t, obj) { -// assert.Equal(t, "two", obj[1]) -// } +// if assert.NotEmpty(t, obj) { +// assert.Equal(t, "two", obj[1]) +// } func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1355,9 +1451,9 @@ func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { // NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // -// if assert.NotEmptyf(t, obj, "error message %s", "formatted") { -// assert.Equal(t, "two", obj[1]) -// } +// if assert.NotEmptyf(t, obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1370,7 +1466,7 @@ func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) // NotEqual asserts that the specified values are NOT equal. // -// assert.NotEqual(t, obj1, obj2) +// assert.NotEqual(t, obj1, obj2) // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). @@ -1386,7 +1482,7 @@ func NotEqual(t TestingT, expected interface{}, actual interface{}, msgAndArgs . // NotEqualValues asserts that two objects are not equal even when converted to the same type // -// assert.NotEqualValues(t, obj1, obj2) +// assert.NotEqualValues(t, obj1, obj2) func NotEqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1399,7 +1495,7 @@ func NotEqualValues(t TestingT, expected interface{}, actual interface{}, msgAnd // NotEqualValuesf asserts that two objects are not equal even when converted to the same type // -// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted") +// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted") func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1412,7 +1508,7 @@ func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg s // NotEqualf asserts that the specified values are NOT equal. // -// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted") +// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted") // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). @@ -1452,7 +1548,7 @@ func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interf // NotNil asserts that the specified object is not nil. // -// assert.NotNil(t, err) +// assert.NotNil(t, err) func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1465,7 +1561,7 @@ func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) { // NotNilf asserts that the specified object is not nil. // -// assert.NotNilf(t, err, "error message %s", "formatted") +// assert.NotNilf(t, err, "error message %s", "formatted") func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1478,7 +1574,7 @@ func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) { // NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. // -// assert.NotPanics(t, func(){ RemainCalm() }) +// assert.NotPanics(t, func(){ RemainCalm() }) func NotPanics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1491,7 +1587,7 @@ func NotPanics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { // NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. // -// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") +// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") func NotPanicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1504,8 +1600,8 @@ func NotPanicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interfac // NotRegexp asserts that a specified regexp does not match a string. // -// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") -// assert.NotRegexp(t, "^start", "it's not starting") +// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") +// assert.NotRegexp(t, "^start", "it's not starting") func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1518,8 +1614,8 @@ func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interf // NotRegexpf asserts that a specified regexp does not match a string. // -// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") -// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") +// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") +// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1532,7 +1628,7 @@ func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args .. // NotSame asserts that two pointers do not reference the same object. // -// assert.NotSame(t, ptr1, ptr2) +// assert.NotSame(t, ptr1, ptr2) // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1548,7 +1644,7 @@ func NotSame(t TestingT, expected interface{}, actual interface{}, msgAndArgs .. // NotSamef asserts that two pointers do not reference the same object. // -// assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted") +// assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted") // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1565,7 +1661,7 @@ func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, // NotSubset asserts that the specified list(array, slice...) contains not all // elements given in the specified subset(array, slice...). // -// assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") +// assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1579,7 +1675,7 @@ func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...i // NotSubsetf asserts that the specified list(array, slice...) contains not all // elements given in the specified subset(array, slice...). // -// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") +// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1614,7 +1710,7 @@ func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) { // Panics asserts that the code inside the specified PanicTestFunc panics. // -// assert.Panics(t, func(){ GoCrazy() }) +// assert.Panics(t, func(){ GoCrazy() }) func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1629,7 +1725,7 @@ func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { // panics, and that the recovered panic value is an error that satisfies the // EqualError comparison. // -// assert.PanicsWithError(t, "crazy error", func(){ GoCrazy() }) +// assert.PanicsWithError(t, "crazy error", func(){ GoCrazy() }) func PanicsWithError(t TestingT, errString string, f assert.PanicTestFunc, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1644,7 +1740,7 @@ func PanicsWithError(t TestingT, errString string, f assert.PanicTestFunc, msgAn // panics, and that the recovered panic value is an error that satisfies the // EqualError comparison. // -// assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +// assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") func PanicsWithErrorf(t TestingT, errString string, f assert.PanicTestFunc, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1658,7 +1754,7 @@ func PanicsWithErrorf(t TestingT, errString string, f assert.PanicTestFunc, msg // PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that // the recovered panic value equals the expected panic value. // -// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) +// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) func PanicsWithValue(t TestingT, expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1672,7 +1768,7 @@ func PanicsWithValue(t TestingT, expected interface{}, f assert.PanicTestFunc, m // PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that // the recovered panic value equals the expected panic value. // -// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") func PanicsWithValuef(t TestingT, expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1685,7 +1781,7 @@ func PanicsWithValuef(t TestingT, expected interface{}, f assert.PanicTestFunc, // Panicsf asserts that the code inside the specified PanicTestFunc panics. // -// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") +// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") func Panicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1698,8 +1794,8 @@ func Panicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{} // Positive asserts that the specified element is positive // -// assert.Positive(t, 1) -// assert.Positive(t, 1.23) +// assert.Positive(t, 1) +// assert.Positive(t, 1.23) func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1712,8 +1808,8 @@ func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) { // Positivef asserts that the specified element is positive // -// assert.Positivef(t, 1, "error message %s", "formatted") -// assert.Positivef(t, 1.23, "error message %s", "formatted") +// assert.Positivef(t, 1, "error message %s", "formatted") +// assert.Positivef(t, 1.23, "error message %s", "formatted") func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1726,8 +1822,8 @@ func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) { // Regexp asserts that a specified regexp matches a string. // -// assert.Regexp(t, regexp.MustCompile("start"), "it's starting") -// assert.Regexp(t, "start...$", "it's not starting") +// assert.Regexp(t, regexp.MustCompile("start"), "it's starting") +// assert.Regexp(t, "start...$", "it's not starting") func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1740,8 +1836,8 @@ func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface // Regexpf asserts that a specified regexp matches a string. // -// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") -// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") +// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") +// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1754,7 +1850,7 @@ func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...in // Same asserts that two pointers reference the same object. // -// assert.Same(t, ptr1, ptr2) +// assert.Same(t, ptr1, ptr2) // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1770,7 +1866,7 @@ func Same(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...in // Samef asserts that two pointers reference the same object. // -// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted") +// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted") // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1787,7 +1883,7 @@ func Samef(t TestingT, expected interface{}, actual interface{}, msg string, arg // Subset asserts that the specified list(array, slice...) contains all // elements given in the specified subset(array, slice...). // -// assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") +// assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1801,7 +1897,7 @@ func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...inte // Subsetf asserts that the specified list(array, slice...) contains all // elements given in the specified subset(array, slice...). // -// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") +// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1814,7 +1910,7 @@ func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args // True asserts that the specified value is true. // -// assert.True(t, myBool) +// assert.True(t, myBool) func True(t TestingT, value bool, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1827,7 +1923,7 @@ func True(t TestingT, value bool, msgAndArgs ...interface{}) { // Truef asserts that the specified value is true. // -// assert.Truef(t, myBool, "error message %s", "formatted") +// assert.Truef(t, myBool, "error message %s", "formatted") func Truef(t TestingT, value bool, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1840,7 +1936,7 @@ func Truef(t TestingT, value bool, msg string, args ...interface{}) { // WithinDuration asserts that the two times are within duration delta of each other. // -// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) +// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) func WithinDuration(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1853,7 +1949,7 @@ func WithinDuration(t TestingT, expected time.Time, actual time.Time, delta time // WithinDurationf asserts that the two times are within duration delta of each other. // -// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1866,7 +1962,7 @@ func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta tim // WithinRange asserts that a time is within a time range (inclusive). // -// assert.WithinRange(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) +// assert.WithinRange(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) func WithinRange(t TestingT, actual time.Time, start time.Time, end time.Time, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1879,7 +1975,7 @@ func WithinRange(t TestingT, actual time.Time, start time.Time, end time.Time, m // WithinRangef asserts that a time is within a time range (inclusive). // -// assert.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") +// assert.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") func WithinRangef(t TestingT, actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go b/vendor/github.com/stretchr/testify/require/require_forward.go index 960bf6f2c..3b5b09330 100644 --- a/vendor/github.com/stretchr/testify/require/require_forward.go +++ b/vendor/github.com/stretchr/testify/require/require_forward.go @@ -31,9 +31,9 @@ func (a *Assertions) Conditionf(comp assert.Comparison, msg string, args ...inte // Contains asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. // -// a.Contains("Hello World", "World") -// a.Contains(["Hello", "World"], "World") -// a.Contains({"Hello": "World"}, "Hello") +// a.Contains("Hello World", "World") +// a.Contains(["Hello", "World"], "World") +// a.Contains({"Hello": "World"}, "Hello") func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -44,9 +44,9 @@ func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs .. // Containsf asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. // -// a.Containsf("Hello World", "World", "error message %s", "formatted") -// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted") -// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted") +// a.Containsf("Hello World", "World", "error message %s", "formatted") +// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted") +// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted") func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -99,7 +99,7 @@ func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg st // Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // -// a.Empty(obj) +// a.Empty(obj) func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -110,7 +110,7 @@ func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) { // Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // -// a.Emptyf(obj, "error message %s", "formatted") +// a.Emptyf(obj, "error message %s", "formatted") func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -120,7 +120,7 @@ func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) // Equal asserts that two objects are equal. // -// a.Equal(123, 123) +// a.Equal(123, 123) // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality @@ -135,8 +135,8 @@ func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs // EqualError asserts that a function returned an error (i.e. not `nil`) // and that it is equal to the provided error. // -// actualObj, err := SomeFunction() -// a.EqualError(err, expectedErrorString) +// actualObj, err := SomeFunction() +// a.EqualError(err, expectedErrorString) func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -147,8 +147,8 @@ func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ... // EqualErrorf asserts that a function returned an error (i.e. not `nil`) // and that it is equal to the provided error. // -// actualObj, err := SomeFunction() -// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted") +// actualObj, err := SomeFunction() +// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted") func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -156,10 +156,44 @@ func (a *Assertions) EqualErrorf(theError error, errString string, msg string, a EqualErrorf(a.t, theError, errString, msg, args...) } +// EqualExportedValues asserts that the types of two objects are equal and their public +// fields are also equal. This is useful for comparing structs that have private fields +// that could potentially differ. +// +// type S struct { +// Exported int +// notExported int +// } +// a.EqualExportedValues(S{1, 2}, S{1, 3}) => true +// a.EqualExportedValues(S{1, 2}, S{2, 3}) => false +func (a *Assertions) EqualExportedValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualExportedValues(a.t, expected, actual, msgAndArgs...) +} + +// EqualExportedValuesf asserts that the types of two objects are equal and their public +// fields are also equal. This is useful for comparing structs that have private fields +// that could potentially differ. +// +// type S struct { +// Exported int +// notExported int +// } +// a.EqualExportedValuesf(S{1, 2}, S{1, 3}, "error message %s", "formatted") => true +// a.EqualExportedValuesf(S{1, 2}, S{2, 3}, "error message %s", "formatted") => false +func (a *Assertions) EqualExportedValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualExportedValuesf(a.t, expected, actual, msg, args...) +} + // EqualValues asserts that two objects are equal or convertable to the same types // and equal. // -// a.EqualValues(uint32(123), int32(123)) +// a.EqualValues(uint32(123), int32(123)) func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -170,7 +204,7 @@ func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAn // EqualValuesf asserts that two objects are equal or convertable to the same types // and equal. // -// a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted") +// a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted") func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -180,7 +214,7 @@ func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg // Equalf asserts that two objects are equal. // -// a.Equalf(123, 123, "error message %s", "formatted") +// a.Equalf(123, 123, "error message %s", "formatted") // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality @@ -194,10 +228,10 @@ func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string // Error asserts that a function returned an error (i.e. not `nil`). // -// actualObj, err := SomeFunction() -// if a.Error(err) { -// assert.Equal(t, expectedError, err) -// } +// actualObj, err := SomeFunction() +// if a.Error(err) { +// assert.Equal(t, expectedError, err) +// } func (a *Assertions) Error(err error, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -226,8 +260,8 @@ func (a *Assertions) ErrorAsf(err error, target interface{}, msg string, args .. // ErrorContains asserts that a function returned an error (i.e. not `nil`) // and that the error contains the specified substring. // -// actualObj, err := SomeFunction() -// a.ErrorContains(err, expectedErrorSubString) +// actualObj, err := SomeFunction() +// a.ErrorContains(err, expectedErrorSubString) func (a *Assertions) ErrorContains(theError error, contains string, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -238,8 +272,8 @@ func (a *Assertions) ErrorContains(theError error, contains string, msgAndArgs . // ErrorContainsf asserts that a function returned an error (i.e. not `nil`) // and that the error contains the specified substring. // -// actualObj, err := SomeFunction() -// a.ErrorContainsf(err, expectedErrorSubString, "error message %s", "formatted") +// actualObj, err := SomeFunction() +// a.ErrorContainsf(err, expectedErrorSubString, "error message %s", "formatted") func (a *Assertions) ErrorContainsf(theError error, contains string, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -267,10 +301,10 @@ func (a *Assertions) ErrorIsf(err error, target error, msg string, args ...inter // Errorf asserts that a function returned an error (i.e. not `nil`). // -// actualObj, err := SomeFunction() -// if a.Errorf(err, "error message %s", "formatted") { -// assert.Equal(t, expectedErrorf, err) -// } +// actualObj, err := SomeFunction() +// if a.Errorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } func (a *Assertions) Errorf(err error, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -281,7 +315,7 @@ func (a *Assertions) Errorf(err error, msg string, args ...interface{}) { // Eventually asserts that given condition will be met in waitFor time, // periodically checking target function each tick. // -// a.Eventually(func() bool { return true; }, time.Second, 10*time.Millisecond) +// a.Eventually(func() bool { return true; }, time.Second, 10*time.Millisecond) func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -289,10 +323,60 @@ func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, ti Eventually(a.t, condition, waitFor, tick, msgAndArgs...) } +// EventuallyWithT asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. In contrast to Eventually, +// it supplies a CollectT to the condition function, so that the condition +// function can use the CollectT to call other assertions. +// The condition is considered "met" if no errors are raised in a tick. +// The supplied CollectT collects all errors from one tick (if there are any). +// If the condition is not met before waitFor, the collected errors of +// the last tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// a.EventuallyWithT(func(c *assert.CollectT) { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +func (a *Assertions) EventuallyWithT(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EventuallyWithT(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// EventuallyWithTf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. In contrast to Eventually, +// it supplies a CollectT to the condition function, so that the condition +// function can use the CollectT to call other assertions. +// The condition is considered "met" if no errors are raised in a tick. +// The supplied CollectT collects all errors from one tick (if there are any). +// If the condition is not met before waitFor, the collected errors of +// the last tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// a.EventuallyWithTf(func(c *assert.CollectT, "error message %s", "formatted") { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +func (a *Assertions) EventuallyWithTf(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EventuallyWithTf(a.t, condition, waitFor, tick, msg, args...) +} + // Eventuallyf asserts that given condition will be met in waitFor time, // periodically checking target function each tick. // -// a.Eventuallyf(func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +// a.Eventuallyf(func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") func (a *Assertions) Eventuallyf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -302,7 +386,7 @@ func (a *Assertions) Eventuallyf(condition func() bool, waitFor time.Duration, t // Exactly asserts that two objects are equal in value and type. // -// a.Exactly(int32(123), int64(123)) +// a.Exactly(int32(123), int64(123)) func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -312,7 +396,7 @@ func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArg // Exactlyf asserts that two objects are equal in value and type. // -// a.Exactlyf(int32(123), int64(123), "error message %s", "formatted") +// a.Exactlyf(int32(123), int64(123), "error message %s", "formatted") func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -354,7 +438,7 @@ func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{ // False asserts that the specified value is false. // -// a.False(myBool) +// a.False(myBool) func (a *Assertions) False(value bool, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -364,7 +448,7 @@ func (a *Assertions) False(value bool, msgAndArgs ...interface{}) { // Falsef asserts that the specified value is false. // -// a.Falsef(myBool, "error message %s", "formatted") +// a.Falsef(myBool, "error message %s", "formatted") func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -392,9 +476,9 @@ func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) { // Greater asserts that the first element is greater than the second // -// a.Greater(2, 1) -// a.Greater(float64(2), float64(1)) -// a.Greater("b", "a") +// a.Greater(2, 1) +// a.Greater(float64(2), float64(1)) +// a.Greater("b", "a") func (a *Assertions) Greater(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -404,10 +488,10 @@ func (a *Assertions) Greater(e1 interface{}, e2 interface{}, msgAndArgs ...inter // GreaterOrEqual asserts that the first element is greater than or equal to the second // -// a.GreaterOrEqual(2, 1) -// a.GreaterOrEqual(2, 2) -// a.GreaterOrEqual("b", "a") -// a.GreaterOrEqual("b", "b") +// a.GreaterOrEqual(2, 1) +// a.GreaterOrEqual(2, 2) +// a.GreaterOrEqual("b", "a") +// a.GreaterOrEqual("b", "b") func (a *Assertions) GreaterOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -417,10 +501,10 @@ func (a *Assertions) GreaterOrEqual(e1 interface{}, e2 interface{}, msgAndArgs . // GreaterOrEqualf asserts that the first element is greater than or equal to the second // -// a.GreaterOrEqualf(2, 1, "error message %s", "formatted") -// a.GreaterOrEqualf(2, 2, "error message %s", "formatted") -// a.GreaterOrEqualf("b", "a", "error message %s", "formatted") -// a.GreaterOrEqualf("b", "b", "error message %s", "formatted") +// a.GreaterOrEqualf(2, 1, "error message %s", "formatted") +// a.GreaterOrEqualf(2, 2, "error message %s", "formatted") +// a.GreaterOrEqualf("b", "a", "error message %s", "formatted") +// a.GreaterOrEqualf("b", "b", "error message %s", "formatted") func (a *Assertions) GreaterOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -430,9 +514,9 @@ func (a *Assertions) GreaterOrEqualf(e1 interface{}, e2 interface{}, msg string, // Greaterf asserts that the first element is greater than the second // -// a.Greaterf(2, 1, "error message %s", "formatted") -// a.Greaterf(float64(2), float64(1), "error message %s", "formatted") -// a.Greaterf("b", "a", "error message %s", "formatted") +// a.Greaterf(2, 1, "error message %s", "formatted") +// a.Greaterf(float64(2), float64(1), "error message %s", "formatted") +// a.Greaterf("b", "a", "error message %s", "formatted") func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -443,7 +527,7 @@ func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args . // HTTPBodyContains asserts that a specified handler returns a // body that contains a string. // -// a.HTTPBodyContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// a.HTTPBodyContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { @@ -456,7 +540,7 @@ func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, u // HTTPBodyContainsf asserts that a specified handler returns a // body that contains a string. // -// a.HTTPBodyContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// a.HTTPBodyContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { @@ -469,7 +553,7 @@ func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, // HTTPBodyNotContains asserts that a specified handler returns a // body that does not contain a string. // -// a.HTTPBodyNotContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// a.HTTPBodyNotContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { @@ -482,7 +566,7 @@ func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string // HTTPBodyNotContainsf asserts that a specified handler returns a // body that does not contain a string. // -// a.HTTPBodyNotContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// a.HTTPBodyNotContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { @@ -494,7 +578,7 @@ func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method strin // HTTPError asserts that a specified handler returns an error status code. // -// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { @@ -506,7 +590,7 @@ func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url stri // HTTPErrorf asserts that a specified handler returns an error status code. // -// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { @@ -518,7 +602,7 @@ func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url str // HTTPRedirect asserts that a specified handler returns a redirect status code. // -// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { @@ -530,7 +614,7 @@ func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url s // HTTPRedirectf asserts that a specified handler returns a redirect status code. // -// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { @@ -542,7 +626,7 @@ func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url // HTTPStatusCode asserts that a specified handler returns a specified status code. // -// a.HTTPStatusCode(myHandler, "GET", "/notImplemented", nil, 501) +// a.HTTPStatusCode(myHandler, "GET", "/notImplemented", nil, 501) // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPStatusCode(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) { @@ -554,7 +638,7 @@ func (a *Assertions) HTTPStatusCode(handler http.HandlerFunc, method string, url // HTTPStatusCodef asserts that a specified handler returns a specified status code. // -// a.HTTPStatusCodef(myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") +// a.HTTPStatusCodef(myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPStatusCodef(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) { @@ -566,7 +650,7 @@ func (a *Assertions) HTTPStatusCodef(handler http.HandlerFunc, method string, ur // HTTPSuccess asserts that a specified handler returns a success status code. // -// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) +// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { @@ -578,7 +662,7 @@ func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url st // HTTPSuccessf asserts that a specified handler returns a success status code. // -// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { @@ -590,7 +674,7 @@ func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url s // Implements asserts that an object is implemented by the specified interface. // -// a.Implements((*MyInterface)(nil), new(MyObject)) +// a.Implements((*MyInterface)(nil), new(MyObject)) func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -600,7 +684,7 @@ func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, // Implementsf asserts that an object is implemented by the specified interface. // -// a.Implementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +// a.Implementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted") func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -610,7 +694,7 @@ func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{} // InDelta asserts that the two numerals are within delta of each other. // -// a.InDelta(math.Pi, 22/7.0, 0.01) +// a.InDelta(math.Pi, 22/7.0, 0.01) func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -652,7 +736,7 @@ func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, del // InDeltaf asserts that the two numerals are within delta of each other. // -// a.InDeltaf(math.Pi, 22/7.0, 0.01, "error message %s", "formatted") +// a.InDeltaf(math.Pi, 22/7.0, 0.01, "error message %s", "formatted") func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -694,9 +778,9 @@ func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilo // IsDecreasing asserts that the collection is decreasing // -// a.IsDecreasing([]int{2, 1, 0}) -// a.IsDecreasing([]float{2, 1}) -// a.IsDecreasing([]string{"b", "a"}) +// a.IsDecreasing([]int{2, 1, 0}) +// a.IsDecreasing([]float{2, 1}) +// a.IsDecreasing([]string{"b", "a"}) func (a *Assertions) IsDecreasing(object interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -706,9 +790,9 @@ func (a *Assertions) IsDecreasing(object interface{}, msgAndArgs ...interface{}) // IsDecreasingf asserts that the collection is decreasing // -// a.IsDecreasingf([]int{2, 1, 0}, "error message %s", "formatted") -// a.IsDecreasingf([]float{2, 1}, "error message %s", "formatted") -// a.IsDecreasingf([]string{"b", "a"}, "error message %s", "formatted") +// a.IsDecreasingf([]int{2, 1, 0}, "error message %s", "formatted") +// a.IsDecreasingf([]float{2, 1}, "error message %s", "formatted") +// a.IsDecreasingf([]string{"b", "a"}, "error message %s", "formatted") func (a *Assertions) IsDecreasingf(object interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -718,9 +802,9 @@ func (a *Assertions) IsDecreasingf(object interface{}, msg string, args ...inter // IsIncreasing asserts that the collection is increasing // -// a.IsIncreasing([]int{1, 2, 3}) -// a.IsIncreasing([]float{1, 2}) -// a.IsIncreasing([]string{"a", "b"}) +// a.IsIncreasing([]int{1, 2, 3}) +// a.IsIncreasing([]float{1, 2}) +// a.IsIncreasing([]string{"a", "b"}) func (a *Assertions) IsIncreasing(object interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -730,9 +814,9 @@ func (a *Assertions) IsIncreasing(object interface{}, msgAndArgs ...interface{}) // IsIncreasingf asserts that the collection is increasing // -// a.IsIncreasingf([]int{1, 2, 3}, "error message %s", "formatted") -// a.IsIncreasingf([]float{1, 2}, "error message %s", "formatted") -// a.IsIncreasingf([]string{"a", "b"}, "error message %s", "formatted") +// a.IsIncreasingf([]int{1, 2, 3}, "error message %s", "formatted") +// a.IsIncreasingf([]float{1, 2}, "error message %s", "formatted") +// a.IsIncreasingf([]string{"a", "b"}, "error message %s", "formatted") func (a *Assertions) IsIncreasingf(object interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -742,9 +826,9 @@ func (a *Assertions) IsIncreasingf(object interface{}, msg string, args ...inter // IsNonDecreasing asserts that the collection is not decreasing // -// a.IsNonDecreasing([]int{1, 1, 2}) -// a.IsNonDecreasing([]float{1, 2}) -// a.IsNonDecreasing([]string{"a", "b"}) +// a.IsNonDecreasing([]int{1, 1, 2}) +// a.IsNonDecreasing([]float{1, 2}) +// a.IsNonDecreasing([]string{"a", "b"}) func (a *Assertions) IsNonDecreasing(object interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -754,9 +838,9 @@ func (a *Assertions) IsNonDecreasing(object interface{}, msgAndArgs ...interface // IsNonDecreasingf asserts that the collection is not decreasing // -// a.IsNonDecreasingf([]int{1, 1, 2}, "error message %s", "formatted") -// a.IsNonDecreasingf([]float{1, 2}, "error message %s", "formatted") -// a.IsNonDecreasingf([]string{"a", "b"}, "error message %s", "formatted") +// a.IsNonDecreasingf([]int{1, 1, 2}, "error message %s", "formatted") +// a.IsNonDecreasingf([]float{1, 2}, "error message %s", "formatted") +// a.IsNonDecreasingf([]string{"a", "b"}, "error message %s", "formatted") func (a *Assertions) IsNonDecreasingf(object interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -766,9 +850,9 @@ func (a *Assertions) IsNonDecreasingf(object interface{}, msg string, args ...in // IsNonIncreasing asserts that the collection is not increasing // -// a.IsNonIncreasing([]int{2, 1, 1}) -// a.IsNonIncreasing([]float{2, 1}) -// a.IsNonIncreasing([]string{"b", "a"}) +// a.IsNonIncreasing([]int{2, 1, 1}) +// a.IsNonIncreasing([]float{2, 1}) +// a.IsNonIncreasing([]string{"b", "a"}) func (a *Assertions) IsNonIncreasing(object interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -778,9 +862,9 @@ func (a *Assertions) IsNonIncreasing(object interface{}, msgAndArgs ...interface // IsNonIncreasingf asserts that the collection is not increasing // -// a.IsNonIncreasingf([]int{2, 1, 1}, "error message %s", "formatted") -// a.IsNonIncreasingf([]float{2, 1}, "error message %s", "formatted") -// a.IsNonIncreasingf([]string{"b", "a"}, "error message %s", "formatted") +// a.IsNonIncreasingf([]int{2, 1, 1}, "error message %s", "formatted") +// a.IsNonIncreasingf([]float{2, 1}, "error message %s", "formatted") +// a.IsNonIncreasingf([]string{"b", "a"}, "error message %s", "formatted") func (a *Assertions) IsNonIncreasingf(object interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -806,7 +890,7 @@ func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg s // JSONEq asserts that two JSON strings are equivalent. // -// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -816,7 +900,7 @@ func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interf // JSONEqf asserts that two JSON strings are equivalent. // -// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -827,7 +911,7 @@ func (a *Assertions) JSONEqf(expected string, actual string, msg string, args .. // Len asserts that the specified object has specific length. // Len also fails if the object has a type that len() not accept. // -// a.Len(mySlice, 3) +// a.Len(mySlice, 3) func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -838,7 +922,7 @@ func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface // Lenf asserts that the specified object has specific length. // Lenf also fails if the object has a type that len() not accept. // -// a.Lenf(mySlice, 3, "error message %s", "formatted") +// a.Lenf(mySlice, 3, "error message %s", "formatted") func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -848,9 +932,9 @@ func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...in // Less asserts that the first element is less than the second // -// a.Less(1, 2) -// a.Less(float64(1), float64(2)) -// a.Less("a", "b") +// a.Less(1, 2) +// a.Less(float64(1), float64(2)) +// a.Less("a", "b") func (a *Assertions) Less(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -860,10 +944,10 @@ func (a *Assertions) Less(e1 interface{}, e2 interface{}, msgAndArgs ...interfac // LessOrEqual asserts that the first element is less than or equal to the second // -// a.LessOrEqual(1, 2) -// a.LessOrEqual(2, 2) -// a.LessOrEqual("a", "b") -// a.LessOrEqual("b", "b") +// a.LessOrEqual(1, 2) +// a.LessOrEqual(2, 2) +// a.LessOrEqual("a", "b") +// a.LessOrEqual("b", "b") func (a *Assertions) LessOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -873,10 +957,10 @@ func (a *Assertions) LessOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...i // LessOrEqualf asserts that the first element is less than or equal to the second // -// a.LessOrEqualf(1, 2, "error message %s", "formatted") -// a.LessOrEqualf(2, 2, "error message %s", "formatted") -// a.LessOrEqualf("a", "b", "error message %s", "formatted") -// a.LessOrEqualf("b", "b", "error message %s", "formatted") +// a.LessOrEqualf(1, 2, "error message %s", "formatted") +// a.LessOrEqualf(2, 2, "error message %s", "formatted") +// a.LessOrEqualf("a", "b", "error message %s", "formatted") +// a.LessOrEqualf("b", "b", "error message %s", "formatted") func (a *Assertions) LessOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -886,9 +970,9 @@ func (a *Assertions) LessOrEqualf(e1 interface{}, e2 interface{}, msg string, ar // Lessf asserts that the first element is less than the second // -// a.Lessf(1, 2, "error message %s", "formatted") -// a.Lessf(float64(1), float64(2), "error message %s", "formatted") -// a.Lessf("a", "b", "error message %s", "formatted") +// a.Lessf(1, 2, "error message %s", "formatted") +// a.Lessf(float64(1), float64(2), "error message %s", "formatted") +// a.Lessf("a", "b", "error message %s", "formatted") func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -898,8 +982,8 @@ func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...i // Negative asserts that the specified element is negative // -// a.Negative(-1) -// a.Negative(-1.23) +// a.Negative(-1) +// a.Negative(-1.23) func (a *Assertions) Negative(e interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -909,8 +993,8 @@ func (a *Assertions) Negative(e interface{}, msgAndArgs ...interface{}) { // Negativef asserts that the specified element is negative // -// a.Negativef(-1, "error message %s", "formatted") -// a.Negativef(-1.23, "error message %s", "formatted") +// a.Negativef(-1, "error message %s", "formatted") +// a.Negativef(-1.23, "error message %s", "formatted") func (a *Assertions) Negativef(e interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -921,7 +1005,7 @@ func (a *Assertions) Negativef(e interface{}, msg string, args ...interface{}) { // Never asserts that the given condition doesn't satisfy in waitFor time, // periodically checking the target function each tick. // -// a.Never(func() bool { return false; }, time.Second, 10*time.Millisecond) +// a.Never(func() bool { return false; }, time.Second, 10*time.Millisecond) func (a *Assertions) Never(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -932,7 +1016,7 @@ func (a *Assertions) Never(condition func() bool, waitFor time.Duration, tick ti // Neverf asserts that the given condition doesn't satisfy in waitFor time, // periodically checking the target function each tick. // -// a.Neverf(func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +// a.Neverf(func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") func (a *Assertions) Neverf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -942,7 +1026,7 @@ func (a *Assertions) Neverf(condition func() bool, waitFor time.Duration, tick t // Nil asserts that the specified object is nil. // -// a.Nil(err) +// a.Nil(err) func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -952,7 +1036,7 @@ func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) { // Nilf asserts that the specified object is nil. // -// a.Nilf(err, "error message %s", "formatted") +// a.Nilf(err, "error message %s", "formatted") func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -980,10 +1064,10 @@ func (a *Assertions) NoDirExistsf(path string, msg string, args ...interface{}) // NoError asserts that a function returned no error (i.e. `nil`). // -// actualObj, err := SomeFunction() -// if a.NoError(err) { -// assert.Equal(t, expectedObj, actualObj) -// } +// actualObj, err := SomeFunction() +// if a.NoError(err) { +// assert.Equal(t, expectedObj, actualObj) +// } func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -993,10 +1077,10 @@ func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) { // NoErrorf asserts that a function returned no error (i.e. `nil`). // -// actualObj, err := SomeFunction() -// if a.NoErrorf(err, "error message %s", "formatted") { -// assert.Equal(t, expectedObj, actualObj) -// } +// actualObj, err := SomeFunction() +// if a.NoErrorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1025,9 +1109,9 @@ func (a *Assertions) NoFileExistsf(path string, msg string, args ...interface{}) // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. // -// a.NotContains("Hello World", "Earth") -// a.NotContains(["Hello", "World"], "Earth") -// a.NotContains({"Hello": "World"}, "Earth") +// a.NotContains("Hello World", "Earth") +// a.NotContains(["Hello", "World"], "Earth") +// a.NotContains({"Hello": "World"}, "Earth") func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1038,9 +1122,9 @@ func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs // NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. // -// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted") -// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted") -// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted") +// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted") +// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted") +// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted") func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1051,9 +1135,9 @@ func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg strin // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // -// if a.NotEmpty(obj) { -// assert.Equal(t, "two", obj[1]) -// } +// if a.NotEmpty(obj) { +// assert.Equal(t, "two", obj[1]) +// } func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1064,9 +1148,9 @@ func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) { // NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // -// if a.NotEmptyf(obj, "error message %s", "formatted") { -// assert.Equal(t, "two", obj[1]) -// } +// if a.NotEmptyf(obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1076,7 +1160,7 @@ func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface // NotEqual asserts that the specified values are NOT equal. // -// a.NotEqual(obj1, obj2) +// a.NotEqual(obj1, obj2) // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). @@ -1089,7 +1173,7 @@ func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndAr // NotEqualValues asserts that two objects are not equal even when converted to the same type // -// a.NotEqualValues(obj1, obj2) +// a.NotEqualValues(obj1, obj2) func (a *Assertions) NotEqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1099,7 +1183,7 @@ func (a *Assertions) NotEqualValues(expected interface{}, actual interface{}, ms // NotEqualValuesf asserts that two objects are not equal even when converted to the same type // -// a.NotEqualValuesf(obj1, obj2, "error message %s", "formatted") +// a.NotEqualValuesf(obj1, obj2, "error message %s", "formatted") func (a *Assertions) NotEqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1109,7 +1193,7 @@ func (a *Assertions) NotEqualValuesf(expected interface{}, actual interface{}, m // NotEqualf asserts that the specified values are NOT equal. // -// a.NotEqualf(obj1, obj2, "error message %s", "formatted") +// a.NotEqualf(obj1, obj2, "error message %s", "formatted") // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). @@ -1140,7 +1224,7 @@ func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...in // NotNil asserts that the specified object is not nil. // -// a.NotNil(err) +// a.NotNil(err) func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1150,7 +1234,7 @@ func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) { // NotNilf asserts that the specified object is not nil. // -// a.NotNilf(err, "error message %s", "formatted") +// a.NotNilf(err, "error message %s", "formatted") func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1160,7 +1244,7 @@ func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{} // NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. // -// a.NotPanics(func(){ RemainCalm() }) +// a.NotPanics(func(){ RemainCalm() }) func (a *Assertions) NotPanics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1170,7 +1254,7 @@ func (a *Assertions) NotPanics(f assert.PanicTestFunc, msgAndArgs ...interface{} // NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. // -// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted") +// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted") func (a *Assertions) NotPanicsf(f assert.PanicTestFunc, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1180,8 +1264,8 @@ func (a *Assertions) NotPanicsf(f assert.PanicTestFunc, msg string, args ...inte // NotRegexp asserts that a specified regexp does not match a string. // -// a.NotRegexp(regexp.MustCompile("starts"), "it's starting") -// a.NotRegexp("^start", "it's not starting") +// a.NotRegexp(regexp.MustCompile("starts"), "it's starting") +// a.NotRegexp("^start", "it's not starting") func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1191,8 +1275,8 @@ func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...in // NotRegexpf asserts that a specified regexp does not match a string. // -// a.NotRegexpf(regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") -// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") +// a.NotRegexpf(regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") +// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1202,7 +1286,7 @@ func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, arg // NotSame asserts that two pointers do not reference the same object. // -// a.NotSame(ptr1, ptr2) +// a.NotSame(ptr1, ptr2) // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1215,7 +1299,7 @@ func (a *Assertions) NotSame(expected interface{}, actual interface{}, msgAndArg // NotSamef asserts that two pointers do not reference the same object. // -// a.NotSamef(ptr1, ptr2, "error message %s", "formatted") +// a.NotSamef(ptr1, ptr2, "error message %s", "formatted") // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1229,7 +1313,7 @@ func (a *Assertions) NotSamef(expected interface{}, actual interface{}, msg stri // NotSubset asserts that the specified list(array, slice...) contains not all // elements given in the specified subset(array, slice...). // -// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") +// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1240,7 +1324,7 @@ func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs // NotSubsetf asserts that the specified list(array, slice...) contains not all // elements given in the specified subset(array, slice...). // -// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") +// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1266,7 +1350,7 @@ func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) { // Panics asserts that the code inside the specified PanicTestFunc panics. // -// a.Panics(func(){ GoCrazy() }) +// a.Panics(func(){ GoCrazy() }) func (a *Assertions) Panics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1278,7 +1362,7 @@ func (a *Assertions) Panics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { // panics, and that the recovered panic value is an error that satisfies the // EqualError comparison. // -// a.PanicsWithError("crazy error", func(){ GoCrazy() }) +// a.PanicsWithError("crazy error", func(){ GoCrazy() }) func (a *Assertions) PanicsWithError(errString string, f assert.PanicTestFunc, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1290,7 +1374,7 @@ func (a *Assertions) PanicsWithError(errString string, f assert.PanicTestFunc, m // panics, and that the recovered panic value is an error that satisfies the // EqualError comparison. // -// a.PanicsWithErrorf("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +// a.PanicsWithErrorf("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") func (a *Assertions) PanicsWithErrorf(errString string, f assert.PanicTestFunc, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1301,7 +1385,7 @@ func (a *Assertions) PanicsWithErrorf(errString string, f assert.PanicTestFunc, // PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that // the recovered panic value equals the expected panic value. // -// a.PanicsWithValue("crazy error", func(){ GoCrazy() }) +// a.PanicsWithValue("crazy error", func(){ GoCrazy() }) func (a *Assertions) PanicsWithValue(expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1312,7 +1396,7 @@ func (a *Assertions) PanicsWithValue(expected interface{}, f assert.PanicTestFun // PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that // the recovered panic value equals the expected panic value. // -// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") func (a *Assertions) PanicsWithValuef(expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1322,7 +1406,7 @@ func (a *Assertions) PanicsWithValuef(expected interface{}, f assert.PanicTestFu // Panicsf asserts that the code inside the specified PanicTestFunc panics. // -// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted") +// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted") func (a *Assertions) Panicsf(f assert.PanicTestFunc, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1332,8 +1416,8 @@ func (a *Assertions) Panicsf(f assert.PanicTestFunc, msg string, args ...interfa // Positive asserts that the specified element is positive // -// a.Positive(1) -// a.Positive(1.23) +// a.Positive(1) +// a.Positive(1.23) func (a *Assertions) Positive(e interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1343,8 +1427,8 @@ func (a *Assertions) Positive(e interface{}, msgAndArgs ...interface{}) { // Positivef asserts that the specified element is positive // -// a.Positivef(1, "error message %s", "formatted") -// a.Positivef(1.23, "error message %s", "formatted") +// a.Positivef(1, "error message %s", "formatted") +// a.Positivef(1.23, "error message %s", "formatted") func (a *Assertions) Positivef(e interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1354,8 +1438,8 @@ func (a *Assertions) Positivef(e interface{}, msg string, args ...interface{}) { // Regexp asserts that a specified regexp matches a string. // -// a.Regexp(regexp.MustCompile("start"), "it's starting") -// a.Regexp("start...$", "it's not starting") +// a.Regexp(regexp.MustCompile("start"), "it's starting") +// a.Regexp("start...$", "it's not starting") func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1365,8 +1449,8 @@ func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...inter // Regexpf asserts that a specified regexp matches a string. // -// a.Regexpf(regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") -// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") +// a.Regexpf(regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") +// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1376,7 +1460,7 @@ func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args . // Same asserts that two pointers reference the same object. // -// a.Same(ptr1, ptr2) +// a.Same(ptr1, ptr2) // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1389,7 +1473,7 @@ func (a *Assertions) Same(expected interface{}, actual interface{}, msgAndArgs . // Samef asserts that two pointers reference the same object. // -// a.Samef(ptr1, ptr2, "error message %s", "formatted") +// a.Samef(ptr1, ptr2, "error message %s", "formatted") // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1403,7 +1487,7 @@ func (a *Assertions) Samef(expected interface{}, actual interface{}, msg string, // Subset asserts that the specified list(array, slice...) contains all // elements given in the specified subset(array, slice...). // -// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") +// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1414,7 +1498,7 @@ func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ... // Subsetf asserts that the specified list(array, slice...) contains all // elements given in the specified subset(array, slice...). // -// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") +// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1424,7 +1508,7 @@ func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, a // True asserts that the specified value is true. // -// a.True(myBool) +// a.True(myBool) func (a *Assertions) True(value bool, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1434,7 +1518,7 @@ func (a *Assertions) True(value bool, msgAndArgs ...interface{}) { // Truef asserts that the specified value is true. // -// a.Truef(myBool, "error message %s", "formatted") +// a.Truef(myBool, "error message %s", "formatted") func (a *Assertions) Truef(value bool, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1444,7 +1528,7 @@ func (a *Assertions) Truef(value bool, msg string, args ...interface{}) { // WithinDuration asserts that the two times are within duration delta of each other. // -// a.WithinDuration(time.Now(), time.Now(), 10*time.Second) +// a.WithinDuration(time.Now(), time.Now(), 10*time.Second) func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1454,7 +1538,7 @@ func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta // WithinDurationf asserts that the two times are within duration delta of each other. // -// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1464,7 +1548,7 @@ func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta // WithinRange asserts that a time is within a time range (inclusive). // -// a.WithinRange(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) +// a.WithinRange(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) func (a *Assertions) WithinRange(actual time.Time, start time.Time, end time.Time, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1474,7 +1558,7 @@ func (a *Assertions) WithinRange(actual time.Time, start time.Time, end time.Tim // WithinRangef asserts that a time is within a time range (inclusive). // -// a.WithinRangef(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") +// a.WithinRangef(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") func (a *Assertions) WithinRangef(actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() diff --git a/vendor/github.com/test-go/testify/LICENSE b/vendor/github.com/test-go/testify/LICENSE deleted file mode 100644 index 473b670a7..000000000 --- a/vendor/github.com/test-go/testify/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell - -Please consider promoting this project if you find it useful. - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of the Software, -and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT -OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE -OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/test-go/testify/assert/assertion_forward.go b/vendor/github.com/test-go/testify/assert/assertion_forward.go deleted file mode 100644 index e6a796046..000000000 --- a/vendor/github.com/test-go/testify/assert/assertion_forward.go +++ /dev/null @@ -1,387 +0,0 @@ -/* -* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen -* THIS FILE MUST NOT BE EDITED BY HAND -*/ - -package assert - -import ( - - http "net/http" - url "net/url" - time "time" -) - - -// Condition uses a Comparison to assert a complex condition. -func (a *Assertions) Condition(comp Comparison, msgAndArgs ...interface{}) bool { - return Condition(a.t, comp, msgAndArgs...) -} - - -// Contains asserts that the specified string, list(array, slice...) or map contains the -// specified substring or element. -// -// a.Contains("Hello World", "World", "But 'Hello World' does contain 'World'") -// a.Contains(["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'") -// a.Contains({"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool { - return Contains(a.t, s, contains, msgAndArgs...) -} - - -// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either -// a slice or a channel with len == 0. -// -// a.Empty(obj) -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool { - return Empty(a.t, object, msgAndArgs...) -} - - -// Equal asserts that two objects are equal. -// -// a.Equal(123, 123, "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { - return Equal(a.t, expected, actual, msgAndArgs...) -} - - -// EqualError asserts that a function returned an error (i.e. not `nil`) -// and that it is equal to the provided error. -// -// actualObj, err := SomeFunction() -// if assert.Error(t, err, "An error was expected") { -// assert.Equal(t, err, expectedError) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) bool { - return EqualError(a.t, theError, errString, msgAndArgs...) -} - - -// EqualValues asserts that two objects are equal or convertable to the same types -// and equal. -// -// a.EqualValues(uint32(123), int32(123), "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { - return EqualValues(a.t, expected, actual, msgAndArgs...) -} - - -// Error asserts that a function returned an error (i.e. not `nil`). -// -// actualObj, err := SomeFunction() -// if a.Error(err, "An error was expected") { -// assert.Equal(t, err, expectedError) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool { - return Error(a.t, err, msgAndArgs...) -} - - -// Exactly asserts that two objects are equal is value and type. -// -// a.Exactly(int32(123), int64(123), "123 and 123 should NOT be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { - return Exactly(a.t, expected, actual, msgAndArgs...) -} - - -// Fail reports a failure through -func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) bool { - return Fail(a.t, failureMessage, msgAndArgs...) -} - - -// FailNow fails test -func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) bool { - return FailNow(a.t, failureMessage, msgAndArgs...) -} - - -// False asserts that the specified value is false. -// -// a.False(myBool, "myBool should be false") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool { - return False(a.t, value, msgAndArgs...) -} - - -// HTTPBodyContains asserts that a specified handler returns a -// body that contains a string. -// -// a.HTTPBodyContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { - return HTTPBodyContains(a.t, handler, method, url, values, str) -} - - -// HTTPBodyNotContains asserts that a specified handler returns a -// body that does not contain a string. -// -// a.HTTPBodyNotContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { - return HTTPBodyNotContains(a.t, handler, method, url, values, str) -} - - -// HTTPError asserts that a specified handler returns an error status code. -// -// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPError(a.t, handler, method, url, values) -} - - -// HTTPRedirect asserts that a specified handler returns a redirect status code. -// -// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPRedirect(a.t, handler, method, url, values) -} - - -// HTTPSuccess asserts that a specified handler returns a success status code. -// -// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPSuccess(a.t, handler, method, url, values) -} - - -// Implements asserts that an object is implemented by the specified interface. -// -// a.Implements((*MyInterface)(nil), new(MyObject), "MyObject") -func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { - return Implements(a.t, interfaceObject, object, msgAndArgs...) -} - - -// InDelta asserts that the two numerals are within delta of each other. -// -// a.InDelta(math.Pi, (22 / 7.0), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { - return InDelta(a.t, expected, actual, delta, msgAndArgs...) -} - - -// InDeltaSlice is the same as InDelta, except it compares two slices. -func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { - return InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...) -} - - -// InEpsilon asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { - return InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) -} - - -// InEpsilonSlice is the same as InEpsilon, except it compares two slices. -func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { - return InEpsilonSlice(a.t, expected, actual, delta, msgAndArgs...) -} - - -// IsType asserts that the specified objects are of the same type. -func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { - return IsType(a.t, expectedType, object, msgAndArgs...) -} - - -// JSONEq asserts that two JSON strings are equivalent. -// -// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool { - return JSONEq(a.t, expected, actual, msgAndArgs...) -} - - -// Len asserts that the specified object has specific length. -// Len also fails if the object has a type that len() not accept. -// -// a.Len(mySlice, 3, "The size of slice is not 3") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) bool { - return Len(a.t, object, length, msgAndArgs...) -} - - -// Nil asserts that the specified object is nil. -// -// a.Nil(err, "err should be nothing") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool { - return Nil(a.t, object, msgAndArgs...) -} - - -// NoError asserts that a function returned no error (i.e. `nil`). -// -// actualObj, err := SomeFunction() -// if a.NoError(err) { -// assert.Equal(t, actualObj, expectedObj) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool { - return NoError(a.t, err, msgAndArgs...) -} - - -// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the -// specified substring or element. -// -// a.NotContains("Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") -// a.NotContains(["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'") -// a.NotContains({"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool { - return NotContains(a.t, s, contains, msgAndArgs...) -} - - -// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either -// a slice or a channel with len == 0. -// -// if a.NotEmpty(obj) { -// assert.Equal(t, "two", obj[1]) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) bool { - return NotEmpty(a.t, object, msgAndArgs...) -} - - -// NotEqual asserts that the specified values are NOT equal. -// -// a.NotEqual(obj1, obj2, "two objects shouldn't be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { - return NotEqual(a.t, expected, actual, msgAndArgs...) -} - - -// NotNil asserts that the specified object is not nil. -// -// a.NotNil(err, "err should be something") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool { - return NotNil(a.t, object, msgAndArgs...) -} - - -// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. -// -// a.NotPanics(func(){ -// RemainCalm() -// }, "Calling RemainCalm() should NOT panic") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool { - return NotPanics(a.t, f, msgAndArgs...) -} - - -// NotRegexp asserts that a specified regexp does not match a string. -// -// a.NotRegexp(regexp.MustCompile("starts"), "it's starting") -// a.NotRegexp("^start", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { - return NotRegexp(a.t, rx, str, msgAndArgs...) -} - - -// NotZero asserts that i is not the zero value for its type and returns the truth. -func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) bool { - return NotZero(a.t, i, msgAndArgs...) -} - - -// Panics asserts that the code inside the specified PanicTestFunc panics. -// -// a.Panics(func(){ -// GoCrazy() -// }, "Calling GoCrazy() should panic") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool { - return Panics(a.t, f, msgAndArgs...) -} - - -// Regexp asserts that a specified regexp matches a string. -// -// a.Regexp(regexp.MustCompile("start"), "it's starting") -// a.Regexp("start...$", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { - return Regexp(a.t, rx, str, msgAndArgs...) -} - - -// True asserts that the specified value is true. -// -// a.True(myBool, "myBool should be true") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool { - return True(a.t, value, msgAndArgs...) -} - - -// WithinDuration asserts that the two times are within duration delta of each other. -// -// a.WithinDuration(time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { - return WithinDuration(a.t, expected, actual, delta, msgAndArgs...) -} - - -// Zero asserts that i is the zero value for its type and returns the truth. -func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) bool { - return Zero(a.t, i, msgAndArgs...) -} diff --git a/vendor/github.com/test-go/testify/assert/assertion_forward.go.tmpl b/vendor/github.com/test-go/testify/assert/assertion_forward.go.tmpl deleted file mode 100644 index 99f9acfbb..000000000 --- a/vendor/github.com/test-go/testify/assert/assertion_forward.go.tmpl +++ /dev/null @@ -1,4 +0,0 @@ -{{.CommentWithoutT "a"}} -func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool { - return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) -} diff --git a/vendor/github.com/test-go/testify/assert/assertions.go b/vendor/github.com/test-go/testify/assert/assertions.go deleted file mode 100644 index b3f4e170d..000000000 --- a/vendor/github.com/test-go/testify/assert/assertions.go +++ /dev/null @@ -1,1052 +0,0 @@ -package assert - -import ( - "bufio" - "bytes" - "encoding/json" - "fmt" - "math" - "reflect" - "regexp" - "runtime" - "strings" - "time" - "unicode" - "unicode/utf8" - - "github.com/davecgh/go-spew/spew" - "github.com/pmezard/go-difflib/difflib" -) - -func init() { - spew.Config.SortKeys = true -} - -// TestingT is an interface wrapper around *testing.T -type TestingT interface { - Errorf(format string, args ...interface{}) -} - -// Comparison a custom function that returns true on success and false on failure -type Comparison func() (success bool) - -/* - Helper functions -*/ - -// ObjectsAreEqual determines if two objects are considered equal. -// -// This function does no assertion of any kind. -func ObjectsAreEqual(expected, actual interface{}) bool { - - if expected == nil || actual == nil { - return expected == actual - } - - return reflect.DeepEqual(expected, actual) - -} - -// ObjectsAreEqualValues gets whether two objects are equal, or if their -// values are equal. -func ObjectsAreEqualValues(expected, actual interface{}) bool { - if ObjectsAreEqual(expected, actual) { - return true - } - - actualType := reflect.TypeOf(actual) - if actualType == nil { - return false - } - expectedValue := reflect.ValueOf(expected) - if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { - // Attempt comparison after type conversion - return reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) - } - - return false -} - -/* CallerInfo is necessary because the assert functions use the testing object -internally, causing it to print the file:line of the assert method, rather than where -the problem actually occurred in calling code.*/ - -// CallerInfo returns an array of strings containing the file and line number -// of each stack frame leading from the current test to the assert call that -// failed. -func CallerInfo() []string { - - pc := uintptr(0) - file := "" - line := 0 - ok := false - name := "" - - callers := []string{} - for i := 0; ; i++ { - pc, file, line, ok = runtime.Caller(i) - if !ok { - // The breaks below failed to terminate the loop, and we ran off the - // end of the call stack. - break - } - - // This is a huge edge case, but it will panic if this is the case, see #180 - if file == "" { - break - } - - f := runtime.FuncForPC(pc) - if f == nil { - break - } - name = f.Name() - - // testing.tRunner is the standard library function that calls - // tests. Subtests are called directly by tRunner, without going through - // the Test/Benchmark/Example function that contains the t.Run calls, so - // with subtests we should break when we hit tRunner, without adding it - // to the list of callers. - if name == "testing.tRunner" { - break - } - - parts := strings.Split(file, "/") - dir := parts[len(parts)-2] - file = parts[len(parts)-1] - if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" { - callers = append(callers, fmt.Sprintf("%s:%d", file, line)) - } - - // Drop the package - segments := strings.Split(name, ".") - name = segments[len(segments)-1] - if isTest(name, "Test") || - isTest(name, "Benchmark") || - isTest(name, "Example") { - break - } - } - - return callers -} - -// Stolen from the `go test` tool. -// isTest tells whether name looks like a test (or benchmark, according to prefix). -// It is a Test (say) if there is a character after Test that is not a lower-case letter. -// We don't want TesticularCancer. -func isTest(name, prefix string) bool { - if !strings.HasPrefix(name, prefix) { - return false - } - if len(name) == len(prefix) { // "Test" is ok - return true - } - rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) - return !unicode.IsLower(rune) -} - -// getWhitespaceString returns a string that is long enough to overwrite the default -// output from the go testing framework. -func getWhitespaceString() string { - - _, file, line, ok := runtime.Caller(1) - if !ok { - return "" - } - parts := strings.Split(file, "/") - file = parts[len(parts)-1] - - return strings.Repeat(" ", len(fmt.Sprintf("%s:%d: ", file, line))) - -} - -func messageFromMsgAndArgs(msgAndArgs ...interface{}) string { - if len(msgAndArgs) == 0 || msgAndArgs == nil { - return "" - } - if len(msgAndArgs) == 1 { - return msgAndArgs[0].(string) - } - if len(msgAndArgs) > 1 { - return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...) - } - return "" -} - -// Indents all lines of the message by appending a number of tabs to each line, in an output format compatible with Go's -// test printing (see inner comment for specifics) -func indentMessageLines(message string, tabs int) string { - outBuf := new(bytes.Buffer) - - for i, scanner := 0, bufio.NewScanner(strings.NewReader(message)); scanner.Scan(); i++ { - if i != 0 { - outBuf.WriteRune('\n') - } - for ii := 0; ii < tabs; ii++ { - outBuf.WriteRune('\t') - // Bizarrely, all lines except the first need one fewer tabs prepended, so deliberately advance the counter - // by 1 prematurely. - if ii == 0 && i > 0 { - ii++ - } - } - outBuf.WriteString(scanner.Text()) - } - - return outBuf.String() -} - -type failNower interface { - FailNow() -} - -// FailNow fails test -func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool { - Fail(t, failureMessage, msgAndArgs...) - - // We cannot extend TestingT with FailNow() and - // maintain backwards compatibility, so we fallback - // to panicking when FailNow is not available in - // TestingT. - // See issue #263 - - if t, ok := t.(failNower); ok { - t.FailNow() - } else { - panic("test failed and t is missing `FailNow()`") - } - return false -} - -// Fail reports a failure through -func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool { - - message := messageFromMsgAndArgs(msgAndArgs...) - - errorTrace := strings.Join(CallerInfo(), "\n\r\t\t\t") - if len(message) > 0 { - t.Errorf("\r%s\r\tError Trace:\t%s\n"+ - "\r\tError:%s\n"+ - "\r\tMessages:\t%s\n\r", - getWhitespaceString(), - errorTrace, - indentMessageLines(failureMessage, 2), - message) - } else { - t.Errorf("\r%s\r\tError Trace:\t%s\n"+ - "\r\tError:%s\n\r", - getWhitespaceString(), - errorTrace, - indentMessageLines(failureMessage, 2)) - } - - return false -} - -// Implements asserts that an object is implemented by the specified interface. -// -// assert.Implements(t, (*MyInterface)(nil), new(MyObject), "MyObject") -func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { - - interfaceType := reflect.TypeOf(interfaceObject).Elem() - - if !reflect.TypeOf(object).Implements(interfaceType) { - return Fail(t, fmt.Sprintf("%T must implement %v", object, interfaceType), msgAndArgs...) - } - - return true - -} - -// IsType asserts that the specified objects are of the same type. -func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { - - if !ObjectsAreEqual(reflect.TypeOf(object), reflect.TypeOf(expectedType)) { - return Fail(t, fmt.Sprintf("Object expected to be of type %v, but was %v", reflect.TypeOf(expectedType), reflect.TypeOf(object)), msgAndArgs...) - } - - return true -} - -// Equal asserts that two objects are equal. -// -// assert.Equal(t, 123, 123, "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { - - if !ObjectsAreEqual(expected, actual) { - diff := diff(expected, actual) - expected, actual = formatUnequalValues(expected, actual) - return Fail(t, fmt.Sprintf("Not equal: %s (expected)\n"+ - " != %s (actual)%s", expected, actual, diff), msgAndArgs...) - } - - return true - -} - -// formatUnequalValues takes two values of arbitrary types and returns string -// representations appropriate to be presented to the user. -// -// If the values are not of like type, the returned strings will be prefixed -// with the type name, and the value will be enclosed in parenthesis similar -// to a type conversion in the Go grammar. -func formatUnequalValues(expected, actual interface{}) (e string, a string) { - aType := reflect.TypeOf(expected) - bType := reflect.TypeOf(actual) - - if aType != bType && isNumericType(aType) && isNumericType(bType) { - return fmt.Sprintf("%v(%#v)", aType, expected), - fmt.Sprintf("%v(%#v)", bType, actual) - } - - return fmt.Sprintf("%#v", expected), - fmt.Sprintf("%#v", actual) -} - -func isNumericType(t reflect.Type) bool { - switch t.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return true - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return true - case reflect.Float32, reflect.Float64: - return true - } - - return false -} - -// EqualValues asserts that two objects are equal or convertable to the same types -// and equal. -// -// assert.EqualValues(t, uint32(123), int32(123), "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { - - if !ObjectsAreEqualValues(expected, actual) { - return Fail(t, fmt.Sprintf("Not equal: %#v (expected)\n"+ - " != %#v (actual)", expected, actual), msgAndArgs...) - } - - return true - -} - -// Exactly asserts that two objects are equal is value and type. -// -// assert.Exactly(t, int32(123), int64(123), "123 and 123 should NOT be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { - - aType := reflect.TypeOf(expected) - bType := reflect.TypeOf(actual) - - if aType != bType { - return Fail(t, fmt.Sprintf("Types expected to match exactly\n\r\t%v != %v", aType, bType), msgAndArgs...) - } - - return Equal(t, expected, actual, msgAndArgs...) - -} - -// NotNil asserts that the specified object is not nil. -// -// assert.NotNil(t, err, "err should be something") -// -// Returns whether the assertion was successful (true) or not (false). -func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { - if !isNil(object) { - return true - } - return Fail(t, "Expected value not to be nil.", msgAndArgs...) -} - -// isNil checks if a specified object is nil or not, without Failing. -func isNil(object interface{}) bool { - if object == nil { - return true - } - - value := reflect.ValueOf(object) - kind := value.Kind() - if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() { - return true - } - - return false -} - -// Nil asserts that the specified object is nil. -// -// assert.Nil(t, err, "err should be nothing") -// -// Returns whether the assertion was successful (true) or not (false). -func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { - if isNil(object) { - return true - } - return Fail(t, fmt.Sprintf("Expected nil, but got: %#v", object), msgAndArgs...) -} - -var numericZeros = []interface{}{ - int(0), - int8(0), - int16(0), - int32(0), - int64(0), - uint(0), - uint8(0), - uint16(0), - uint32(0), - uint64(0), - float32(0), - float64(0), -} - -// isEmpty gets whether the specified object is considered empty or not. -func isEmpty(object interface{}) bool { - - if object == nil { - return true - } else if object == "" { - return true - } else if object == false { - return true - } - - for _, v := range numericZeros { - if object == v { - return true - } - } - - objValue := reflect.ValueOf(object) - - switch objValue.Kind() { - case reflect.Map: - fallthrough - case reflect.Slice, reflect.Chan: - { - return (objValue.Len() == 0) - } - case reflect.Struct: - switch object.(type) { - case time.Time: - return object.(time.Time).IsZero() - } - case reflect.Ptr: - { - if objValue.IsNil() { - return true - } - switch object.(type) { - case *time.Time: - return object.(*time.Time).IsZero() - default: - return false - } - } - } - return false -} - -// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either -// a slice or a channel with len == 0. -// -// assert.Empty(t, obj) -// -// Returns whether the assertion was successful (true) or not (false). -func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { - - pass := isEmpty(object) - if !pass { - Fail(t, fmt.Sprintf("Should be empty, but was %v", object), msgAndArgs...) - } - - return pass - -} - -// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either -// a slice or a channel with len == 0. -// -// if assert.NotEmpty(t, obj) { -// assert.Equal(t, "two", obj[1]) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { - - pass := !isEmpty(object) - if !pass { - Fail(t, fmt.Sprintf("Should NOT be empty, but was %v", object), msgAndArgs...) - } - - return pass - -} - -// getLen try to get length of object. -// return (false, 0) if impossible. -func getLen(x interface{}) (ok bool, length int) { - v := reflect.ValueOf(x) - defer func() { - if e := recover(); e != nil { - ok = false - } - }() - return true, v.Len() -} - -// Len asserts that the specified object has specific length. -// Len also fails if the object has a type that len() not accept. -// -// assert.Len(t, mySlice, 3, "The size of slice is not 3") -// -// Returns whether the assertion was successful (true) or not (false). -func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) bool { - ok, l := getLen(object) - if !ok { - return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", object), msgAndArgs...) - } - - if l != length { - return Fail(t, fmt.Sprintf("\"%s\" should have %d item(s), but has %d", object, length, l), msgAndArgs...) - } - return true -} - -// True asserts that the specified value is true. -// -// assert.True(t, myBool, "myBool should be true") -// -// Returns whether the assertion was successful (true) or not (false). -func True(t TestingT, value bool, msgAndArgs ...interface{}) bool { - - if value != true { - return Fail(t, "Should be true", msgAndArgs...) - } - - return true - -} - -// False asserts that the specified value is false. -// -// assert.False(t, myBool, "myBool should be false") -// -// Returns whether the assertion was successful (true) or not (false). -func False(t TestingT, value bool, msgAndArgs ...interface{}) bool { - - if value != false { - return Fail(t, "Should be false", msgAndArgs...) - } - - return true - -} - -// NotEqual asserts that the specified values are NOT equal. -// -// assert.NotEqual(t, obj1, obj2, "two objects shouldn't be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { - - if ObjectsAreEqual(expected, actual) { - return Fail(t, fmt.Sprintf("Should not be: %#v\n", actual), msgAndArgs...) - } - - return true - -} - -// containsElement try loop over the list check if the list includes the element. -// return (false, false) if impossible. -// return (true, false) if element was not found. -// return (true, true) if element was found. -func includeElement(list interface{}, element interface{}) (ok, found bool) { - - listValue := reflect.ValueOf(list) - elementValue := reflect.ValueOf(element) - defer func() { - if e := recover(); e != nil { - ok = false - found = false - } - }() - - if reflect.TypeOf(list).Kind() == reflect.String { - return true, strings.Contains(listValue.String(), elementValue.String()) - } - - if reflect.TypeOf(list).Kind() == reflect.Map { - mapKeys := listValue.MapKeys() - for i := 0; i < len(mapKeys); i++ { - if ObjectsAreEqual(mapKeys[i].Interface(), element) { - return true, true - } - } - return true, false - } - - for i := 0; i < listValue.Len(); i++ { - if ObjectsAreEqual(listValue.Index(i).Interface(), element) { - return true, true - } - } - return true, false - -} - -// Contains asserts that the specified string, list(array, slice...) or map contains the -// specified substring or element. -// -// assert.Contains(t, "Hello World", "World", "But 'Hello World' does contain 'World'") -// assert.Contains(t, ["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'") -// assert.Contains(t, {"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'") -// -// Returns whether the assertion was successful (true) or not (false). -func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool { - - ok, found := includeElement(s, contains) - if !ok { - return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", s), msgAndArgs...) - } - if !found { - return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", s, contains), msgAndArgs...) - } - - return true - -} - -// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the -// specified substring or element. -// -// assert.NotContains(t, "Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") -// assert.NotContains(t, ["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'") -// assert.NotContains(t, {"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'") -// -// Returns whether the assertion was successful (true) or not (false). -func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool { - - ok, found := includeElement(s, contains) - if !ok { - return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", s), msgAndArgs...) - } - if found { - return Fail(t, fmt.Sprintf("\"%s\" should not contain \"%s\"", s, contains), msgAndArgs...) - } - - return true - -} - -// Condition uses a Comparison to assert a complex condition. -func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool { - result := comp() - if !result { - Fail(t, "Condition failed!", msgAndArgs...) - } - return result -} - -// PanicTestFunc defines a func that should be passed to the assert.Panics and assert.NotPanics -// methods, and represents a simple func that takes no arguments, and returns nothing. -type PanicTestFunc func() - -// didPanic returns true if the function passed to it panics. Otherwise, it returns false. -func didPanic(f PanicTestFunc) (bool, interface{}) { - - didPanic := false - var message interface{} - func() { - - defer func() { - if message = recover(); message != nil { - didPanic = true - } - }() - - // call the target function - f() - - }() - - return didPanic, message - -} - -// Panics asserts that the code inside the specified PanicTestFunc panics. -// -// assert.Panics(t, func(){ -// GoCrazy() -// }, "Calling GoCrazy() should panic") -// -// Returns whether the assertion was successful (true) or not (false). -func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { - - if funcDidPanic, panicValue := didPanic(f); !funcDidPanic { - return Fail(t, fmt.Sprintf("func %#v should panic\n\r\tPanic value:\t%v", f, panicValue), msgAndArgs...) - } - - return true -} - -// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. -// -// assert.NotPanics(t, func(){ -// RemainCalm() -// }, "Calling RemainCalm() should NOT panic") -// -// Returns whether the assertion was successful (true) or not (false). -func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { - - if funcDidPanic, panicValue := didPanic(f); funcDidPanic { - return Fail(t, fmt.Sprintf("func %#v should not panic\n\r\tPanic value:\t%v", f, panicValue), msgAndArgs...) - } - - return true -} - -// WithinDuration asserts that the two times are within duration delta of each other. -// -// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") -// -// Returns whether the assertion was successful (true) or not (false). -func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { - - dt := expected.Sub(actual) - if dt < -delta || dt > delta { - return Fail(t, fmt.Sprintf("Max difference between %v and %v allowed is %v, but difference was %v", expected, actual, delta, dt), msgAndArgs...) - } - - return true -} - -func toFloat(x interface{}) (float64, bool) { - var xf float64 - xok := true - - switch xn := x.(type) { - case uint8: - xf = float64(xn) - case uint16: - xf = float64(xn) - case uint32: - xf = float64(xn) - case uint64: - xf = float64(xn) - case int: - xf = float64(xn) - case int8: - xf = float64(xn) - case int16: - xf = float64(xn) - case int32: - xf = float64(xn) - case int64: - xf = float64(xn) - case float32: - xf = float64(xn) - case float64: - xf = float64(xn) - default: - xok = false - } - - return xf, xok -} - -// InDelta asserts that the two numerals are within delta of each other. -// -// assert.InDelta(t, math.Pi, (22 / 7.0), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). -func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { - - af, aok := toFloat(expected) - bf, bok := toFloat(actual) - - if !aok || !bok { - return Fail(t, fmt.Sprintf("Parameters must be numerical"), msgAndArgs...) - } - - if math.IsNaN(af) { - return Fail(t, fmt.Sprintf("Actual must not be NaN"), msgAndArgs...) - } - - if math.IsNaN(bf) { - return Fail(t, fmt.Sprintf("Expected %v with delta %v, but was NaN", expected, delta), msgAndArgs...) - } - - dt := af - bf - if dt < -delta || dt > delta { - return Fail(t, fmt.Sprintf("Max difference between %v and %v allowed is %v, but difference was %v", expected, actual, delta, dt), msgAndArgs...) - } - - return true -} - -// InDeltaSlice is the same as InDelta, except it compares two slices. -func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { - if expected == nil || actual == nil || - reflect.TypeOf(actual).Kind() != reflect.Slice || - reflect.TypeOf(expected).Kind() != reflect.Slice { - return Fail(t, fmt.Sprintf("Parameters must be slice"), msgAndArgs...) - } - - actualSlice := reflect.ValueOf(actual) - expectedSlice := reflect.ValueOf(expected) - - for i := 0; i < actualSlice.Len(); i++ { - result := InDelta(t, actualSlice.Index(i).Interface(), expectedSlice.Index(i).Interface(), delta) - if !result { - return result - } - } - - return true -} - -func calcRelativeError(expected, actual interface{}) (float64, error) { - af, aok := toFloat(expected) - if !aok { - return 0, fmt.Errorf("expected value %q cannot be converted to float", expected) - } - if af == 0 { - return 0, fmt.Errorf("expected value must have a value other than zero to calculate the relative error") - } - bf, bok := toFloat(actual) - if !bok { - return 0, fmt.Errorf("expected value %q cannot be converted to float", actual) - } - - return math.Abs(af-bf) / math.Abs(af), nil -} - -// InEpsilon asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). -func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { - actualEpsilon, err := calcRelativeError(expected, actual) - if err != nil { - return Fail(t, err.Error(), msgAndArgs...) - } - if actualEpsilon > epsilon { - return Fail(t, fmt.Sprintf("Relative error is too high: %#v (expected)\n"+ - " < %#v (actual)", actualEpsilon, epsilon), msgAndArgs...) - } - - return true -} - -// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. -func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { - if expected == nil || actual == nil || - reflect.TypeOf(actual).Kind() != reflect.Slice || - reflect.TypeOf(expected).Kind() != reflect.Slice { - return Fail(t, fmt.Sprintf("Parameters must be slice"), msgAndArgs...) - } - - actualSlice := reflect.ValueOf(actual) - expectedSlice := reflect.ValueOf(expected) - - for i := 0; i < actualSlice.Len(); i++ { - result := InEpsilon(t, actualSlice.Index(i).Interface(), expectedSlice.Index(i).Interface(), epsilon) - if !result { - return result - } - } - - return true -} - -/* - Errors -*/ - -// NoError asserts that a function returned no error (i.e. `nil`). -// -// actualObj, err := SomeFunction() -// if assert.NoError(t, err) { -// assert.Equal(t, actualObj, expectedObj) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool { - if err != nil { - return Fail(t, fmt.Sprintf("Received unexpected error %+v", err), msgAndArgs...) - } - - return true -} - -// Error asserts that a function returned an error (i.e. not `nil`). -// -// actualObj, err := SomeFunction() -// if assert.Error(t, err, "An error was expected") { -// assert.Equal(t, err, expectedError) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func Error(t TestingT, err error, msgAndArgs ...interface{}) bool { - - if err == nil { - return Fail(t, "An error is expected but got nil.", msgAndArgs...) - } - - return true -} - -// EqualError asserts that a function returned an error (i.e. not `nil`) -// and that it is equal to the provided error. -// -// actualObj, err := SomeFunction() -// assert.EqualError(t, err, expectedErrorString, "An error was expected") -// -// Returns whether the assertion was successful (true) or not (false). -func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) bool { - - message := messageFromMsgAndArgs(msgAndArgs...) - if !NotNil(t, theError, "An error is expected but got nil. %s", message) { - return false - } - s := "An error with value \"%s\" is expected but got \"%s\". %s" - return Equal(t, errString, theError.Error(), - s, errString, theError.Error(), message) -} - -// matchRegexp return true if a specified regexp matches a string. -func matchRegexp(rx interface{}, str interface{}) bool { - - var r *regexp.Regexp - if rr, ok := rx.(*regexp.Regexp); ok { - r = rr - } else { - r = regexp.MustCompile(fmt.Sprint(rx)) - } - - return (r.FindStringIndex(fmt.Sprint(str)) != nil) - -} - -// Regexp asserts that a specified regexp matches a string. -// -// assert.Regexp(t, regexp.MustCompile("start"), "it's starting") -// assert.Regexp(t, "start...$", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). -func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { - - match := matchRegexp(rx, str) - - if !match { - Fail(t, fmt.Sprintf("Expect \"%v\" to match \"%v\"", str, rx), msgAndArgs...) - } - - return match -} - -// NotRegexp asserts that a specified regexp does not match a string. -// -// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") -// assert.NotRegexp(t, "^start", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). -func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { - match := matchRegexp(rx, str) - - if match { - Fail(t, fmt.Sprintf("Expect \"%v\" to NOT match \"%v\"", str, rx), msgAndArgs...) - } - - return !match - -} - -// Zero asserts that i is the zero value for its type and returns the truth. -func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { - if i != nil && !reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) { - return Fail(t, fmt.Sprintf("Should be zero, but was %v", i), msgAndArgs...) - } - return true -} - -// NotZero asserts that i is not the zero value for its type and returns the truth. -func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { - if i == nil || reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) { - return Fail(t, fmt.Sprintf("Should not be zero, but was %v", i), msgAndArgs...) - } - return true -} - -// JSONEq asserts that two JSON strings are equivalent. -// -// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) -// -// Returns whether the assertion was successful (true) or not (false). -func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool { - var expectedJSONAsInterface, actualJSONAsInterface interface{} - - if err := json.Unmarshal([]byte(expected), &expectedJSONAsInterface); err != nil { - return Fail(t, fmt.Sprintf("Expected value ('%s') is not valid json.\nJSON parsing error: '%s'", expected, err.Error()), msgAndArgs...) - } - - if err := json.Unmarshal([]byte(actual), &actualJSONAsInterface); err != nil { - return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid json.\nJSON parsing error: '%s'", actual, err.Error()), msgAndArgs...) - } - - return Equal(t, expectedJSONAsInterface, actualJSONAsInterface, msgAndArgs...) -} - -func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) { - t := reflect.TypeOf(v) - k := t.Kind() - - if k == reflect.Ptr { - t = t.Elem() - k = t.Kind() - } - return t, k -} - -// diff returns a diff of both values as long as both are of the same type and -// are a struct, map, slice or array. Otherwise it returns an empty string. -func diff(expected interface{}, actual interface{}) string { - if expected == nil || actual == nil { - return "" - } - - et, ek := typeAndKind(expected) - at, _ := typeAndKind(actual) - - if et != at { - return "" - } - - if ek != reflect.Struct && ek != reflect.Map && ek != reflect.Slice && ek != reflect.Array { - return "" - } - - e := spew.Sdump(expected) - a := spew.Sdump(actual) - - diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ - A: difflib.SplitLines(e), - B: difflib.SplitLines(a), - FromFile: "Expected", - FromDate: "", - ToFile: "Actual", - ToDate: "", - Context: 1, - }) - - return "\n\nDiff:\n" + diff -} diff --git a/vendor/github.com/test-go/testify/assert/doc.go b/vendor/github.com/test-go/testify/assert/doc.go deleted file mode 100644 index c9dccc4d6..000000000 --- a/vendor/github.com/test-go/testify/assert/doc.go +++ /dev/null @@ -1,45 +0,0 @@ -// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system. -// -// Example Usage -// -// The following is a complete example using assert in a standard test function: -// import ( -// "testing" -// "github.com/stretchr/testify/assert" -// ) -// -// func TestSomething(t *testing.T) { -// -// var a string = "Hello" -// var b string = "Hello" -// -// assert.Equal(t, a, b, "The two words should be the same.") -// -// } -// -// if you assert many times, use the format below: -// -// import ( -// "testing" -// "github.com/stretchr/testify/assert" -// ) -// -// func TestSomething(t *testing.T) { -// assert := assert.New(t) -// -// var a string = "Hello" -// var b string = "Hello" -// -// assert.Equal(a, b, "The two words should be the same.") -// } -// -// Assertions -// -// Assertions allow you to easily write test code, and are global funcs in the `assert` package. -// All assertion functions take, as the first argument, the `*testing.T` object provided by the -// testing framework. This allows the assertion funcs to write the failings and other details to -// the correct place. -// -// Every assertion function also takes an optional string message as the final argument, -// allowing custom error messages to be appended to the message the assertion method outputs. -package assert diff --git a/vendor/github.com/test-go/testify/assert/errors.go b/vendor/github.com/test-go/testify/assert/errors.go deleted file mode 100644 index ac9dc9d1d..000000000 --- a/vendor/github.com/test-go/testify/assert/errors.go +++ /dev/null @@ -1,10 +0,0 @@ -package assert - -import ( - "errors" -) - -// AnError is an error instance useful for testing. If the code does not care -// about error specifics, and only needs to return the error for example, this -// error should be used to make the test code more readable. -var AnError = errors.New("assert.AnError general error for testing") diff --git a/vendor/github.com/test-go/testify/assert/forward_assertions.go b/vendor/github.com/test-go/testify/assert/forward_assertions.go deleted file mode 100644 index b867e95ea..000000000 --- a/vendor/github.com/test-go/testify/assert/forward_assertions.go +++ /dev/null @@ -1,16 +0,0 @@ -package assert - -// Assertions provides assertion methods around the -// TestingT interface. -type Assertions struct { - t TestingT -} - -// New makes a new Assertions object for the specified TestingT. -func New(t TestingT) *Assertions { - return &Assertions{ - t: t, - } -} - -//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl diff --git a/vendor/github.com/test-go/testify/assert/http_assertions.go b/vendor/github.com/test-go/testify/assert/http_assertions.go deleted file mode 100644 index fa7ab89b1..000000000 --- a/vendor/github.com/test-go/testify/assert/http_assertions.go +++ /dev/null @@ -1,106 +0,0 @@ -package assert - -import ( - "fmt" - "net/http" - "net/http/httptest" - "net/url" - "strings" -) - -// httpCode is a helper that returns HTTP code of the response. It returns -1 -// if building a new request fails. -func httpCode(handler http.HandlerFunc, method, url string, values url.Values) int { - w := httptest.NewRecorder() - req, err := http.NewRequest(method, url+"?"+values.Encode(), nil) - if err != nil { - return -1 - } - handler(w, req) - return w.Code -} - -// HTTPSuccess asserts that a specified handler returns a success status code. -// -// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) -// -// Returns whether the assertion was successful (true) or not (false). -func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { - code := httpCode(handler, method, url, values) - if code == -1 { - return false - } - return code >= http.StatusOK && code <= http.StatusPartialContent -} - -// HTTPRedirect asserts that a specified handler returns a redirect status code. -// -// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} -// -// Returns whether the assertion was successful (true) or not (false). -func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { - code := httpCode(handler, method, url, values) - if code == -1 { - return false - } - return code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect -} - -// HTTPError asserts that a specified handler returns an error status code. -// -// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} -// -// Returns whether the assertion was successful (true) or not (false). -func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { - code := httpCode(handler, method, url, values) - if code == -1 { - return false - } - return code >= http.StatusBadRequest -} - -// HTTPBody is a helper that returns HTTP body of the response. It returns -// empty string if building a new request fails. -func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string { - w := httptest.NewRecorder() - req, err := http.NewRequest(method, url+"?"+values.Encode(), nil) - if err != nil { - return "" - } - handler(w, req) - return w.Body.String() -} - -// HTTPBodyContains asserts that a specified handler returns a -// body that contains a string. -// -// assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") -// -// Returns whether the assertion was successful (true) or not (false). -func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool { - body := HTTPBody(handler, method, url, values) - - contains := strings.Contains(body, fmt.Sprint(str)) - if !contains { - Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body)) - } - - return contains -} - -// HTTPBodyNotContains asserts that a specified handler returns a -// body that does not contain a string. -// -// assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") -// -// Returns whether the assertion was successful (true) or not (false). -func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool { - body := HTTPBody(handler, method, url, values) - - contains := strings.Contains(body, fmt.Sprint(str)) - if contains { - Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body)) - } - - return !contains -} diff --git a/vendor/github.com/test-go/testify/mock/doc.go b/vendor/github.com/test-go/testify/mock/doc.go deleted file mode 100644 index 7324128ef..000000000 --- a/vendor/github.com/test-go/testify/mock/doc.go +++ /dev/null @@ -1,44 +0,0 @@ -// Package mock provides a system by which it is possible to mock your objects -// and verify calls are happening as expected. -// -// Example Usage -// -// The mock package provides an object, Mock, that tracks activity on another object. It is usually -// embedded into a test object as shown below: -// -// type MyTestObject struct { -// // add a Mock object instance -// mock.Mock -// -// // other fields go here as normal -// } -// -// When implementing the methods of an interface, you wire your functions up -// to call the Mock.Called(args...) method, and return the appropriate values. -// -// For example, to mock a method that saves the name and age of a person and returns -// the year of their birth or an error, you might write this: -// -// func (o *MyTestObject) SavePersonDetails(firstname, lastname string, age int) (int, error) { -// args := o.Called(firstname, lastname, age) -// return args.Int(0), args.Error(1) -// } -// -// The Int, Error and Bool methods are examples of strongly typed getters that take the argument -// index position. Given this argument list: -// -// (12, true, "Something") -// -// You could read them out strongly typed like this: -// -// args.Int(0) -// args.Bool(1) -// args.String(2) -// -// For objects of your own type, use the generic Arguments.Get(index) method and make a type assertion: -// -// return args.Get(0).(*MyObject), args.Get(1).(*AnotherObjectOfMine) -// -// This may cause a panic if the object you are getting is nil (the type assertion will fail), in those -// cases you should check for nil first. -package mock diff --git a/vendor/github.com/test-go/testify/mock/mock.go b/vendor/github.com/test-go/testify/mock/mock.go deleted file mode 100644 index 20d7b8b1f..000000000 --- a/vendor/github.com/test-go/testify/mock/mock.go +++ /dev/null @@ -1,763 +0,0 @@ -package mock - -import ( - "fmt" - "reflect" - "regexp" - "runtime" - "strings" - "sync" - "time" - - "github.com/davecgh/go-spew/spew" - "github.com/pmezard/go-difflib/difflib" - "github.com/stretchr/objx" - "github.com/stretchr/testify/assert" -) - -func inin() { - spew.Config.SortKeys = true -} - -// TestingT is an interface wrapper around *testing.T -type TestingT interface { - Logf(format string, args ...interface{}) - Errorf(format string, args ...interface{}) - FailNow() -} - -/* - Call -*/ - -// Call represents a method call and is used for setting expectations, -// as well as recording activity. -type Call struct { - Parent *Mock - - // The name of the method that was or will be called. - Method string - - // Holds the arguments of the method. - Arguments Arguments - - // Holds the arguments that should be returned when - // this method is called. - ReturnArguments Arguments - - // The number of times to return the return arguments when setting - // expectations. 0 means to always return the value. - Repeatability int - - // Amount of times this call has been called - totalCalls int - - // Holds a channel that will be used to block the Return until it either - // receives a message or is closed. nil means it returns immediately. - WaitFor <-chan time.Time - - // Holds a handler used to manipulate arguments content that are passed by - // reference. It's useful when mocking methods such as unmarshalers or - // decoders. - RunFn func(Arguments) -} - -func newCall(parent *Mock, methodName string, methodArguments ...interface{}) *Call { - return &Call{ - Parent: parent, - Method: methodName, - Arguments: methodArguments, - ReturnArguments: make([]interface{}, 0), - Repeatability: 0, - WaitFor: nil, - RunFn: nil, - } -} - -func (c *Call) lock() { - c.Parent.mutex.Lock() -} - -func (c *Call) unlock() { - c.Parent.mutex.Unlock() -} - -// Return specifies the return arguments for the expectation. -// -// Mock.On("DoSomething").Return(errors.New("failed")) -func (c *Call) Return(returnArguments ...interface{}) *Call { - c.lock() - defer c.unlock() - - c.ReturnArguments = returnArguments - - return c -} - -// Once indicates that that the mock should only return the value once. -// -// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Once() -func (c *Call) Once() *Call { - return c.Times(1) -} - -// Twice indicates that that the mock should only return the value twice. -// -// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Twice() -func (c *Call) Twice() *Call { - return c.Times(2) -} - -// Times indicates that that the mock should only return the indicated number -// of times. -// -// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Times(5) -func (c *Call) Times(i int) *Call { - c.lock() - defer c.unlock() - c.Repeatability = i - return c -} - -// WaitUntil sets the channel that will block the mock's return until its closed -// or a message is received. -// -// Mock.On("MyMethod", arg1, arg2).WaitUntil(time.After(time.Second)) -func (c *Call) WaitUntil(w <-chan time.Time) *Call { - c.lock() - defer c.unlock() - c.WaitFor = w - return c -} - -// After sets how long to block until the call returns -// -// Mock.On("MyMethod", arg1, arg2).After(time.Second) -func (c *Call) After(d time.Duration) *Call { - return c.WaitUntil(time.After(d)) -} - -// Run sets a handler to be called before returning. It can be used when -// mocking a method such as unmarshalers that takes a pointer to a struct and -// sets properties in such struct -// -// Mock.On("Unmarshal", AnythingOfType("*map[string]interface{}").Return().Run(func(args Arguments) { -// arg := args.Get(0).(*map[string]interface{}) -// arg["foo"] = "bar" -// }) -func (c *Call) Run(fn func(Arguments)) *Call { - c.lock() - defer c.unlock() - c.RunFn = fn - return c -} - -// On chains a new expectation description onto the mocked interface. This -// allows syntax like. -// -// Mock. -// On("MyMethod", 1).Return(nil). -// On("MyOtherMethod", 'a', 'b', 'c').Return(errors.New("Some Error")) -func (c *Call) On(methodName string, arguments ...interface{}) *Call { - return c.Parent.On(methodName, arguments...) -} - -// Mock is the workhorse used to track activity on another object. -// For an example of its usage, refer to the "Example Usage" section at the top -// of this document. -type Mock struct { - // Represents the calls that are expected of - // an object. - ExpectedCalls []*Call - - // Holds the calls that were made to this mocked object. - Calls []Call - - // TestData holds any data that might be useful for testing. Testify ignores - // this data completely allowing you to do whatever you like with it. - testData objx.Map - - mutex sync.Mutex -} - -// TestData holds any data that might be useful for testing. Testify ignores -// this data completely allowing you to do whatever you like with it. -func (m *Mock) TestData() objx.Map { - - if m.testData == nil { - m.testData = make(objx.Map) - } - - return m.testData -} - -/* - Setting expectations -*/ - -// On starts a description of an expectation of the specified method -// being called. -// -// Mock.On("MyMethod", arg1, arg2) -func (m *Mock) On(methodName string, arguments ...interface{}) *Call { - for _, arg := range arguments { - if v := reflect.ValueOf(arg); v.Kind() == reflect.Func { - panic(fmt.Sprintf("cannot use Func in expectations. Use mock.AnythingOfType(\"%T\")", arg)) - } - } - - m.mutex.Lock() - defer m.mutex.Unlock() - c := newCall(m, methodName, arguments...) - m.ExpectedCalls = append(m.ExpectedCalls, c) - return c -} - -// /* -// Recording and responding to activity -// */ - -func (m *Mock) findExpectedCall(method string, arguments ...interface{}) (int, *Call) { - m.mutex.Lock() - defer m.mutex.Unlock() - for i, call := range m.ExpectedCalls { - if call.Method == method && call.Repeatability > -1 { - - _, diffCount := call.Arguments.Diff(arguments) - if diffCount == 0 { - return i, call - } - - } - } - return -1, nil -} - -func (m *Mock) findClosestCall(method string, arguments ...interface{}) (bool, *Call) { - diffCount := 0 - var closestCall *Call - - for _, call := range m.expectedCalls() { - if call.Method == method { - - _, tempDiffCount := call.Arguments.Diff(arguments) - if tempDiffCount < diffCount || diffCount == 0 { - diffCount = tempDiffCount - closestCall = call - } - - } - } - - if closestCall == nil { - return false, nil - } - - return true, closestCall -} - -func callString(method string, arguments Arguments, includeArgumentValues bool) string { - - var argValsString string - if includeArgumentValues { - var argVals []string - for argIndex, arg := range arguments { - argVals = append(argVals, fmt.Sprintf("%d: %#v", argIndex, arg)) - } - argValsString = fmt.Sprintf("\n\t\t%s", strings.Join(argVals, "\n\t\t")) - } - - return fmt.Sprintf("%s(%s)%s", method, arguments.String(), argValsString) -} - -// Called tells the mock object that a method has been called, and gets an array -// of arguments to return. Panics if the call is unexpected (i.e. not preceded by -// appropriate .On .Return() calls) -// If Call.WaitFor is set, blocks until the channel is closed or receives a message. -func (m *Mock) Called(arguments ...interface{}) Arguments { - // get the calling function's name - pc, _, _, ok := runtime.Caller(1) - if !ok { - panic("Couldn't get the caller information") - } - functionPath := runtime.FuncForPC(pc).Name() - //Next four lines are required to use GCCGO function naming conventions. - //For Ex: github_com_docker_libkv_store_mock.WatchTree.pN39_github_com_docker_libkv_store_mock.Mock - //uses inteface information unlike golang github.com/docker/libkv/store/mock.(*Mock).WatchTree - //With GCCGO we need to remove interface information starting from pN
. - re := regexp.MustCompile("\\.pN\\d+_") - if re.MatchString(functionPath) { - functionPath = re.Split(functionPath, -1)[0] - } - parts := strings.Split(functionPath, ".") - functionName := parts[len(parts)-1] - - found, call := m.findExpectedCall(functionName, arguments...) - - if found < 0 { - // we have to fail here - because we don't know what to do - // as the return arguments. This is because: - // - // a) this is a totally unexpected call to this method, - // b) the arguments are not what was expected, or - // c) the developer has forgotten to add an accompanying On...Return pair. - - closestFound, closestCall := m.findClosestCall(functionName, arguments...) - - if closestFound { - panic(fmt.Sprintf("\n\nmock: Unexpected Method Call\n-----------------------------\n\n%s\n\nThe closest call I have is: \n\n%s\n\n%s\n", callString(functionName, arguments, true), callString(functionName, closestCall.Arguments, true), diffArguments(arguments, closestCall.Arguments))) - } else { - panic(fmt.Sprintf("\nassert: mock: I don't know what to return because the method call was unexpected.\n\tEither do Mock.On(\"%s\").Return(...) first, or remove the %s() call.\n\tThis method was unexpected:\n\t\t%s\n\tat: %s", functionName, functionName, callString(functionName, arguments, true), assert.CallerInfo())) - } - } else { - m.mutex.Lock() - switch { - case call.Repeatability == 1: - call.Repeatability = -1 - call.totalCalls++ - - case call.Repeatability > 1: - call.Repeatability-- - call.totalCalls++ - - case call.Repeatability == 0: - call.totalCalls++ - } - m.mutex.Unlock() - } - - // add the call - m.mutex.Lock() - m.Calls = append(m.Calls, *newCall(m, functionName, arguments...)) - m.mutex.Unlock() - - // block if specified - if call.WaitFor != nil { - <-call.WaitFor - } - - if call.RunFn != nil { - call.RunFn(arguments) - } - - return call.ReturnArguments -} - -/* - Assertions -*/ - -type assertExpectationser interface { - AssertExpectations(TestingT) bool -} - -// AssertExpectationsForObjects asserts that everything specified with On and Return -// of the specified objects was in fact called as expected. -// -// Calls may have occurred in any order. -func AssertExpectationsForObjects(t TestingT, testObjects ...interface{}) bool { - for _, obj := range testObjects { - if m, ok := obj.(Mock); ok { - t.Logf("Deprecated mock.AssertExpectationsForObjects(myMock.Mock) use mock.AssertExpectationsForObjects(myMock)") - obj = &m - } - m := obj.(assertExpectationser) - if !m.AssertExpectations(t) { - return false - } - } - return true -} - -// AssertExpectations asserts that everything specified with On and Return was -// in fact called as expected. Calls may have occurred in any order. -func (m *Mock) AssertExpectations(t TestingT) bool { - var somethingMissing bool - var failedExpectations int - - // iterate through each expectation - expectedCalls := m.expectedCalls() - for _, expectedCall := range expectedCalls { - if !m.methodWasCalled(expectedCall.Method, expectedCall.Arguments) && expectedCall.totalCalls == 0 { - somethingMissing = true - failedExpectations++ - t.Logf("\u274C\t%s(%s)", expectedCall.Method, expectedCall.Arguments.String()) - } else { - m.mutex.Lock() - if expectedCall.Repeatability > 0 { - somethingMissing = true - failedExpectations++ - } else { - t.Logf("\u2705\t%s(%s)", expectedCall.Method, expectedCall.Arguments.String()) - } - m.mutex.Unlock() - } - } - - if somethingMissing { - t.Errorf("FAIL: %d out of %d expectation(s) were met.\n\tThe code you are testing needs to make %d more call(s).\n\tat: %s", len(expectedCalls)-failedExpectations, len(expectedCalls), failedExpectations, assert.CallerInfo()) - } - - return !somethingMissing -} - -// AssertNumberOfCalls asserts that the method was called expectedCalls times. -func (m *Mock) AssertNumberOfCalls(t TestingT, methodName string, expectedCalls int) bool { - var actualCalls int - for _, call := range m.calls() { - if call.Method == methodName { - actualCalls++ - } - } - return assert.Equal(t, expectedCalls, actualCalls, fmt.Sprintf("Expected number of calls (%d) does not match the actual number of calls (%d).", expectedCalls, actualCalls)) -} - -// AssertCalled asserts that the method was called. -// It can produce a false result when an argument is a pointer type and the underlying value changed after calling the mocked method. -func (m *Mock) AssertCalled(t TestingT, methodName string, arguments ...interface{}) bool { - if !assert.True(t, m.methodWasCalled(methodName, arguments), fmt.Sprintf("The \"%s\" method should have been called with %d argument(s), but was not.", methodName, len(arguments))) { - t.Logf("%v", m.expectedCalls()) - return false - } - return true -} - -// AssertNotCalled asserts that the method was not called. -// It can produce a false result when an argument is a pointer type and the underlying value changed after calling the mocked method. -func (m *Mock) AssertNotCalled(t TestingT, methodName string, arguments ...interface{}) bool { - if !assert.False(t, m.methodWasCalled(methodName, arguments), fmt.Sprintf("The \"%s\" method was called with %d argument(s), but should NOT have been.", methodName, len(arguments))) { - t.Logf("%v", m.expectedCalls()) - return false - } - return true -} - -func (m *Mock) methodWasCalled(methodName string, expected []interface{}) bool { - for _, call := range m.calls() { - if call.Method == methodName { - - _, differences := Arguments(expected).Diff(call.Arguments) - - if differences == 0 { - // found the expected call - return true - } - - } - } - // we didn't find the expected call - return false -} - -func (m *Mock) expectedCalls() []*Call { - m.mutex.Lock() - defer m.mutex.Unlock() - return append([]*Call{}, m.ExpectedCalls...) -} - -func (m *Mock) calls() []Call { - m.mutex.Lock() - defer m.mutex.Unlock() - return append([]Call{}, m.Calls...) -} - -/* - Arguments -*/ - -// Arguments holds an array of method arguments or return values. -type Arguments []interface{} - -const ( - // Anything is used in Diff and Assert when the argument being tested - // shouldn't be taken into consideration. - Anything string = "mock.Anything" -) - -// AnythingOfTypeArgument is a string that contains the type of an argument -// for use when type checking. Used in Diff and Assert. -type AnythingOfTypeArgument string - -// AnythingOfType returns an AnythingOfTypeArgument object containing the -// name of the type to check for. Used in Diff and Assert. -// -// For example: -// Assert(t, AnythingOfType("string"), AnythingOfType("int")) -func AnythingOfType(t string) AnythingOfTypeArgument { - return AnythingOfTypeArgument(t) -} - -// argumentMatcher performs custom argument matching, returning whether or -// not the argument is matched by the expectation fixture function. -type argumentMatcher struct { - // fn is a function which accepts one argument, and returns a bool. - fn reflect.Value -} - -func (f argumentMatcher) Matches(argument interface{}) bool { - expectType := f.fn.Type().In(0) - - if reflect.TypeOf(argument).AssignableTo(expectType) { - result := f.fn.Call([]reflect.Value{reflect.ValueOf(argument)}) - return result[0].Bool() - } - return false -} - -func (f argumentMatcher) String() string { - return fmt.Sprintf("func(%s) bool", f.fn.Type().In(0).Name()) -} - -// MatchedBy can be used to match a mock call based on only certain properties -// from a complex struct or some calculation. It takes a function that will be -// evaluated with the called argument and will return true when there's a match -// and false otherwise. -// -// Example: -// m.On("Do", MatchedBy(func(req *http.Request) bool { return req.Host == "example.com" })) -// -// |fn|, must be a function accepting a single argument (of the expected type) -// which returns a bool. If |fn| doesn't match the required signature, -// MathedBy() panics. -func MatchedBy(fn interface{}) argumentMatcher { - fnType := reflect.TypeOf(fn) - - if fnType.Kind() != reflect.Func { - panic(fmt.Sprintf("assert: arguments: %s is not a func", fn)) - } - if fnType.NumIn() != 1 { - panic(fmt.Sprintf("assert: arguments: %s does not take exactly one argument", fn)) - } - if fnType.NumOut() != 1 || fnType.Out(0).Kind() != reflect.Bool { - panic(fmt.Sprintf("assert: arguments: %s does not return a bool", fn)) - } - - return argumentMatcher{fn: reflect.ValueOf(fn)} -} - -// Get Returns the argument at the specified index. -func (args Arguments) Get(index int) interface{} { - if index+1 > len(args) { - panic(fmt.Sprintf("assert: arguments: Cannot call Get(%d) because there are %d argument(s).", index, len(args))) - } - return args[index] -} - -// Is gets whether the objects match the arguments specified. -func (args Arguments) Is(objects ...interface{}) bool { - for i, obj := range args { - if obj != objects[i] { - return false - } - } - return true -} - -// Diff gets a string describing the differences between the arguments -// and the specified objects. -// -// Returns the diff string and number of differences found. -func (args Arguments) Diff(objects []interface{}) (string, int) { - - var output = "\n" - var differences int - - var maxArgCount = len(args) - if len(objects) > maxArgCount { - maxArgCount = len(objects) - } - - for i := 0; i < maxArgCount; i++ { - var actual, expected interface{} - - if len(objects) <= i { - actual = "(Missing)" - } else { - actual = objects[i] - } - - if len(args) <= i { - expected = "(Missing)" - } else { - expected = args[i] - } - - if matcher, ok := expected.(argumentMatcher); ok { - if matcher.Matches(actual) { - output = fmt.Sprintf("%s\t%d: \u2705 %s matched by %s\n", output, i, actual, matcher) - } else { - differences++ - output = fmt.Sprintf("%s\t%d: \u2705 %s not matched by %s\n", output, i, actual, matcher) - } - } else if reflect.TypeOf(expected) == reflect.TypeOf((*AnythingOfTypeArgument)(nil)).Elem() { - - // type checking - if reflect.TypeOf(actual).Name() != string(expected.(AnythingOfTypeArgument)) && reflect.TypeOf(actual).String() != string(expected.(AnythingOfTypeArgument)) { - // not match - differences++ - output = fmt.Sprintf("%s\t%d: \u274C type %s != type %s - %s\n", output, i, expected, reflect.TypeOf(actual).Name(), actual) - } - - } else { - - // normal checking - - if assert.ObjectsAreEqual(expected, Anything) || assert.ObjectsAreEqual(actual, Anything) || assert.ObjectsAreEqual(actual, expected) { - // match - output = fmt.Sprintf("%s\t%d: \u2705 %s == %s\n", output, i, actual, expected) - } else { - // not match - differences++ - output = fmt.Sprintf("%s\t%d: \u274C %s != %s\n", output, i, actual, expected) - } - } - - } - - if differences == 0 { - return "No differences.", differences - } - - return output, differences - -} - -// Assert compares the arguments with the specified objects and fails if -// they do not exactly match. -func (args Arguments) Assert(t TestingT, objects ...interface{}) bool { - - // get the differences - diff, diffCount := args.Diff(objects) - - if diffCount == 0 { - return true - } - - // there are differences... report them... - t.Logf(diff) - t.Errorf("%sArguments do not match.", assert.CallerInfo()) - - return false - -} - -// String gets the argument at the specified index. Panics if there is no argument, or -// if the argument is of the wrong type. -// -// If no index is provided, String() returns a complete string representation -// of the arguments. -func (args Arguments) String(indexOrNil ...int) string { - - if len(indexOrNil) == 0 { - // normal String() method - return a string representation of the args - var argsStr []string - for _, arg := range args { - argsStr = append(argsStr, fmt.Sprintf("%s", reflect.TypeOf(arg))) - } - return strings.Join(argsStr, ",") - } else if len(indexOrNil) == 1 { - // Index has been specified - get the argument at that index - var index = indexOrNil[0] - var s string - var ok bool - if s, ok = args.Get(index).(string); !ok { - panic(fmt.Sprintf("assert: arguments: String(%d) failed because object wasn't correct type: %s", index, args.Get(index))) - } - return s - } - - panic(fmt.Sprintf("assert: arguments: Wrong number of arguments passed to String. Must be 0 or 1, not %d", len(indexOrNil))) - -} - -// Int gets the argument at the specified index. Panics if there is no argument, or -// if the argument is of the wrong type. -func (args Arguments) Int(index int) int { - var s int - var ok bool - if s, ok = args.Get(index).(int); !ok { - panic(fmt.Sprintf("assert: arguments: Int(%d) failed because object wasn't correct type: %v", index, args.Get(index))) - } - return s -} - -// Error gets the argument at the specified index. Panics if there is no argument, or -// if the argument is of the wrong type. -func (args Arguments) Error(index int) error { - obj := args.Get(index) - var s error - var ok bool - if obj == nil { - return nil - } - if s, ok = obj.(error); !ok { - panic(fmt.Sprintf("assert: arguments: Error(%d) failed because object wasn't correct type: %v", index, args.Get(index))) - } - return s -} - -// Bool gets the argument at the specified index. Panics if there is no argument, or -// if the argument is of the wrong type. -func (args Arguments) Bool(index int) bool { - var s bool - var ok bool - if s, ok = args.Get(index).(bool); !ok { - panic(fmt.Sprintf("assert: arguments: Bool(%d) failed because object wasn't correct type: %v", index, args.Get(index))) - } - return s -} - -func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) { - t := reflect.TypeOf(v) - k := t.Kind() - - if k == reflect.Ptr { - t = t.Elem() - k = t.Kind() - } - return t, k -} - -func diffArguments(expected Arguments, actual Arguments) string { - for x := range expected { - if diffString := diff(expected[x], actual[x]); diffString != "" { - return fmt.Sprintf("Difference found in argument %v:\n\n%s", x, diffString) - } - } - - return "" -} - -// diff returns a diff of both values as long as both are of the same type and -// are a struct, map, slice or array. Otherwise it returns an empty string. -func diff(expected interface{}, actual interface{}) string { - if expected == nil || actual == nil { - return "" - } - - et, ek := typeAndKind(expected) - at, _ := typeAndKind(actual) - - if et != at { - return "" - } - - if ek != reflect.Struct && ek != reflect.Map && ek != reflect.Slice && ek != reflect.Array { - return "" - } - - e := spew.Sdump(expected) - a := spew.Sdump(actual) - - diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ - A: difflib.SplitLines(e), - B: difflib.SplitLines(a), - FromFile: "Expected", - FromDate: "", - ToFile: "Actual", - ToDate: "", - Context: 1, - }) - - return diff -} diff --git a/vendor/github.com/test-go/testify/require/doc.go b/vendor/github.com/test-go/testify/require/doc.go deleted file mode 100644 index 169de3922..000000000 --- a/vendor/github.com/test-go/testify/require/doc.go +++ /dev/null @@ -1,28 +0,0 @@ -// Package require implements the same assertions as the `assert` package but -// stops test execution when a test fails. -// -// Example Usage -// -// The following is a complete example using require in a standard test function: -// import ( -// "testing" -// "github.com/stretchr/testify/require" -// ) -// -// func TestSomething(t *testing.T) { -// -// var a string = "Hello" -// var b string = "Hello" -// -// require.Equal(t, a, b, "The two words should be the same.") -// -// } -// -// Assertions -// -// The `require` package have same global functions as in the `assert` package, -// but instead of returning a boolean result they call `t.FailNow()`. -// -// Every assertion function also takes an optional string message as the final argument, -// allowing custom error messages to be appended to the message the assertion method outputs. -package require diff --git a/vendor/github.com/test-go/testify/require/forward_requirements.go b/vendor/github.com/test-go/testify/require/forward_requirements.go deleted file mode 100644 index d3c2ab9bc..000000000 --- a/vendor/github.com/test-go/testify/require/forward_requirements.go +++ /dev/null @@ -1,16 +0,0 @@ -package require - -// Assertions provides assertion methods around the -// TestingT interface. -type Assertions struct { - t TestingT -} - -// New makes a new Assertions object for the specified TestingT. -func New(t TestingT) *Assertions { - return &Assertions{ - t: t, - } -} - -//go:generate go run ../_codegen/main.go -output-package=require -template=require_forward.go.tmpl diff --git a/vendor/github.com/test-go/testify/require/require.go b/vendor/github.com/test-go/testify/require/require.go deleted file mode 100644 index 1bcfcb0d9..000000000 --- a/vendor/github.com/test-go/testify/require/require.go +++ /dev/null @@ -1,464 +0,0 @@ -/* -* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen -* THIS FILE MUST NOT BE EDITED BY HAND -*/ - -package require - -import ( - - assert "github.com/stretchr/testify/assert" - http "net/http" - url "net/url" - time "time" -) - - -// Condition uses a Comparison to assert a complex condition. -func Condition(t TestingT, comp assert.Comparison, msgAndArgs ...interface{}) { - if !assert.Condition(t, comp, msgAndArgs...) { - t.FailNow() - } -} - - -// Contains asserts that the specified string, list(array, slice...) or map contains the -// specified substring or element. -// -// assert.Contains(t, "Hello World", "World", "But 'Hello World' does contain 'World'") -// assert.Contains(t, ["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'") -// assert.Contains(t, {"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'") -// -// Returns whether the assertion was successful (true) or not (false). -func Contains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { - if !assert.Contains(t, s, contains, msgAndArgs...) { - t.FailNow() - } -} - - -// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either -// a slice or a channel with len == 0. -// -// assert.Empty(t, obj) -// -// Returns whether the assertion was successful (true) or not (false). -func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { - if !assert.Empty(t, object, msgAndArgs...) { - t.FailNow() - } -} - - -// Equal asserts that two objects are equal. -// -// assert.Equal(t, 123, 123, "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func Equal(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { - if !assert.Equal(t, expected, actual, msgAndArgs...) { - t.FailNow() - } -} - - -// EqualError asserts that a function returned an error (i.e. not `nil`) -// and that it is equal to the provided error. -// -// actualObj, err := SomeFunction() -// if assert.Error(t, err, "An error was expected") { -// assert.Equal(t, err, expectedError) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) { - if !assert.EqualError(t, theError, errString, msgAndArgs...) { - t.FailNow() - } -} - - -// EqualValues asserts that two objects are equal or convertable to the same types -// and equal. -// -// assert.EqualValues(t, uint32(123), int32(123), "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { - if !assert.EqualValues(t, expected, actual, msgAndArgs...) { - t.FailNow() - } -} - - -// Error asserts that a function returned an error (i.e. not `nil`). -// -// actualObj, err := SomeFunction() -// if assert.Error(t, err, "An error was expected") { -// assert.Equal(t, err, expectedError) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func Error(t TestingT, err error, msgAndArgs ...interface{}) { - if !assert.Error(t, err, msgAndArgs...) { - t.FailNow() - } -} - - -// Exactly asserts that two objects are equal is value and type. -// -// assert.Exactly(t, int32(123), int64(123), "123 and 123 should NOT be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { - if !assert.Exactly(t, expected, actual, msgAndArgs...) { - t.FailNow() - } -} - - -// Fail reports a failure through -func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) { - if !assert.Fail(t, failureMessage, msgAndArgs...) { - t.FailNow() - } -} - - -// FailNow fails test -func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) { - if !assert.FailNow(t, failureMessage, msgAndArgs...) { - t.FailNow() - } -} - - -// False asserts that the specified value is false. -// -// assert.False(t, myBool, "myBool should be false") -// -// Returns whether the assertion was successful (true) or not (false). -func False(t TestingT, value bool, msgAndArgs ...interface{}) { - if !assert.False(t, value, msgAndArgs...) { - t.FailNow() - } -} - - -// HTTPBodyContains asserts that a specified handler returns a -// body that contains a string. -// -// assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") -// -// Returns whether the assertion was successful (true) or not (false). -func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) { - if !assert.HTTPBodyContains(t, handler, method, url, values, str) { - t.FailNow() - } -} - - -// HTTPBodyNotContains asserts that a specified handler returns a -// body that does not contain a string. -// -// assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") -// -// Returns whether the assertion was successful (true) or not (false). -func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) { - if !assert.HTTPBodyNotContains(t, handler, method, url, values, str) { - t.FailNow() - } -} - - -// HTTPError asserts that a specified handler returns an error status code. -// -// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} -// -// Returns whether the assertion was successful (true) or not (false). -func HTTPError(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) { - if !assert.HTTPError(t, handler, method, url, values) { - t.FailNow() - } -} - - -// HTTPRedirect asserts that a specified handler returns a redirect status code. -// -// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} -// -// Returns whether the assertion was successful (true) or not (false). -func HTTPRedirect(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) { - if !assert.HTTPRedirect(t, handler, method, url, values) { - t.FailNow() - } -} - - -// HTTPSuccess asserts that a specified handler returns a success status code. -// -// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) -// -// Returns whether the assertion was successful (true) or not (false). -func HTTPSuccess(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) { - if !assert.HTTPSuccess(t, handler, method, url, values) { - t.FailNow() - } -} - - -// Implements asserts that an object is implemented by the specified interface. -// -// assert.Implements(t, (*MyInterface)(nil), new(MyObject), "MyObject") -func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { - if !assert.Implements(t, interfaceObject, object, msgAndArgs...) { - t.FailNow() - } -} - - -// InDelta asserts that the two numerals are within delta of each other. -// -// assert.InDelta(t, math.Pi, (22 / 7.0), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). -func InDelta(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { - if !assert.InDelta(t, expected, actual, delta, msgAndArgs...) { - t.FailNow() - } -} - - -// InDeltaSlice is the same as InDelta, except it compares two slices. -func InDeltaSlice(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { - if !assert.InDeltaSlice(t, expected, actual, delta, msgAndArgs...) { - t.FailNow() - } -} - - -// InEpsilon asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). -func InEpsilon(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { - if !assert.InEpsilon(t, expected, actual, epsilon, msgAndArgs...) { - t.FailNow() - } -} - - -// InEpsilonSlice is the same as InEpsilon, except it compares two slices. -func InEpsilonSlice(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { - if !assert.InEpsilonSlice(t, expected, actual, delta, msgAndArgs...) { - t.FailNow() - } -} - - -// IsType asserts that the specified objects are of the same type. -func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { - if !assert.IsType(t, expectedType, object, msgAndArgs...) { - t.FailNow() - } -} - - -// JSONEq asserts that two JSON strings are equivalent. -// -// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) -// -// Returns whether the assertion was successful (true) or not (false). -func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { - if !assert.JSONEq(t, expected, actual, msgAndArgs...) { - t.FailNow() - } -} - - -// Len asserts that the specified object has specific length. -// Len also fails if the object has a type that len() not accept. -// -// assert.Len(t, mySlice, 3, "The size of slice is not 3") -// -// Returns whether the assertion was successful (true) or not (false). -func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) { - if !assert.Len(t, object, length, msgAndArgs...) { - t.FailNow() - } -} - - -// Nil asserts that the specified object is nil. -// -// assert.Nil(t, err, "err should be nothing") -// -// Returns whether the assertion was successful (true) or not (false). -func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) { - if !assert.Nil(t, object, msgAndArgs...) { - t.FailNow() - } -} - - -// NoError asserts that a function returned no error (i.e. `nil`). -// -// actualObj, err := SomeFunction() -// if assert.NoError(t, err) { -// assert.Equal(t, actualObj, expectedObj) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func NoError(t TestingT, err error, msgAndArgs ...interface{}) { - if !assert.NoError(t, err, msgAndArgs...) { - t.FailNow() - } -} - - -// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the -// specified substring or element. -// -// assert.NotContains(t, "Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") -// assert.NotContains(t, ["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'") -// assert.NotContains(t, {"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'") -// -// Returns whether the assertion was successful (true) or not (false). -func NotContains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { - if !assert.NotContains(t, s, contains, msgAndArgs...) { - t.FailNow() - } -} - - -// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either -// a slice or a channel with len == 0. -// -// if assert.NotEmpty(t, obj) { -// assert.Equal(t, "two", obj[1]) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { - if !assert.NotEmpty(t, object, msgAndArgs...) { - t.FailNow() - } -} - - -// NotEqual asserts that the specified values are NOT equal. -// -// assert.NotEqual(t, obj1, obj2, "two objects shouldn't be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func NotEqual(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { - if !assert.NotEqual(t, expected, actual, msgAndArgs...) { - t.FailNow() - } -} - - -// NotNil asserts that the specified object is not nil. -// -// assert.NotNil(t, err, "err should be something") -// -// Returns whether the assertion was successful (true) or not (false). -func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) { - if !assert.NotNil(t, object, msgAndArgs...) { - t.FailNow() - } -} - - -// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. -// -// assert.NotPanics(t, func(){ -// RemainCalm() -// }, "Calling RemainCalm() should NOT panic") -// -// Returns whether the assertion was successful (true) or not (false). -func NotPanics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { - if !assert.NotPanics(t, f, msgAndArgs...) { - t.FailNow() - } -} - - -// NotRegexp asserts that a specified regexp does not match a string. -// -// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") -// assert.NotRegexp(t, "^start", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). -func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { - if !assert.NotRegexp(t, rx, str, msgAndArgs...) { - t.FailNow() - } -} - - -// NotZero asserts that i is not the zero value for its type and returns the truth. -func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) { - if !assert.NotZero(t, i, msgAndArgs...) { - t.FailNow() - } -} - - -// Panics asserts that the code inside the specified PanicTestFunc panics. -// -// assert.Panics(t, func(){ -// GoCrazy() -// }, "Calling GoCrazy() should panic") -// -// Returns whether the assertion was successful (true) or not (false). -func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { - if !assert.Panics(t, f, msgAndArgs...) { - t.FailNow() - } -} - - -// Regexp asserts that a specified regexp matches a string. -// -// assert.Regexp(t, regexp.MustCompile("start"), "it's starting") -// assert.Regexp(t, "start...$", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). -func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { - if !assert.Regexp(t, rx, str, msgAndArgs...) { - t.FailNow() - } -} - - -// True asserts that the specified value is true. -// -// assert.True(t, myBool, "myBool should be true") -// -// Returns whether the assertion was successful (true) or not (false). -func True(t TestingT, value bool, msgAndArgs ...interface{}) { - if !assert.True(t, value, msgAndArgs...) { - t.FailNow() - } -} - - -// WithinDuration asserts that the two times are within duration delta of each other. -// -// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") -// -// Returns whether the assertion was successful (true) or not (false). -func WithinDuration(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { - if !assert.WithinDuration(t, expected, actual, delta, msgAndArgs...) { - t.FailNow() - } -} - - -// Zero asserts that i is the zero value for its type and returns the truth. -func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) { - if !assert.Zero(t, i, msgAndArgs...) { - t.FailNow() - } -} diff --git a/vendor/github.com/test-go/testify/require/require.go.tmpl b/vendor/github.com/test-go/testify/require/require.go.tmpl deleted file mode 100644 index ab1b1e9fd..000000000 --- a/vendor/github.com/test-go/testify/require/require.go.tmpl +++ /dev/null @@ -1,6 +0,0 @@ -{{.Comment}} -func {{.DocInfo.Name}}(t TestingT, {{.Params}}) { - if !assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { - t.FailNow() - } -} diff --git a/vendor/github.com/test-go/testify/require/require_forward.go b/vendor/github.com/test-go/testify/require/require_forward.go deleted file mode 100644 index 58324f105..000000000 --- a/vendor/github.com/test-go/testify/require/require_forward.go +++ /dev/null @@ -1,388 +0,0 @@ -/* -* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen -* THIS FILE MUST NOT BE EDITED BY HAND -*/ - -package require - -import ( - - assert "github.com/stretchr/testify/assert" - http "net/http" - url "net/url" - time "time" -) - - -// Condition uses a Comparison to assert a complex condition. -func (a *Assertions) Condition(comp assert.Comparison, msgAndArgs ...interface{}) { - Condition(a.t, comp, msgAndArgs...) -} - - -// Contains asserts that the specified string, list(array, slice...) or map contains the -// specified substring or element. -// -// a.Contains("Hello World", "World", "But 'Hello World' does contain 'World'") -// a.Contains(["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'") -// a.Contains({"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { - Contains(a.t, s, contains, msgAndArgs...) -} - - -// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either -// a slice or a channel with len == 0. -// -// a.Empty(obj) -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) { - Empty(a.t, object, msgAndArgs...) -} - - -// Equal asserts that two objects are equal. -// -// a.Equal(123, 123, "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { - Equal(a.t, expected, actual, msgAndArgs...) -} - - -// EqualError asserts that a function returned an error (i.e. not `nil`) -// and that it is equal to the provided error. -// -// actualObj, err := SomeFunction() -// if assert.Error(t, err, "An error was expected") { -// assert.Equal(t, err, expectedError) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) { - EqualError(a.t, theError, errString, msgAndArgs...) -} - - -// EqualValues asserts that two objects are equal or convertable to the same types -// and equal. -// -// a.EqualValues(uint32(123), int32(123), "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { - EqualValues(a.t, expected, actual, msgAndArgs...) -} - - -// Error asserts that a function returned an error (i.e. not `nil`). -// -// actualObj, err := SomeFunction() -// if a.Error(err, "An error was expected") { -// assert.Equal(t, err, expectedError) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Error(err error, msgAndArgs ...interface{}) { - Error(a.t, err, msgAndArgs...) -} - - -// Exactly asserts that two objects are equal is value and type. -// -// a.Exactly(int32(123), int64(123), "123 and 123 should NOT be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { - Exactly(a.t, expected, actual, msgAndArgs...) -} - - -// Fail reports a failure through -func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) { - Fail(a.t, failureMessage, msgAndArgs...) -} - - -// FailNow fails test -func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) { - FailNow(a.t, failureMessage, msgAndArgs...) -} - - -// False asserts that the specified value is false. -// -// a.False(myBool, "myBool should be false") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) False(value bool, msgAndArgs ...interface{}) { - False(a.t, value, msgAndArgs...) -} - - -// HTTPBodyContains asserts that a specified handler returns a -// body that contains a string. -// -// a.HTTPBodyContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) { - HTTPBodyContains(a.t, handler, method, url, values, str) -} - - -// HTTPBodyNotContains asserts that a specified handler returns a -// body that does not contain a string. -// -// a.HTTPBodyNotContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) { - HTTPBodyNotContains(a.t, handler, method, url, values, str) -} - - -// HTTPError asserts that a specified handler returns an error status code. -// -// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values) { - HTTPError(a.t, handler, method, url, values) -} - - -// HTTPRedirect asserts that a specified handler returns a redirect status code. -// -// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values) { - HTTPRedirect(a.t, handler, method, url, values) -} - - -// HTTPSuccess asserts that a specified handler returns a success status code. -// -// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values) { - HTTPSuccess(a.t, handler, method, url, values) -} - - -// Implements asserts that an object is implemented by the specified interface. -// -// a.Implements((*MyInterface)(nil), new(MyObject), "MyObject") -func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { - Implements(a.t, interfaceObject, object, msgAndArgs...) -} - - -// InDelta asserts that the two numerals are within delta of each other. -// -// a.InDelta(math.Pi, (22 / 7.0), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { - InDelta(a.t, expected, actual, delta, msgAndArgs...) -} - - -// InDeltaSlice is the same as InDelta, except it compares two slices. -func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { - InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...) -} - - -// InEpsilon asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { - InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) -} - - -// InEpsilonSlice is the same as InEpsilon, except it compares two slices. -func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { - InEpsilonSlice(a.t, expected, actual, delta, msgAndArgs...) -} - - -// IsType asserts that the specified objects are of the same type. -func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { - IsType(a.t, expectedType, object, msgAndArgs...) -} - - -// JSONEq asserts that two JSON strings are equivalent. -// -// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) { - JSONEq(a.t, expected, actual, msgAndArgs...) -} - - -// Len asserts that the specified object has specific length. -// Len also fails if the object has a type that len() not accept. -// -// a.Len(mySlice, 3, "The size of slice is not 3") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) { - Len(a.t, object, length, msgAndArgs...) -} - - -// Nil asserts that the specified object is nil. -// -// a.Nil(err, "err should be nothing") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) { - Nil(a.t, object, msgAndArgs...) -} - - -// NoError asserts that a function returned no error (i.e. `nil`). -// -// actualObj, err := SomeFunction() -// if a.NoError(err) { -// assert.Equal(t, actualObj, expectedObj) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) { - NoError(a.t, err, msgAndArgs...) -} - - -// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the -// specified substring or element. -// -// a.NotContains("Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") -// a.NotContains(["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'") -// a.NotContains({"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { - NotContains(a.t, s, contains, msgAndArgs...) -} - - -// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either -// a slice or a channel with len == 0. -// -// if a.NotEmpty(obj) { -// assert.Equal(t, "two", obj[1]) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) { - NotEmpty(a.t, object, msgAndArgs...) -} - - -// NotEqual asserts that the specified values are NOT equal. -// -// a.NotEqual(obj1, obj2, "two objects shouldn't be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { - NotEqual(a.t, expected, actual, msgAndArgs...) -} - - -// NotNil asserts that the specified object is not nil. -// -// a.NotNil(err, "err should be something") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) { - NotNil(a.t, object, msgAndArgs...) -} - - -// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. -// -// a.NotPanics(func(){ -// RemainCalm() -// }, "Calling RemainCalm() should NOT panic") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NotPanics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { - NotPanics(a.t, f, msgAndArgs...) -} - - -// NotRegexp asserts that a specified regexp does not match a string. -// -// a.NotRegexp(regexp.MustCompile("starts"), "it's starting") -// a.NotRegexp("^start", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { - NotRegexp(a.t, rx, str, msgAndArgs...) -} - - -// NotZero asserts that i is not the zero value for its type and returns the truth. -func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) { - NotZero(a.t, i, msgAndArgs...) -} - - -// Panics asserts that the code inside the specified PanicTestFunc panics. -// -// a.Panics(func(){ -// GoCrazy() -// }, "Calling GoCrazy() should panic") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Panics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { - Panics(a.t, f, msgAndArgs...) -} - - -// Regexp asserts that a specified regexp matches a string. -// -// a.Regexp(regexp.MustCompile("start"), "it's starting") -// a.Regexp("start...$", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { - Regexp(a.t, rx, str, msgAndArgs...) -} - - -// True asserts that the specified value is true. -// -// a.True(myBool, "myBool should be true") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) True(value bool, msgAndArgs ...interface{}) { - True(a.t, value, msgAndArgs...) -} - - -// WithinDuration asserts that the two times are within duration delta of each other. -// -// a.WithinDuration(time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { - WithinDuration(a.t, expected, actual, delta, msgAndArgs...) -} - - -// Zero asserts that i is the zero value for its type and returns the truth. -func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) { - Zero(a.t, i, msgAndArgs...) -} diff --git a/vendor/github.com/test-go/testify/require/require_forward.go.tmpl b/vendor/github.com/test-go/testify/require/require_forward.go.tmpl deleted file mode 100644 index b93569e0a..000000000 --- a/vendor/github.com/test-go/testify/require/require_forward.go.tmpl +++ /dev/null @@ -1,4 +0,0 @@ -{{.CommentWithoutT "a"}} -func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) { - {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) -} diff --git a/vendor/github.com/test-go/testify/require/requirements.go b/vendor/github.com/test-go/testify/require/requirements.go deleted file mode 100644 index 41147562d..000000000 --- a/vendor/github.com/test-go/testify/require/requirements.go +++ /dev/null @@ -1,9 +0,0 @@ -package require - -// TestingT is an interface wrapper around *testing.T -type TestingT interface { - Errorf(format string, args ...interface{}) - FailNow() -} - -//go:generate go run ../_codegen/main.go -output-package=require -template=require.go.tmpl diff --git a/vendor/github.com/urfave/cli/v2/app.go b/vendor/github.com/urfave/cli/v2/app.go index 4b6675a2d..19b76c9b6 100644 --- a/vendor/github.com/urfave/cli/v2/app.go +++ b/vendor/github.com/urfave/cli/v2/app.go @@ -332,11 +332,18 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { return a.rootCommand.Run(cCtx, arguments...) } -// This is a stub function to keep public API unchanged from old code -// -// Deprecated: use App.Run or App.RunContext +// RunAsSubcommand is for legacy/compatibility purposes only. New code should only +// use App.RunContext. This function is slated to be removed in v3. func (a *App) RunAsSubcommand(ctx *Context) (err error) { - return a.RunContext(ctx.Context, ctx.Args().Slice()) + a.Setup() + + cCtx := NewContext(a, nil, ctx) + cCtx.shellComplete = ctx.shellComplete + + a.rootCommand = a.newRootCommand() + cCtx.Command = a.rootCommand + + return a.rootCommand.Run(cCtx, ctx.Args().Slice()...) } func (a *App) suggestFlagFromError(err error, command string) (string, error) { diff --git a/vendor/github.com/urfave/cli/v2/context.go b/vendor/github.com/urfave/cli/v2/context.go index dbf50e495..a45c120b5 100644 --- a/vendor/github.com/urfave/cli/v2/context.go +++ b/vendor/github.com/urfave/cli/v2/context.go @@ -56,6 +56,7 @@ func (cCtx *Context) Set(name, value string) error { // IsSet determines if the flag was actually set func (cCtx *Context) IsSet(name string) bool { + if fs := cCtx.lookupFlagSet(name); fs != nil { isSet := false fs.Visit(func(f *flag.Flag) { @@ -72,7 +73,23 @@ func (cCtx *Context) IsSet(name string) bool { return false } - return f.IsSet() + if f.IsSet() { + return true + } + + // now redo flagset search on aliases + aliases := f.Names() + fs.Visit(func(f *flag.Flag) { + for _, alias := range aliases { + if f.Name == alias { + isSet = true + } + } + }) + + if isSet { + return true + } } return false @@ -204,9 +221,10 @@ func (cCtx *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr { var flagPresent bool var flagName string - for _, key := range f.Names() { - flagName = key + flagNames := f.Names() + flagName = flagNames[0] + for _, key := range flagNames { if cCtx.IsSet(strings.TrimSpace(key)) { flagPresent = true } diff --git a/vendor/github.com/urfave/cli/v2/docs.go b/vendor/github.com/urfave/cli/v2/docs.go index 8b1c9c8a2..6cd0624ae 100644 --- a/vendor/github.com/urfave/cli/v2/docs.go +++ b/vendor/github.com/urfave/cli/v2/docs.go @@ -153,9 +153,14 @@ func prepareFlags( // flagDetails returns a string containing the flags metadata func flagDetails(flag DocGenerationFlag) string { description := flag.GetUsage() - value := flag.GetValue() - if value != "" { - description += " (default: " + value + ")" + if flag.TakesValue() { + defaultText := flag.GetDefaultText() + if defaultText == "" { + defaultText = flag.GetValue() + } + if defaultText != "" { + description += " (default: " + defaultText + ")" + } } return ": " + description } diff --git a/vendor/github.com/urfave/cli/v2/flag_bool.go b/vendor/github.com/urfave/cli/v2/flag_bool.go index f64d1cd92..369d18b77 100644 --- a/vendor/github.com/urfave/cli/v2/flag_bool.go +++ b/vendor/github.com/urfave/cli/v2/flag_bool.go @@ -52,7 +52,7 @@ func (b *boolValue) String() string { func (b *boolValue) IsBoolFlag() bool { return true } func (b *boolValue) Count() int { - if b.count != nil { + if b.count != nil && *b.count > 0 { return *b.count } return 0 @@ -130,6 +130,11 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error { if count == nil { count = new(int) } + + // since count will be incremented for each alias as well + // subtract number of aliases from overall count + *count -= len(f.Aliases) + if dest == nil { dest = new(bool) } diff --git a/vendor/github.com/urfave/cli/v2/flag_generic.go b/vendor/github.com/urfave/cli/v2/flag_generic.go index 4f9ac0a7f..039ffdfee 100644 --- a/vendor/github.com/urfave/cli/v2/flag_generic.go +++ b/vendor/github.com/urfave/cli/v2/flag_generic.go @@ -117,13 +117,8 @@ func (cCtx *Context) Generic(name string) interface{} { } func lookupGeneric(name string, set *flag.FlagSet) interface{} { - f := set.Lookup(name) - if f != nil { - parsed, err := f.Value, error(nil) - if err != nil { - return nil - } - return parsed + if f := set.Lookup(name); f != nil { + return f.Value } return nil } diff --git a/vendor/github.com/urfave/cli/v2/flag_path.go b/vendor/github.com/urfave/cli/v2/flag_path.go index 6434d3224..c4986779d 100644 --- a/vendor/github.com/urfave/cli/v2/flag_path.go +++ b/vendor/github.com/urfave/cli/v2/flag_path.go @@ -90,13 +90,8 @@ func (cCtx *Context) Path(name string) string { } func lookupPath(name string, set *flag.FlagSet) string { - f := set.Lookup(name) - if f != nil { - parsed, err := f.Value.String(), error(nil) - if err != nil { - return "" - } - return parsed + if f := set.Lookup(name); f != nil { + return f.Value.String() } return "" } diff --git a/vendor/github.com/urfave/cli/v2/flag_string.go b/vendor/github.com/urfave/cli/v2/flag_string.go index 3050086e8..4e55a2ca3 100644 --- a/vendor/github.com/urfave/cli/v2/flag_string.go +++ b/vendor/github.com/urfave/cli/v2/flag_string.go @@ -87,10 +87,8 @@ func (cCtx *Context) String(name string) string { } func lookupString(name string, set *flag.FlagSet) string { - f := set.Lookup(name) - if f != nil { - parsed := f.Value.String() - return parsed + if f := set.Lookup(name); f != nil { + return f.Value.String() } return "" } diff --git a/vendor/github.com/urfave/cli/v2/flag_timestamp.go b/vendor/github.com/urfave/cli/v2/flag_timestamp.go index fa0671fe4..83f750a31 100644 --- a/vendor/github.com/urfave/cli/v2/flag_timestamp.go +++ b/vendor/github.com/urfave/cli/v2/flag_timestamp.go @@ -145,11 +145,6 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error { f.defaultValue = f.Value.clone() - if f.Destination != nil { - f.Destination.SetLayout(f.Layout) - f.Destination.SetLocation(f.Timezone) - } - if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if err := f.Value.Set(val); err != nil { return fmt.Errorf("could not parse %q as timestamp value from %s for flag %s: %s", val, source, f.Name, err) @@ -157,6 +152,10 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error { f.HasBeenSet = true } + if f.Destination != nil { + *f.Destination = *f.Value + } + for _, name := range f.Names() { if f.Destination != nil { set.Var(f.Destination, name, f.Usage) diff --git a/vendor/github.com/urfave/cli/v2/godoc-current.txt b/vendor/github.com/urfave/cli/v2/godoc-current.txt index bd5e1defc..6016bd82e 100644 --- a/vendor/github.com/urfave/cli/v2/godoc-current.txt +++ b/vendor/github.com/urfave/cli/v2/godoc-current.txt @@ -141,7 +141,7 @@ USAGE: DESCRIPTION: {{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}} -COMMANDS:{{template "visibleCommandTemplate" .}}{{end}}{{if .VisibleFlagCategories}} +COMMANDS:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}} OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} @@ -357,9 +357,8 @@ func (a *App) RunAndExitOnError() code in the cli.ExitCoder func (a *App) RunAsSubcommand(ctx *Context) (err error) - This is a stub function to keep public API unchanged from old code - - Deprecated: use App.Run or App.RunContext + RunAsSubcommand is for legacy/compatibility purposes only. New code should + only use App.RunContext. This function is slated to be removed in v3. func (a *App) RunContext(ctx context.Context, arguments []string) (err error) RunContext is like Run except it takes a Context that will be passed to diff --git a/vendor/github.com/urfave/cli/v2/help.go b/vendor/github.com/urfave/cli/v2/help.go index c7b8f55a5..885d7843c 100644 --- a/vendor/github.com/urfave/cli/v2/help.go +++ b/vendor/github.com/urfave/cli/v2/help.go @@ -42,6 +42,7 @@ var helpCommand = &Command{ // 3 $ app foo // 4 $ app help foo // 5 $ app foo help + // 6 $ app foo -h (with no other sub-commands nor flags defined) // Case 4. when executing a help command set the context to parent // to allow resolution of subsequent args. This will transform @@ -77,6 +78,8 @@ var helpCommand = &Command{ HelpPrinter(cCtx.App.Writer, templ, cCtx.Command) return nil } + + // Case 6, handling incorporated in the callee itself return ShowSubcommandHelp(cCtx) }, } @@ -206,9 +209,15 @@ func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) { func DefaultCompleteWithFlags(cmd *Command) func(cCtx *Context) { return func(cCtx *Context) { - if len(os.Args) > 2 { - lastArg := os.Args[len(os.Args)-2] + var lastArg string + // TODO: This shouldnt depend on os.Args rather it should + // depend on root arguments passed to App + if len(os.Args) > 2 { + lastArg = os.Args[len(os.Args)-2] + } + + if lastArg != "" { if strings.HasPrefix(lastArg, "-") { if cmd != nil { printFlagSuggestions(lastArg, cmd.Flags, cCtx.App.Writer) @@ -292,8 +301,12 @@ func ShowSubcommandHelp(cCtx *Context) error { if cCtx == nil { return nil } - - HelpPrinter(cCtx.App.Writer, SubcommandHelpTemplate, cCtx.Command) + // use custom template when provided (fixes #1703) + templ := SubcommandHelpTemplate + if cCtx.Command != nil && cCtx.Command.CustomHelpTemplate != "" { + templ = cCtx.Command.CustomHelpTemplate + } + HelpPrinter(cCtx.App.Writer, templ, cCtx.Command) return nil } diff --git a/vendor/github.com/urfave/cli/v2/template.go b/vendor/github.com/urfave/cli/v2/template.go index b565ba61e..da98890eb 100644 --- a/vendor/github.com/urfave/cli/v2/template.go +++ b/vendor/github.com/urfave/cli/v2/template.go @@ -88,7 +88,7 @@ USAGE: DESCRIPTION: {{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}} -COMMANDS:{{template "visibleCommandTemplate" .}}{{end}}{{if .VisibleFlagCategories}} +COMMANDS:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}} OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} diff --git a/vendor/github.com/xo/terminfo/caps.go b/vendor/github.com/xo/terminfo/caps.go index 9674aaa7b..e5e1d41f1 100644 --- a/vendor/github.com/xo/terminfo/caps.go +++ b/vendor/github.com/xo/terminfo/caps.go @@ -1,7 +1,5 @@ package terminfo -//go:generate go run gen.go - // BoolCapName returns the bool capability name. func BoolCapName(i int) string { return boolCapNames[2*i] diff --git a/vendor/github.com/xo/terminfo/capvals.go b/vendor/github.com/xo/terminfo/capvals.go index 8528740ea..0c2274e3a 100644 --- a/vendor/github.com/xo/terminfo/capvals.go +++ b/vendor/github.com/xo/terminfo/capvals.go @@ -1,138 +1,94 @@ package terminfo // Code generated by gen.go. DO NOT EDIT. - // Bool capabilities. const ( // The AutoLeftMargin [auto_left_margin, bw] bool capability indicates cub1 wraps from column 0 to last column. AutoLeftMargin = iota - // The AutoRightMargin [auto_right_margin, am] bool capability indicates terminal has automatic margins. AutoRightMargin - // The NoEscCtlc [no_esc_ctlc, xsb] bool capability indicates beehive (f1=escape, f2=ctrl C). NoEscCtlc - // The CeolStandoutGlitch [ceol_standout_glitch, xhp] bool capability indicates standout not erased by overwriting (hp). CeolStandoutGlitch - // The EatNewlineGlitch [eat_newline_glitch, xenl] bool capability indicates newline ignored after 80 cols (concept). EatNewlineGlitch - // The EraseOverstrike [erase_overstrike, eo] bool capability indicates can erase overstrikes with a blank. EraseOverstrike - // The GenericType [generic_type, gn] bool capability indicates generic line type. GenericType - // The HardCopy [hard_copy, hc] bool capability indicates hardcopy terminal. HardCopy - // The HasMetaKey [has_meta_key, km] bool capability indicates Has a meta key (i.e., sets 8th-bit). HasMetaKey - // The HasStatusLine [has_status_line, hs] bool capability indicates has extra status line. HasStatusLine - // The InsertNullGlitch [insert_null_glitch, in] bool capability indicates insert mode distinguishes nulls. InsertNullGlitch - // The MemoryAbove [memory_above, da] bool capability indicates display may be retained above the screen. MemoryAbove - // The MemoryBelow [memory_below, db] bool capability indicates display may be retained below the screen. MemoryBelow - // The MoveInsertMode [move_insert_mode, mir] bool capability indicates safe to move while in insert mode. MoveInsertMode - // The MoveStandoutMode [move_standout_mode, msgr] bool capability indicates safe to move while in standout mode. MoveStandoutMode - // The OverStrike [over_strike, os] bool capability indicates terminal can overstrike. OverStrike - // The StatusLineEscOk [status_line_esc_ok, eslok] bool capability indicates escape can be used on the status line. StatusLineEscOk - // The DestTabsMagicSmso [dest_tabs_magic_smso, xt] bool capability indicates tabs destructive, magic so char (t1061). DestTabsMagicSmso - // The TildeGlitch [tilde_glitch, hz] bool capability indicates cannot print ~'s (Hazeltine). TildeGlitch - // The TransparentUnderline [transparent_underline, ul] bool capability indicates underline character overstrikes. TransparentUnderline - // The XonXoff [xon_xoff, xon] bool capability indicates terminal uses xon/xoff handshaking. XonXoff - // The NeedsXonXoff [needs_xon_xoff, nxon] bool capability indicates padding will not work, xon/xoff required. NeedsXonXoff - // The PrtrSilent [prtr_silent, mc5i] bool capability indicates printer will not echo on screen. PrtrSilent - // The HardCursor [hard_cursor, chts] bool capability indicates cursor is hard to see. HardCursor - // The NonRevRmcup [non_rev_rmcup, nrrmc] bool capability indicates smcup does not reverse rmcup. NonRevRmcup - // The NoPadChar [no_pad_char, npc] bool capability indicates pad character does not exist. NoPadChar - // The NonDestScrollRegion [non_dest_scroll_region, ndscr] bool capability indicates scrolling region is non-destructive. NonDestScrollRegion - // The CanChange [can_change, ccc] bool capability indicates terminal can re-define existing colors. CanChange - // The BackColorErase [back_color_erase, bce] bool capability indicates screen erased with background color. BackColorErase - // The HueLightnessSaturation [hue_lightness_saturation, hls] bool capability indicates terminal uses only HLS color notation (Tektronix). HueLightnessSaturation - // The ColAddrGlitch [col_addr_glitch, xhpa] bool capability indicates only positive motion for hpa/mhpa caps. ColAddrGlitch - // The CrCancelsMicroMode [cr_cancels_micro_mode, crxm] bool capability indicates using cr turns off micro mode. CrCancelsMicroMode - // The HasPrintWheel [has_print_wheel, daisy] bool capability indicates printer needs operator to change character set. HasPrintWheel - // The RowAddrGlitch [row_addr_glitch, xvpa] bool capability indicates only positive motion for vpa/mvpa caps. RowAddrGlitch - // The SemiAutoRightMargin [semi_auto_right_margin, sam] bool capability indicates printing in last column causes cr. SemiAutoRightMargin - // The CpiChangesRes [cpi_changes_res, cpix] bool capability indicates changing character pitch changes resolution. CpiChangesRes - // The LpiChangesRes [lpi_changes_res, lpix] bool capability indicates changing line pitch changes resolution. LpiChangesRes - // The BackspacesWithBs [backspaces_with_bs, OTbs] bool capability indicates uses ^H to move left. BackspacesWithBs - // The CrtNoScrolling [crt_no_scrolling, OTns] bool capability indicates crt cannot scroll. CrtNoScrolling - // The NoCorrectlyWorkingCr [no_correctly_working_cr, OTnc] bool capability indicates no way to go to start of line. NoCorrectlyWorkingCr - // The GnuHasMetaKey [gnu_has_meta_key, OTMT] bool capability indicates has meta key. GnuHasMetaKey - // The LinefeedIsNewline [linefeed_is_newline, OTNL] bool capability indicates move down with \n. LinefeedIsNewline - // The HasHardwareTabs [has_hardware_tabs, OTpt] bool capability indicates has 8-char tabs invoked with ^I. HasHardwareTabs - // The ReturnDoesClrEol [return_does_clr_eol, OTxr] bool capability indicates return clears the line. ReturnDoesClrEol ) @@ -141,118 +97,80 @@ const ( const ( // The Columns [columns, cols] num capability is number of columns in a line. Columns = iota - // The InitTabs [init_tabs, it] num capability is tabs initially every # spaces. InitTabs - // The Lines [lines, lines] num capability is number of lines on screen or page. Lines - // The LinesOfMemory [lines_of_memory, lm] num capability is lines of memory if > line. 0 means varies. LinesOfMemory - // The MagicCookieGlitch [magic_cookie_glitch, xmc] num capability is number of blank characters left by smso or rmso. MagicCookieGlitch - // The PaddingBaudRate [padding_baud_rate, pb] num capability is lowest baud rate where padding needed. PaddingBaudRate - // The VirtualTerminal [virtual_terminal, vt] num capability is virtual terminal number (CB/unix). VirtualTerminal - // The WidthStatusLine [width_status_line, wsl] num capability is number of columns in status line. WidthStatusLine - // The NumLabels [num_labels, nlab] num capability is number of labels on screen. NumLabels - // The LabelHeight [label_height, lh] num capability is rows in each label. LabelHeight - // The LabelWidth [label_width, lw] num capability is columns in each label. LabelWidth - // The MaxAttributes [max_attributes, ma] num capability is maximum combined attributes terminal can handle. MaxAttributes - // The MaximumWindows [maximum_windows, wnum] num capability is maximum number of definable windows. MaximumWindows - // The MaxColors [max_colors, colors] num capability is maximum number of colors on screen. MaxColors - // The MaxPairs [max_pairs, pairs] num capability is maximum number of color-pairs on the screen. MaxPairs - // The NoColorVideo [no_color_video, ncv] num capability is video attributes that cannot be used with colors. NoColorVideo - // The BufferCapacity [buffer_capacity, bufsz] num capability is numbers of bytes buffered before printing. BufferCapacity - // The DotVertSpacing [dot_vert_spacing, spinv] num capability is spacing of pins vertically in pins per inch. DotVertSpacing - // The DotHorzSpacing [dot_horz_spacing, spinh] num capability is spacing of dots horizontally in dots per inch. DotHorzSpacing - // The MaxMicroAddress [max_micro_address, maddr] num capability is maximum value in micro_..._address. MaxMicroAddress - // The MaxMicroJump [max_micro_jump, mjump] num capability is maximum value in parm_..._micro. MaxMicroJump - // The MicroColSize [micro_col_size, mcs] num capability is character step size when in micro mode. MicroColSize - // The MicroLineSize [micro_line_size, mls] num capability is line step size when in micro mode. MicroLineSize - // The NumberOfPins [number_of_pins, npins] num capability is numbers of pins in print-head. NumberOfPins - // The OutputResChar [output_res_char, orc] num capability is horizontal resolution in units per line. OutputResChar - // The OutputResLine [output_res_line, orl] num capability is vertical resolution in units per line. OutputResLine - // The OutputResHorzInch [output_res_horz_inch, orhi] num capability is horizontal resolution in units per inch. OutputResHorzInch - // The OutputResVertInch [output_res_vert_inch, orvi] num capability is vertical resolution in units per inch. OutputResVertInch - // The PrintRate [print_rate, cps] num capability is print rate in characters per second. PrintRate - // The WideCharSize [wide_char_size, widcs] num capability is character step size when in double wide mode. WideCharSize - // The Buttons [buttons, btns] num capability is number of buttons on mouse. Buttons - // The BitImageEntwining [bit_image_entwining, bitwin] num capability is number of passes for each bit-image row. BitImageEntwining - // The BitImageType [bit_image_type, bitype] num capability is type of bit-image device. BitImageType - // The MagicCookieGlitchUl [magic_cookie_glitch_ul, OTug] num capability is number of blanks left by ul. MagicCookieGlitchUl - // The CarriageReturnDelay [carriage_return_delay, OTdC] num capability is pad needed for CR. CarriageReturnDelay - // The NewLineDelay [new_line_delay, OTdN] num capability is pad needed for LF. NewLineDelay - // The BackspaceDelay [backspace_delay, OTdB] num capability is padding required for ^H. BackspaceDelay - // The HorizontalTabDelay [horizontal_tab_delay, OTdT] num capability is padding required for ^I. HorizontalTabDelay - // The NumberOfFunctionKeys [number_of_function_keys, OTkn] num capability is count of function keys. NumberOfFunctionKeys ) @@ -261,1254 +179,838 @@ const ( const ( // The BackTab [back_tab, cbt] string capability is the back tab (P). BackTab = iota - // The Bell [bell, bel] string capability is the audible signal (bell) (P). Bell - // The CarriageReturn [carriage_return, cr] string capability is the carriage return (P*) (P*). CarriageReturn - // The ChangeScrollRegion [change_scroll_region, csr] string capability is the change region to line #1 to line #2 (P). ChangeScrollRegion - // The ClearAllTabs [clear_all_tabs, tbc] string capability is the clear all tab stops (P). ClearAllTabs - // The ClearScreen [clear_screen, clear] string capability is the clear screen and home cursor (P*). ClearScreen - // The ClrEol [clr_eol, el] string capability is the clear to end of line (P). ClrEol - // The ClrEos [clr_eos, ed] string capability is the clear to end of screen (P*). ClrEos - // The ColumnAddress [column_address, hpa] string capability is the horizontal position #1, absolute (P). ColumnAddress - // The CommandCharacter [command_character, cmdch] string capability is the terminal settable cmd character in prototype !?. CommandCharacter - // The CursorAddress [cursor_address, cup] string capability is the move to row #1 columns #2. CursorAddress - // The CursorDown [cursor_down, cud1] string capability is the down one line. CursorDown - // The CursorHome [cursor_home, home] string capability is the home cursor (if no cup). CursorHome - // The CursorInvisible [cursor_invisible, civis] string capability is the make cursor invisible. CursorInvisible - // The CursorLeft [cursor_left, cub1] string capability is the move left one space. CursorLeft - // The CursorMemAddress [cursor_mem_address, mrcup] string capability is the memory relative cursor addressing, move to row #1 columns #2. CursorMemAddress - // The CursorNormal [cursor_normal, cnorm] string capability is the make cursor appear normal (undo civis/cvvis). CursorNormal - // The CursorRight [cursor_right, cuf1] string capability is the non-destructive space (move right one space). CursorRight - // The CursorToLl [cursor_to_ll, ll] string capability is the last line, first column (if no cup). CursorToLl - // The CursorUp [cursor_up, cuu1] string capability is the up one line. CursorUp - // The CursorVisible [cursor_visible, cvvis] string capability is the make cursor very visible. CursorVisible - // The DeleteCharacter [delete_character, dch1] string capability is the delete character (P*). DeleteCharacter - // The DeleteLine [delete_line, dl1] string capability is the delete line (P*). DeleteLine - // The DisStatusLine [dis_status_line, dsl] string capability is the disable status line. DisStatusLine - // The DownHalfLine [down_half_line, hd] string capability is the half a line down. DownHalfLine - // The EnterAltCharsetMode [enter_alt_charset_mode, smacs] string capability is the start alternate character set (P). EnterAltCharsetMode - // The EnterBlinkMode [enter_blink_mode, blink] string capability is the turn on blinking. EnterBlinkMode - // The EnterBoldMode [enter_bold_mode, bold] string capability is the turn on bold (extra bright) mode. EnterBoldMode - // The EnterCaMode [enter_ca_mode, smcup] string capability is the string to start programs using cup. EnterCaMode - // The EnterDeleteMode [enter_delete_mode, smdc] string capability is the enter delete mode. EnterDeleteMode - // The EnterDimMode [enter_dim_mode, dim] string capability is the turn on half-bright mode. EnterDimMode - // The EnterInsertMode [enter_insert_mode, smir] string capability is the enter insert mode. EnterInsertMode - // The EnterSecureMode [enter_secure_mode, invis] string capability is the turn on blank mode (characters invisible). EnterSecureMode - // The EnterProtectedMode [enter_protected_mode, prot] string capability is the turn on protected mode. EnterProtectedMode - // The EnterReverseMode [enter_reverse_mode, rev] string capability is the turn on reverse video mode. EnterReverseMode - // The EnterStandoutMode [enter_standout_mode, smso] string capability is the begin standout mode. EnterStandoutMode - // The EnterUnderlineMode [enter_underline_mode, smul] string capability is the begin underline mode. EnterUnderlineMode - // The EraseChars [erase_chars, ech] string capability is the erase #1 characters (P). EraseChars - // The ExitAltCharsetMode [exit_alt_charset_mode, rmacs] string capability is the end alternate character set (P). ExitAltCharsetMode - // The ExitAttributeMode [exit_attribute_mode, sgr0] string capability is the turn off all attributes. ExitAttributeMode - // The ExitCaMode [exit_ca_mode, rmcup] string capability is the strings to end programs using cup. ExitCaMode - // The ExitDeleteMode [exit_delete_mode, rmdc] string capability is the end delete mode. ExitDeleteMode - // The ExitInsertMode [exit_insert_mode, rmir] string capability is the exit insert mode. ExitInsertMode - // The ExitStandoutMode [exit_standout_mode, rmso] string capability is the exit standout mode. ExitStandoutMode - // The ExitUnderlineMode [exit_underline_mode, rmul] string capability is the exit underline mode. ExitUnderlineMode - // The FlashScreen [flash_screen, flash] string capability is the visible bell (may not move cursor). FlashScreen - // The FormFeed [form_feed, ff] string capability is the hardcopy terminal page eject (P*). FormFeed - // The FromStatusLine [from_status_line, fsl] string capability is the return from status line. FromStatusLine - // The Init1string [init_1string, is1] string capability is the initialization string. Init1string - // The Init2string [init_2string, is2] string capability is the initialization string. Init2string - // The Init3string [init_3string, is3] string capability is the initialization string. Init3string - // The InitFile [init_file, if] string capability is the name of initialization file. InitFile - // The InsertCharacter [insert_character, ich1] string capability is the insert character (P). InsertCharacter - // The InsertLine [insert_line, il1] string capability is the insert line (P*). InsertLine - // The InsertPadding [insert_padding, ip] string capability is the insert padding after inserted character. InsertPadding - // The KeyBackspace [key_backspace, kbs] string capability is the backspace key. KeyBackspace - // The KeyCatab [key_catab, ktbc] string capability is the clear-all-tabs key. KeyCatab - // The KeyClear [key_clear, kclr] string capability is the clear-screen or erase key. KeyClear - // The KeyCtab [key_ctab, kctab] string capability is the clear-tab key. KeyCtab - // The KeyDc [key_dc, kdch1] string capability is the delete-character key. KeyDc - // The KeyDl [key_dl, kdl1] string capability is the delete-line key. KeyDl - // The KeyDown [key_down, kcud1] string capability is the down-arrow key. KeyDown - // The KeyEic [key_eic, krmir] string capability is the sent by rmir or smir in insert mode. KeyEic - // The KeyEol [key_eol, kel] string capability is the clear-to-end-of-line key. KeyEol - // The KeyEos [key_eos, ked] string capability is the clear-to-end-of-screen key. KeyEos - // The KeyF0 [key_f0, kf0] string capability is the F0 function key. KeyF0 - // The KeyF1 [key_f1, kf1] string capability is the F1 function key. KeyF1 - // The KeyF10 [key_f10, kf10] string capability is the F10 function key. KeyF10 - // The KeyF2 [key_f2, kf2] string capability is the F2 function key. KeyF2 - // The KeyF3 [key_f3, kf3] string capability is the F3 function key. KeyF3 - // The KeyF4 [key_f4, kf4] string capability is the F4 function key. KeyF4 - // The KeyF5 [key_f5, kf5] string capability is the F5 function key. KeyF5 - // The KeyF6 [key_f6, kf6] string capability is the F6 function key. KeyF6 - // The KeyF7 [key_f7, kf7] string capability is the F7 function key. KeyF7 - // The KeyF8 [key_f8, kf8] string capability is the F8 function key. KeyF8 - // The KeyF9 [key_f9, kf9] string capability is the F9 function key. KeyF9 - // The KeyHome [key_home, khome] string capability is the home key. KeyHome - // The KeyIc [key_ic, kich1] string capability is the insert-character key. KeyIc - // The KeyIl [key_il, kil1] string capability is the insert-line key. KeyIl - // The KeyLeft [key_left, kcub1] string capability is the left-arrow key. KeyLeft - // The KeyLl [key_ll, kll] string capability is the lower-left key (home down). KeyLl - // The KeyNpage [key_npage, knp] string capability is the next-page key. KeyNpage - // The KeyPpage [key_ppage, kpp] string capability is the previous-page key. KeyPpage - // The KeyRight [key_right, kcuf1] string capability is the right-arrow key. KeyRight - // The KeySf [key_sf, kind] string capability is the scroll-forward key. KeySf - // The KeySr [key_sr, kri] string capability is the scroll-backward key. KeySr - // The KeyStab [key_stab, khts] string capability is the set-tab key. KeyStab - // The KeyUp [key_up, kcuu1] string capability is the up-arrow key. KeyUp - // The KeypadLocal [keypad_local, rmkx] string capability is the leave 'keyboard_transmit' mode. KeypadLocal - // The KeypadXmit [keypad_xmit, smkx] string capability is the enter 'keyboard_transmit' mode. KeypadXmit - // The LabF0 [lab_f0, lf0] string capability is the label on function key f0 if not f0. LabF0 - // The LabF1 [lab_f1, lf1] string capability is the label on function key f1 if not f1. LabF1 - // The LabF10 [lab_f10, lf10] string capability is the label on function key f10 if not f10. LabF10 - // The LabF2 [lab_f2, lf2] string capability is the label on function key f2 if not f2. LabF2 - // The LabF3 [lab_f3, lf3] string capability is the label on function key f3 if not f3. LabF3 - // The LabF4 [lab_f4, lf4] string capability is the label on function key f4 if not f4. LabF4 - // The LabF5 [lab_f5, lf5] string capability is the label on function key f5 if not f5. LabF5 - // The LabF6 [lab_f6, lf6] string capability is the label on function key f6 if not f6. LabF6 - // The LabF7 [lab_f7, lf7] string capability is the label on function key f7 if not f7. LabF7 - // The LabF8 [lab_f8, lf8] string capability is the label on function key f8 if not f8. LabF8 - // The LabF9 [lab_f9, lf9] string capability is the label on function key f9 if not f9. LabF9 - // The MetaOff [meta_off, rmm] string capability is the turn off meta mode. MetaOff - // The MetaOn [meta_on, smm] string capability is the turn on meta mode (8th-bit on). MetaOn - // The Newline [newline, nel] string capability is the newline (behave like cr followed by lf). Newline - // The PadChar [pad_char, pad] string capability is the padding char (instead of null). PadChar - // The ParmDch [parm_dch, dch] string capability is the delete #1 characters (P*). ParmDch - // The ParmDeleteLine [parm_delete_line, dl] string capability is the delete #1 lines (P*). ParmDeleteLine - // The ParmDownCursor [parm_down_cursor, cud] string capability is the down #1 lines (P*). ParmDownCursor - // The ParmIch [parm_ich, ich] string capability is the insert #1 characters (P*). ParmIch - // The ParmIndex [parm_index, indn] string capability is the scroll forward #1 lines (P). ParmIndex - // The ParmInsertLine [parm_insert_line, il] string capability is the insert #1 lines (P*). ParmInsertLine - // The ParmLeftCursor [parm_left_cursor, cub] string capability is the move #1 characters to the left (P). ParmLeftCursor - // The ParmRightCursor [parm_right_cursor, cuf] string capability is the move #1 characters to the right (P*). ParmRightCursor - // The ParmRindex [parm_rindex, rin] string capability is the scroll back #1 lines (P). ParmRindex - // The ParmUpCursor [parm_up_cursor, cuu] string capability is the up #1 lines (P*). ParmUpCursor - // The PkeyKey [pkey_key, pfkey] string capability is the program function key #1 to type string #2. PkeyKey - // The PkeyLocal [pkey_local, pfloc] string capability is the program function key #1 to execute string #2. PkeyLocal - // The PkeyXmit [pkey_xmit, pfx] string capability is the program function key #1 to transmit string #2. PkeyXmit - // The PrintScreen [print_screen, mc0] string capability is the print contents of screen. PrintScreen - // The PrtrOff [prtr_off, mc4] string capability is the turn off printer. PrtrOff - // The PrtrOn [prtr_on, mc5] string capability is the turn on printer. PrtrOn - // The RepeatChar [repeat_char, rep] string capability is the repeat char #1 #2 times (P*). RepeatChar - // The Reset1string [reset_1string, rs1] string capability is the reset string. Reset1string - // The Reset2string [reset_2string, rs2] string capability is the reset string. Reset2string - // The Reset3string [reset_3string, rs3] string capability is the reset string. Reset3string - // The ResetFile [reset_file, rf] string capability is the name of reset file. ResetFile - // The RestoreCursor [restore_cursor, rc] string capability is the restore cursor to position of last save_cursor. RestoreCursor - // The RowAddress [row_address, vpa] string capability is the vertical position #1 absolute (P). RowAddress - // The SaveCursor [save_cursor, sc] string capability is the save current cursor position (P). SaveCursor - // The ScrollForward [scroll_forward, ind] string capability is the scroll text up (P). ScrollForward - // The ScrollReverse [scroll_reverse, ri] string capability is the scroll text down (P). ScrollReverse - // The SetAttributes [set_attributes, sgr] string capability is the define video attributes #1-#9 (PG9). SetAttributes - // The SetTab [set_tab, hts] string capability is the set a tab in every row, current columns. SetTab - // The SetWindow [set_window, wind] string capability is the current window is lines #1-#2 cols #3-#4. SetWindow - // The Tab [tab, ht] string capability is the tab to next 8-space hardware tab stop. Tab - // The ToStatusLine [to_status_line, tsl] string capability is the move to status line, column #1. ToStatusLine - // The UnderlineChar [underline_char, uc] string capability is the underline char and move past it. UnderlineChar - // The UpHalfLine [up_half_line, hu] string capability is the half a line up. UpHalfLine - // The InitProg [init_prog, iprog] string capability is the path name of program for initialization. InitProg - // The KeyA1 [key_a1, ka1] string capability is the upper left of keypad. KeyA1 - // The KeyA3 [key_a3, ka3] string capability is the upper right of keypad. KeyA3 - // The KeyB2 [key_b2, kb2] string capability is the center of keypad. KeyB2 - // The KeyC1 [key_c1, kc1] string capability is the lower left of keypad. KeyC1 - // The KeyC3 [key_c3, kc3] string capability is the lower right of keypad. KeyC3 - // The PrtrNon [prtr_non, mc5p] string capability is the turn on printer for #1 bytes. PrtrNon - // The CharPadding [char_padding, rmp] string capability is the like ip but when in insert mode. CharPadding - // The AcsChars [acs_chars, acsc] string capability is the graphics charset pairs, based on vt100. AcsChars - // The PlabNorm [plab_norm, pln] string capability is the program label #1 to show string #2. PlabNorm - // The KeyBtab [key_btab, kcbt] string capability is the back-tab key. KeyBtab - // The EnterXonMode [enter_xon_mode, smxon] string capability is the turn on xon/xoff handshaking. EnterXonMode - // The ExitXonMode [exit_xon_mode, rmxon] string capability is the turn off xon/xoff handshaking. ExitXonMode - // The EnterAmMode [enter_am_mode, smam] string capability is the turn on automatic margins. EnterAmMode - // The ExitAmMode [exit_am_mode, rmam] string capability is the turn off automatic margins. ExitAmMode - // The XonCharacter [xon_character, xonc] string capability is the XON character. XonCharacter - // The XoffCharacter [xoff_character, xoffc] string capability is the XOFF character. XoffCharacter - // The EnaAcs [ena_acs, enacs] string capability is the enable alternate char set. EnaAcs - // The LabelOn [label_on, smln] string capability is the turn on soft labels. LabelOn - // The LabelOff [label_off, rmln] string capability is the turn off soft labels. LabelOff - // The KeyBeg [key_beg, kbeg] string capability is the begin key. KeyBeg - // The KeyCancel [key_cancel, kcan] string capability is the cancel key. KeyCancel - // The KeyClose [key_close, kclo] string capability is the close key. KeyClose - // The KeyCommand [key_command, kcmd] string capability is the command key. KeyCommand - // The KeyCopy [key_copy, kcpy] string capability is the copy key. KeyCopy - // The KeyCreate [key_create, kcrt] string capability is the create key. KeyCreate - // The KeyEnd [key_end, kend] string capability is the end key. KeyEnd - // The KeyEnter [key_enter, kent] string capability is the enter/send key. KeyEnter - // The KeyExit [key_exit, kext] string capability is the exit key. KeyExit - // The KeyFind [key_find, kfnd] string capability is the find key. KeyFind - // The KeyHelp [key_help, khlp] string capability is the help key. KeyHelp - // The KeyMark [key_mark, kmrk] string capability is the mark key. KeyMark - // The KeyMessage [key_message, kmsg] string capability is the message key. KeyMessage - // The KeyMove [key_move, kmov] string capability is the move key. KeyMove - // The KeyNext [key_next, knxt] string capability is the next key. KeyNext - // The KeyOpen [key_open, kopn] string capability is the open key. KeyOpen - // The KeyOptions [key_options, kopt] string capability is the options key. KeyOptions - // The KeyPrevious [key_previous, kprv] string capability is the previous key. KeyPrevious - // The KeyPrint [key_print, kprt] string capability is the print key. KeyPrint - // The KeyRedo [key_redo, krdo] string capability is the redo key. KeyRedo - // The KeyReference [key_reference, kref] string capability is the reference key. KeyReference - // The KeyRefresh [key_refresh, krfr] string capability is the refresh key. KeyRefresh - // The KeyReplace [key_replace, krpl] string capability is the replace key. KeyReplace - // The KeyRestart [key_restart, krst] string capability is the restart key. KeyRestart - // The KeyResume [key_resume, kres] string capability is the resume key. KeyResume - // The KeySave [key_save, ksav] string capability is the save key. KeySave - // The KeySuspend [key_suspend, kspd] string capability is the suspend key. KeySuspend - // The KeyUndo [key_undo, kund] string capability is the undo key. KeyUndo - // The KeySbeg [key_sbeg, kBEG] string capability is the shifted begin key. KeySbeg - // The KeyScancel [key_scancel, kCAN] string capability is the shifted cancel key. KeyScancel - // The KeyScommand [key_scommand, kCMD] string capability is the shifted command key. KeyScommand - // The KeyScopy [key_scopy, kCPY] string capability is the shifted copy key. KeyScopy - // The KeyScreate [key_screate, kCRT] string capability is the shifted create key. KeyScreate - // The KeySdc [key_sdc, kDC] string capability is the shifted delete-character key. KeySdc - // The KeySdl [key_sdl, kDL] string capability is the shifted delete-line key. KeySdl - // The KeySelect [key_select, kslt] string capability is the select key. KeySelect - // The KeySend [key_send, kEND] string capability is the shifted end key. KeySend - // The KeySeol [key_seol, kEOL] string capability is the shifted clear-to-end-of-line key. KeySeol - // The KeySexit [key_sexit, kEXT] string capability is the shifted exit key. KeySexit - // The KeySfind [key_sfind, kFND] string capability is the shifted find key. KeySfind - // The KeyShelp [key_shelp, kHLP] string capability is the shifted help key. KeyShelp - // The KeyShome [key_shome, kHOM] string capability is the shifted home key. KeyShome - // The KeySic [key_sic, kIC] string capability is the shifted insert-character key. KeySic - // The KeySleft [key_sleft, kLFT] string capability is the shifted left-arrow key. KeySleft - // The KeySmessage [key_smessage, kMSG] string capability is the shifted message key. KeySmessage - // The KeySmove [key_smove, kMOV] string capability is the shifted move key. KeySmove - // The KeySnext [key_snext, kNXT] string capability is the shifted next key. KeySnext - // The KeySoptions [key_soptions, kOPT] string capability is the shifted options key. KeySoptions - // The KeySprevious [key_sprevious, kPRV] string capability is the shifted previous key. KeySprevious - // The KeySprint [key_sprint, kPRT] string capability is the shifted print key. KeySprint - // The KeySredo [key_sredo, kRDO] string capability is the shifted redo key. KeySredo - // The KeySreplace [key_sreplace, kRPL] string capability is the shifted replace key. KeySreplace - // The KeySright [key_sright, kRIT] string capability is the shifted right-arrow key. KeySright - // The KeySrsume [key_srsume, kRES] string capability is the shifted resume key. KeySrsume - // The KeySsave [key_ssave, kSAV] string capability is the shifted save key. KeySsave - // The KeySsuspend [key_ssuspend, kSPD] string capability is the shifted suspend key. KeySsuspend - // The KeySundo [key_sundo, kUND] string capability is the shifted undo key. KeySundo - // The ReqForInput [req_for_input, rfi] string capability is the send next input char (for ptys). ReqForInput - // The KeyF11 [key_f11, kf11] string capability is the F11 function key. KeyF11 - // The KeyF12 [key_f12, kf12] string capability is the F12 function key. KeyF12 - // The KeyF13 [key_f13, kf13] string capability is the F13 function key. KeyF13 - // The KeyF14 [key_f14, kf14] string capability is the F14 function key. KeyF14 - // The KeyF15 [key_f15, kf15] string capability is the F15 function key. KeyF15 - // The KeyF16 [key_f16, kf16] string capability is the F16 function key. KeyF16 - // The KeyF17 [key_f17, kf17] string capability is the F17 function key. KeyF17 - // The KeyF18 [key_f18, kf18] string capability is the F18 function key. KeyF18 - // The KeyF19 [key_f19, kf19] string capability is the F19 function key. KeyF19 - // The KeyF20 [key_f20, kf20] string capability is the F20 function key. KeyF20 - // The KeyF21 [key_f21, kf21] string capability is the F21 function key. KeyF21 - // The KeyF22 [key_f22, kf22] string capability is the F22 function key. KeyF22 - // The KeyF23 [key_f23, kf23] string capability is the F23 function key. KeyF23 - // The KeyF24 [key_f24, kf24] string capability is the F24 function key. KeyF24 - // The KeyF25 [key_f25, kf25] string capability is the F25 function key. KeyF25 - // The KeyF26 [key_f26, kf26] string capability is the F26 function key. KeyF26 - // The KeyF27 [key_f27, kf27] string capability is the F27 function key. KeyF27 - // The KeyF28 [key_f28, kf28] string capability is the F28 function key. KeyF28 - // The KeyF29 [key_f29, kf29] string capability is the F29 function key. KeyF29 - // The KeyF30 [key_f30, kf30] string capability is the F30 function key. KeyF30 - // The KeyF31 [key_f31, kf31] string capability is the F31 function key. KeyF31 - // The KeyF32 [key_f32, kf32] string capability is the F32 function key. KeyF32 - // The KeyF33 [key_f33, kf33] string capability is the F33 function key. KeyF33 - // The KeyF34 [key_f34, kf34] string capability is the F34 function key. KeyF34 - // The KeyF35 [key_f35, kf35] string capability is the F35 function key. KeyF35 - // The KeyF36 [key_f36, kf36] string capability is the F36 function key. KeyF36 - // The KeyF37 [key_f37, kf37] string capability is the F37 function key. KeyF37 - // The KeyF38 [key_f38, kf38] string capability is the F38 function key. KeyF38 - // The KeyF39 [key_f39, kf39] string capability is the F39 function key. KeyF39 - // The KeyF40 [key_f40, kf40] string capability is the F40 function key. KeyF40 - // The KeyF41 [key_f41, kf41] string capability is the F41 function key. KeyF41 - // The KeyF42 [key_f42, kf42] string capability is the F42 function key. KeyF42 - // The KeyF43 [key_f43, kf43] string capability is the F43 function key. KeyF43 - // The KeyF44 [key_f44, kf44] string capability is the F44 function key. KeyF44 - // The KeyF45 [key_f45, kf45] string capability is the F45 function key. KeyF45 - // The KeyF46 [key_f46, kf46] string capability is the F46 function key. KeyF46 - // The KeyF47 [key_f47, kf47] string capability is the F47 function key. KeyF47 - // The KeyF48 [key_f48, kf48] string capability is the F48 function key. KeyF48 - // The KeyF49 [key_f49, kf49] string capability is the F49 function key. KeyF49 - // The KeyF50 [key_f50, kf50] string capability is the F50 function key. KeyF50 - // The KeyF51 [key_f51, kf51] string capability is the F51 function key. KeyF51 - // The KeyF52 [key_f52, kf52] string capability is the F52 function key. KeyF52 - // The KeyF53 [key_f53, kf53] string capability is the F53 function key. KeyF53 - // The KeyF54 [key_f54, kf54] string capability is the F54 function key. KeyF54 - // The KeyF55 [key_f55, kf55] string capability is the F55 function key. KeyF55 - // The KeyF56 [key_f56, kf56] string capability is the F56 function key. KeyF56 - // The KeyF57 [key_f57, kf57] string capability is the F57 function key. KeyF57 - // The KeyF58 [key_f58, kf58] string capability is the F58 function key. KeyF58 - // The KeyF59 [key_f59, kf59] string capability is the F59 function key. KeyF59 - // The KeyF60 [key_f60, kf60] string capability is the F60 function key. KeyF60 - // The KeyF61 [key_f61, kf61] string capability is the F61 function key. KeyF61 - // The KeyF62 [key_f62, kf62] string capability is the F62 function key. KeyF62 - // The KeyF63 [key_f63, kf63] string capability is the F63 function key. KeyF63 - // The ClrBol [clr_bol, el1] string capability is the Clear to beginning of line. ClrBol - // The ClearMargins [clear_margins, mgc] string capability is the clear right and left soft margins. ClearMargins - - // The SetLeftMargin [set_left_margin, smgl] string capability is the set left soft margin at current column. See smgl. (ML is not in BSD termcap). + // The SetLeftMargin [set_left_margin, smgl] string capability is the set left soft margin at current column. (ML is not in BSD termcap). SetLeftMargin - // The SetRightMargin [set_right_margin, smgr] string capability is the set right soft margin at current column. SetRightMargin - // The LabelFormat [label_format, fln] string capability is the label format. LabelFormat - // The SetClock [set_clock, sclk] string capability is the set clock, #1 hrs #2 mins #3 secs. SetClock - // The DisplayClock [display_clock, dclk] string capability is the display clock. DisplayClock - // The RemoveClock [remove_clock, rmclk] string capability is the remove clock. RemoveClock - // The CreateWindow [create_window, cwin] string capability is the define a window #1 from #2,#3 to #4,#5. CreateWindow - // The GotoWindow [goto_window, wingo] string capability is the go to window #1. GotoWindow - // The Hangup [hangup, hup] string capability is the hang-up phone. Hangup - // The DialPhone [dial_phone, dial] string capability is the dial number #1. DialPhone - // The QuickDial [quick_dial, qdial] string capability is the dial number #1 without checking. QuickDial - // The Tone [tone, tone] string capability is the select touch tone dialing. Tone - // The Pulse [pulse, pulse] string capability is the select pulse dialing. Pulse - // The FlashHook [flash_hook, hook] string capability is the flash switch hook. FlashHook - // The FixedPause [fixed_pause, pause] string capability is the pause for 2-3 seconds. FixedPause - // The WaitTone [wait_tone, wait] string capability is the wait for dial-tone. WaitTone - // The User0 [user0, u0] string capability is the User string #0. User0 - // The User1 [user1, u1] string capability is the User string #1. User1 - // The User2 [user2, u2] string capability is the User string #2. User2 - // The User3 [user3, u3] string capability is the User string #3. User3 - // The User4 [user4, u4] string capability is the User string #4. User4 - // The User5 [user5, u5] string capability is the User string #5. User5 - // The User6 [user6, u6] string capability is the User string #6. User6 - // The User7 [user7, u7] string capability is the User string #7. User7 - // The User8 [user8, u8] string capability is the User string #8. User8 - // The User9 [user9, u9] string capability is the User string #9. User9 - // The OrigPair [orig_pair, op] string capability is the Set default pair to its original value. OrigPair - // The OrigColors [orig_colors, oc] string capability is the Set all color pairs to the original ones. OrigColors - // The InitializeColor [initialize_color, initc] string capability is the initialize color #1 to (#2,#3,#4). InitializeColor - // The InitializePair [initialize_pair, initp] string capability is the Initialize color pair #1 to fg=(#2,#3,#4), bg=(#5,#6,#7). InitializePair - // The SetColorPair [set_color_pair, scp] string capability is the Set current color pair to #1. SetColorPair - // The SetForeground [set_foreground, setf] string capability is the Set foreground color #1. SetForeground - // The SetBackground [set_background, setb] string capability is the Set background color #1. SetBackground - // The ChangeCharPitch [change_char_pitch, cpi] string capability is the Change number of characters per inch to #1. ChangeCharPitch - // The ChangeLinePitch [change_line_pitch, lpi] string capability is the Change number of lines per inch to #1. ChangeLinePitch - // The ChangeResHorz [change_res_horz, chr] string capability is the Change horizontal resolution to #1. ChangeResHorz - // The ChangeResVert [change_res_vert, cvr] string capability is the Change vertical resolution to #1. ChangeResVert - // The DefineChar [define_char, defc] string capability is the Define a character #1, #2 dots wide, descender #3. DefineChar - // The EnterDoublewideMode [enter_doublewide_mode, swidm] string capability is the Enter double-wide mode. EnterDoublewideMode - // The EnterDraftQuality [enter_draft_quality, sdrfq] string capability is the Enter draft-quality mode. EnterDraftQuality - // The EnterItalicsMode [enter_italics_mode, sitm] string capability is the Enter italic mode. EnterItalicsMode - // The EnterLeftwardMode [enter_leftward_mode, slm] string capability is the Start leftward carriage motion. EnterLeftwardMode - // The EnterMicroMode [enter_micro_mode, smicm] string capability is the Start micro-motion mode. EnterMicroMode - // The EnterNearLetterQuality [enter_near_letter_quality, snlq] string capability is the Enter NLQ mode. EnterNearLetterQuality - // The EnterNormalQuality [enter_normal_quality, snrmq] string capability is the Enter normal-quality mode. EnterNormalQuality - // The EnterShadowMode [enter_shadow_mode, sshm] string capability is the Enter shadow-print mode. EnterShadowMode - // The EnterSubscriptMode [enter_subscript_mode, ssubm] string capability is the Enter subscript mode. EnterSubscriptMode - // The EnterSuperscriptMode [enter_superscript_mode, ssupm] string capability is the Enter superscript mode. EnterSuperscriptMode - // The EnterUpwardMode [enter_upward_mode, sum] string capability is the Start upward carriage motion. EnterUpwardMode - // The ExitDoublewideMode [exit_doublewide_mode, rwidm] string capability is the End double-wide mode. ExitDoublewideMode - // The ExitItalicsMode [exit_italics_mode, ritm] string capability is the End italic mode. ExitItalicsMode - // The ExitLeftwardMode [exit_leftward_mode, rlm] string capability is the End left-motion mode. ExitLeftwardMode - // The ExitMicroMode [exit_micro_mode, rmicm] string capability is the End micro-motion mode. ExitMicroMode - // The ExitShadowMode [exit_shadow_mode, rshm] string capability is the End shadow-print mode. ExitShadowMode - // The ExitSubscriptMode [exit_subscript_mode, rsubm] string capability is the End subscript mode. ExitSubscriptMode - // The ExitSuperscriptMode [exit_superscript_mode, rsupm] string capability is the End superscript mode. ExitSuperscriptMode - // The ExitUpwardMode [exit_upward_mode, rum] string capability is the End reverse character motion. ExitUpwardMode - // The MicroColumnAddress [micro_column_address, mhpa] string capability is the Like column_address in micro mode. MicroColumnAddress - // The MicroDown [micro_down, mcud1] string capability is the Like cursor_down in micro mode. MicroDown - // The MicroLeft [micro_left, mcub1] string capability is the Like cursor_left in micro mode. MicroLeft - // The MicroRight [micro_right, mcuf1] string capability is the Like cursor_right in micro mode. MicroRight - // The MicroRowAddress [micro_row_address, mvpa] string capability is the Like row_address #1 in micro mode. MicroRowAddress - // The MicroUp [micro_up, mcuu1] string capability is the Like cursor_up in micro mode. MicroUp - // The OrderOfPins [order_of_pins, porder] string capability is the Match software bits to print-head pins. OrderOfPins - // The ParmDownMicro [parm_down_micro, mcud] string capability is the Like parm_down_cursor in micro mode. ParmDownMicro - // The ParmLeftMicro [parm_left_micro, mcub] string capability is the Like parm_left_cursor in micro mode. ParmLeftMicro - // The ParmRightMicro [parm_right_micro, mcuf] string capability is the Like parm_right_cursor in micro mode. ParmRightMicro - // The ParmUpMicro [parm_up_micro, mcuu] string capability is the Like parm_up_cursor in micro mode. ParmUpMicro - // The SelectCharSet [select_char_set, scs] string capability is the Select character set, #1. SelectCharSet - // The SetBottomMargin [set_bottom_margin, smgb] string capability is the Set bottom margin at current line. SetBottomMargin - // The SetBottomMarginParm [set_bottom_margin_parm, smgbp] string capability is the Set bottom margin at line #1 or (if smgtp is not given) #2 lines from bottom. SetBottomMarginParm - // The SetLeftMarginParm [set_left_margin_parm, smglp] string capability is the Set left (right) margin at column #1. SetLeftMarginParm - // The SetRightMarginParm [set_right_margin_parm, smgrp] string capability is the Set right margin at column #1. SetRightMarginParm - // The SetTopMargin [set_top_margin, smgt] string capability is the Set top margin at current line. SetTopMargin - // The SetTopMarginParm [set_top_margin_parm, smgtp] string capability is the Set top (bottom) margin at row #1. SetTopMarginParm - // The StartBitImage [start_bit_image, sbim] string capability is the Start printing bit image graphics. StartBitImage - // The StartCharSetDef [start_char_set_def, scsd] string capability is the Start character set definition #1, with #2 characters in the set. StartCharSetDef - // The StopBitImage [stop_bit_image, rbim] string capability is the Stop printing bit image graphics. StopBitImage - // The StopCharSetDef [stop_char_set_def, rcsd] string capability is the End definition of character set #1. StopCharSetDef - // The SubscriptCharacters [subscript_characters, subcs] string capability is the List of subscriptable characters. SubscriptCharacters - // The SuperscriptCharacters [superscript_characters, supcs] string capability is the List of superscriptable characters. SuperscriptCharacters - // The TheseCauseCr [these_cause_cr, docr] string capability is the Printing any of these characters causes CR. TheseCauseCr - // The ZeroMotion [zero_motion, zerom] string capability is the No motion for subsequent character. ZeroMotion - // The CharSetNames [char_set_names, csnm] string capability is the Produce #1'th item from list of character set names. CharSetNames - // The KeyMouse [key_mouse, kmous] string capability is the Mouse event has occurred. KeyMouse - // The MouseInfo [mouse_info, minfo] string capability is the Mouse status information. MouseInfo - // The ReqMousePos [req_mouse_pos, reqmp] string capability is the Request mouse position. ReqMousePos - // The GetMouse [get_mouse, getm] string capability is the Curses should get button events, parameter #1 not documented. GetMouse - // The SetAForeground [set_a_foreground, setaf] string capability is the Set foreground color to #1, using ANSI escape. SetAForeground - // The SetABackground [set_a_background, setab] string capability is the Set background color to #1, using ANSI escape. SetABackground - // The PkeyPlab [pkey_plab, pfxl] string capability is the Program function key #1 to type string #2 and show string #3. PkeyPlab - // The DeviceType [device_type, devt] string capability is the Indicate language/codeset support. DeviceType - // The CodeSetInit [code_set_init, csin] string capability is the Init sequence for multiple codesets. CodeSetInit - // The Set0DesSeq [set0_des_seq, s0ds] string capability is the Shift to codeset 0 (EUC set 0, ASCII). Set0DesSeq - // The Set1DesSeq [set1_des_seq, s1ds] string capability is the Shift to codeset 1. Set1DesSeq - // The Set2DesSeq [set2_des_seq, s2ds] string capability is the Shift to codeset 2. Set2DesSeq - // The Set3DesSeq [set3_des_seq, s3ds] string capability is the Shift to codeset 3. Set3DesSeq - // The SetLrMargin [set_lr_margin, smglr] string capability is the Set both left and right margins to #1, #2. (ML is not in BSD termcap). SetLrMargin - // The SetTbMargin [set_tb_margin, smgtb] string capability is the Sets both top and bottom margins to #1, #2. SetTbMargin - // The BitImageRepeat [bit_image_repeat, birep] string capability is the Repeat bit image cell #1 #2 times. BitImageRepeat - // The BitImageNewline [bit_image_newline, binel] string capability is the Move to next row of the bit image. BitImageNewline - // The BitImageCarriageReturn [bit_image_carriage_return, bicr] string capability is the Move to beginning of same row. BitImageCarriageReturn - // The ColorNames [color_names, colornm] string capability is the Give name for color #1. ColorNames - // The DefineBitImageRegion [define_bit_image_region, defbi] string capability is the Define rectangular bit image region. DefineBitImageRegion - // The EndBitImageRegion [end_bit_image_region, endbi] string capability is the End a bit-image region. EndBitImageRegion - // The SetColorBand [set_color_band, setcolor] string capability is the Change to ribbon color #1. SetColorBand - // The SetPageLength [set_page_length, slines] string capability is the Set page length to #1 lines. SetPageLength - // The DisplayPcChar [display_pc_char, dispc] string capability is the Display PC character #1. DisplayPcChar - // The EnterPcCharsetMode [enter_pc_charset_mode, smpch] string capability is the Enter PC character display mode. EnterPcCharsetMode - // The ExitPcCharsetMode [exit_pc_charset_mode, rmpch] string capability is the Exit PC character display mode. ExitPcCharsetMode - // The EnterScancodeMode [enter_scancode_mode, smsc] string capability is the Enter PC scancode mode. EnterScancodeMode - // The ExitScancodeMode [exit_scancode_mode, rmsc] string capability is the Exit PC scancode mode. ExitScancodeMode - // The PcTermOptions [pc_term_options, pctrm] string capability is the PC terminal options. PcTermOptions - // The ScancodeEscape [scancode_escape, scesc] string capability is the Escape for scancode emulation. ScancodeEscape - // The AltScancodeEsc [alt_scancode_esc, scesa] string capability is the Alternate escape for scancode emulation. AltScancodeEsc - // The EnterHorizontalHlMode [enter_horizontal_hl_mode, ehhlm] string capability is the Enter horizontal highlight mode. EnterHorizontalHlMode - // The EnterLeftHlMode [enter_left_hl_mode, elhlm] string capability is the Enter left highlight mode. EnterLeftHlMode - // The EnterLowHlMode [enter_low_hl_mode, elohlm] string capability is the Enter low highlight mode. EnterLowHlMode - // The EnterRightHlMode [enter_right_hl_mode, erhlm] string capability is the Enter right highlight mode. EnterRightHlMode - // The EnterTopHlMode [enter_top_hl_mode, ethlm] string capability is the Enter top highlight mode. EnterTopHlMode - // The EnterVerticalHlMode [enter_vertical_hl_mode, evhlm] string capability is the Enter vertical highlight mode. EnterVerticalHlMode - // The SetAAttributes [set_a_attributes, sgr1] string capability is the Define second set of video attributes #1-#6. SetAAttributes - // The SetPglenInch [set_pglen_inch, slength] string capability is the Set page length to #1 hundredth of an inch (some implementations use sL for termcap). SetPglenInch - // The TermcapInit2 [termcap_init2, OTi2] string capability is the secondary initialization string. TermcapInit2 - // The TermcapReset [termcap_reset, OTrs] string capability is the terminal reset string. TermcapReset - // The LinefeedIfNotLf [linefeed_if_not_lf, OTnl] string capability is the use to move down. LinefeedIfNotLf - // The BackspaceIfNotBs [backspace_if_not_bs, OTbc] string capability is the move left, if not ^H. BackspaceIfNotBs - // The OtherNonFunctionKeys [other_non_function_keys, OTko] string capability is the list of self-mapped keycaps. OtherNonFunctionKeys - // The ArrowKeyMap [arrow_key_map, OTma] string capability is the map motion-keys for vi version 2. ArrowKeyMap - // The AcsUlcorner [acs_ulcorner, OTG2] string capability is the single upper left. AcsUlcorner - // The AcsLlcorner [acs_llcorner, OTG3] string capability is the single lower left. AcsLlcorner - // The AcsUrcorner [acs_urcorner, OTG1] string capability is the single upper right. AcsUrcorner - // The AcsLrcorner [acs_lrcorner, OTG4] string capability is the single lower right. AcsLrcorner - // The AcsLtee [acs_ltee, OTGR] string capability is the tee pointing right. AcsLtee - // The AcsRtee [acs_rtee, OTGL] string capability is the tee pointing left. AcsRtee - // The AcsBtee [acs_btee, OTGU] string capability is the tee pointing up. AcsBtee - // The AcsTtee [acs_ttee, OTGD] string capability is the tee pointing down. AcsTtee - // The AcsHline [acs_hline, OTGH] string capability is the single horizontal line. AcsHline - // The AcsVline [acs_vline, OTGV] string capability is the single vertical line. AcsVline - // The AcsPlus [acs_plus, OTGC] string capability is the single intersection. AcsPlus - // The MemoryLock [memory_lock, meml] string capability is the lock memory above cursor. MemoryLock - // The MemoryUnlock [memory_unlock, memu] string capability is the unlock memory. MemoryUnlock - // The BoxChars1 [box_chars_1, box1] string capability is the box characters primary set. BoxChars1 ) - const ( // CapCountBool is the count of bool capabilities. CapCountBool = ReturnDoesClrEol + 1 - // CapCountNum is the count of num capabilities. CapCountNum = NumberOfFunctionKeys + 1 - // CapCountString is the count of string capabilities. CapCountString = BoxChars1 + 1 ) diff --git a/vendor/github.com/xo/terminfo/color.go b/vendor/github.com/xo/terminfo/color.go index 453c29c24..76c439fc9 100644 --- a/vendor/github.com/xo/terminfo/color.go +++ b/vendor/github.com/xo/terminfo/color.go @@ -70,14 +70,12 @@ func ColorLevelFromEnv() (ColorLevel, error) { } return ColorLevelHundreds, nil } - // otherwise determine from TERM's max_colors capability if term := os.Getenv("TERM"); term != "" { ti, err := Load(term) if err != nil { return ColorLevelNone, err } - v, ok := ti.Nums[MaxColors] switch { case !ok || v <= 16: @@ -86,6 +84,5 @@ func ColorLevelFromEnv() (ColorLevel, error) { return ColorLevelHundreds, nil } } - return ColorLevelBasic, nil } diff --git a/vendor/github.com/xo/terminfo/util.go b/vendor/github.com/xo/terminfo/dec.go similarity index 92% rename from vendor/github.com/xo/terminfo/util.go rename to vendor/github.com/xo/terminfo/dec.go index 56f47e811..dacc88e38 100644 --- a/vendor/github.com/xo/terminfo/util.go +++ b/vendor/github.com/xo/terminfo/dec.go @@ -7,12 +7,11 @@ import ( const ( // maxFileLength is the max file length. maxFileLength = 4096 - // magic is the file magic for terminfo files. - magic = 0432 - - // magicExtended is the file magic for terminfo files with the extended number format. - magicExtended = 01036 + magic = 0o432 + // magicExtended is the file magic for terminfo files with the extended + // number format. + magicExtended = 0o1036 ) // header fields. @@ -99,12 +98,12 @@ func readStrings(idx []int, buf []byte, n int) (map[int][]byte, int, error) { type decoder struct { buf []byte pos int - len int + n int } // readBytes reads the next n bytes of buf, incrementing pos by n. func (d *decoder) readBytes(n int) ([]byte, error) { - if d.len < d.pos+n { + if d.n < d.pos+n { return nil, ErrUnexpectedFileEnd } n, d.pos = d.pos, d.pos+n @@ -115,15 +114,12 @@ func (d *decoder) readBytes(n int) ([]byte, error) { func (d *decoder) readInts(n, w int) ([]int, error) { w /= 8 l := n * w - buf, err := d.readBytes(l) if err != nil { return nil, err } - // align d.pos += d.pos % 2 - z := make([]int, n) for i, j := 0, 0; i < l; i, j = i+w, j+1 { switch w { @@ -135,7 +131,6 @@ func (d *decoder) readInts(n, w int) ([]int, error) { z[j] = int(buf[i+3])<<24 | int(buf[i+2])<<16 | int(buf[i+1])<<8 | int(buf[i]) } } - return z, nil } @@ -145,7 +140,6 @@ func (d *decoder) readBools(n int) (map[int]bool, map[int]bool, error) { if err != nil { return nil, nil, err } - // process bools, boolsM := make(map[int]bool), make(map[int]bool) for i, b := range buf { @@ -154,7 +148,6 @@ func (d *decoder) readBools(n int) (map[int]bool, map[int]bool, error) { boolsM[i] = true } } - return bools, boolsM, nil } @@ -164,7 +157,6 @@ func (d *decoder) readNums(n, w int) (map[int]int, map[int]bool, error) { if err != nil { return nil, nil, err } - // process nums, numsM := make(map[int]int), make(map[int]bool) for i := 0; i < n; i++ { @@ -173,7 +165,6 @@ func (d *decoder) readNums(n, w int) (map[int]int, map[int]bool, error) { numsM[i] = true } } - return nums, numsM, nil } @@ -184,16 +175,13 @@ func (d *decoder) readStringTable(n, sz int) ([][]byte, []int, error) { if err != nil { return nil, nil, err } - // read string data table data, err := d.readBytes(sz) if err != nil { return nil, nil, err } - // align d.pos += d.pos % 2 - // process s := make([][]byte, n) var m []int @@ -209,7 +197,6 @@ func (d *decoder) readStringTable(n, sz int) ([][]byte, []int, error) { } } } - return s, m, nil } @@ -220,7 +207,6 @@ func (d *decoder) readStrings(n, sz int) (map[int][]byte, map[int]bool, error) { if err != nil { return nil, nil, err } - strs := make(map[int][]byte) for k, v := range s { if k == AcsChars { @@ -228,39 +214,32 @@ func (d *decoder) readStrings(n, sz int) (map[int][]byte, map[int]bool, error) { } strs[k] = v } - strsM := make(map[int]bool, len(m)) for _, k := range m { strsM[k] = true } - return strs, strsM, nil } // canonicalizeAscChars reorders chars to be unique, in order. // -// see repair_ascc in ncurses-6.0/progs/dump_entry.c +// see repair_ascc in ncurses-6.3/progs/dump_entry.c func canonicalizeAscChars(z []byte) []byte { - var c chars + var c []byte enc := make(map[byte]byte, len(z)/2) for i := 0; i < len(z); i += 2 { if _, ok := enc[z[i]]; !ok { a, b := z[i], z[i+1] - //log.Printf(">>> a: %d %c, b: %d %c", a, a, b, b) + // log.Printf(">>> a: %d %c, b: %d %c", a, a, b, b) c, enc[a] = append(c, b), b } } - sort.Sort(c) - + sort.Slice(c, func(i, j int) bool { + return c[i] < c[j] + }) r := make([]byte, 2*len(c)) for i := 0; i < len(c); i++ { r[i*2], r[i*2+1] = c[i], enc[c[i]] } return r } - -type chars []byte - -func (c chars) Len() int { return len(c) } -func (c chars) Swap(i, j int) { c[i], c[j] = c[j], c[i] } -func (c chars) Less(i, j int) bool { return c[i] < c[j] } diff --git a/vendor/github.com/xo/terminfo/load.go b/vendor/github.com/xo/terminfo/load.go index 9b0b94286..d7cd266cb 100644 --- a/vendor/github.com/xo/terminfo/load.go +++ b/vendor/github.com/xo/terminfo/load.go @@ -23,34 +23,27 @@ func Load(name string) (*Terminfo, error) { if name == "" { return nil, ErrEmptyTermName } - termCache.RLock() ti, ok := termCache.db[name] termCache.RUnlock() - if ok { return ti, nil } - var checkDirs []string - // check $TERMINFO if dir := os.Getenv("TERMINFO"); dir != "" { checkDirs = append(checkDirs, dir) } - // check $HOME/.terminfo u, err := user.Current() if err != nil { return nil, err } checkDirs = append(checkDirs, path.Join(u.HomeDir, ".terminfo")) - // check $TERMINFO_DIRS if dirs := os.Getenv("TERMINFO_DIRS"); dirs != "" { checkDirs = append(checkDirs, strings.Split(dirs, ":")...) } - // check fallback directories checkDirs = append(checkDirs, "/etc/terminfo", "/lib/terminfo", "/usr/share/terminfo") for _, dir := range checkDirs { @@ -61,7 +54,6 @@ func Load(name string) (*Terminfo, error) { return ti, nil } } - return nil, ErrDatabaseDirectoryNotFound } diff --git a/vendor/github.com/xo/terminfo/param.go b/vendor/github.com/xo/terminfo/param.go index e6b8a1bc0..ed4cb86b6 100644 --- a/vendor/github.com/xo/terminfo/param.go +++ b/vendor/github.com/xo/terminfo/param.go @@ -13,25 +13,18 @@ import ( type parametizer struct { // z is the string to parameterize z []byte - // pos is the current position in s. pos int - // nest is the current nest level. nest int - // s is the variable stack. s stack - // skipElse keeps the state of skipping else. skipElse bool - // buf is the result buffer. buf *bytes.Buffer - // params are the parameters to interpolate. params [9]interface{} - // vars are dynamic variables. vars [26]interface{} } @@ -54,19 +47,15 @@ var parametizerPool = sync.Pool{ func newParametizer(z []byte) *parametizer { p := parametizerPool.Get().(*parametizer) p.z = z - return p } // reset resets the parametizer. func (p *parametizer) reset() { p.pos, p.nest = 0, 0 - p.s.reset() p.buf.Reset() - p.params, p.vars = [9]interface{}{}, [26]interface{}{} - parametizerPool.Put(p) } @@ -106,13 +95,11 @@ func (p *parametizer) scanTextFn() stateFn { p.writeFrom(ppos) return nil } - if ch == '%' { p.writeFrom(ppos) p.pos++ return p.scanCodeFn } - p.pos++ } } @@ -122,11 +109,9 @@ func (p *parametizer) scanCodeFn() stateFn { if err != nil { return nil } - switch ch { case '%': p.buf.WriteByte('%') - case ':': // this character is used to avoid interpreting "%-" and "%+" as operators. // the next character is where the format really begins. @@ -136,71 +121,52 @@ func (p *parametizer) scanCodeFn() stateFn { return nil } return p.scanFormatFn - case '#', ' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.': return p.scanFormatFn - case 'o': p.buf.WriteString(strconv.FormatInt(int64(p.s.popInt()), 8)) - case 'd': p.buf.WriteString(strconv.Itoa(p.s.popInt())) - case 'x': p.buf.WriteString(strconv.FormatInt(int64(p.s.popInt()), 16)) - case 'X': p.buf.WriteString(strings.ToUpper(strconv.FormatInt(int64(p.s.popInt()), 16))) - case 's': p.buf.WriteString(p.s.popString()) - case 'c': p.buf.WriteByte(p.s.popByte()) - case 'p': p.pos++ return p.pushParamFn - case 'P': p.pos++ return p.setDsVarFn - case 'g': p.pos++ return p.getDsVarFn - case '\'': p.pos++ ch, err = p.peek() if err != nil { return nil } - p.s.push(ch) - // skip the '\'' p.pos++ - case '{': p.pos++ return p.pushIntfn - case 'l': p.s.push(len(p.s.popString())) - case '+': bi, ai := p.s.popInt(), p.s.popInt() p.s.push(ai + bi) - case '-': bi, ai := p.s.popInt(), p.s.popInt() p.s.push(ai - bi) - case '*': bi, ai := p.s.popInt(), p.s.popInt() p.s.push(ai * bi) - case '/': bi, ai := p.s.popInt(), p.s.popInt() if bi != 0 { @@ -208,7 +174,6 @@ func (p *parametizer) scanCodeFn() stateFn { } else { p.s.push(0) } - case 'm': bi, ai := p.s.popInt(), p.s.popInt() if bi != 0 { @@ -216,101 +181,77 @@ func (p *parametizer) scanCodeFn() stateFn { } else { p.s.push(0) } - case '&': bi, ai := p.s.popInt(), p.s.popInt() p.s.push(ai & bi) - case '|': bi, ai := p.s.popInt(), p.s.popInt() p.s.push(ai | bi) - case '^': bi, ai := p.s.popInt(), p.s.popInt() p.s.push(ai ^ bi) - case '=': bi, ai := p.s.popInt(), p.s.popInt() p.s.push(ai == bi) - case '>': bi, ai := p.s.popInt(), p.s.popInt() p.s.push(ai > bi) - case '<': bi, ai := p.s.popInt(), p.s.popInt() p.s.push(ai < bi) - case 'A': bi, ai := p.s.popBool(), p.s.popBool() p.s.push(ai && bi) - case 'O': bi, ai := p.s.popBool(), p.s.popBool() p.s.push(ai || bi) - case '!': p.s.push(!p.s.popBool()) - case '~': p.s.push(^p.s.popInt()) - case 'i': for i := range p.params[:2] { if n, ok := p.params[i].(int); ok { p.params[i] = n + 1 } } - case '?', ';': - case 't': return p.scanThenFn - case 'e': p.skipElse = true return p.skipTextFn } - p.pos++ - return p.scanTextFn } func (p *parametizer) scanFormatFn() stateFn { // the character was already read, so no need to check the error. ch, _ := p.peek() - // 6 should be the maximum length of a format string, for example "%:-9.9d". f := []byte{'%', ch, 0, 0, 0, 0} - var err error - for { p.pos++ ch, err = p.peek() if err != nil { return nil } - f = append(f, ch) switch ch { case 'o', 'd', 'x', 'X': fmt.Fprintf(p.buf, string(f), p.s.popInt()) break - case 's': fmt.Fprintf(p.buf, string(f), p.s.popString()) break - case 'c': fmt.Fprintf(p.buf, string(f), p.s.popByte()) break } } - p.pos++ - return p.scanTextFn } @@ -319,16 +260,13 @@ func (p *parametizer) pushParamFn() stateFn { if err != nil { return nil } - if ai := int(ch - '1'); ai >= 0 && ai < len(p.params) { p.s.push(p.params[ai]) } else { p.s.push(0) } - // skip the '}' p.pos++ - return p.scanTextFn } @@ -337,7 +275,6 @@ func (p *parametizer) setDsVarFn() stateFn { if err != nil { return nil } - if ch >= 'A' && ch <= 'Z' { staticVars.Lock() staticVars.vars[int(ch-'A')] = p.s.pop() @@ -345,7 +282,6 @@ func (p *parametizer) setDsVarFn() stateFn { } else if ch >= 'a' && ch <= 'z' { p.vars[int(ch-'a')] = p.s.pop() } - p.pos++ return p.scanTextFn } @@ -355,20 +291,16 @@ func (p *parametizer) getDsVarFn() stateFn { if err != nil { return nil } - var a byte if ch >= 'A' && ch <= 'Z' { a = 'A' } else if ch >= 'a' && ch <= 'z' { a = 'a' } - staticVars.Lock() p.s.push(staticVars.vars[int(ch-a)]) staticVars.Unlock() - p.pos++ - return p.scanTextFn } @@ -379,26 +311,21 @@ func (p *parametizer) pushIntfn() stateFn { if err != nil { return nil } - p.pos++ if ch < '0' || ch > '9' { p.s.push(ai) return p.scanTextFn } - ai = (ai * 10) + int(ch-'0') } } func (p *parametizer) scanThenFn() stateFn { p.pos++ - if p.s.popBool() { return p.scanTextFn } - p.skipElse = false - return p.skipTextFn } @@ -408,17 +335,14 @@ func (p *parametizer) skipTextFn() stateFn { if err != nil { return nil } - p.pos++ if ch == '%' { break } } - if p.skipElse { return p.skipElseFn } - return p.skipThenFn } @@ -427,7 +351,6 @@ func (p *parametizer) skipThenFn() stateFn { if err != nil { return nil } - p.pos++ switch ch { case ';': @@ -435,16 +358,13 @@ func (p *parametizer) skipThenFn() stateFn { return p.scanTextFn } p.nest-- - case '?': p.nest++ - case 'e': if p.nest == 0 { return p.scanTextFn } } - return p.skipTextFn } @@ -453,7 +373,6 @@ func (p *parametizer) skipElseFn() stateFn { if err != nil { return nil } - p.pos++ switch ch { case ';': @@ -461,11 +380,9 @@ func (p *parametizer) skipElseFn() stateFn { return p.scanTextFn } p.nest-- - case '?': p.nest++ } - return p.skipTextFn } @@ -473,13 +390,11 @@ func (p *parametizer) skipElseFn() stateFn { func Printf(z []byte, params ...interface{}) string { p := newParametizer(z) defer p.reset() - // make sure we always have 9 parameters -- makes it easier // later to skip checks and its faster for i := 0; i < len(p.params) && i < len(params); i++ { p.params[i] = params[i] } - return p.exec() } diff --git a/vendor/github.com/xo/terminfo/terminfo.go b/vendor/github.com/xo/terminfo/terminfo.go index 8ebbf9599..69e3b6064 100644 --- a/vendor/github.com/xo/terminfo/terminfo.go +++ b/vendor/github.com/xo/terminfo/terminfo.go @@ -1,6 +1,8 @@ // Package terminfo implements reading terminfo files in pure go. package terminfo +//go:generate go run gen.go + import ( "io" "io/ioutil" @@ -20,34 +22,24 @@ func (err Error) Error() string { const ( // ErrInvalidFileSize is the invalid file size error. ErrInvalidFileSize Error = "invalid file size" - // ErrUnexpectedFileEnd is the unexpected file end error. ErrUnexpectedFileEnd Error = "unexpected file end" - // ErrInvalidStringTable is the invalid string table error. ErrInvalidStringTable Error = "invalid string table" - // ErrInvalidMagic is the invalid magic error. ErrInvalidMagic Error = "invalid magic" - // ErrInvalidHeader is the invalid header error. ErrInvalidHeader Error = "invalid header" - // ErrInvalidNames is the invalid names error. ErrInvalidNames Error = "invalid names" - // ErrInvalidExtendedHeader is the invalid extended header error. ErrInvalidExtendedHeader Error = "invalid extended header" - // ErrEmptyTermName is the empty term name error. ErrEmptyTermName Error = "empty term name" - // ErrDatabaseDirectoryNotFound is the database directory not found error. ErrDatabaseDirectoryNotFound Error = "database directory not found" - // ErrFileNotFound is the file not found error. ErrFileNotFound Error = "file not found" - // ErrInvalidTermProgramVersion is the invalid TERM_PROGRAM_VERSION error. ErrInvalidTermProgramVersion Error = "invalid TERM_PROGRAM_VERSION" ) @@ -56,43 +48,30 @@ const ( type Terminfo struct { // File is the original source file. File string - // Names are the provided cap names. Names []string - // Bools are the bool capabilities. Bools map[int]bool - // BoolsM are the missing bool capabilities. BoolsM map[int]bool - // Nums are the num capabilities. Nums map[int]int - // NumsM are the missing num capabilities. NumsM map[int]bool - // Strings are the string capabilities. Strings map[int][]byte - // StringsM are the missing string capabilities. StringsM map[int]bool - // ExtBools are the extended bool capabilities. ExtBools map[int]bool - // ExtBoolsNames is the map of extended bool capabilities to their index. ExtBoolNames map[int][]byte - // ExtNums are the extended num capabilities. ExtNums map[int]int - // ExtNumsNames is the map of extended num capabilities to their index. ExtNumNames map[int][]byte - // ExtStrings are the extended string capabilities. ExtStrings map[int][]byte - // ExtStringsNames is the map of extended string capabilities to their index. ExtStringNames map[int][]byte } @@ -100,75 +79,63 @@ type Terminfo struct { // Decode decodes the terminfo data contained in buf. func Decode(buf []byte) (*Terminfo, error) { var err error - // check max file length if len(buf) >= maxFileLength { return nil, ErrInvalidFileSize } - d := &decoder{ buf: buf, - len: len(buf), + n: len(buf), } - // read header h, err := d.readInts(6, 16) if err != nil { return nil, err } - var numWidth int - // check magic - if h[fieldMagic] == magic { + switch { + case h[fieldMagic] == magic: numWidth = 16 - } else if h[fieldMagic] == magicExtended { + case h[fieldMagic] == magicExtended: numWidth = 32 - } else { + default: return nil, ErrInvalidMagic } - // check header if hasInvalidCaps(h) { return nil, ErrInvalidHeader } - // check remaining length - if d.len-d.pos < capLength(h) { + if d.n-d.pos < capLength(h) { return nil, ErrUnexpectedFileEnd } - // read names names, err := d.readBytes(h[fieldNameSize]) if err != nil { return nil, err } - // check name is terminated properly i := findNull(names, 0) if i == -1 { return nil, ErrInvalidNames } names = names[:i] - // read bool caps bools, boolsM, err := d.readBools(h[fieldBoolCount]) if err != nil { return nil, err } - // read num caps nums, numsM, err := d.readNums(h[fieldNumCount], numWidth) if err != nil { return nil, err } - // read string caps strs, strsM, err := d.readStrings(h[fieldStringCount], h[fieldTableSize]) if err != nil { return nil, err } - ti := &Terminfo{ Names: strings.Split(string(names), "|"), Bools: bools, @@ -178,57 +145,47 @@ func Decode(buf []byte) (*Terminfo, error) { Strings: strs, StringsM: strsM, } - // at the end of file, so no extended caps - if d.pos >= d.len { + if d.pos >= d.n { return ti, nil } - // decode extended header eh, err := d.readInts(5, 16) if err != nil { return nil, err } - // check extended offset field if hasInvalidExtOffset(eh) { return nil, ErrInvalidExtendedHeader } - // check extended cap lengths - if d.len-d.pos != extCapLength(eh, numWidth) { + if d.n-d.pos != extCapLength(eh, numWidth) { return nil, ErrInvalidExtendedHeader } - // read extended bool caps ti.ExtBools, _, err = d.readBools(eh[fieldExtBoolCount]) if err != nil { return nil, err } - // read extended num caps ti.ExtNums, _, err = d.readNums(eh[fieldExtNumCount], numWidth) if err != nil { return nil, err } - // read extended string data table indexes extIndexes, err := d.readInts(eh[fieldExtOffsetCount], 16) if err != nil { return nil, err } - // read string data table extData, err := d.readBytes(eh[fieldExtTableSize]) if err != nil { return nil, err } - // precautionary check that exactly at end of file - if d.pos != d.len { + if d.pos != d.n { return nil, ErrUnexpectedFileEnd } - var last int // read extended string caps ti.ExtStrings, last, err = readStrings(extIndexes, extData, eh[fieldExtStringCount]) @@ -236,28 +193,24 @@ func Decode(buf []byte) (*Terminfo, error) { return nil, err } extIndexes, extData = extIndexes[eh[fieldExtStringCount]:], extData[last:] - // read extended bool names ti.ExtBoolNames, _, err = readStrings(extIndexes, extData, eh[fieldExtBoolCount]) if err != nil { return nil, err } extIndexes = extIndexes[eh[fieldExtBoolCount]:] - // read extended num names ti.ExtNumNames, _, err = readStrings(extIndexes, extData, eh[fieldExtNumCount]) if err != nil { return nil, err } extIndexes = extIndexes[eh[fieldExtNumCount]:] - // read extended string names ti.ExtStringNames, _, err = readStrings(extIndexes, extData, eh[fieldExtStringCount]) if err != nil { return nil, err } - //extIndexes = extIndexes[eh[fieldExtStringCount]:] - + // extIndexes = extIndexes[eh[fieldExtStringCount]:] return ti, nil } @@ -279,23 +232,19 @@ func Open(dir, name string) (*Terminfo, error) { if buf == nil { return nil, ErrFileNotFound } - // decode ti, err := Decode(buf) if err != nil { return nil, err } - // save original file name ti.File = filename - // add to cache termCache.Lock() for _, n := range ti.Names { termCache.db[n] = ti } termCache.Unlock() - return ti, nil } @@ -441,7 +390,6 @@ func (ti *Terminfo) Fprintf(w io.Writer, i int, v ...interface{}) { // them for this terminal. func (ti *Terminfo) Colorf(fg, bg int, str string) string { maxColors := int(ti.Nums[MaxColors]) - // map bright colors to lower versions if the color table only holds 8. if maxColors == 8 { if fg > 7 && fg < 16 { @@ -451,7 +399,6 @@ func (ti *Terminfo) Colorf(fg, bg int, str string) string { bg -= 8 } } - var s string if maxColors > fg && fg >= 0 { s += ti.Printf(SetAForeground, fg) @@ -480,20 +427,17 @@ func (ti *Terminfo) Goto(row, col int) string { // most strings don't need padding, which is good news! return io.WriteString(w, s) } - end := strings.Index(s, ">") if end == -1 { // unterminated... just emit bytes unadulterated. return io.WriteString(w, "$<"+s) } - var c int c, err = io.WriteString(w, s[:start]) if err != nil { return n + c, err } n += c - s = s[start+2:] val := s[:end] s = s[end+1:] @@ -518,13 +462,11 @@ func (ti *Terminfo) Goto(row, col int) string { break } } - z, pad := ((baud/8)/unit)*ms, ti.Strings[PadChar] b := make([]byte, len(pad)*z) for bp := copy(b, pad); bp < len(b); bp *= 2 { copy(b[bp:], b[:bp]) } - if (!ti.Bools[XonXoff] && baud > int(ti.Nums[PaddingBaudRate])) || mandatory { c, err = w.Write(b) if err != nil { @@ -533,6 +475,5 @@ func (ti *Terminfo) Goto(row, col int) string { n += c } } - return n, nil }*/ diff --git a/vendor/go.opentelemetry.io/contrib/.gitattributes b/vendor/go.opentelemetry.io/contrib/.gitattributes new file mode 100644 index 000000000..314766e91 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/.gitattributes @@ -0,0 +1,3 @@ +* text=auto eol=lf +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf diff --git a/vendor/go.opentelemetry.io/contrib/.gitignore b/vendor/go.opentelemetry.io/contrib/.gitignore new file mode 100644 index 000000000..21ab270fe --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/.gitignore @@ -0,0 +1,13 @@ +.DS_Store +Thumbs.db + +.tools/ +.idea/ +.vscode/ +*.iml +*.so +coverage.* +example + +instrumentation/google.golang.org/grpc/otelgrpc/example/server/server +instrumentation/google.golang.org/grpc/otelgrpc/example/client/client \ No newline at end of file diff --git a/vendor/go.opentelemetry.io/contrib/.golangci.yml b/vendor/go.opentelemetry.io/contrib/.golangci.yml new file mode 100644 index 000000000..eb43a1bdd --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/.golangci.yml @@ -0,0 +1,32 @@ +# See https://github.com/golangci/golangci-lint#config-file +run: + issues-exit-code: 1 #Default + tests: true #Default + +linters: + enable: + - misspell + - goimports + - revive + - gofmt + +issues: + exclude-rules: + # helpers in tests often (rightfully) pass a *testing.T as their first argument + - path: _test\.go + text: "context.Context should be the first parameter of a function" + linters: + - revive + # Yes, they are, but it's okay in a test + - path: _test\.go + text: "exported func.*returns unexported type.*which can be annoying to use" + linters: + - revive + +linters-settings: + misspell: + locale: US + #ignore-words: + # - someword + goimports: + local-prefixes: go.opentelemetry.io diff --git a/vendor/go.opentelemetry.io/contrib/CHANGELOG.md b/vendor/go.opentelemetry.io/contrib/CHANGELOG.md new file mode 100644 index 000000000..0481d9c0a --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/CHANGELOG.md @@ -0,0 +1,386 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.0.0/0.25.0] - 2021-10-06 + +- Resource detectors and propagators (with the exception of `go. + opentelemetry.io/contrib/propagators/opencensus`) are now stable and + released at v1.0.0. +- Update dependency on the `go.opentelemetry.io/otel` project to `v1.0.1`. +- Update dependency on `go.opentelemetry.io/otel/metric` to `v0.24.0`. + +## [0.24.0] - 2021-09-21 + +- Update dependency on the `go.opentelemetry.io/otel` project to `v1.0.0`. + +## [0.23.0] - 2021-09-08 + +### Added + +- Add `WithoutSubSpans`, `WithRedactedHeaders`, `WithoutHeaders`, and `WithInsecureHeaders` options for `otelhttptrace.NewClientTrace`. (#879) + +### Changed + +- Split `go.opentelemetry.io/contrib/propagators` module into `b3`, `jaeger`, `ot` modules. (#985) +- `otelmongodb` span attributes, name and span status now conform to specification. (#769) +- Migrated EC2 resource detector support from root module `go.opentelemetry.io/contrib/detectors/aws` to a separate EC2 resource detector module `go.opentelemetry.io/contrib/detectors/aws/ec2` (#1017) +- Add `cloud.provider` and `cloud.platform` to AWS detectors. (#1043) +- `otelhttptrace.NewClientTrace` now redacts known sensitive headers by default. (#879) + +### Fixed + +- Fix span not marked as error in `otelhttp.Transport` when `RoundTrip` fails with an error. (#950) + +## [0.22.0] - 2021-07-26 + +### Added + +- Add the `zpages` span processor. (#894) + +### Changed + +- The `b3.B3` type has been removed. + `b3.New()` and `b3.WithInjectEncoding(encoding)` are added to replace it. (#868) + +### Fixed + +- Fix deadlocks and race conditions in `otelsarama.WrapAsyncProducer`. + The `messaging.message_id` and `messaging.kafka.partition` attributes are now not set if a message was not processed. (#754) (#755) (#881) +- Fix `otelsarama.WrapAsyncProducer` so that the messages from the `Errors` channel contain the original `Metadata`. (#754) + +## [0.21.0] - 2021-06-18 + +### Fixed + +- Dockerfile based examples for `otelgin` and `otelmacaron`. (#767) + +### Changed + +- Supported minimum version of Go bumped from 1.14 to 1.15. (#787) +- EKS Resource Detector now use the Kubernetes Go client to obtain the ConfigMap. (#813) + +### Removed + +- Remove service name from `otelmongodb` configuration and span attributes. (#763) + +## [0.20.0] - 2021-04-23 + +### Changed + +- The `go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo` instrumentation now accepts a `WithCommandAttributeDisabled`, + so the caller can specify whether to opt-out of tracing the mongo command. (#712) +- Upgrade to v0.20.0 of `go.opentelemetry.io/otel`. (#758) +- The B3 and Jaeger propagators now store their debug or deferred state in the context.Context instead of the SpanContext. (#758) + +## [0.19.0] - 2021-03-19 + +### Changed + +- Upgrade to v0.19.0 of `go.opentelemetry.io/otel`. +- Fix Span names created in HTTP Instrumentation package to conform with guidelines. (#757) + +## [0.18.0] - 2021-03-04 + +### Fixed + +- `otelmemcache` no longer sets span status to OK instead of leaving it unset. (#477) +- Fix goroutine leak in gRPC `StreamClientInterceptor`. (#581) + +### Removed + +- Remove service name from `otelmemcache` configuration and span attributes. (#477) + +## [0.17.0] - 2021-02-15 + +### Added + +- Add `ot-tracer` propagator (#562) + +### Changed + +- Rename project default branch from `master` to `main`. + +### Fixed + +- Added failure message for AWS ECS resource detector for better debugging (#568) +- Goroutine leak in gRPC StreamClientInterceptor while streamer returns an error. (#581) + +## [0.16.0] - 2021-01-13 + +### Fixed + +- Fix module path for AWS ECS resource detector (#517) + +## [0.15.1] - 2020-12-14 + +### Added + +- Add registry link check to `Makefile` and pre-release script. (#446) +- A new AWS X-Ray ID Generator (#459) +- Migrate CircleCI jobs to GitHub Actions (#476) +- Add CodeQL GitHub Action (#506) +- Add gosec workflow to GitHub Actions (#507) + +### Fixed + +- Fixes the body replacement in otelhttp to not to mutate a nil body. (#484) + +## [0.15.0] - 2020-12-11 + +### Added + +- A new Amazon EKS resource detector. (#465) +- A new `gcp.CloudRun` detector for detecting resource from a Cloud Run instance. (#455) + +## [0.14.0] - 2020-11-20 + +### Added + +- `otelhttp.{Get,Head,Post,PostForm}` convenience wrappers for their `http` counterparts. (#390) +- The AWS detector now adds the cloud zone, host image ID, host type, and host name to the returned `Resource`. (#410) +- Add Amazon ECS Resource Detector for AWS X-Ray. (#466) +- Add propagator for AWS X-Ray (#462) + +### Changed + +- Add semantic version to `Tracer` / `Meter` created by instrumentation packages `otelsaram`, `otelrestful`, `otelmongo`, `otelhttp` and `otelhttptrace`. (#412) +- Update instrumentation guidelines about tracer / meter semantic version. (#412) +- Replace internal tracer and meter helpers by helpers from `go.opentelemetry.io/otel`. (#414) +- gRPC instrumentation sets span attribute `rpc.grpc.status_code`. (#453) + +## Fixed + +- `/detectors/aws` no longer fails if instance metadata is not available (e.g. not running in AWS) (#401) +- The AWS detector now returns a partial resource and an appropriate error if it encounters an error part way through determining a `Resource` identity. (#410) +- The `host` instrumentation unit test has been updated to not depend on the system it runs on. (#426) + +## [0.13.0] - 2020-10-09 + +## Added + +- A Jaeger propagator. (#375) + +## Changed + +- The `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` package instrumentation no longer accepts a `Tracer` as an argument to the interceptor function. + Instead, a new `WithTracerProvider` option is added to configure the `TracerProvider` used when creating the `Tracer` for the instrumentation. (#373) +- The `go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron` instrumentation now accepts a `TracerProvider` rather than a `Tracer`. (#374) +- Remove `go.opentelemetry.io/otel/sdk` dependency from instrumentation. (#381) +- Use `httpsnoop` in `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` to ensure `http.ResponseWriter` additional interfaces are preserved. (#388) + +### Fixed + +- The `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho.Middleware` no longer sends duplicate errors to the global `ErrorHandler`. (#377, #364) +- The import comment in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` is now correctly quoted. (#379) +- The B3 propagator sets the sample bitmask when the sampling decision is `debug`. (#369) + +## [0.12.0] - 2020-09-25 + +### Changed + +- Replace `WithTracer` with `WithTracerProvider` in the `go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron` instrumentation. (#374) + +### Added + +- Benchmark tests for the gRPC instrumentation. (#296) +- Integration testing for the gRPC instrumentation. (#297) +- Allow custom labels to be added to net/http metrics. (#306) +- Added B3 propagator, moving it out of open.telemetry.io/otel repo. (#344) + +### Changed + +- Unify instrumentation about provider options for `go.mongodb.org/mongo-driver`, `gin-gonic/gin`, `gorilla/mux`, + `labstack/echo`, `emicklei/go-restful`, `bradfitz/gomemcache`, `Shopify/sarama`, `net/http` and `beego`. (#303) +- Update instrumentation guidelines about uniform provider options. Also, update style guide. (#303) +- Make config struct of instrumentation unexported. (#303) +- Instrumentations have been updated to adhere to the [configuration style guide's](https://github.com/open-telemetry/opentelemetry-go/blob/master/CONTRIBUTING.md#config) + updated recommendation to use `newConfig()` instead of `configure()`. (#336) +- A new instrumentation naming scheme is implemented to avoid package name conflicts for instrumented packages while still remaining discoverable. (#359) + - `google.golang.org/grpc` -> `google.golang.org/grpc/otelgrpc` + - `go.mongodb.org/mongo-driver` -> `go.mongodb.org/mongo-driver/mongo/otelmongo` + - `net/http` -> `net/http/otelhttp` + - `net/http/httptrace` -> `net/http/httptrace/otelhttptrace` + - `github.com/labstack/echo` -> `github.com/labstack/echo/otelecho` + - `github.com/bradfitz/gomemcache` -> `github.com/bradfitz/gomemcache/memcache/otelmemcache` + - `github.com/gin-gonic/gin` -> `github.com/gin-gonic/gin/otelgin` + - `github.com/gocql/gocql` -> `github.com/gocql/gocql/otelgocql` + - `github.com/emicklei/go-restful` -> `github.com/emicklei/go-restful/otelrestful` + - `github.com/Shopify/sarama` -> `github.com/Shopify/sarama/otelsarama` + - `github.com/gorilla/mux` -> `github.com/gorilla/mux/otelmux` + - `github.com/astaxie/beego` -> `github.com/astaxie/beego/otelbeego` + - `gopkg.in/macaron.v1` -> `gopkg.in/macaron.v1/otelmacaron` +- Rename `OTelBeegoHandler` to `Handler` in the `go.opentelemetry.io/contrib/instrumentation/github.com/astaxie/beego/otelbeego` package. (#359) + +## [0.11.0] - 2020-08-25 + +### Added + +- Top-level `Version()` and `SemVersion()` functions defining the current version of the contrib package. (#225) +- Instrumentation for the `github.com/astaxie/beego` package. (#200) +- Instrumentation for the `github.com/bradfitz/gomemcache` package. (#204) +- Host metrics instrumentation. (#231) +- Cortex histogram and distribution support. (#237) +- Cortex example project. (#238) +- Cortex HTTP authentication. (#246) + +### Changed + +- Remove service name as a parameter of Sarama instrumentation. (#221) +- Replace `WithTracer` with `WithTracerProvider` in Sarama instrumentation. (#221) +- Switch to use common top-level module `SemVersion()` when creating versioned tracer in `bradfitz/gomemcache`. (#226) +- Use `IntegrationShouldRun` in `gomemcache_test`. (#254) +- Use Go 1.15 for CI builds. (#236) +- Improved configuration for `runtime` instrumentation. (#224) + +### Fixed + +- Update dependabot configuration to include newly added `bradfitz/gomemcache` package. (#226) +- Correct `runtime` instrumentation name. (#241) + +## [0.10.1] - 2020-08-13 + +### Added + +- The `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc` module has been added to replace the instrumentation that had previoiusly existed in the `go.opentelemetry.io/otel/instrumentation/grpctrace` package. (#189) +- Instrumentation for the stdlib `net/http` and `net/http/httptrace` packages. (#190) +- Initial Cortex exporter. (#202, #205, #210, #211, #215) + +### Fixed + +- Bump google.golang.org/grpc from 1.30.0 to 1.31.0. (#166) +- Bump go.mongodb.org/mongo-driver from 1.3.5 to 1.4.0 in /instrumentation/go.mongodb.org/mongo-driver. (#170) +- Bump google.golang.org/grpc in /instrumentation/github.com/gin-gonic/gin. (#173) +- Bump google.golang.org/grpc in /instrumentation/github.com/labstack/echo. (#176) +- Bump google.golang.org/grpc from 1.30.0 to 1.31.0 in /instrumentation/github.com/Shopify/sarama. (#179) +- Bump cloud.google.com/go from 0.61.0 to 0.63.0 in /detectors/gcp. (#181, #199) +- Bump github.com/aws/aws-sdk-go from 1.33.15 to 1.34.1 in /detectors/aws. (#184, #192, #193, #198, #201, #203) +- Bump github.com/golangci/golangci-lint from 1.29.0 to 1.30.0 in /tools. (#186) +- Setup CI to run tests that require external resources (Cassandra and MongoDB). (#191) +- Bump github.com/Shopify/sarama from 1.26.4 to 1.27.0 in /instrumentation/github.com/Shopify/sarama. (#206) + +## [0.10.0] - 2020-07-31 + +This release upgrades its [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.10.0) dependency to v0.10.0 and includes new instrumentation for popular Kafka and Cassandra clients. + +### Added + +- A detector that generate resources from GCE instance. (#132) +- A detector that generate resources from AWS instances. (#139) +- Instrumentation for the Kafka client github.com/Shopify/sarama. (#134, #153) +- Links and status message for mock span in the internal testing library. (#134) +- Instrumentation for the Cassandra client github.com/gocql/gocql. (#137) +- A detector that generate resources from GKE clusters. (#154) + +### Fixed + +- Bump github.com/aws/aws-sdk-go from 1.33.8 to 1.33.15 in /detectors/aws. (#155, #157, #159, #162) +- Bump github.com/golangci/golangci-lint from 1.28.3 to 1.29.0 in /tools. (#146) + +## [0.9.0] - 2020-07-20 + +This release upgrades its [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.9.0) dependency to v0.9.0. + +### Fixed + +- Bump github.com/emicklei/go-restful/v3 from 3.0.0 to 3.2.0 in /instrumentation/github.com/emicklei/go-restful. (#133) +- Update dependabot configuration to correctly check all included packages. (#131) +- Update `RELEASING.md` with correct `tag.sh` command. (#130) + +## [0.8.0] - 2020-07-10 + +This release upgrades its [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.8.0) dependency to v0.8.0, includes minor fixes, and new instrumentation. + +### Added + +- Create this `CHANGELOG.md`. (#114) +- Add `emicklei/go-restful/v3` trace instrumentation. (#115) + +### Changed + +- Update `CONTRIBUTING.md` to ask for updates to `CHANGELOG.md` with each pull request. (#114) +- Move all `github.com` package instrumentation under a `github.com` directory. (#118) + +### Fixed + +- Update README to include information about external instrumentation. + To start, this includes native instrumentation found in the `go-redis/redis` package. (#117) +- Bump github.com/golangci/golangci-lint from 1.27.0 to 1.28.2 in /tools. (#122, #123, #125) +- Bump go.mongodb.org/mongo-driver from 1.3.4 to 1.3.5 in /instrumentation/go.mongodb.org/mongo-driver. (#124) + +## [0.7.0] - 2020-06-29 + +This release upgrades its [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.7.0) dependency to v0.7.0. + +### Added + +- Create `RELEASING.md` instructions. (#101) +- Apply transitive dependabot go.mod updates as part of a new automatic Github workflow. (#94) +- New dependabot integration to automate package upgrades. (#61) +- Add automatic tag generation script for release. (#60) + +### Changed + +- Upgrade Datadog metrics exporter to include Resource tags. (#46) +- Added output validation to Datadog example. (#96) +- Move Macaron package to match layout guidelines. (#92) +- Update top-level README and instrumentation README. (#92) +- Bump google.golang.org/grpc from 1.29.1 to 1.30.0. (#99) +- Bump github.com/golangci/golangci-lint from 1.21.0 to 1.27.0 in /tools. (#77) +- Bump go.mongodb.org/mongo-driver from 1.3.2 to 1.3.4 in /instrumentation/go.mongodb.org/mongo-driver. (#76) +- Bump github.com/stretchr/testify from 1.5.1 to 1.6.1. (#74) +- Bump gopkg.in/macaron.v1 from 1.3.5 to 1.3.9 in /instrumentation/macaron. (#68) +- Bump github.com/gin-gonic/gin from 1.6.2 to 1.6.3 in /instrumentation/gin-gonic/gin. (#73) +- Bump github.com/DataDog/datadog-go from 3.5.0+incompatible to 3.7.2+incompatible in /exporters/metric/datadog. (#78) +- Replaced `internal/trace/http.go` helpers with `api/standard` helpers from otel-go repo. (#112) + +## [0.6.1] - 2020-06-08 + +First official tagged release of `contrib` repository. + +### Added + +- `labstack/echo` trace instrumentation (#42) +- `mongodb` trace instrumentation (#26) +- Go Runtime metrics (#9) +- `gorilla/mux` trace instrumentation (#19) +- `gin-gonic` trace instrumentation (#15) +- `macaron` trace instrumentation (#20) +- `dogstatsd` metrics exporter (#10) +- `datadog` metrics exporter (#22) +- Tags to all modules in repository +- Repository folder structure and automated build (#3) + +### Changes + +- Prefix support for dogstatsd (#34) +- Update Go Runtime package to use batch observer (#44) + +[Unreleased]: https://github.com/open-telemetry/opentelemetry-go-contrib/compare/v0.24.0...HEAD +[0.24.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.24.0 +[0.23.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.23.0 +[0.22.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.22.0 +[0.21.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.21.0 +[0.20.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.20.0 +[0.19.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.19.0 +[0.18.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.18.0 +[0.17.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.17.0 +[0.16.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.16.0 +[0.15.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.15.1 +[0.15.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.15.0 +[0.14.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.14.0 +[0.13.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.13.0 +[0.12.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.12.0 +[0.11.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.11.0 +[0.10.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.10.1 +[0.10.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.10.0 +[0.9.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.9.0 +[0.8.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.8.0 +[0.7.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.7.0 +[0.6.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.6.1 diff --git a/vendor/go.opentelemetry.io/contrib/CODEOWNERS b/vendor/go.opentelemetry.io/contrib/CODEOWNERS new file mode 100644 index 000000000..376de4b97 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/CODEOWNERS @@ -0,0 +1,17 @@ +##################################################### +# +# List of approvers for this repository +# +##################################################### +# +# Learn about membership in OpenTelemetry community: +# https://github.com/open-telemetry/community/blob/main/community-membership.md +# +# +# Learn about CODEOWNERS file format: +# https://help.github.com/en/articles/about-code-owners +# + +* @jmacd @MrAlias @Aneurysm9 @evantorrie @XSAM @dashpole @paivagustavo @MadVikingGod @pellared + +CODEOWNERS @MrAlias @Aneurysm9 diff --git a/vendor/go.opentelemetry.io/contrib/CONTRIBUTING.md b/vendor/go.opentelemetry.io/contrib/CONTRIBUTING.md new file mode 100644 index 000000000..38e4f2e64 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/CONTRIBUTING.md @@ -0,0 +1,137 @@ +# Contributing to opentelemetry-go-contrib + +The Go special interest group (SIG) meets regularly. See the +OpenTelemetry +[community](https://github.com/open-telemetry/community#golang-sdk) +repo for information on this and other language SIGs. + +See the [public meeting +notes](https://docs.google.com/document/d/1A63zSWX0x2CyCK_LoNhmQC4rqhLpYXJzXbEPDUQ2n6w/edit#heading=h.9tngw7jdwd6b) +for a summary description of past meetings. To request edit access, +join the meeting or get in touch on +[Slack](https://cloud-native.slack.com/archives/C01NPAXACKT). + +## Development + +There are some generated files checked into the repo. To make sure +that the generated files are up-to-date, run `make` (or `make +precommit` - the `precommit` target is the default). + +The `precommit` target also fixes the formatting of the code and +checks the status of the go module files. + +If after running `make precommit` the output of `git status` contains +`nothing to commit, working tree clean` then it means that everything +is up-to-date and properly formatted. + +## Pull Requests + +### How to Send Pull Requests + +Everyone is welcome to contribute code to `opentelemetry-go-contrib` via +GitHub pull requests (PRs). + +To create a new PR, fork the project in GitHub and clone the upstream +repo: + +```sh +$ git clone https://github.com/open-telemetry/opentelemetry-go-contrib +``` +This would put the project in the `opentelemetry-go-contrib` directory in +current working directory. + +Enter the newly created directory and add your fork as a new remote: + +```sh +$ git remote add git@github.com:/opentelemetry-go +``` + +Check out a new branch, make modifications, run linters and tests, update +`CHANGELOG.md` and push the branch to your fork: + +```sh +$ git checkout -b +# edit files +# update changelog +$ make precommit +$ git add -p +$ git commit +$ git push +``` + +Open a pull request against the main `opentelemetry-go-contrib` repo. Be sure to add the pull +request ID to the entry you added to `CHANGELOG.md`. + +### How to Receive Comments + +* If the PR is not ready for review, please put `[WIP]` in the title, + tag it as `work-in-progress`, or mark it as + [`draft`](https://github.blog/2019-02-14-introducing-draft-pull-requests/). +* Make sure CLA is signed and CI is clear. + +### How to Get PRs Merged + +A PR is considered to be **ready to merge** when: + +* It has received two approvals from Approvers/Maintainers (at + different companies). +* Feedback has been addressed. +* Any substantive changes to your PR will require that you clear any prior + Approval reviews, this includes changes resulting from other feedback. Unless + the approver explicitly stated that their approval will persist across + changes it should be assumed that the PR needs their review again. Other + project members (e.g. approvers, maintainers) can help with this if there are + any questions or if you forget to clear reviews. +* It has been open for review for at least one working day. This gives + people reasonable time to review. +* Trivial change (typo, cosmetic, doc, etc.) doesn't have to wait for + one day. +* `CHANGELOG.md` has been updated to reflect what has been + added, changed, removed, or fixed. +* Urgent fix can take exception as long as it has been actively + communicated. + +Any Maintainer can merge the PR once it is **ready to merge**. + +## Style Guide + +* Make sure to run `make precommit` - this will find and fix the code + formatting. +* Check [opentelemetry-go Style Guide](https://github.com/open-telemetry/opentelemetry-go/blob/main/CONTRIBUTING.md#style-guide) + +## Adding a new Contrib package + +To add a new contrib package follow an existing one. An empty Sample instrumentation +provides base structure with an example and a test. Each contrib package +should be its own module. A contrib package may contain more than one go package. + +### Folder Structure +- instrumentation/\ (**Common**) +- instrumentation/\/trace (**specific to trace**) +- instrumentation/\/metrics (**specific to metrics**) + +#### Example +- instrumentation/gorm/trace +- instrumentation/kafka/metrics + +## Approvers and Maintainers + +Approvers: + +- [Evan Torrie](https://github.com/evantorrie), Verizon Media +- [Josh MacDonald](https://github.com/jmacd), LightStep +- [Sam Xie](https://github.com/XSAM) +- [David Ashpole](https://github.com/dashpole), Google +- [Gustavo Silva Paiva](https://github.com/paivagustavo), LightStep +- [Aaron Clawson](https://github.com/MadVikingGod) +- [Robert Pająk](https://github.com/pellared), Splunk + +Maintainers: + +- [Anthony Mirabella](https://github.com/Aneurysm9), Centene +- [Tyler Yahn](https://github.com/MrAlias), New Relic + +### Become an Approver or a Maintainer + +See the [community membership document in OpenTelemetry community +repo](https://github.com/open-telemetry/community/blob/main/community-membership.md). diff --git a/vendor/go.opentelemetry.io/contrib/LICENSE b/vendor/go.opentelemetry.io/contrib/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/LICENSE @@ -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. diff --git a/vendor/go.opentelemetry.io/contrib/Makefile b/vendor/go.opentelemetry.io/contrib/Makefile new file mode 100644 index 000000000..5d6197381 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/Makefile @@ -0,0 +1,225 @@ +TOOLS_MOD_DIR := ./tools + +# All source code and documents. Used in spell check. +ALL_DOCS := $(shell find . -name '*.md' -type f | sort) +# All directories with go.mod files related to opentelemetry library. Used for building, testing and linting. +ALL_GO_MOD_DIRS := $(filter-out $(TOOLS_MOD_DIR), $(shell find . -type f -name 'go.mod' -exec dirname {} \; | sort)) +ALL_COVERAGE_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | egrep -v '^./example|^$(TOOLS_MOD_DIR)' | sort) + +# URLs to check if all contrib entries exist in the registry. +REGISTRY_BASE_URL = https://raw.githubusercontent.com/open-telemetry/opentelemetry.io/main/content/en/registry +CONTRIB_REPO_URL = https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main + +GO = go +GOTEST_MIN = go test -v -timeout 30s +GOTEST = $(GOTEST_MIN) -race +GOTEST_WITH_COVERAGE = $(GOTEST) -coverprofile=coverage.out -covermode=atomic + +.DEFAULT_GOAL := precommit + +.PHONY: precommit + +TOOLS_DIR := $(abspath ./.tools) + +$(TOOLS_DIR)/golangci-lint: $(TOOLS_MOD_DIR)/go.mod $(TOOLS_MOD_DIR)/go.sum $(TOOLS_MOD_DIR)/tools.go + cd $(TOOLS_MOD_DIR) && \ + go build -o $(TOOLS_DIR)/golangci-lint github.com/golangci/golangci-lint/cmd/golangci-lint + +$(TOOLS_DIR)/misspell: $(TOOLS_MOD_DIR)/go.mod $(TOOLS_MOD_DIR)/go.sum $(TOOLS_MOD_DIR)/tools.go + cd $(TOOLS_MOD_DIR) && \ + go build -o $(TOOLS_DIR)/misspell github.com/client9/misspell/cmd/misspell + +$(TOOLS_DIR)/gocovmerge: $(TOOLS_MOD_DIR)/go.mod $(TOOLS_MOD_DIR)/go.sum $(TOOLS_MOD_DIR)/tools.go + cd $(TOOLS_MOD_DIR) && \ + go build -o $(TOOLS_DIR)/gocovmerge github.com/wadey/gocovmerge + +$(TOOLS_DIR)/stringer: $(TOOLS_MOD_DIR)/go.mod $(TOOLS_MOD_DIR)/go.sum $(TOOLS_MOD_DIR)/tools.go + cd $(TOOLS_MOD_DIR) && \ + go build -o $(TOOLS_DIR)/stringer golang.org/x/tools/cmd/stringer + +MULTIMOD=$(TOOLS_DIR)/multimod +$(TOOLS_DIR)/multimod: $(TOOLS_MOD_DIR)/go.mod $(TOOLS_MOD_DIR)/go.sum $(TOOLS_MOD_DIR)/tools.go + cd $(TOOLS_MOD_DIR) && \ + go build -o $(TOOLS_DIR)/multimod go.opentelemetry.io/build-tools/multimod + +precommit: dependabot-check license-check generate lint build test + +.PHONY: test-with-coverage +test-with-coverage: $(TOOLS_DIR)/gocovmerge + set -e; \ + printf "" > coverage.txt; \ + for dir in $(ALL_COVERAGE_MOD_DIRS); do \ + CMD="$(GOTEST_WITH_COVERAGE)"; \ + echo "$$dir" | \ + grep -q 'test$$' && \ + CMD="$$CMD -coverpkg=go.opentelemetry.io/contrib/$$( dirname "$$dir" | sed -e "s/^\.\///g" )/..."; \ + echo "$$CMD $$dir/..."; \ + (cd "$$dir" && \ + $$CMD ./... && \ + go tool cover -html=coverage.out -o coverage.html); \ + done; \ + $(TOOLS_DIR)/gocovmerge $$(find . -name coverage.out) > coverage.txt + +.PHONY: ci +ci: precommit check-clean-work-tree test-with-coverage + +.PHONY: test-gocql +test-gocql: + @if ./tools/should_build.sh gocql; then \ + set -e; \ + docker run --name cass-integ --rm -p 9042:9042 -d cassandra:3; \ + CMD=cassandra IMG_NAME=cass-integ ./tools/wait.sh; \ + (cd instrumentation/github.com/gocql/gocql/otelgocql/test/ && \ + $(GOTEST_WITH_COVERAGE) -coverpkg=go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql/otelgocql/... ./... && \ + go tool cover -html=coverage.out -o coverage.html); \ + cp ./instrumentation/github.com/gocql/gocql/otelgocql/test/coverage.out ./; \ + docker stop cass-integ; \ + fi + +.PHONY: test-mongo-driver +test-mongo-driver: + @if ./tools/should_build.sh mongo-driver; then \ + set -e; \ + docker run --name mongo-integ --rm -p 27017:27017 -d mongo; \ + CMD=mongo IMG_NAME=mongo-integ ./tools/wait.sh; \ + (cd instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test && \ + $(GOTEST_WITH_COVERAGE) -coverpkg=go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/... ./... && \ + go tool cover -html=coverage.out -o coverage.html); \ + cp ./instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test/coverage.out ./; \ + docker stop mongo-integ; \ + fi + +.PHONY: test-gomemcache +test-gomemcache: + @if ./tools/should_build.sh gomemcache; then \ + set -e; \ + docker run --name gomemcache-integ --rm -p 11211:11211 -d memcached; \ + CMD=gomemcache IMG_NAME=gomemcache-integ ./tools/wait.sh; \ + (cd instrumentation/github.com/bradfitz/gomemcache/memcache/otelmemcache/test && \ + $(GOTEST_WITH_COVERAGE) -coverpkg=go.opentelemetry.io/contrib/instrumentation/github.com/bradfitz/gomemcache/memcache/otelmemcache/... ./... && \ + go tool cover -html=coverage.out -o coverage.html); \ + docker stop gomemcache-integ ; \ + cp ./instrumentation/github.com/bradfitz/gomemcache/memcache/otelmemcache/test/coverage.out ./; \ + fi + +.PHONY: check-clean-work-tree +check-clean-work-tree: + @if ! git diff --quiet; then \ + echo; \ + echo 'Working tree is not clean, did you forget to run "make precommit"?'; \ + echo; \ + git status; \ + exit 1; \ + fi + +.PHONY: build +build: + # TODO: Fix this on windows. + set -e; for dir in $(ALL_GO_MOD_DIRS); do \ + echo "compiling all packages in $${dir}"; \ + (cd "$${dir}" && \ + go build ./... && \ + go test -run xxxxxMatchNothingxxxxx ./... >/dev/null); \ + done + +.PHONY: test +test: + set -e; for dir in $(ALL_GO_MOD_DIRS); do \ + echo "go test ./... + race in $${dir}"; \ + (cd "$${dir}" && \ + $(GOTEST) ./...); \ + done + +.PHONY: test-short +test-short: + set -e; for dir in $(ALL_GO_MOD_DIRS); do \ + echo "go test ./... + race in $${dir}"; \ + (cd "$${dir}" && \ + $(GOTEST_MIN) -short ./...); \ + done + +.PHONY: lint +lint: $(TOOLS_DIR)/golangci-lint $(TOOLS_DIR)/misspell lint-modules + set -e; for dir in $(ALL_GO_MOD_DIRS); do \ + echo "golangci-lint in $${dir}"; \ + (cd "$${dir}" && \ + $(TOOLS_DIR)/golangci-lint run --fix && \ + $(TOOLS_DIR)/golangci-lint run); \ + done + $(TOOLS_DIR)/misspell -w $(ALL_DOCS) + +.PHONY: lint-modules +lint-modules: + set -e; for dir in $(ALL_GO_MOD_DIRS) $(TOOLS_MOD_DIR); do \ + echo "$(GO) mod tidy in $${dir}"; \ + (cd "$${dir}" && \ + $(GO) mod tidy); \ + done + +.PHONY: generate +generate: $(TOOLS_DIR)/stringer + PATH="$(TOOLS_DIR):$${PATH}" go generate ./... + +.PHONY: license-check +license-check: + @licRes=$$(for f in $$(find . -type f \( -iname '*.go' -o -iname '*.sh' \) ! -path './vendor/*' ! -path './exporters/otlp/internal/opentelemetry-proto/*') ; do \ + awk '/Copyright The OpenTelemetry Authors|generated|GENERATED/ && NR<=3 { found=1; next } END { if (!found) print FILENAME }' $$f; \ + done); \ + if [ -n "$${licRes}" ]; then \ + echo "license header checking failed:"; echo "$${licRes}"; \ + exit 1; \ + fi + +.PHONY: registry-links-check +registry-links-check: + @checkRes=$$( \ + for f in $$( find ./instrumentation ./exporters ./detectors ! -path './instrumentation/net/*' -type f -name 'go.mod' -exec dirname {} \; | egrep -v '/example|/utils' | sort ) \ + ./instrumentation/net/http; do \ + TYPE="instrumentation"; \ + if $$(echo "$$f" | grep -q "exporters"); then \ + TYPE="exporter"; \ + fi; \ + if $$(echo "$$f" | grep -q "detectors"); then \ + TYPE="detector"; \ + fi; \ + NAME=$$(echo "$$f" | sed -e 's/.*\///' -e 's/.*otel//'); \ + LINK=$(CONTRIB_REPO_URL)/$$(echo "$$f" | sed -e 's/..//' -e 's/\/otel.*$$//'); \ + if ! $$(curl -s $(REGISTRY_BASE_URL)/$${TYPE}-go-$${NAME}.md | grep -q "$${LINK}"); then \ + echo "$$f"; \ + fi \ + done; \ + ); \ + if [ -n "$$checkRes" ]; then \ + echo "WARNING: registry link check failed for the following packages:"; echo "$${checkRes}"; \ + fi + +.PHONY: dependabot-check +dependabot-check: + @result=$$( \ + for f in $$( find . -type f -name go.mod -exec dirname {} \; | sed 's/^.\/\?/\//' ); \ + do grep -q "$$f" .github/dependabot.yml \ + || echo "$$f"; \ + done; \ + ); \ + if [ -n "$$result" ]; then \ + echo "missing go.mod dependabot check:"; echo "$$result"; \ + exit 1; \ + fi + +COREPATH ?= "../opentelemetry-go" +.PHONY: sync-core +sync-core: | $(MULTIMOD) + @[ ! -d $COREPATH ] || ( echo ">> Path to core repository must be set in COREPATH and must exist"; exit 1 ) + $(MULTIMOD) verify && $(MULTIMOD) sync -a -o ${COREPATH} + + +.PHONY: prerelease +prerelease: | $(MULTIMOD) + @[ "${MODSET}" ] || ( echo ">> env var MODSET is not set"; exit 1 ) + $(MULTIMOD) verify && $(MULTIMOD) prerelease -m ${MODSET} + +COMMIT ?= "HEAD" +.PHONY: add-tags +add-tags: | $(MULTIMOD) + @[ "${MODSET}" ] || ( echo ">> env var MODSET is not set"; exit 1 ) + $(MULTIMOD) verify && $(MULTIMOD) tag -m ${MODSET} -c ${COMMIT} diff --git a/vendor/go.opentelemetry.io/contrib/README.md b/vendor/go.opentelemetry.io/contrib/README.md new file mode 100644 index 000000000..5bc581352 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/README.md @@ -0,0 +1,40 @@ +# OpenTelemetry-Go Contrib + +[![build_and_test](https://github.com/open-telemetry/opentelemetry-go-contrib/workflows/build_and_test/badge.svg)](https://github.com/open-telemetry/opentelemetry-go-contrib/actions?query=workflow%3Abuild_and_test+branch%3Amain) +[![codecov.io](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/coverage.svg?branch=main)](https://app.codecov.io/gh/open-telemetry/opentelemetry-go-contrib?branch=main) +[![Docs](https://godoc.org/go.opentelemetry.io/contrib?status.svg)](https://pkg.go.dev/go.opentelemetry.io/contrib) +[![Go Report Card](https://goreportcard.com/badge/go.opentelemetry.io/contrib)](https://goreportcard.com/report/go.opentelemetry.io/contrib) +[![Slack](https://img.shields.io/badge/slack-@cncf/otel--go-brightgreen.svg?logo=slack)](https://cloud-native.slack.com/archives/C01NPAXACKT) + +Collection of 3rd-party instrumentation and exporters for [OpenTelemetry-Go](https://github.com/open-telemetry/opentelemetry-go). + +## Contents + +- [Instrumentation](./instrumentation/): Packages providing OpenTelemetry instrumentation for 3rd-party libraries. +- [Exporters](./exporters/): Packages providing OpenTelemetry exporters for 3rd-party telemetry systems. +- [Propagators](./propagators/): Packages providing OpenTelemetry context propagators for 3rd-party propagation formats. +- [Detectors](./detectors/): Packages providing OpenTelemetry resource detectors for 3rd-party cloud computing environments. + +## Project Status + +This project is currently in a pre-GA phase. Our progress towards a GA release +candidate is tracked in [this project +board](https://github.com/orgs/open-telemetry/projects/5). + +### Compatibility + +This project is tested on the following systems. + +| OS | Go Version | Architecture | +| ------- | ---------- | ------------ | +| Ubuntu | 1.16 | amd64 | +| Ubuntu | 1.15 | amd64 | +| Ubuntu | 1.16 | 386 | +| Ubuntu | 1.15 | 386 | + +While this project should work for other systems, no compatibility guarantees +are made for those systems currently. + +## Contributing + +For information on how to contribute, consult [the contributing guidelines](./CONTRIBUTING.md) diff --git a/vendor/go.opentelemetry.io/contrib/RELEASING.md b/vendor/go.opentelemetry.io/contrib/RELEASING.md new file mode 100644 index 000000000..f7fd359c4 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/RELEASING.md @@ -0,0 +1,96 @@ +# Release Process + +There are two types of release for the `go.opentelemetry.io/contrib` repo +and submodules. + +1. **Case 1** A release due to changes independent of the +`go.opentelemetry.io/otel` module, e.g. perhaps a critical bug fix in +one of the contrib modules. + +2. **Case 2** A release due to a breaking API change in +`go.opentelemetry.io/otel` which all modules in this repo +depend on. + +## Pre-Release + +Update go.mod for submodules to depend on the upcoming new release of +the module in this repo, `go.opentelemetry.io/contrib`. Decide on the +next version of the semantic tag to apply to the contrib +module based on whether you fall into Case 1 or Case 2. + +### Case 1 + +If the changes are all internal to this repo, then the new tag will +most often be a patch or minor version upgrade to the existing tag on +this module. Let's call this ``. + +### Case 2 + +If a new release is required due to breaking changes in +`go.opentelemetry.io/otel`, then the new semantic tag for this repo +should be bumped to match the `go.opentelemetry.io/otel` new tag. +Let's call this ``. The script checks that +`go.opentelemetry.io/otel@v` is a valid tag, so you need +to wait until that tag has been pushed in the main repo. + +In nearly all cases, `` should be the same as +``. + +1. Run `pre_release.sh` script to create a branch `pre_release_`. + The script will also run `go mod tidy` and `make ci`. + + * **Case 1** `./pre_release.sh -t ` + * **Case 2** `./pre_release.sh -o [-t ]` + +2. If you used `-o ` to rewrite the modules to depend on + a new version of `go.opentelemetry.io/otel`, there will likely be + breaking changes that require fixes to the files in this + `contrib` repo. Make the appropriate fixes to address any API + breaks and run through the + + ``` + git commit -m "fixes due to API changes" + make precommit + ``` + + cycle until everything passes + +4. Push the changes to upstream. + + ``` + git diff main + git push + ``` + +5. Create a PR on github and merge the PR once approved. + + +### Tag +Now create a `` on the commit hash of the changes made in pre-release step, + +1. Run the tag.sh script. + + ``` + ./tag.sh + ``` +2. Push tags upstream. Make sure you push upstream for all the sub-module tags as well. + + ``` + git push upstream + git push upstream + ... + ``` + +## Release +Now create a release for the new `` on github. +The release body should include all the release notes in the Changelog for this release. +Additionally, the `tag.sh` script generates commit logs since last release which can be used to suppliment the release notes. + + + + + + + + + diff --git a/vendor/go.opentelemetry.io/contrib/doc.go b/vendor/go.opentelemetry.io/contrib/doc.go new file mode 100644 index 000000000..6c55ff6a2 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/doc.go @@ -0,0 +1,20 @@ +// Copyright The OpenTelemetry 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. + +// This package provides all of its functionality through its +// submodules. The submodules in the exporters directory provide +// implementations for trace and metric exporters for third-party +// collectors, and submodules in the instrumentation directory provide the +// instrumentation for the popular go libraries. +package contrib diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/LICENSE b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/LICENSE @@ -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. diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/client.go b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/client.go new file mode 100644 index 000000000..92b8cf73c --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/client.go @@ -0,0 +1,61 @@ +// Copyright The OpenTelemetry 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 otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + +import ( + "context" + "io" + "net/http" + "net/url" + "strings" +) + +// DefaultClient is the default Client and is used by Get, Head, Post and PostForm. +// Please be careful of intitialization order - for example, if you change +// the global propagator, the DefaultClient might still be using the old one. +var DefaultClient = &http.Client{Transport: NewTransport(http.DefaultTransport)} + +// Get is a convenient replacement for http.Get that adds a span around the request. +func Get(ctx context.Context, targetURL string) (resp *http.Response, err error) { + req, err := http.NewRequestWithContext(ctx, "GET", targetURL, nil) + if err != nil { + return nil, err + } + return DefaultClient.Do(req) +} + +// Head is a convenient replacement for http.Head that adds a span around the request. +func Head(ctx context.Context, targetURL string) (resp *http.Response, err error) { + req, err := http.NewRequestWithContext(ctx, "HEAD", targetURL, nil) + if err != nil { + return nil, err + } + return DefaultClient.Do(req) +} + +// Post is a convenient replacement for http.Post that adds a span around the request. +func Post(ctx context.Context, targetURL, contentType string, body io.Reader) (resp *http.Response, err error) { + req, err := http.NewRequestWithContext(ctx, "POST", targetURL, body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", contentType) + return DefaultClient.Do(req) +} + +// PostForm is a convenient replacement for http.PostForm that adds a span around the request. +func PostForm(ctx context.Context, targetURL string, data url.Values) (resp *http.Response, err error) { + return Post(ctx, targetURL, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) +} diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/common.go b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/common.go new file mode 100644 index 000000000..303e5505e --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/common.go @@ -0,0 +1,46 @@ +// Copyright The OpenTelemetry 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 otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + +import ( + "net/http" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +// Attribute keys that can be added to a span. +const ( + ReadBytesKey = attribute.Key("http.read_bytes") // if anything was read from the request body, the total number of bytes read + ReadErrorKey = attribute.Key("http.read_error") // If an error occurred while reading a request, the string of the error (io.EOF is not recorded) + WroteBytesKey = attribute.Key("http.wrote_bytes") // if anything was written to the response writer, the total number of bytes written + WriteErrorKey = attribute.Key("http.write_error") // if an error occurred while writing a reply, the string of the error (io.EOF is not recorded) +) + +// Server HTTP metrics. +const ( + RequestCount = "http.server.request_count" // Incoming request count total + RequestContentLength = "http.server.request_content_length" // Incoming request bytes total + ResponseContentLength = "http.server.response_content_length" // Incoming response bytes total + ServerLatency = "http.server.duration" // Incoming end to end duration, microseconds +) + +// Filter is a predicate used to determine whether a given http.request should +// be traced. A Filter must return true if the request should be traced. +type Filter func(*http.Request) bool + +func newTracer(tp trace.TracerProvider) trace.Tracer { + return tp.Tracer(instrumentationName, trace.WithInstrumentationVersion(Version())) +} diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/config.go b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/config.go new file mode 100644 index 000000000..e4fa1b8d9 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/config.go @@ -0,0 +1,208 @@ +// Copyright The OpenTelemetry 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 otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + +import ( + "context" + "net/http" + "net/http/httptrace" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" +) + +const ( + instrumentationName = "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" +) + +// config represents the configuration options available for the http.Handler +// and http.Transport types. +type config struct { + ServerName string + Tracer trace.Tracer + Meter metric.Meter + Propagators propagation.TextMapPropagator + SpanStartOptions []trace.SpanStartOption + PublicEndpoint bool + PublicEndpointFn func(*http.Request) bool + ReadEvent bool + WriteEvent bool + Filters []Filter + SpanNameFormatter func(string, *http.Request) string + ClientTrace func(context.Context) *httptrace.ClientTrace + + TracerProvider trace.TracerProvider + MeterProvider metric.MeterProvider +} + +// Option interface used for setting optional config properties. +type Option interface { + apply(*config) +} + +type optionFunc func(*config) + +func (o optionFunc) apply(c *config) { + o(c) +} + +// newConfig creates a new config struct and applies opts to it. +func newConfig(opts ...Option) *config { + c := &config{ + Propagators: otel.GetTextMapPropagator(), + MeterProvider: otel.GetMeterProvider(), + } + for _, opt := range opts { + opt.apply(c) + } + + // Tracer is only initialized if manually specified. Otherwise, can be passed with the tracing context. + if c.TracerProvider != nil { + c.Tracer = newTracer(c.TracerProvider) + } + + c.Meter = c.MeterProvider.Meter( + instrumentationName, + metric.WithInstrumentationVersion(Version()), + ) + + return c +} + +// WithTracerProvider specifies a tracer provider to use for creating a tracer. +// If none is specified, the global provider is used. +func WithTracerProvider(provider trace.TracerProvider) Option { + return optionFunc(func(cfg *config) { + if provider != nil { + cfg.TracerProvider = provider + } + }) +} + +// WithMeterProvider specifies a meter provider to use for creating a meter. +// If none is specified, the global provider is used. +func WithMeterProvider(provider metric.MeterProvider) Option { + return optionFunc(func(cfg *config) { + if provider != nil { + cfg.MeterProvider = provider + } + }) +} + +// WithPublicEndpoint configures the Handler to link the span with an incoming +// span context. If this option is not provided, then the association is a child +// association instead of a link. +func WithPublicEndpoint() Option { + return optionFunc(func(c *config) { + c.PublicEndpoint = true + }) +} + +// WithPublicEndpointFn runs with every request, and allows conditionnally +// configuring the Handler to link the span with an incoming span context. If +// this option is not provided or returns false, then the association is a +// child association instead of a link. +// Note: WithPublicEndpoint takes precedence over WithPublicEndpointFn. +func WithPublicEndpointFn(fn func(*http.Request) bool) Option { + return optionFunc(func(c *config) { + c.PublicEndpointFn = fn + }) +} + +// WithPropagators configures specific propagators. If this +// option isn't specified, then the global TextMapPropagator is used. +func WithPropagators(ps propagation.TextMapPropagator) Option { + return optionFunc(func(c *config) { + if ps != nil { + c.Propagators = ps + } + }) +} + +// WithSpanOptions configures an additional set of +// trace.SpanOptions, which are applied to each new span. +func WithSpanOptions(opts ...trace.SpanStartOption) Option { + return optionFunc(func(c *config) { + c.SpanStartOptions = append(c.SpanStartOptions, opts...) + }) +} + +// WithFilter adds a filter to the list of filters used by the handler. +// If any filter indicates to exclude a request then the request will not be +// traced. All filters must allow a request to be traced for a Span to be created. +// If no filters are provided then all requests are traced. +// Filters will be invoked for each processed request, it is advised to make them +// simple and fast. +func WithFilter(f Filter) Option { + return optionFunc(func(c *config) { + c.Filters = append(c.Filters, f) + }) +} + +type event int + +// Different types of events that can be recorded, see WithMessageEvents. +const ( + ReadEvents event = iota + WriteEvents +) + +// WithMessageEvents configures the Handler to record the specified events +// (span.AddEvent) on spans. By default only summary attributes are added at the +// end of the request. +// +// Valid events are: +// - ReadEvents: Record the number of bytes read after every http.Request.Body.Read +// using the ReadBytesKey +// - WriteEvents: Record the number of bytes written after every http.ResponeWriter.Write +// using the WriteBytesKey +func WithMessageEvents(events ...event) Option { + return optionFunc(func(c *config) { + for _, e := range events { + switch e { + case ReadEvents: + c.ReadEvent = true + case WriteEvents: + c.WriteEvent = true + } + } + }) +} + +// WithSpanNameFormatter takes a function that will be called on every +// request and the returned string will become the Span Name. +func WithSpanNameFormatter(f func(operation string, r *http.Request) string) Option { + return optionFunc(func(c *config) { + c.SpanNameFormatter = f + }) +} + +// WithClientTrace takes a function that returns client trace instance that will be +// applied to the requests sent through the otelhttp Transport. +func WithClientTrace(f func(context.Context) *httptrace.ClientTrace) Option { + return optionFunc(func(c *config) { + c.ClientTrace = f + }) +} + +// WithServerName returns an Option that sets the name of the (virtual) server +// handling requests. +func WithServerName(server string) Option { + return optionFunc(func(c *config) { + c.ServerName = server + }) +} diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/doc.go b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/doc.go new file mode 100644 index 000000000..38c7f01c7 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/doc.go @@ -0,0 +1,18 @@ +// Copyright The OpenTelemetry 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 otelhttp provides an http.Handler and functions that are intended +// to be used to add tracing by wrapping existing handlers (with Handler) and +// routes WithRouteTag. +package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/handler.go b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/handler.go new file mode 100644 index 000000000..f103aa627 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/handler.go @@ -0,0 +1,269 @@ +// Copyright The OpenTelemetry 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 otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + +import ( + "io" + "net/http" + "time" + + "github.com/felixge/httpsnoop" + + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/propagation" + semconv "go.opentelemetry.io/otel/semconv/v1.17.0" + "go.opentelemetry.io/otel/trace" +) + +// middleware is an http middleware which wraps the next handler in a span. +type middleware struct { + operation string + server string + + tracer trace.Tracer + meter metric.Meter + propagators propagation.TextMapPropagator + spanStartOptions []trace.SpanStartOption + readEvent bool + writeEvent bool + filters []Filter + spanNameFormatter func(string, *http.Request) string + counters map[string]metric.Int64Counter + valueRecorders map[string]metric.Float64Histogram + publicEndpoint bool + publicEndpointFn func(*http.Request) bool +} + +func defaultHandlerFormatter(operation string, _ *http.Request) string { + return operation +} + +// NewHandler wraps the passed handler in a span named after the operation and +// enriches it with metrics. +func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler { + return NewMiddleware(operation, opts...)(handler) +} + +// NewMiddleware returns a tracing and metrics instrumentation middleware. +// The handler returned by the middleware wraps a handler +// in a span named after the operation and enriches it with metrics. +func NewMiddleware(operation string, opts ...Option) func(http.Handler) http.Handler { + h := middleware{ + operation: operation, + } + + defaultOpts := []Option{ + WithSpanOptions(trace.WithSpanKind(trace.SpanKindServer)), + WithSpanNameFormatter(defaultHandlerFormatter), + } + + c := newConfig(append(defaultOpts, opts...)...) + h.configure(c) + h.createMeasures() + + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + h.serveHTTP(w, r, next) + }) + } +} + +func (h *middleware) configure(c *config) { + h.tracer = c.Tracer + h.meter = c.Meter + h.propagators = c.Propagators + h.spanStartOptions = c.SpanStartOptions + h.readEvent = c.ReadEvent + h.writeEvent = c.WriteEvent + h.filters = c.Filters + h.spanNameFormatter = c.SpanNameFormatter + h.publicEndpoint = c.PublicEndpoint + h.publicEndpointFn = c.PublicEndpointFn + h.server = c.ServerName +} + +func handleErr(err error) { + if err != nil { + otel.Handle(err) + } +} + +func (h *middleware) createMeasures() { + h.counters = make(map[string]metric.Int64Counter) + h.valueRecorders = make(map[string]metric.Float64Histogram) + + requestBytesCounter, err := h.meter.Int64Counter(RequestContentLength) + handleErr(err) + + responseBytesCounter, err := h.meter.Int64Counter(ResponseContentLength) + handleErr(err) + + serverLatencyMeasure, err := h.meter.Float64Histogram(ServerLatency) + handleErr(err) + + h.counters[RequestContentLength] = requestBytesCounter + h.counters[ResponseContentLength] = responseBytesCounter + h.valueRecorders[ServerLatency] = serverLatencyMeasure +} + +// serveHTTP sets up tracing and calls the given next http.Handler with the span +// context injected into the request context. +func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http.Handler) { + requestStartTime := time.Now() + for _, f := range h.filters { + if !f(r) { + // Simply pass through to the handler if a filter rejects the request + next.ServeHTTP(w, r) + return + } + } + + ctx := h.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header)) + opts := []trace.SpanStartOption{ + trace.WithAttributes(semconvutil.HTTPServerRequest(h.server, r)...), + } + if h.server != "" { + hostAttr := semconv.NetHostName(h.server) + opts = append(opts, trace.WithAttributes(hostAttr)) + } + opts = append(opts, h.spanStartOptions...) + if h.publicEndpoint || (h.publicEndpointFn != nil && h.publicEndpointFn(r.WithContext(ctx))) { + opts = append(opts, trace.WithNewRoot()) + // Linking incoming span context if any for public endpoint. + if s := trace.SpanContextFromContext(ctx); s.IsValid() && s.IsRemote() { + opts = append(opts, trace.WithLinks(trace.Link{SpanContext: s})) + } + } + + tracer := h.tracer + + if tracer == nil { + if span := trace.SpanFromContext(r.Context()); span.SpanContext().IsValid() { + tracer = newTracer(span.TracerProvider()) + } else { + tracer = newTracer(otel.GetTracerProvider()) + } + } + + ctx, span := tracer.Start(ctx, h.spanNameFormatter(h.operation, r), opts...) + defer span.End() + + readRecordFunc := func(int64) {} + if h.readEvent { + readRecordFunc = func(n int64) { + span.AddEvent("read", trace.WithAttributes(ReadBytesKey.Int64(n))) + } + } + + var bw bodyWrapper + // if request body is nil or NoBody, we don't want to mutate the body as it + // will affect the identity of it in an unforeseeable way because we assert + // ReadCloser fulfills a certain interface and it is indeed nil or NoBody. + if r.Body != nil && r.Body != http.NoBody { + bw.ReadCloser = r.Body + bw.record = readRecordFunc + r.Body = &bw + } + + writeRecordFunc := func(int64) {} + if h.writeEvent { + writeRecordFunc = func(n int64) { + span.AddEvent("write", trace.WithAttributes(WroteBytesKey.Int64(n))) + } + } + + rww := &respWriterWrapper{ + ResponseWriter: w, + record: writeRecordFunc, + ctx: ctx, + props: h.propagators, + statusCode: http.StatusOK, // default status code in case the Handler doesn't write anything + } + + // Wrap w to use our ResponseWriter methods while also exposing + // other interfaces that w may implement (http.CloseNotifier, + // http.Flusher, http.Hijacker, http.Pusher, io.ReaderFrom). + + w = httpsnoop.Wrap(w, httpsnoop.Hooks{ + Header: func(httpsnoop.HeaderFunc) httpsnoop.HeaderFunc { + return rww.Header + }, + Write: func(httpsnoop.WriteFunc) httpsnoop.WriteFunc { + return rww.Write + }, + WriteHeader: func(httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc { + return rww.WriteHeader + }, + }) + + labeler := &Labeler{} + ctx = injectLabeler(ctx, labeler) + + next.ServeHTTP(w, r.WithContext(ctx)) + + setAfterServeAttributes(span, bw.read, rww.written, rww.statusCode, bw.err, rww.err) + + // Add metrics + attributes := append(labeler.Get(), semconvutil.HTTPServerRequest(h.server, r)...) + if rww.statusCode > 0 { + attributes = append(attributes, semconv.HTTPStatusCode(rww.statusCode)) + } + o := metric.WithAttributes(attributes...) + h.counters[RequestContentLength].Add(ctx, bw.read, o) + h.counters[ResponseContentLength].Add(ctx, rww.written, o) + + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond) + + h.valueRecorders[ServerLatency].Record(ctx, elapsedTime, o) +} + +func setAfterServeAttributes(span trace.Span, read, wrote int64, statusCode int, rerr, werr error) { + attributes := []attribute.KeyValue{} + + // TODO: Consider adding an event after each read and write, possibly as an + // option (defaulting to off), so as to not create needlessly verbose spans. + if read > 0 { + attributes = append(attributes, ReadBytesKey.Int64(read)) + } + if rerr != nil && rerr != io.EOF { + attributes = append(attributes, ReadErrorKey.String(rerr.Error())) + } + if wrote > 0 { + attributes = append(attributes, WroteBytesKey.Int64(wrote)) + } + if statusCode > 0 { + attributes = append(attributes, semconv.HTTPStatusCode(statusCode)) + } + span.SetStatus(semconvutil.HTTPServerStatus(statusCode)) + + if werr != nil && werr != io.EOF { + attributes = append(attributes, WriteErrorKey.String(werr.Error())) + } + span.SetAttributes(attributes...) +} + +// WithRouteTag annotates a span with the provided route name using the +// RouteKey Tag. +func WithRouteTag(route string, h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + span := trace.SpanFromContext(r.Context()) + span.SetAttributes(semconv.HTTPRoute(route)) + h.ServeHTTP(w, r) + }) +} diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil/gen.go b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil/gen.go new file mode 100644 index 000000000..edf4ce3d3 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil/gen.go @@ -0,0 +1,21 @@ +// Copyright The OpenTelemetry 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 semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil" + +// Generate semconvutil package: +//go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/httpconv_test.go.tmpl "--data={}" --out=httpconv_test.go +//go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/httpconv.go.tmpl "--data={}" --out=httpconv.go +//go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/netconv_test.go.tmpl "--data={}" --out=netconv_test.go +//go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/netconv.go.tmpl "--data={}" --out=netconv.go diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv.go b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv.go new file mode 100644 index 000000000..9bab232e3 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv.go @@ -0,0 +1,460 @@ +// Code created by gotmpl. DO NOT MODIFY. +// source: internal/shared/semconvutil/httpconv.go.tmpl + +// Copyright The OpenTelemetry 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 semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil" + +import ( + "fmt" + "net/http" + "strings" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + semconv "go.opentelemetry.io/otel/semconv/v1.17.0" +) + +// HTTPClientResponse returns trace attributes for an HTTP response received by a +// client from a server. It will return the following attributes if the related +// values are defined in resp: "http.status.code", +// "http.response_content_length". +// +// This does not add all OpenTelemetry required attributes for an HTTP event, +// it assumes ClientRequest was used to create the span with a complete set of +// attributes. If a complete set of attributes can be generated using the +// request contained in resp. For example: +// +// append(HTTPClientResponse(resp), ClientRequest(resp.Request)...) +func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { + return hc.ClientResponse(resp) +} + +// HTTPClientRequest returns trace attributes for an HTTP request made by a client. +// The following attributes are always returned: "http.url", "http.flavor", +// "http.method", "net.peer.name". The following attributes are returned if the +// related values are defined in req: "net.peer.port", "http.user_agent", +// "http.request_content_length", "enduser.id". +func HTTPClientRequest(req *http.Request) []attribute.KeyValue { + return hc.ClientRequest(req) +} + +// HTTPClientStatus returns a span status code and message for an HTTP status code +// value received by a client. +func HTTPClientStatus(code int) (codes.Code, string) { + return hc.ClientStatus(code) +} + +// HTTPServerRequest returns trace attributes for an HTTP request received by a +// server. +// +// The server must be the primary server name if it is known. For example this +// would be the ServerName directive +// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache +// server, and the server_name directive +// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an +// nginx server. More generically, the primary server name would be the host +// header value that matches the default virtual host of an HTTP server. It +// should include the host identifier and if a port is used to route to the +// server that port identifier should be included as an appropriate port +// suffix. +// +// If the primary server name is not known, server should be an empty string. +// The req Host will be used to determine the server instead. +// +// The following attributes are always returned: "http.method", "http.scheme", +// "http.flavor", "http.target", "net.host.name". The following attributes are +// returned if they related values are defined in req: "net.host.port", +// "net.sock.peer.addr", "net.sock.peer.port", "http.user_agent", "enduser.id", +// "http.client_ip". +func HTTPServerRequest(server string, req *http.Request) []attribute.KeyValue { + return hc.ServerRequest(server, req) +} + +// HTTPServerStatus returns a span status code and message for an HTTP status code +// value returned by a server. Status codes in the 400-499 range are not +// returned as errors. +func HTTPServerStatus(code int) (codes.Code, string) { + return hc.ServerStatus(code) +} + +// HTTPRequestHeader returns the contents of h as attributes. +// +// Instrumentation should require an explicit configuration of which headers to +// captured and then prune what they pass here. Including all headers can be a +// security risk - explicit configuration helps avoid leaking sensitive +// information. +// +// The User-Agent header is already captured in the http.user_agent attribute +// from ClientRequest and ServerRequest. Instrumentation may provide an option +// to capture that header here even though it is not recommended. Otherwise, +// instrumentation should filter that out of what is passed. +func HTTPRequestHeader(h http.Header) []attribute.KeyValue { + return hc.RequestHeader(h) +} + +// HTTPResponseHeader returns the contents of h as attributes. +// +// Instrumentation should require an explicit configuration of which headers to +// captured and then prune what they pass here. Including all headers can be a +// security risk - explicit configuration helps avoid leaking sensitive +// information. +// +// The User-Agent header is already captured in the http.user_agent attribute +// from ClientRequest and ServerRequest. Instrumentation may provide an option +// to capture that header here even though it is not recommended. Otherwise, +// instrumentation should filter that out of what is passed. +func HTTPResponseHeader(h http.Header) []attribute.KeyValue { + return hc.ResponseHeader(h) +} + +// httpConv are the HTTP semantic convention attributes defined for a version +// of the OpenTelemetry specification. +type httpConv struct { + NetConv *netConv + + EnduserIDKey attribute.Key + HTTPClientIPKey attribute.Key + HTTPFlavorKey attribute.Key + HTTPMethodKey attribute.Key + HTTPRequestContentLengthKey attribute.Key + HTTPResponseContentLengthKey attribute.Key + HTTPRouteKey attribute.Key + HTTPSchemeHTTP attribute.KeyValue + HTTPSchemeHTTPS attribute.KeyValue + HTTPStatusCodeKey attribute.Key + HTTPTargetKey attribute.Key + HTTPURLKey attribute.Key + HTTPUserAgentKey attribute.Key +} + +var hc = &httpConv{ + NetConv: nc, + + EnduserIDKey: semconv.EnduserIDKey, + HTTPClientIPKey: semconv.HTTPClientIPKey, + HTTPFlavorKey: semconv.HTTPFlavorKey, + HTTPMethodKey: semconv.HTTPMethodKey, + HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey, + HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey, + HTTPRouteKey: semconv.HTTPRouteKey, + HTTPSchemeHTTP: semconv.HTTPSchemeHTTP, + HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS, + HTTPStatusCodeKey: semconv.HTTPStatusCodeKey, + HTTPTargetKey: semconv.HTTPTargetKey, + HTTPURLKey: semconv.HTTPURLKey, + HTTPUserAgentKey: semconv.HTTPUserAgentKey, +} + +// ClientResponse returns attributes for an HTTP response received by a client +// from a server. The following attributes are returned if the related values +// are defined in resp: "http.status.code", "http.response_content_length". +// +// This does not add all OpenTelemetry required attributes for an HTTP event, +// it assumes ClientRequest was used to create the span with a complete set of +// attributes. If a complete set of attributes can be generated using the +// request contained in resp. For example: +// +// append(ClientResponse(resp), ClientRequest(resp.Request)...) +func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { + var n int + if resp.StatusCode > 0 { + n++ + } + if resp.ContentLength > 0 { + n++ + } + + attrs := make([]attribute.KeyValue, 0, n) + if resp.StatusCode > 0 { + attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) + } + if resp.ContentLength > 0 { + attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength))) + } + return attrs +} + +// ClientRequest returns attributes for an HTTP request made by a client. The +// following attributes are always returned: "http.url", "http.flavor", +// "http.method", "net.peer.name". The following attributes are returned if the +// related values are defined in req: "net.peer.port", "http.user_agent", +// "http.request_content_length", "enduser.id". +func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue { + n := 3 // URL, peer name, proto, and method. + var h string + if req.URL != nil { + h = req.URL.Host + } + peer, p := firstHostPort(h, req.Header.Get("Host")) + port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) + if port > 0 { + n++ + } + useragent := req.UserAgent() + if useragent != "" { + n++ + } + if req.ContentLength > 0 { + n++ + } + userID, _, hasUserID := req.BasicAuth() + if hasUserID { + n++ + } + attrs := make([]attribute.KeyValue, 0, n) + + attrs = append(attrs, c.method(req.Method)) + attrs = append(attrs, c.proto(req.Proto)) + + var u string + if req.URL != nil { + // Remove any username/password info that may be in the URL. + userinfo := req.URL.User + req.URL.User = nil + u = req.URL.String() + // Restore any username/password info that was removed. + req.URL.User = userinfo + } + attrs = append(attrs, c.HTTPURLKey.String(u)) + + attrs = append(attrs, c.NetConv.PeerName(peer)) + if port > 0 { + attrs = append(attrs, c.NetConv.PeerPort(port)) + } + + if useragent != "" { + attrs = append(attrs, c.HTTPUserAgentKey.String(useragent)) + } + + if l := req.ContentLength; l > 0 { + attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l)) + } + + if hasUserID { + attrs = append(attrs, c.EnduserIDKey.String(userID)) + } + + return attrs +} + +// ServerRequest returns attributes for an HTTP request received by a server. +// +// The server must be the primary server name if it is known. For example this +// would be the ServerName directive +// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache +// server, and the server_name directive +// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an +// nginx server. More generically, the primary server name would be the host +// header value that matches the default virtual host of an HTTP server. It +// should include the host identifier and if a port is used to route to the +// server that port identifier should be included as an appropriate port +// suffix. +// +// If the primary server name is not known, server should be an empty string. +// The req Host will be used to determine the server instead. +// +// The following attributes are always returned: "http.method", "http.scheme", +// "http.flavor", "http.target", "net.host.name". The following attributes are +// returned if they related values are defined in req: "net.host.port", +// "net.sock.peer.addr", "net.sock.peer.port", "http.user_agent", "enduser.id", +// "http.client_ip". +func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.KeyValue { + // TODO: This currently does not add the specification required + // `http.target` attribute. It has too high of a cardinality to safely be + // added. An alternate should be added, or this comment removed, when it is + // addressed by the specification. If it is ultimately decided to continue + // not including the attribute, the HTTPTargetKey field of the httpConv + // should be removed as well. + + n := 4 // Method, scheme, proto, and host name. + var host string + var p int + if server == "" { + host, p = splitHostPort(req.Host) + } else { + // Prioritize the primary server name. + host, p = splitHostPort(server) + if p < 0 { + _, p = splitHostPort(req.Host) + } + } + hostPort := requiredHTTPPort(req.TLS != nil, p) + if hostPort > 0 { + n++ + } + peer, peerPort := splitHostPort(req.RemoteAddr) + if peer != "" { + n++ + if peerPort > 0 { + n++ + } + } + useragent := req.UserAgent() + if useragent != "" { + n++ + } + userID, _, hasUserID := req.BasicAuth() + if hasUserID { + n++ + } + clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) + if clientIP != "" { + n++ + } + attrs := make([]attribute.KeyValue, 0, n) + + attrs = append(attrs, c.method(req.Method)) + attrs = append(attrs, c.scheme(req.TLS != nil)) + attrs = append(attrs, c.proto(req.Proto)) + attrs = append(attrs, c.NetConv.HostName(host)) + + if hostPort > 0 { + attrs = append(attrs, c.NetConv.HostPort(hostPort)) + } + + if peer != "" { + // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a + // file-path that would be interpreted with a sock family. + attrs = append(attrs, c.NetConv.SockPeerAddr(peer)) + if peerPort > 0 { + attrs = append(attrs, c.NetConv.SockPeerPort(peerPort)) + } + } + + if useragent != "" { + attrs = append(attrs, c.HTTPUserAgentKey.String(useragent)) + } + + if hasUserID { + attrs = append(attrs, c.EnduserIDKey.String(userID)) + } + + if clientIP != "" { + attrs = append(attrs, c.HTTPClientIPKey.String(clientIP)) + } + + return attrs +} + +func (c *httpConv) method(method string) attribute.KeyValue { + if method == "" { + return c.HTTPMethodKey.String(http.MethodGet) + } + return c.HTTPMethodKey.String(method) +} + +func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive + if https { + return c.HTTPSchemeHTTPS + } + return c.HTTPSchemeHTTP +} + +func (c *httpConv) proto(proto string) attribute.KeyValue { + switch proto { + case "HTTP/1.0": + return c.HTTPFlavorKey.String("1.0") + case "HTTP/1.1": + return c.HTTPFlavorKey.String("1.1") + case "HTTP/2": + return c.HTTPFlavorKey.String("2.0") + case "HTTP/3": + return c.HTTPFlavorKey.String("3.0") + default: + return c.HTTPFlavorKey.String(proto) + } +} + +func serverClientIP(xForwardedFor string) string { + if idx := strings.Index(xForwardedFor, ","); idx >= 0 { + xForwardedFor = xForwardedFor[:idx] + } + return xForwardedFor +} + +func requiredHTTPPort(https bool, port int) int { // nolint:revive + if https { + if port > 0 && port != 443 { + return port + } + } else { + if port > 0 && port != 80 { + return port + } + } + return -1 +} + +// Return the request host and port from the first non-empty source. +func firstHostPort(source ...string) (host string, port int) { + for _, hostport := range source { + host, port = splitHostPort(hostport) + if host != "" || port > 0 { + break + } + } + return +} + +// RequestHeader returns the contents of h as OpenTelemetry attributes. +func (c *httpConv) RequestHeader(h http.Header) []attribute.KeyValue { + return c.header("http.request.header", h) +} + +// ResponseHeader returns the contents of h as OpenTelemetry attributes. +func (c *httpConv) ResponseHeader(h http.Header) []attribute.KeyValue { + return c.header("http.response.header", h) +} + +func (c *httpConv) header(prefix string, h http.Header) []attribute.KeyValue { + key := func(k string) attribute.Key { + k = strings.ToLower(k) + k = strings.ReplaceAll(k, "-", "_") + k = fmt.Sprintf("%s.%s", prefix, k) + return attribute.Key(k) + } + + attrs := make([]attribute.KeyValue, 0, len(h)) + for k, v := range h { + attrs = append(attrs, key(k).StringSlice(v)) + } + return attrs +} + +// ClientStatus returns a span status code and message for an HTTP status code +// value received by a client. +func (c *httpConv) ClientStatus(code int) (codes.Code, string) { + if code < 100 || code >= 600 { + return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) + } + if code >= 400 { + return codes.Error, "" + } + return codes.Unset, "" +} + +// ServerStatus returns a span status code and message for an HTTP status code +// value returned by a server. Status codes in the 400-499 range are not +// returned as errors. +func (c *httpConv) ServerStatus(code int) (codes.Code, string) { + if code < 100 || code >= 600 { + return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) + } + if code >= 500 { + return codes.Error, "" + } + return codes.Unset, "" +} diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil/netconv.go b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil/netconv.go new file mode 100644 index 000000000..bde889343 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil/netconv.go @@ -0,0 +1,368 @@ +// Code created by gotmpl. DO NOT MODIFY. +// source: internal/shared/semconvutil/netconv.go.tmpl + +// Copyright The OpenTelemetry 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 semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil" + +import ( + "net" + "strconv" + "strings" + + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.17.0" +) + +// NetTransport returns a trace attribute describing the transport protocol of the +// passed network. See the net.Dial for information about acceptable network +// values. +func NetTransport(network string) attribute.KeyValue { + return nc.Transport(network) +} + +// NetClient returns trace attributes for a client network connection to address. +// See net.Dial for information about acceptable address values, address should +// be the same as the one used to create conn. If conn is nil, only network +// peer attributes will be returned that describe address. Otherwise, the +// socket level information about conn will also be included. +func NetClient(address string, conn net.Conn) []attribute.KeyValue { + return nc.Client(address, conn) +} + +// NetServer returns trace attributes for a network listener listening at address. +// See net.Listen for information about acceptable address values, address +// should be the same as the one used to create ln. If ln is nil, only network +// host attributes will be returned that describe address. Otherwise, the +// socket level information about ln will also be included. +func NetServer(address string, ln net.Listener) []attribute.KeyValue { + return nc.Server(address, ln) +} + +// netConv are the network semantic convention attributes defined for a version +// of the OpenTelemetry specification. +type netConv struct { + NetHostNameKey attribute.Key + NetHostPortKey attribute.Key + NetPeerNameKey attribute.Key + NetPeerPortKey attribute.Key + NetSockFamilyKey attribute.Key + NetSockPeerAddrKey attribute.Key + NetSockPeerPortKey attribute.Key + NetSockHostAddrKey attribute.Key + NetSockHostPortKey attribute.Key + NetTransportOther attribute.KeyValue + NetTransportTCP attribute.KeyValue + NetTransportUDP attribute.KeyValue + NetTransportInProc attribute.KeyValue +} + +var nc = &netConv{ + NetHostNameKey: semconv.NetHostNameKey, + NetHostPortKey: semconv.NetHostPortKey, + NetPeerNameKey: semconv.NetPeerNameKey, + NetPeerPortKey: semconv.NetPeerPortKey, + NetSockFamilyKey: semconv.NetSockFamilyKey, + NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, + NetSockPeerPortKey: semconv.NetSockPeerPortKey, + NetSockHostAddrKey: semconv.NetSockHostAddrKey, + NetSockHostPortKey: semconv.NetSockHostPortKey, + NetTransportOther: semconv.NetTransportOther, + NetTransportTCP: semconv.NetTransportTCP, + NetTransportUDP: semconv.NetTransportUDP, + NetTransportInProc: semconv.NetTransportInProc, +} + +func (c *netConv) Transport(network string) attribute.KeyValue { + switch network { + case "tcp", "tcp4", "tcp6": + return c.NetTransportTCP + case "udp", "udp4", "udp6": + return c.NetTransportUDP + case "unix", "unixgram", "unixpacket": + return c.NetTransportInProc + default: + // "ip:*", "ip4:*", and "ip6:*" all are considered other. + return c.NetTransportOther + } +} + +// Host returns attributes for a network host address. +func (c *netConv) Host(address string) []attribute.KeyValue { + h, p := splitHostPort(address) + var n int + if h != "" { + n++ + if p > 0 { + n++ + } + } + + if n == 0 { + return nil + } + + attrs := make([]attribute.KeyValue, 0, n) + attrs = append(attrs, c.HostName(h)) + if p > 0 { + attrs = append(attrs, c.HostPort(int(p))) + } + return attrs +} + +// Server returns attributes for a network listener listening at address. See +// net.Listen for information about acceptable address values, address should +// be the same as the one used to create ln. If ln is nil, only network host +// attributes will be returned that describe address. Otherwise, the socket +// level information about ln will also be included. +func (c *netConv) Server(address string, ln net.Listener) []attribute.KeyValue { + if ln == nil { + return c.Host(address) + } + + lAddr := ln.Addr() + if lAddr == nil { + return c.Host(address) + } + + hostName, hostPort := splitHostPort(address) + sockHostAddr, sockHostPort := splitHostPort(lAddr.String()) + network := lAddr.Network() + sockFamily := family(network, sockHostAddr) + + n := nonZeroStr(hostName, network, sockHostAddr, sockFamily) + n += positiveInt(hostPort, sockHostPort) + attr := make([]attribute.KeyValue, 0, n) + if hostName != "" { + attr = append(attr, c.HostName(hostName)) + if hostPort > 0 { + // Only if net.host.name is set should net.host.port be. + attr = append(attr, c.HostPort(hostPort)) + } + } + if network != "" { + attr = append(attr, c.Transport(network)) + } + if sockFamily != "" { + attr = append(attr, c.NetSockFamilyKey.String(sockFamily)) + } + if sockHostAddr != "" { + attr = append(attr, c.NetSockHostAddrKey.String(sockHostAddr)) + if sockHostPort > 0 { + // Only if net.sock.host.addr is set should net.sock.host.port be. + attr = append(attr, c.NetSockHostPortKey.Int(sockHostPort)) + } + } + return attr +} + +func (c *netConv) HostName(name string) attribute.KeyValue { + return c.NetHostNameKey.String(name) +} + +func (c *netConv) HostPort(port int) attribute.KeyValue { + return c.NetHostPortKey.Int(port) +} + +// Client returns attributes for a client network connection to address. See +// net.Dial for information about acceptable address values, address should be +// the same as the one used to create conn. If conn is nil, only network peer +// attributes will be returned that describe address. Otherwise, the socket +// level information about conn will also be included. +func (c *netConv) Client(address string, conn net.Conn) []attribute.KeyValue { + if conn == nil { + return c.Peer(address) + } + + lAddr, rAddr := conn.LocalAddr(), conn.RemoteAddr() + + var network string + switch { + case lAddr != nil: + network = lAddr.Network() + case rAddr != nil: + network = rAddr.Network() + default: + return c.Peer(address) + } + + peerName, peerPort := splitHostPort(address) + var ( + sockFamily string + sockPeerAddr string + sockPeerPort int + sockHostAddr string + sockHostPort int + ) + + if lAddr != nil { + sockHostAddr, sockHostPort = splitHostPort(lAddr.String()) + } + + if rAddr != nil { + sockPeerAddr, sockPeerPort = splitHostPort(rAddr.String()) + } + + switch { + case sockHostAddr != "": + sockFamily = family(network, sockHostAddr) + case sockPeerAddr != "": + sockFamily = family(network, sockPeerAddr) + } + + n := nonZeroStr(peerName, network, sockPeerAddr, sockHostAddr, sockFamily) + n += positiveInt(peerPort, sockPeerPort, sockHostPort) + attr := make([]attribute.KeyValue, 0, n) + if peerName != "" { + attr = append(attr, c.PeerName(peerName)) + if peerPort > 0 { + // Only if net.peer.name is set should net.peer.port be. + attr = append(attr, c.PeerPort(peerPort)) + } + } + if network != "" { + attr = append(attr, c.Transport(network)) + } + if sockFamily != "" { + attr = append(attr, c.NetSockFamilyKey.String(sockFamily)) + } + if sockPeerAddr != "" { + attr = append(attr, c.NetSockPeerAddrKey.String(sockPeerAddr)) + if sockPeerPort > 0 { + // Only if net.sock.peer.addr is set should net.sock.peer.port be. + attr = append(attr, c.NetSockPeerPortKey.Int(sockPeerPort)) + } + } + if sockHostAddr != "" { + attr = append(attr, c.NetSockHostAddrKey.String(sockHostAddr)) + if sockHostPort > 0 { + // Only if net.sock.host.addr is set should net.sock.host.port be. + attr = append(attr, c.NetSockHostPortKey.Int(sockHostPort)) + } + } + return attr +} + +func family(network, address string) string { + switch network { + case "unix", "unixgram", "unixpacket": + return "unix" + default: + if ip := net.ParseIP(address); ip != nil { + if ip.To4() == nil { + return "inet6" + } + return "inet" + } + } + return "" +} + +func nonZeroStr(strs ...string) int { + var n int + for _, str := range strs { + if str != "" { + n++ + } + } + return n +} + +func positiveInt(ints ...int) int { + var n int + for _, i := range ints { + if i > 0 { + n++ + } + } + return n +} + +// Peer returns attributes for a network peer address. +func (c *netConv) Peer(address string) []attribute.KeyValue { + h, p := splitHostPort(address) + var n int + if h != "" { + n++ + if p > 0 { + n++ + } + } + + if n == 0 { + return nil + } + + attrs := make([]attribute.KeyValue, 0, n) + attrs = append(attrs, c.PeerName(h)) + if p > 0 { + attrs = append(attrs, c.PeerPort(int(p))) + } + return attrs +} + +func (c *netConv) PeerName(name string) attribute.KeyValue { + return c.NetPeerNameKey.String(name) +} + +func (c *netConv) PeerPort(port int) attribute.KeyValue { + return c.NetPeerPortKey.Int(port) +} + +func (c *netConv) SockPeerAddr(addr string) attribute.KeyValue { + return c.NetSockPeerAddrKey.String(addr) +} + +func (c *netConv) SockPeerPort(port int) attribute.KeyValue { + return c.NetSockPeerPortKey.Int(port) +} + +// splitHostPort splits a network address hostport of the form "host", +// "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", +// "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and +// port. +// +// An empty host is returned if it is not provided or unparsable. A negative +// port is returned if it is not provided or unparsable. +func splitHostPort(hostport string) (host string, port int) { + port = -1 + + if strings.HasPrefix(hostport, "[") { + addrEnd := strings.LastIndex(hostport, "]") + if addrEnd < 0 { + // Invalid hostport. + return + } + if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 { + host = hostport[1:addrEnd] + return + } + } else { + if i := strings.LastIndex(hostport, ":"); i < 0 { + host = hostport + return + } + } + + host, pStr, err := net.SplitHostPort(hostport) + if err != nil { + return + } + + p, err := strconv.ParseUint(pStr, 10, 16) + if err != nil { + return + } + return host, int(p) +} diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/labeler.go b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/labeler.go new file mode 100644 index 000000000..26a51a180 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/labeler.go @@ -0,0 +1,65 @@ +// Copyright The OpenTelemetry 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 otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + +import ( + "context" + "sync" + + "go.opentelemetry.io/otel/attribute" +) + +// Labeler is used to allow instrumented HTTP handlers to add custom attributes to +// the metrics recorded by the net/http instrumentation. +type Labeler struct { + mu sync.Mutex + attributes []attribute.KeyValue +} + +// Add attributes to a Labeler. +func (l *Labeler) Add(ls ...attribute.KeyValue) { + l.mu.Lock() + defer l.mu.Unlock() + l.attributes = append(l.attributes, ls...) +} + +// Get returns a copy of the attributes added to the Labeler. +func (l *Labeler) Get() []attribute.KeyValue { + l.mu.Lock() + defer l.mu.Unlock() + ret := make([]attribute.KeyValue, len(l.attributes)) + copy(ret, l.attributes) + return ret +} + +type labelerContextKeyType int + +const lablelerContextKey labelerContextKeyType = 0 + +func injectLabeler(ctx context.Context, l *Labeler) context.Context { + return context.WithValue(ctx, lablelerContextKey, l) +} + +// LabelerFromContext retrieves a Labeler instance from the provided context if +// one is available. If no Labeler was found in the provided context a new, empty +// Labeler is returned and the second return value is false. In this case it is +// safe to use the Labeler but any attributes added to it will not be used. +func LabelerFromContext(ctx context.Context) (*Labeler, bool) { + l, ok := ctx.Value(lablelerContextKey).(*Labeler) + if !ok { + l = &Labeler{} + } + return l, ok +} diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/transport.go b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/transport.go new file mode 100644 index 000000000..24c8cb264 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/transport.go @@ -0,0 +1,193 @@ +// Copyright The OpenTelemetry 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 otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + +import ( + "context" + "io" + "net/http" + "net/http/httptrace" + + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" +) + +// Transport implements the http.RoundTripper interface and wraps +// outbound HTTP(S) requests with a span. +type Transport struct { + rt http.RoundTripper + + tracer trace.Tracer + propagators propagation.TextMapPropagator + spanStartOptions []trace.SpanStartOption + filters []Filter + spanNameFormatter func(string, *http.Request) string + clientTrace func(context.Context) *httptrace.ClientTrace +} + +var _ http.RoundTripper = &Transport{} + +// NewTransport wraps the provided http.RoundTripper with one that +// starts a span and injects the span context into the outbound request headers. +// +// If the provided http.RoundTripper is nil, http.DefaultTransport will be used +// as the base http.RoundTripper. +func NewTransport(base http.RoundTripper, opts ...Option) *Transport { + if base == nil { + base = http.DefaultTransport + } + + t := Transport{ + rt: base, + } + + defaultOpts := []Option{ + WithSpanOptions(trace.WithSpanKind(trace.SpanKindClient)), + WithSpanNameFormatter(defaultTransportFormatter), + } + + c := newConfig(append(defaultOpts, opts...)...) + t.applyConfig(c) + + return &t +} + +func (t *Transport) applyConfig(c *config) { + t.tracer = c.Tracer + t.propagators = c.Propagators + t.spanStartOptions = c.SpanStartOptions + t.filters = c.Filters + t.spanNameFormatter = c.SpanNameFormatter + t.clientTrace = c.ClientTrace +} + +func defaultTransportFormatter(_ string, r *http.Request) string { + return "HTTP " + r.Method +} + +// RoundTrip creates a Span and propagates its context via the provided request's headers +// before handing the request to the configured base RoundTripper. The created span will +// end when the response body is closed or when a read from the body returns io.EOF. +func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) { + for _, f := range t.filters { + if !f(r) { + // Simply pass through to the base RoundTripper if a filter rejects the request + return t.rt.RoundTrip(r) + } + } + + tracer := t.tracer + + if tracer == nil { + if span := trace.SpanFromContext(r.Context()); span.SpanContext().IsValid() { + tracer = newTracer(span.TracerProvider()) + } else { + tracer = newTracer(otel.GetTracerProvider()) + } + } + + opts := append([]trace.SpanStartOption{}, t.spanStartOptions...) // start with the configured options + + ctx, span := tracer.Start(r.Context(), t.spanNameFormatter("", r), opts...) + + if t.clientTrace != nil { + ctx = httptrace.WithClientTrace(ctx, t.clientTrace(ctx)) + } + + r = r.WithContext(ctx) + span.SetAttributes(semconvutil.HTTPClientRequest(r)...) + t.propagators.Inject(ctx, propagation.HeaderCarrier(r.Header)) + + res, err := t.rt.RoundTrip(r) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + span.End() + return res, err + } + + span.SetAttributes(semconvutil.HTTPClientResponse(res)...) + span.SetStatus(semconvutil.HTTPClientStatus(res.StatusCode)) + res.Body = newWrappedBody(span, res.Body) + + return res, err +} + +// newWrappedBody returns a new and appropriately scoped *wrappedBody as an +// io.ReadCloser. If the passed body implements io.Writer, the returned value +// will implement io.ReadWriteCloser. +func newWrappedBody(span trace.Span, body io.ReadCloser) io.ReadCloser { + // The successful protocol switch responses will have a body that + // implement an io.ReadWriteCloser. Ensure this interface type continues + // to be satisfied if that is the case. + if _, ok := body.(io.ReadWriteCloser); ok { + return &wrappedBody{span: span, body: body} + } + + // Remove the implementation of the io.ReadWriteCloser and only implement + // the io.ReadCloser. + return struct{ io.ReadCloser }{&wrappedBody{span: span, body: body}} +} + +// wrappedBody is the response body type returned by the transport +// instrumentation to complete a span. Errors encountered when using the +// response body are recorded in span tracking the response. +// +// The span tracking the response is ended when this body is closed. +// +// If the response body implements the io.Writer interface (i.e. for +// successful protocol switches), the wrapped body also will. +type wrappedBody struct { + span trace.Span + body io.ReadCloser +} + +var _ io.ReadWriteCloser = &wrappedBody{} + +func (wb *wrappedBody) Write(p []byte) (int, error) { + // This will not panic given the guard in newWrappedBody. + n, err := wb.body.(io.Writer).Write(p) + if err != nil { + wb.span.RecordError(err) + wb.span.SetStatus(codes.Error, err.Error()) + } + return n, err +} + +func (wb *wrappedBody) Read(b []byte) (int, error) { + n, err := wb.body.Read(b) + + switch err { + case nil: + // nothing to do here but fall through to the return + case io.EOF: + wb.span.End() + default: + wb.span.RecordError(err) + wb.span.SetStatus(codes.Error, err.Error()) + } + return n, err +} + +func (wb *wrappedBody) Close() error { + wb.span.End() + if wb.body != nil { + return wb.body.Close() + } + return nil +} diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/version.go b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/version.go new file mode 100644 index 000000000..bbcbb7416 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/version.go @@ -0,0 +1,28 @@ +// Copyright The OpenTelemetry 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 otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + +// Version is the current release version of the otelhttp instrumentation. +func Version() string { + return "0.42.0" + // This string is updated by the pre_release.sh script during release +} + +// SemVersion is the semantic version to be supplied to tracer/meter creation. +// +// Deprecated: Use [Version] instead. +func SemVersion() string { + return Version() +} diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/wrap.go b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/wrap.go new file mode 100644 index 000000000..11a35ed16 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/wrap.go @@ -0,0 +1,99 @@ +// Copyright The OpenTelemetry 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 otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + +import ( + "context" + "io" + "net/http" + + "go.opentelemetry.io/otel/propagation" +) + +var _ io.ReadCloser = &bodyWrapper{} + +// bodyWrapper wraps a http.Request.Body (an io.ReadCloser) to track the number +// of bytes read and the last error. +type bodyWrapper struct { + io.ReadCloser + record func(n int64) // must not be nil + + read int64 + err error +} + +func (w *bodyWrapper) Read(b []byte) (int, error) { + n, err := w.ReadCloser.Read(b) + n1 := int64(n) + w.read += n1 + w.err = err + w.record(n1) + return n, err +} + +func (w *bodyWrapper) Close() error { + return w.ReadCloser.Close() +} + +var _ http.ResponseWriter = &respWriterWrapper{} + +// respWriterWrapper wraps a http.ResponseWriter in order to track the number of +// bytes written, the last error, and to catch the first written statusCode. +// TODO: The wrapped http.ResponseWriter doesn't implement any of the optional +// types (http.Hijacker, http.Pusher, http.CloseNotifier, http.Flusher, etc) +// that may be useful when using it in real life situations. +type respWriterWrapper struct { + http.ResponseWriter + record func(n int64) // must not be nil + + // used to inject the header + ctx context.Context + + props propagation.TextMapPropagator + + written int64 + statusCode int + err error + wroteHeader bool +} + +func (w *respWriterWrapper) Header() http.Header { + return w.ResponseWriter.Header() +} + +func (w *respWriterWrapper) Write(p []byte) (int, error) { + if !w.wroteHeader { + w.WriteHeader(http.StatusOK) + } + n, err := w.ResponseWriter.Write(p) + n1 := int64(n) + w.record(n1) + w.written += n1 + w.err = err + return n, err +} + +// WriteHeader persists initial statusCode for span attribution. +// All calls to WriteHeader will be propagated to the underlying ResponseWriter +// and will persist the statusCode from the first call. +// Blocking consecutive calls to WriteHeader alters expected behavior and will +// remove warning logs from net/http where developers will notice incorrect handler implementations. +func (w *respWriterWrapper) WriteHeader(statusCode int) { + if !w.wroteHeader { + w.wroteHeader = true + w.statusCode = statusCode + } + w.ResponseWriter.WriteHeader(statusCode) +} diff --git a/vendor/go.opentelemetry.io/contrib/pre_release.sh b/vendor/go.opentelemetry.io/contrib/pre_release.sh new file mode 100644 index 000000000..16ee96df4 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/pre_release.sh @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +# Copyright The OpenTelemetry 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. + +# +# This script is used for +# a) creating a new tagged release of go.opentelemetry.io/contrib +# b) bumping the referenced version of go.opentelemetry.io/otel +# +# The options can be used together or individually. +# +set -e + +declare CONTRIB_TAG +declare OTEL_TAG +declare PREV_OTEL_TAG + +help() { + printf "\n" + printf "Usage: %s [-o otel_tag] [-t tag]\n" "$0" + printf "\t-o Otel release tag. Update all go.mod to reference go.opentelemetry.io/otel .\n" + printf "\t-p Previous Otel release tag. Update all go.mod that reference go.opentelemetry.io/otel .\n" + printf "\t-t New Contrib unreleased tag. Update all go.mod files with this tag.\n" + exit 1 # Exit script after printing help +} + +while getopts "t:o:p:" opt +do + case "$opt" in + t ) CONTRIB_TAG="$OPTARG" ;; + o ) OTEL_TAG="$OPTARG" ;; + p ) PREV_OTEL_TAG="$OPTARG" ;; + ? ) help ;; # Print help + esac +done + +declare -r SEMVER_REGEX="^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(\\-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$" + +validate_tag() { + local newTag=$1 + if [[ "${newTag}" =~ ${SEMVER_REGEX} ]]; then + printf "%s is valid semver tag.\n" "${newTag}" + else + printf "%s is not a valid semver tag.\n" "${newTag}" + return 1 + fi +} + +# Print help in case parameters are empty +if [[ -z "$CONTRIB_TAG" && -z "$OTEL_TAG" ]] +then + printf "At least one of '-o' or '-t' must be specified.\n" + help +fi + + +## Validate tags first +if [ -n "${OTEL_TAG}" ]; then + validate_tag "${OTEL_TAG}" || exit $? + + # check that OTEL_TAG is a currently released tag for go.opentelemetry.io/otel + TMPDIR=$(mktemp -d "/tmp/otel-contrib.XXXXXX") || exit 1 + trap "rm -fr ${TMPDIR}" EXIT + (cd "${TMPDIR}" && go mod init tagtest) + # requires go 1.14 for support of '-modfile' + if ! go get -modfile="${TMPDIR}/go.mod" -d -v "go.opentelemetry.io/otel@${OTEL_TAG}"; then + printf "go.opentelemetry.io/otel %s does not exist. Please supply a valid tag\n" "${OTEL_TAG}" + exit 1 + fi +fi +if [ -n "${CONTRIB_TAG}" ]; then + validate_tag "${CONTRIB_TAG}" || exit $? + TAG_FOUND=$(git tag --list "${CONTRIB_TAG}") + if [[ ${TAG_FOUND} = "${CONTRIB_TAG}" ]] ; then + printf "Tag %s already exists in this repo\n" "${CONTRIB_TAG}" + exit 1 + fi +else + CONTRIB_TAG=${OTEL_TAG} # if contrib_tag not specified, but OTEL_TAG is, then set it to OTEL_TAG +fi + +# Get version for contrib.go +OTEL_CONTRIB_VERSION=$(echo "${CONTRIB_TAG}" | egrep -o "${SEMVER_REGEX}") +# Strip leading v +OTEL_CONTRIB_VERSION="${OTEL_CONTRIB_VERSION#v}" + +cd "$(dirname "$0")" + +if ! git diff --quiet; then \ + printf "Working tree is not clean, can't proceed\n" + git status + git diff + exit 1 +fi + +# Update contrib.go version definition +cp contrib.go contrib.go.bak +sed "s/\(return \"\)[0-9]*\.[0-9]*\.[0-9]*\"/\1${OTEL_CONTRIB_VERSION}\"/" ./contrib.go.bak > ./contrib.go +rm -f ./contrib.go.bak + +declare -r BRANCH_NAME=pre_release_${CONTRIB_TAG} + +defaultFromTag="v[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[^0-9]*.*" + +patch_gomods() { + local pkg_=$1 + local oldTag=${2:-$defaultFromTag} + local newTag=$3 + # now do the same for all the directories underneath + PACKAGE_DIRS=$(find . -mindepth 2 -type f -name 'go.mod' -exec dirname {} \; | egrep -v 'tools' | sed 's|^\.\/||' | sort) + # quote any '.' characters in the pkg name + local quoted_pkg_=${pkg_//./\\.} + for dir in $PACKAGE_DIRS; do + cp "${dir}/go.mod" "${dir}/go.mod.bak" + sed "s|${quoted_pkg_}\([^ ]*\) ${oldTag}$|${pkg_}\1 ${newTag}|" "${dir}/go.mod.bak" >"${dir}/go.mod" + rm -f "${dir}/go.mod.bak" + done +} + +# branch off from existing main +git checkout -b "${BRANCH_NAME}" main + +# Update go.mods +if [ -n "${OTEL_TAG}" ]; then + # first update the top most module + go get "go.opentelemetry.io/otel@${OTEL_TAG}" + patch_gomods go.opentelemetry.io/otel "${PREV_OTEL_TAG}" "${OTEL_TAG}" +fi + +if [ -n "${CONTRIB_TAG}" ]; then + patch_gomods go.opentelemetry.io/contrib "" "${CONTRIB_TAG}" +fi + +git diff +# Run lint to update go.sum +make lint + +# Add changes and commit. +git add . +make ci +# Check whether registry links are up to date +make registry-links-check + +declare COMMIT_MSG="" +if [ -n "${OTEL_TAG}" ]; then + COMMIT_MSG+="Bumping otel version to ${OTEL_TAG}" +fi +COMMIT_MSG+=". Prepare for releasing ${CONTRIB_TAG}" +git commit -m "${COMMIT_MSG}" + +printf "Now run following to verify the changes.\ngit diff main\n" +printf "\nThen push the changes to upstream\n" diff --git a/vendor/go.opentelemetry.io/contrib/tag.sh b/vendor/go.opentelemetry.io/contrib/tag.sh new file mode 100644 index 000000000..2a0ef1d26 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/tag.sh @@ -0,0 +1,178 @@ +#!/usr/bin/env bash + +# Copyright The OpenTelemetry 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. + +readonly PROGNAME=$(basename "$0") +readonly PROGDIR=$(readlink -m "$(dirname "$0")") + +readonly EXCLUDE_PACKAGES="tools" +readonly SEMVER_REGEX="v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(\\-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?" + +usage() { + cat <<- EOF +Usage: $PROGNAME [OPTIONS] SEMVER_TAG COMMIT_HASH + +Creates git tag for all Go packages in project. + +OPTIONS: + -h --help Show this help. + +ARGUMENTS: + SEMVER_TAG Semantic version to tag with. + COMMIT_HASH Git commit hash to tag. +EOF +} + +cmdline() { + local arg commit + + for arg + do + local delim="" + case "$arg" in + # Translate long form options to short form. + --help) args="${args}-h ";; + # Pass through for everything else. + *) [[ "${arg:0:1}" == "-" ]] || delim="\"" + args="${args}${delim}${arg}${delim} ";; + esac + done + + # Reset and process short form options. + eval set -- "$args" + + while getopts "h" OPTION + do + case $OPTION in + h) + usage + exit 0 + ;; + *) + echo "unknown option: $OPTION" + usage + exit 1 + ;; + esac + done + + # Positional arguments. + shift $((OPTIND-1)) + readonly TAG="$1" + if [ -z "$TAG" ] + then + echo "missing SEMVER_TAG" + usage + exit 1 + fi + if [[ ! "$TAG" =~ $SEMVER_REGEX ]] + then + printf "invalid semantic version: %s\n" "$TAG" + exit 2 + fi + if [[ "$( git tag --list "$TAG" )" ]] + then + printf "tag already exists: %s\n" "$TAG" + exit 2 + fi + + shift + commit="$1" + if [ -z "$commit" ] + then + echo "missing COMMIT_HASH" + usage + exit 1 + fi + # Verify rev is for a commit and unify hashes into a complete SHA1. + readonly SHA="$( git rev-parse --quiet --verify "${commit}^{commit}" )" + if [ -z "$SHA" ] + then + printf "invalid commit hash: %s\n" "$commit" + exit 2 + fi + if [ "$( git merge-base "$SHA" HEAD )" != "$SHA" ] + then + printf "commit '%s' not found on this branch\n" "$commit" + exit 2 + fi +} + +package_dirs() { + # Return a list of package directories in the form: + # + # package/directory/a + # package/directory/b + # deeper/package/directory/a + # ... + # + # Making sure to exclude any packages in the EXCLUDE_PACKAGES regexp. + find . -mindepth 2 -type f -name 'go.mod' -exec dirname {} \; \ + | grep -E -v "$EXCLUDE_PACKAGES" \ + | sed 's/^\.\///' \ + | sort +} + +git_tag() { + local tag="$1" + local commit="$2" + + git tag -a "$tag" -s -m "Version $tag" "$commit" +} + +previous_version() { + local current="$1" + + # Requires git > 2.0 + git tag -l --sort=v:refname \ + | grep -E "^${SEMVER_REGEX}$" \ + | grep -v "$current" \ + | tail -1 +} + +print_changes() { + local tag="$1" + local previous + + previous="$( previous_version "$tag" )" + if [ -n "$previous" ] + then + printf "\nRaw changes made between %s and %s\n" "$previous" "$tag" + printf "======================================\n" + git --no-pager log --pretty=oneline "${previous}..$tag" + fi +} + +main() { + local dir + + cmdline "$@" + + cd "$PROGDIR" || exit 3 + + # Create tag for root package. + git_tag "$TAG" "$SHA" + printf "created tag: %s\n" "$TAG" + + # Create tag for all sub-packages. + for dir in $( package_dirs ) + do + git_tag "${dir}/$TAG" "$SHA" + printf "created tag: %s\n" "${dir}/$TAG" + done + + print_changes "$TAG" +} +main "$@" diff --git a/vendor/go.opentelemetry.io/contrib/version.go b/vendor/go.opentelemetry.io/contrib/version.go new file mode 100644 index 000000000..98869cd10 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/version.go @@ -0,0 +1,28 @@ +// Copyright The OpenTelemetry 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 contrib contains common values used across all +// instrumentation, exporter, and detector contributions. +package contrib // import "go.opentelemetry.io/contrib" + +// Version is the current release version of OpenTelemetry Contrib in use. +func Version() string { + return "1.0.0" + // This string is updated by the pre_release.sh script during release +} + +// SemVersion is the semantic version to be supplied to tracer/meter creation. +func SemVersion() string { + return "semver:" + Version() +} diff --git a/vendor/go.opentelemetry.io/contrib/versions.yaml b/vendor/go.opentelemetry.io/contrib/versions.yaml new file mode 100644 index 000000000..c16ed0d69 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/versions.yaml @@ -0,0 +1,90 @@ +# Copyright The OpenTelemetry 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. + +module-sets: + stable-v1: + version: v1.0.0 + modules: + - go.opentelemetry.io/contrib + - go.opentelemetry.io/contrib/tools + - go.opentelemetry.io/contrib/propagators/aws + - go.opentelemetry.io/contrib/propagators/ot + - go.opentelemetry.io/contrib/propagators/jaeger + - go.opentelemetry.io/contrib/propagators/b3 + - go.opentelemetry.io/contrib/detectors/gcp + - go.opentelemetry.io/contrib/detectors/aws/ec2 + - go.opentelemetry.io/contrib/detectors/aws/ecs + - go.opentelemetry.io/contrib/detectors/aws/eks + experimental-instrumentation: + version: v0.25.0 + modules: + - go.opentelemetry.io/contrib/propagators/opencensus + - go.opentelemetry.io/contrib/propagators/opencensus/examples + - go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron + - go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron/example + - go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron/test + - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp + - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/example + - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/test + - go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace + - go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/example + - go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/test + - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc + - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example + - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/test + - go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo + - go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test + - go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux + - go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/example + - go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/test + - go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin + - go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/example + - go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/test + - go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho + - go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/example + - go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/test + - go.opentelemetry.io/contrib/instrumentation/github.com/Shopify/sarama/otelsarama + - go.opentelemetry.io/contrib/instrumentation/github.com/Shopify/sarama/otelsarama/example + - go.opentelemetry.io/contrib/instrumentation/github.com/Shopify/sarama/otelsarama/test + - go.opentelemetry.io/contrib/instrumentation/github.com/go-kit/kit/otelkit + - go.opentelemetry.io/contrib/instrumentation/github.com/go-kit/kit/otelkit/example + - go.opentelemetry.io/contrib/instrumentation/github.com/go-kit/kit/otelkit/test + - go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql/otelgocql + - go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql/otelgocql/example + - go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql/otelgocql/test + - go.opentelemetry.io/contrib/instrumentation/github.com/astaxie/beego/otelbeego + - go.opentelemetry.io/contrib/instrumentation/github.com/astaxie/beego/otelbeego/example + - go.opentelemetry.io/contrib/instrumentation/github.com/astaxie/beego/otelbeego/test + - go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws + - go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example + - go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/test + - go.opentelemetry.io/contrib/instrumentation/github.com/bradfitz/gomemcache/memcache/otelmemcache + - go.opentelemetry.io/contrib/instrumentation/github.com/bradfitz/gomemcache/memcache/otelmemcache/example + - go.opentelemetry.io/contrib/instrumentation/github.com/bradfitz/gomemcache/memcache/otelmemcache/test + - go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful + - go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/example + - go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/test + - go.opentelemetry.io/contrib/zpages + experimental-metrics: + version: v0.25.0 + modules: + - go.opentelemetry.io/contrib/exporters/metric/dogstatsd + - go.opentelemetry.io/contrib/exporters/metric/cortex + - go.opentelemetry.io/contrib/exporters/metric/cortex/example + - go.opentelemetry.io/contrib/exporters/metric/cortex/utils + - go.opentelemetry.io/contrib/exporters/metric/datadog + - go.opentelemetry.io/contrib/instrumentation/host + - go.opentelemetry.io/contrib/instrumentation/host/example + - go.opentelemetry.io/contrib/instrumentation/runtime + - go.opentelemetry.io/contrib/instrumentation/runtime/example diff --git a/vendor/go.opentelemetry.io/contrib/zpages/version.go b/vendor/go.opentelemetry.io/contrib/zpages/version.go index a33fa16c8..a868cc4c1 100644 --- a/vendor/go.opentelemetry.io/contrib/zpages/version.go +++ b/vendor/go.opentelemetry.io/contrib/zpages/version.go @@ -16,11 +16,13 @@ package zpages // import "go.opentelemetry.io/contrib/zpages" // Version is the current release version of the zpages span processor. func Version() string { - return "0.41.1" + return "0.42.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. +// +// Deprecated: Use [Version] instead. func SemVersion() string { - return "semver:" + Version() + return Version() } diff --git a/vendor/go.opentelemetry.io/otel/.codespellignore b/vendor/go.opentelemetry.io/otel/.codespellignore new file mode 100644 index 000000000..ae6a3bcf1 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/.codespellignore @@ -0,0 +1,5 @@ +ot +fo +te +collison +consequentially diff --git a/vendor/go.opentelemetry.io/otel/.codespellrc b/vendor/go.opentelemetry.io/otel/.codespellrc new file mode 100644 index 000000000..4afbb1fb3 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/.codespellrc @@ -0,0 +1,10 @@ +# https://github.com/codespell-project/codespell +[codespell] +builtin = clear,rare,informal +check-filenames = +check-hidden = +ignore-words = .codespellignore +interactive = 1 +skip = .git,go.mod,go.sum,semconv,venv,.tools +uri-ignore-words-list = * +write = diff --git a/vendor/go.opentelemetry.io/otel/.gitignore b/vendor/go.opentelemetry.io/otel/.gitignore index cf29d10a7..aa6993762 100644 --- a/vendor/go.opentelemetry.io/otel/.gitignore +++ b/vendor/go.opentelemetry.io/otel/.gitignore @@ -2,6 +2,7 @@ Thumbs.db .tools/ +venv/ .idea/ .vscode/ *.iml diff --git a/vendor/go.opentelemetry.io/otel/.golangci.yml b/vendor/go.opentelemetry.io/otel/.golangci.yml index e28904f6a..dbb6670b3 100644 --- a/vendor/go.opentelemetry.io/otel/.golangci.yml +++ b/vendor/go.opentelemetry.io/otel/.golangci.yml @@ -113,7 +113,7 @@ linters-settings: - name: constant-logical-expr disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#context-as-argument - # TODO (#3372) reenable linter when it is compatible. https://github.com/golangci/golangci-lint/issues/3280 + # TODO (#3372) re-enable linter when it is compatible. https://github.com/golangci/golangci-lint/issues/3280 - name: context-as-argument disabled: true arguments: diff --git a/vendor/go.opentelemetry.io/otel/CHANGELOG.md b/vendor/go.opentelemetry.io/otel/CHANGELOG.md index 7630a4f33..d9f145f86 100644 --- a/vendor/go.opentelemetry.io/otel/CHANGELOG.md +++ b/vendor/go.opentelemetry.io/otel/CHANGELOG.md @@ -8,6 +8,55 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +## [1.16.0/0.39.0] 2023-05-18 + +This release contains the first stable release of the OpenTelemetry Go [metric API]. +Our project stability guarantees now apply to the `go.opentelemetry.io/otel/metric` package. +See our [versioning policy](VERSIONING.md) for more information about these stability guarantees. + +### Added + +- The `go.opentelemetry.io/otel/semconv/v1.19.0` package. + The package contains semantic conventions from the `v1.19.0` version of the OpenTelemetry specification. (#3848) +- The `go.opentelemetry.io/otel/semconv/v1.20.0` package. + The package contains semantic conventions from the `v1.20.0` version of the OpenTelemetry specification. (#4078) + +### Changed + +- Use `strings.Cut()` instead of `string.SplitN()` for better readability and memory use. (#4049) + +### Removed + +- The deprecated `go.opentelemetry.io/otel/metric/instrument` package is removed. + Use `go.opentelemetry.io/otel/metric` instead. (#4055) + +### Fixed + +- Fix build for BSD based systems in `go.opentelemetry.io/otel/sdk/resource`. (#4077) + +## [1.16.0-rc.1/0.39.0-rc.1] 2023-05-03 + +This is a release candidate for the v1.16.0/v0.39.0 release. +That release is expected to include the `v1` release of the OpenTelemetry Go metric API and will provide stability guarantees of that API. +See our [versioning policy](VERSIONING.md) for more information about these stability guarantees. + +### Added + +- Support global `MeterProvider` in `go.opentelemetry.io/otel`. (#4039) + - Use `Meter` for a `metric.Meter` from the global `metric.MeterProvider`. + - Use `GetMeterProivder` for a global `metric.MeterProvider`. + - Use `SetMeterProivder` to set the global `metric.MeterProvider`. + +### Changed + +- Move the `go.opentelemetry.io/otel/metric` module to the `stable-v1` module set. + This stages the metric API to be released as a stable module. (#4038) + +### Removed + +- The `go.opentelemetry.io/otel/metric/global` package is removed. + Use `go.opentelemetry.io/otel` instead. (#4039) + ## [1.15.1/0.38.1] 2023-05-02 ### Fixed @@ -2443,7 +2492,9 @@ It contains api and sdk for trace and meter. - CircleCI build CI manifest files. - CODEOWNERS file to track owners of this project. -[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.15.1...HEAD +[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.16.0...HEAD +[1.16.0/0.39.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.16.0 +[1.16.0-rc.1/0.39.0-rc.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.16.0-rc.1 [1.15.1/0.38.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.15.1 [1.15.0/0.38.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.15.0 [1.15.0-rc.2/0.38.0-rc.2]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.15.0-rc.2 @@ -2512,3 +2563,5 @@ It contains api and sdk for trace and meter. [Go 1.20]: https://go.dev/doc/go1.20 [Go 1.19]: https://go.dev/doc/go1.19 [Go 1.18]: https://go.dev/doc/go1.18 + +[metric API]:https://pkg.go.dev/go.opentelemetry.io/otel/metric diff --git a/vendor/go.opentelemetry.io/otel/CONTRIBUTING.md b/vendor/go.opentelemetry.io/otel/CONTRIBUTING.md index f521d6543..b2df5de34 100644 --- a/vendor/go.opentelemetry.io/otel/CONTRIBUTING.md +++ b/vendor/go.opentelemetry.io/otel/CONTRIBUTING.md @@ -28,6 +28,11 @@ precommit` - the `precommit` target is the default). The `precommit` target also fixes the formatting of the code and checks the status of the go module files. +Additionally, there is a `codespell` target that checks for common +typos in the code. It is not run by default, but you can run it +manually with `make codespell`. It will set up a virtual environment +in `venv` and install `codespell` there. + If after running `make precommit` the output of `git status` contains `nothing to commit, working tree clean` then it means that everything is up-to-date and properly formatted. @@ -150,10 +155,10 @@ Any [Maintainer] can merge the PR once the above criteria have been met. ## Design Choices As with other OpenTelemetry clients, opentelemetry-go follows the -[opentelemetry-specification](https://github.com/open-telemetry/opentelemetry-specification). +[OpenTelemetry Specification](https://opentelemetry.io/docs/specs/otel). It's especially valuable to read through the [library -guidelines](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/library-guidelines.md). +guidelines](https://opentelemetry.io/docs/specs/otel/library-guidelines). ### Focus on Capabilities, Not Structure Compliance diff --git a/vendor/go.opentelemetry.io/otel/Makefile b/vendor/go.opentelemetry.io/otel/Makefile index 91ead91b4..26e4bed22 100644 --- a/vendor/go.opentelemetry.io/otel/Makefile +++ b/vendor/go.opentelemetry.io/otel/Makefile @@ -25,8 +25,8 @@ TIMEOUT = 60 .DEFAULT_GOAL := precommit .PHONY: precommit ci -precommit: dependabot-generate license-check vanity-import-fix misspell go-mod-tidy golangci-lint-fix test-default -ci: dependabot-check license-check lint vanity-import-check build test-default check-clean-work-tree test-coverage +precommit: generate dependabot-generate license-check vanity-import-fix misspell go-mod-tidy golangci-lint-fix test-default +ci: generate dependabot-check license-check lint vanity-import-check build test-default check-clean-work-tree test-coverage # Tools @@ -74,9 +74,42 @@ $(TOOLS)/gojq: PACKAGE=github.com/itchyny/gojq/cmd/gojq .PHONY: tools tools: $(CROSSLINK) $(DBOTCONF) $(GOLANGCI_LINT) $(MISSPELL) $(GOCOVMERGE) $(STRINGER) $(PORTO) $(GOJQ) $(SEMCONVGEN) $(MULTIMOD) $(SEMCONVKIT) -# Build +# Virtualized python tools via docker -.PHONY: generate build +# The directory where the virtual environment is created. +VENVDIR := venv + +# The directory where the python tools are installed. +PYTOOLS := $(VENVDIR)/bin + +# The pip executable in the virtual environment. +PIP := $(PYTOOLS)/pip + +# The directory in the docker image where the current directory is mounted. +WORKDIR := /workdir + +# The python image to use for the virtual environment. +PYTHONIMAGE := python:3.11.3-slim-bullseye + +# Run the python image with the current directory mounted. +DOCKERPY := docker run --rm -v "$(CURDIR):$(WORKDIR)" -w $(WORKDIR) $(PYTHONIMAGE) + +# Create a virtual environment for Python tools. +$(PYTOOLS): +# The `--upgrade` flag is needed to ensure that the virtual environment is +# created with the latest pip version. + @$(DOCKERPY) bash -c "python3 -m venv $(VENVDIR) && $(PIP) install --upgrade pip" + +# Install python packages into the virtual environment. +$(PYTOOLS)/%: | $(PYTOOLS) + @$(DOCKERPY) $(PIP) install -r requirements.txt + +CODESPELL = $(PYTOOLS)/codespell +$(CODESPELL): PACKAGE=codespell + +# Generate + +.PHONY: generate generate: $(OTEL_GO_MOD_DIRS:%=generate/%) generate/%: DIR=$* @@ -85,7 +118,11 @@ generate/%: | $(STRINGER) $(PORTO) && cd $(DIR) \ && PATH="$(TOOLS):$${PATH}" $(GO) generate ./... && $(PORTO) -w . -build: generate $(OTEL_GO_MOD_DIRS:%=build/%) $(OTEL_GO_MOD_DIRS:%=build-tests/%) +# Build + +.PHONY: build + +build: $(OTEL_GO_MOD_DIRS:%=build/%) $(OTEL_GO_MOD_DIRS:%=build-tests/%) build/%: DIR=$* build/%: @echo "$(GO) build $(DIR)/..." \ @@ -176,6 +213,10 @@ vanity-import-fix: | $(PORTO) misspell: | $(MISSPELL) @$(MISSPELL) -w $(ALL_DOCS) +.PHONY: codespell +codespell: | $(CODESPELL) + @$(DOCKERPY) $(CODESPELL) + .PHONY: license-check license-check: @licRes=$$(for f in $$(find . -type f \( -iname '*.go' -o -iname '*.sh' \) ! -path '**/third_party/*' ! -path './.git/*' ) ; do \ @@ -211,6 +252,7 @@ semconv-generate: | $(SEMCONVGEN) $(SEMCONVKIT) [ "$(TAG)" ] || ( echo "TAG unset: missing opentelemetry specification tag"; exit 1 ) [ "$(OTEL_SPEC_REPO)" ] || ( echo "OTEL_SPEC_REPO unset: missing path to opentelemetry specification repo"; exit 1 ) $(SEMCONVGEN) -i "$(OTEL_SPEC_REPO)/semantic_conventions/." --only=span -p conventionType=trace -f trace.go -t "$(SEMCONVPKG)/template.j2" -s "$(TAG)" + $(SEMCONVGEN) -i "$(OTEL_SPEC_REPO)/semantic_conventions/." --only=attribute_group -p conventionType=trace -f attribute_group.go -t "$(SEMCONVPKG)/template.j2" -s "$(TAG)" $(SEMCONVGEN) -i "$(OTEL_SPEC_REPO)/semantic_conventions/." --only=event -p conventionType=event -f event.go -t "$(SEMCONVPKG)/template.j2" -s "$(TAG)" $(SEMCONVGEN) -i "$(OTEL_SPEC_REPO)/semantic_conventions/." --only=resource -p conventionType=resource -f resource.go -t "$(SEMCONVPKG)/template.j2" -s "$(TAG)" $(SEMCONVKIT) -output "$(SEMCONVPKG)/$(TAG)" -tag "$(TAG)" diff --git a/vendor/go.opentelemetry.io/otel/RELEASING.md b/vendor/go.opentelemetry.io/otel/RELEASING.md index 77d56c936..5e6daf6c4 100644 --- a/vendor/go.opentelemetry.io/otel/RELEASING.md +++ b/vendor/go.opentelemetry.io/otel/RELEASING.md @@ -2,10 +2,10 @@ ## Semantic Convention Generation -New versions of the [OpenTelemetry specification] mean new versions of the `semconv` package need to be generated. +New versions of the [OpenTelemetry Specification] mean new versions of the `semconv` package need to be generated. The `semconv-generate` make target is used for this. -1. Checkout a local copy of the [OpenTelemetry specification] to the desired release tag. +1. Checkout a local copy of the [OpenTelemetry Specification] to the desired release tag. 2. Pull the latest `otel/semconvgen` image: `docker pull otel/semconvgen:latest` 3. Run the `make semconv-generate ...` target from this repository. @@ -14,7 +14,6 @@ For example, ```sh export TAG="v1.13.0" # Change to the release version you are generating. export OTEL_SPEC_REPO="/absolute/path/to/opentelemetry-specification" -git -C "$OTEL_SPEC_REPO" checkout "tags/$TAG" -b "$TAG" docker pull otel/semconvgen:latest make semconv-generate # Uses the exported TAG and OTEL_SPEC_REPO. ``` @@ -124,4 +123,4 @@ Once verified be sure to [make a release for the `contrib` repository](https://g Update [the documentation](./website_docs) for [the OpenTelemetry website](https://opentelemetry.io/docs/go/). Importantly, bump any package versions referenced to be the latest one you just released and ensure all code examples still compile and are accurate. -[OpenTelemetry specification]: https://github.com/open-telemetry/opentelemetry-specification +[OpenTelemetry Specification]: https://github.com/open-telemetry/opentelemetry-specification diff --git a/vendor/go.opentelemetry.io/otel/baggage/baggage.go b/vendor/go.opentelemetry.io/otel/baggage/baggage.go index a36db8f8d..46e523a80 100644 --- a/vendor/go.opentelemetry.io/otel/baggage/baggage.go +++ b/vendor/go.opentelemetry.io/otel/baggage/baggage.go @@ -289,45 +289,37 @@ func parseMember(member string) (Member, error) { props properties ) - parts := strings.SplitN(member, propertyDelimiter, 2) - switch len(parts) { - case 2: + keyValue, properties, found := strings.Cut(member, propertyDelimiter) + if found { // Parse the member properties. - for _, pStr := range strings.Split(parts[1], propertyDelimiter) { + for _, pStr := range strings.Split(properties, propertyDelimiter) { p, err := parseProperty(pStr) if err != nil { return newInvalidMember(), err } props = append(props, p) } - fallthrough - case 1: - // Parse the member key/value pair. + } + // Parse the member key/value pair. - // Take into account a value can contain equal signs (=). - kv := strings.SplitN(parts[0], keyValueDelimiter, 2) - if len(kv) != 2 { - return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidMember, member) - } - // "Leading and trailing whitespaces are allowed but MUST be trimmed - // when converting the header into a data structure." - key = strings.TrimSpace(kv[0]) - var err error - value, err = url.QueryUnescape(strings.TrimSpace(kv[1])) - if err != nil { - return newInvalidMember(), fmt.Errorf("%w: %q", err, value) - } - if !keyRe.MatchString(key) { - return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key) - } - if !valueRe.MatchString(value) { - return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value) - } - default: - // This should never happen unless a developer has changed the string - // splitting somehow. Panic instead of failing silently and allowing - // the bug to slip past the CI checks. - panic("failed to parse baggage member") + // Take into account a value can contain equal signs (=). + k, v, found := strings.Cut(keyValue, keyValueDelimiter) + if !found { + return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidMember, member) + } + // "Leading and trailing whitespaces are allowed but MUST be trimmed + // when converting the header into a data structure." + key = strings.TrimSpace(k) + var err error + value, err = url.QueryUnescape(strings.TrimSpace(v)) + if err != nil { + return newInvalidMember(), fmt.Errorf("%w: %q", err, value) + } + if !keyRe.MatchString(key) { + return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key) + } + if !valueRe.MatchString(value) { + return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value) } return Member{key: key, value: value, properties: props, hasData: true}, nil diff --git a/vendor/go.opentelemetry.io/otel/codes/doc.go b/vendor/go.opentelemetry.io/otel/codes/doc.go index df3e0f1b6..4e328fbb4 100644 --- a/vendor/go.opentelemetry.io/otel/codes/doc.go +++ b/vendor/go.opentelemetry.io/otel/codes/doc.go @@ -16,6 +16,6 @@ Package codes defines the canonical error codes used by OpenTelemetry. It conforms to [the OpenTelemetry -specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#statuscanonicalcode). +specification](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/trace/api.md#set-status). */ package codes // import "go.opentelemetry.io/otel/codes" diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/internal/envconfig/envconfig.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/internal/envconfig/envconfig.go index 53ff3126b..444eefbb3 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/internal/envconfig/envconfig.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/internal/envconfig/envconfig.go @@ -166,20 +166,20 @@ func stringToHeader(value string) map[string]string { headers := make(map[string]string) for _, header := range headersPairs { - nameValue := strings.SplitN(header, "=", 2) - if len(nameValue) < 2 { - global.Error(errors.New("missing '="), "parse headers", "input", nameValue) + n, v, found := strings.Cut(header, "=") + if !found { + global.Error(errors.New("missing '="), "parse headers", "input", header) continue } - name, err := url.QueryUnescape(nameValue[0]) + name, err := url.QueryUnescape(n) if err != nil { - global.Error(err, "escape header key", "key", nameValue[0]) + global.Error(err, "escape header key", "key", n) continue } trimmedName := strings.TrimSpace(name) - value, err := url.QueryUnescape(nameValue[1]) + value, err := url.QueryUnescape(v) if err != nil { - global.Error(err, "escape header value", "value", nameValue[1]) + global.Error(err, "escape header value", "value", v) continue } trimmedValue := strings.TrimSpace(value) diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/README.md b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/README.md index 6e9cc0366..502952231 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/README.md +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/README.md @@ -2,7 +2,7 @@ [![Go Reference](https://pkg.go.dev/badge/go.opentelemetry.io/otel/exporters/otlp/otlptrace.svg)](https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp/otlptrace) -[OpenTelemetry Protocol Exporter](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.5.0/specification/protocol/exporter.md) implementation. +[OpenTelemetry Protocol Exporter](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/protocol/exporter.md) implementation. ## Installation @@ -36,7 +36,7 @@ The `otlptracehttp` package implements a client for the span exporter that sends The following environment variables can be used (instead of options objects) to override the default configuration. For more information about how each of these environment variables is interpreted, see [the OpenTelemetry -specification](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/protocol/exporter.md). +specification](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/protocol/exporter.md). | Environment variable | Option | Default value | | ------------------------------------------------------------------------ |------------------------------ | -------------------------------------------------------- | diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/header.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/header.go index 7fcd89a40..36d3ca8e1 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/header.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/header.go @@ -19,7 +19,7 @@ import ( ) // GetUserAgentHeader returns an OTLP header value form "OTel OTLP Exporter Go/{{ .Version }}" -// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#user-agent +// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/protocol/exporter.md#user-agent func GetUserAgentHeader() string { return "OTel OTLP Exporter Go/" + otlptrace.Version() } diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/version.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/version.go index 851e8d1b0..db70dc531 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/version.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/version.go @@ -16,5 +16,5 @@ package otlptrace // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace" // Version is the current release version of the OpenTelemetry OTLP trace exporter in use. func Version() string { - return "1.15.1" + return "1.16.0" } diff --git a/vendor/go.opentelemetry.io/otel/internal/global/instruments.go b/vendor/go.opentelemetry.io/otel/internal/global/instruments.go new file mode 100644 index 000000000..a33eded87 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/internal/global/instruments.go @@ -0,0 +1,359 @@ +// Copyright The OpenTelemetry 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 global // import "go.opentelemetry.io/otel/internal/global" + +import ( + "context" + "sync/atomic" + + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/metric/embedded" +) + +// unwrapper unwraps to return the underlying instrument implementation. +type unwrapper interface { + Unwrap() metric.Observable +} + +type afCounter struct { + embedded.Float64ObservableCounter + metric.Float64Observable + + name string + opts []metric.Float64ObservableCounterOption + + delegate atomic.Value //metric.Float64ObservableCounter +} + +var _ unwrapper = (*afCounter)(nil) +var _ metric.Float64ObservableCounter = (*afCounter)(nil) + +func (i *afCounter) setDelegate(m metric.Meter) { + ctr, err := m.Float64ObservableCounter(i.name, i.opts...) + if err != nil { + GetErrorHandler().Handle(err) + return + } + i.delegate.Store(ctr) +} + +func (i *afCounter) Unwrap() metric.Observable { + if ctr := i.delegate.Load(); ctr != nil { + return ctr.(metric.Float64ObservableCounter) + } + return nil +} + +type afUpDownCounter struct { + embedded.Float64ObservableUpDownCounter + metric.Float64Observable + + name string + opts []metric.Float64ObservableUpDownCounterOption + + delegate atomic.Value //metric.Float64ObservableUpDownCounter +} + +var _ unwrapper = (*afUpDownCounter)(nil) +var _ metric.Float64ObservableUpDownCounter = (*afUpDownCounter)(nil) + +func (i *afUpDownCounter) setDelegate(m metric.Meter) { + ctr, err := m.Float64ObservableUpDownCounter(i.name, i.opts...) + if err != nil { + GetErrorHandler().Handle(err) + return + } + i.delegate.Store(ctr) +} + +func (i *afUpDownCounter) Unwrap() metric.Observable { + if ctr := i.delegate.Load(); ctr != nil { + return ctr.(metric.Float64ObservableUpDownCounter) + } + return nil +} + +type afGauge struct { + embedded.Float64ObservableGauge + metric.Float64Observable + + name string + opts []metric.Float64ObservableGaugeOption + + delegate atomic.Value //metric.Float64ObservableGauge +} + +var _ unwrapper = (*afGauge)(nil) +var _ metric.Float64ObservableGauge = (*afGauge)(nil) + +func (i *afGauge) setDelegate(m metric.Meter) { + ctr, err := m.Float64ObservableGauge(i.name, i.opts...) + if err != nil { + GetErrorHandler().Handle(err) + return + } + i.delegate.Store(ctr) +} + +func (i *afGauge) Unwrap() metric.Observable { + if ctr := i.delegate.Load(); ctr != nil { + return ctr.(metric.Float64ObservableGauge) + } + return nil +} + +type aiCounter struct { + embedded.Int64ObservableCounter + metric.Int64Observable + + name string + opts []metric.Int64ObservableCounterOption + + delegate atomic.Value //metric.Int64ObservableCounter +} + +var _ unwrapper = (*aiCounter)(nil) +var _ metric.Int64ObservableCounter = (*aiCounter)(nil) + +func (i *aiCounter) setDelegate(m metric.Meter) { + ctr, err := m.Int64ObservableCounter(i.name, i.opts...) + if err != nil { + GetErrorHandler().Handle(err) + return + } + i.delegate.Store(ctr) +} + +func (i *aiCounter) Unwrap() metric.Observable { + if ctr := i.delegate.Load(); ctr != nil { + return ctr.(metric.Int64ObservableCounter) + } + return nil +} + +type aiUpDownCounter struct { + embedded.Int64ObservableUpDownCounter + metric.Int64Observable + + name string + opts []metric.Int64ObservableUpDownCounterOption + + delegate atomic.Value //metric.Int64ObservableUpDownCounter +} + +var _ unwrapper = (*aiUpDownCounter)(nil) +var _ metric.Int64ObservableUpDownCounter = (*aiUpDownCounter)(nil) + +func (i *aiUpDownCounter) setDelegate(m metric.Meter) { + ctr, err := m.Int64ObservableUpDownCounter(i.name, i.opts...) + if err != nil { + GetErrorHandler().Handle(err) + return + } + i.delegate.Store(ctr) +} + +func (i *aiUpDownCounter) Unwrap() metric.Observable { + if ctr := i.delegate.Load(); ctr != nil { + return ctr.(metric.Int64ObservableUpDownCounter) + } + return nil +} + +type aiGauge struct { + embedded.Int64ObservableGauge + metric.Int64Observable + + name string + opts []metric.Int64ObservableGaugeOption + + delegate atomic.Value //metric.Int64ObservableGauge +} + +var _ unwrapper = (*aiGauge)(nil) +var _ metric.Int64ObservableGauge = (*aiGauge)(nil) + +func (i *aiGauge) setDelegate(m metric.Meter) { + ctr, err := m.Int64ObservableGauge(i.name, i.opts...) + if err != nil { + GetErrorHandler().Handle(err) + return + } + i.delegate.Store(ctr) +} + +func (i *aiGauge) Unwrap() metric.Observable { + if ctr := i.delegate.Load(); ctr != nil { + return ctr.(metric.Int64ObservableGauge) + } + return nil +} + +// Sync Instruments. +type sfCounter struct { + embedded.Float64Counter + + name string + opts []metric.Float64CounterOption + + delegate atomic.Value //metric.Float64Counter +} + +var _ metric.Float64Counter = (*sfCounter)(nil) + +func (i *sfCounter) setDelegate(m metric.Meter) { + ctr, err := m.Float64Counter(i.name, i.opts...) + if err != nil { + GetErrorHandler().Handle(err) + return + } + i.delegate.Store(ctr) +} + +func (i *sfCounter) Add(ctx context.Context, incr float64, opts ...metric.AddOption) { + if ctr := i.delegate.Load(); ctr != nil { + ctr.(metric.Float64Counter).Add(ctx, incr, opts...) + } +} + +type sfUpDownCounter struct { + embedded.Float64UpDownCounter + + name string + opts []metric.Float64UpDownCounterOption + + delegate atomic.Value //metric.Float64UpDownCounter +} + +var _ metric.Float64UpDownCounter = (*sfUpDownCounter)(nil) + +func (i *sfUpDownCounter) setDelegate(m metric.Meter) { + ctr, err := m.Float64UpDownCounter(i.name, i.opts...) + if err != nil { + GetErrorHandler().Handle(err) + return + } + i.delegate.Store(ctr) +} + +func (i *sfUpDownCounter) Add(ctx context.Context, incr float64, opts ...metric.AddOption) { + if ctr := i.delegate.Load(); ctr != nil { + ctr.(metric.Float64UpDownCounter).Add(ctx, incr, opts...) + } +} + +type sfHistogram struct { + embedded.Float64Histogram + + name string + opts []metric.Float64HistogramOption + + delegate atomic.Value //metric.Float64Histogram +} + +var _ metric.Float64Histogram = (*sfHistogram)(nil) + +func (i *sfHistogram) setDelegate(m metric.Meter) { + ctr, err := m.Float64Histogram(i.name, i.opts...) + if err != nil { + GetErrorHandler().Handle(err) + return + } + i.delegate.Store(ctr) +} + +func (i *sfHistogram) Record(ctx context.Context, x float64, opts ...metric.RecordOption) { + if ctr := i.delegate.Load(); ctr != nil { + ctr.(metric.Float64Histogram).Record(ctx, x, opts...) + } +} + +type siCounter struct { + embedded.Int64Counter + + name string + opts []metric.Int64CounterOption + + delegate atomic.Value //metric.Int64Counter +} + +var _ metric.Int64Counter = (*siCounter)(nil) + +func (i *siCounter) setDelegate(m metric.Meter) { + ctr, err := m.Int64Counter(i.name, i.opts...) + if err != nil { + GetErrorHandler().Handle(err) + return + } + i.delegate.Store(ctr) +} + +func (i *siCounter) Add(ctx context.Context, x int64, opts ...metric.AddOption) { + if ctr := i.delegate.Load(); ctr != nil { + ctr.(metric.Int64Counter).Add(ctx, x, opts...) + } +} + +type siUpDownCounter struct { + embedded.Int64UpDownCounter + + name string + opts []metric.Int64UpDownCounterOption + + delegate atomic.Value //metric.Int64UpDownCounter +} + +var _ metric.Int64UpDownCounter = (*siUpDownCounter)(nil) + +func (i *siUpDownCounter) setDelegate(m metric.Meter) { + ctr, err := m.Int64UpDownCounter(i.name, i.opts...) + if err != nil { + GetErrorHandler().Handle(err) + return + } + i.delegate.Store(ctr) +} + +func (i *siUpDownCounter) Add(ctx context.Context, x int64, opts ...metric.AddOption) { + if ctr := i.delegate.Load(); ctr != nil { + ctr.(metric.Int64UpDownCounter).Add(ctx, x, opts...) + } +} + +type siHistogram struct { + embedded.Int64Histogram + + name string + opts []metric.Int64HistogramOption + + delegate atomic.Value //metric.Int64Histogram +} + +var _ metric.Int64Histogram = (*siHistogram)(nil) + +func (i *siHistogram) setDelegate(m metric.Meter) { + ctr, err := m.Int64Histogram(i.name, i.opts...) + if err != nil { + GetErrorHandler().Handle(err) + return + } + i.delegate.Store(ctr) +} + +func (i *siHistogram) Record(ctx context.Context, x int64, opts ...metric.RecordOption) { + if ctr := i.delegate.Load(); ctr != nil { + ctr.(metric.Int64Histogram).Record(ctx, x, opts...) + } +} diff --git a/vendor/go.opentelemetry.io/otel/internal/global/meter.go b/vendor/go.opentelemetry.io/otel/internal/global/meter.go new file mode 100644 index 000000000..0097db478 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/internal/global/meter.go @@ -0,0 +1,354 @@ +// Copyright The OpenTelemetry 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 global // import "go.opentelemetry.io/otel/internal/global" + +import ( + "container/list" + "sync" + "sync/atomic" + + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/metric/embedded" +) + +// meterProvider is a placeholder for a configured SDK MeterProvider. +// +// All MeterProvider functionality is forwarded to a delegate once +// configured. +type meterProvider struct { + embedded.MeterProvider + + mtx sync.Mutex + meters map[il]*meter + + delegate metric.MeterProvider +} + +// setDelegate configures p to delegate all MeterProvider functionality to +// provider. +// +// All Meters provided prior to this function call are switched out to be +// Meters provided by provider. All instruments and callbacks are recreated and +// delegated. +// +// It is guaranteed by the caller that this happens only once. +func (p *meterProvider) setDelegate(provider metric.MeterProvider) { + p.mtx.Lock() + defer p.mtx.Unlock() + + p.delegate = provider + + if len(p.meters) == 0 { + return + } + + for _, meter := range p.meters { + meter.setDelegate(provider) + } + + p.meters = nil +} + +// Meter implements MeterProvider. +func (p *meterProvider) Meter(name string, opts ...metric.MeterOption) metric.Meter { + p.mtx.Lock() + defer p.mtx.Unlock() + + if p.delegate != nil { + return p.delegate.Meter(name, opts...) + } + + // At this moment it is guaranteed that no sdk is installed, save the meter in the meters map. + + c := metric.NewMeterConfig(opts...) + key := il{ + name: name, + version: c.InstrumentationVersion(), + } + + if p.meters == nil { + p.meters = make(map[il]*meter) + } + + if val, ok := p.meters[key]; ok { + return val + } + + t := &meter{name: name, opts: opts} + p.meters[key] = t + return t +} + +// meter is a placeholder for a metric.Meter. +// +// All Meter functionality is forwarded to a delegate once configured. +// Otherwise, all functionality is forwarded to a NoopMeter. +type meter struct { + embedded.Meter + + name string + opts []metric.MeterOption + + mtx sync.Mutex + instruments []delegatedInstrument + + registry list.List + + delegate atomic.Value // metric.Meter +} + +type delegatedInstrument interface { + setDelegate(metric.Meter) +} + +// setDelegate configures m to delegate all Meter functionality to Meters +// created by provider. +// +// All subsequent calls to the Meter methods will be passed to the delegate. +// +// It is guaranteed by the caller that this happens only once. +func (m *meter) setDelegate(provider metric.MeterProvider) { + meter := provider.Meter(m.name, m.opts...) + m.delegate.Store(meter) + + m.mtx.Lock() + defer m.mtx.Unlock() + + for _, inst := range m.instruments { + inst.setDelegate(meter) + } + + for e := m.registry.Front(); e != nil; e = e.Next() { + r := e.Value.(*registration) + r.setDelegate(meter) + m.registry.Remove(e) + } + + m.instruments = nil + m.registry.Init() +} + +func (m *meter) Int64Counter(name string, options ...metric.Int64CounterOption) (metric.Int64Counter, error) { + if del, ok := m.delegate.Load().(metric.Meter); ok { + return del.Int64Counter(name, options...) + } + m.mtx.Lock() + defer m.mtx.Unlock() + i := &siCounter{name: name, opts: options} + m.instruments = append(m.instruments, i) + return i, nil +} + +func (m *meter) Int64UpDownCounter(name string, options ...metric.Int64UpDownCounterOption) (metric.Int64UpDownCounter, error) { + if del, ok := m.delegate.Load().(metric.Meter); ok { + return del.Int64UpDownCounter(name, options...) + } + m.mtx.Lock() + defer m.mtx.Unlock() + i := &siUpDownCounter{name: name, opts: options} + m.instruments = append(m.instruments, i) + return i, nil +} + +func (m *meter) Int64Histogram(name string, options ...metric.Int64HistogramOption) (metric.Int64Histogram, error) { + if del, ok := m.delegate.Load().(metric.Meter); ok { + return del.Int64Histogram(name, options...) + } + m.mtx.Lock() + defer m.mtx.Unlock() + i := &siHistogram{name: name, opts: options} + m.instruments = append(m.instruments, i) + return i, nil +} + +func (m *meter) Int64ObservableCounter(name string, options ...metric.Int64ObservableCounterOption) (metric.Int64ObservableCounter, error) { + if del, ok := m.delegate.Load().(metric.Meter); ok { + return del.Int64ObservableCounter(name, options...) + } + m.mtx.Lock() + defer m.mtx.Unlock() + i := &aiCounter{name: name, opts: options} + m.instruments = append(m.instruments, i) + return i, nil +} + +func (m *meter) Int64ObservableUpDownCounter(name string, options ...metric.Int64ObservableUpDownCounterOption) (metric.Int64ObservableUpDownCounter, error) { + if del, ok := m.delegate.Load().(metric.Meter); ok { + return del.Int64ObservableUpDownCounter(name, options...) + } + m.mtx.Lock() + defer m.mtx.Unlock() + i := &aiUpDownCounter{name: name, opts: options} + m.instruments = append(m.instruments, i) + return i, nil +} + +func (m *meter) Int64ObservableGauge(name string, options ...metric.Int64ObservableGaugeOption) (metric.Int64ObservableGauge, error) { + if del, ok := m.delegate.Load().(metric.Meter); ok { + return del.Int64ObservableGauge(name, options...) + } + m.mtx.Lock() + defer m.mtx.Unlock() + i := &aiGauge{name: name, opts: options} + m.instruments = append(m.instruments, i) + return i, nil +} + +func (m *meter) Float64Counter(name string, options ...metric.Float64CounterOption) (metric.Float64Counter, error) { + if del, ok := m.delegate.Load().(metric.Meter); ok { + return del.Float64Counter(name, options...) + } + m.mtx.Lock() + defer m.mtx.Unlock() + i := &sfCounter{name: name, opts: options} + m.instruments = append(m.instruments, i) + return i, nil +} + +func (m *meter) Float64UpDownCounter(name string, options ...metric.Float64UpDownCounterOption) (metric.Float64UpDownCounter, error) { + if del, ok := m.delegate.Load().(metric.Meter); ok { + return del.Float64UpDownCounter(name, options...) + } + m.mtx.Lock() + defer m.mtx.Unlock() + i := &sfUpDownCounter{name: name, opts: options} + m.instruments = append(m.instruments, i) + return i, nil +} + +func (m *meter) Float64Histogram(name string, options ...metric.Float64HistogramOption) (metric.Float64Histogram, error) { + if del, ok := m.delegate.Load().(metric.Meter); ok { + return del.Float64Histogram(name, options...) + } + m.mtx.Lock() + defer m.mtx.Unlock() + i := &sfHistogram{name: name, opts: options} + m.instruments = append(m.instruments, i) + return i, nil +} + +func (m *meter) Float64ObservableCounter(name string, options ...metric.Float64ObservableCounterOption) (metric.Float64ObservableCounter, error) { + if del, ok := m.delegate.Load().(metric.Meter); ok { + return del.Float64ObservableCounter(name, options...) + } + m.mtx.Lock() + defer m.mtx.Unlock() + i := &afCounter{name: name, opts: options} + m.instruments = append(m.instruments, i) + return i, nil +} + +func (m *meter) Float64ObservableUpDownCounter(name string, options ...metric.Float64ObservableUpDownCounterOption) (metric.Float64ObservableUpDownCounter, error) { + if del, ok := m.delegate.Load().(metric.Meter); ok { + return del.Float64ObservableUpDownCounter(name, options...) + } + m.mtx.Lock() + defer m.mtx.Unlock() + i := &afUpDownCounter{name: name, opts: options} + m.instruments = append(m.instruments, i) + return i, nil +} + +func (m *meter) Float64ObservableGauge(name string, options ...metric.Float64ObservableGaugeOption) (metric.Float64ObservableGauge, error) { + if del, ok := m.delegate.Load().(metric.Meter); ok { + return del.Float64ObservableGauge(name, options...) + } + m.mtx.Lock() + defer m.mtx.Unlock() + i := &afGauge{name: name, opts: options} + m.instruments = append(m.instruments, i) + return i, nil +} + +// RegisterCallback captures the function that will be called during Collect. +func (m *meter) RegisterCallback(f metric.Callback, insts ...metric.Observable) (metric.Registration, error) { + if del, ok := m.delegate.Load().(metric.Meter); ok { + insts = unwrapInstruments(insts) + return del.RegisterCallback(f, insts...) + } + + m.mtx.Lock() + defer m.mtx.Unlock() + + reg := ®istration{instruments: insts, function: f} + e := m.registry.PushBack(reg) + reg.unreg = func() error { + m.mtx.Lock() + _ = m.registry.Remove(e) + m.mtx.Unlock() + return nil + } + return reg, nil +} + +type wrapped interface { + unwrap() metric.Observable +} + +func unwrapInstruments(instruments []metric.Observable) []metric.Observable { + out := make([]metric.Observable, 0, len(instruments)) + + for _, inst := range instruments { + if in, ok := inst.(wrapped); ok { + out = append(out, in.unwrap()) + } else { + out = append(out, inst) + } + } + + return out +} + +type registration struct { + embedded.Registration + + instruments []metric.Observable + function metric.Callback + + unreg func() error + unregMu sync.Mutex +} + +func (c *registration) setDelegate(m metric.Meter) { + insts := unwrapInstruments(c.instruments) + + c.unregMu.Lock() + defer c.unregMu.Unlock() + + if c.unreg == nil { + // Unregister already called. + return + } + + reg, err := m.RegisterCallback(c.function, insts...) + if err != nil { + GetErrorHandler().Handle(err) + } + + c.unreg = reg.Unregister +} + +func (c *registration) Unregister() error { + c.unregMu.Lock() + defer c.unregMu.Unlock() + if c.unreg == nil { + // Unregister already called. + return nil + } + + var err error + err, c.unreg = c.unreg(), nil + return err +} diff --git a/vendor/go.opentelemetry.io/otel/internal/global/state.go b/vendor/go.opentelemetry.io/otel/internal/global/state.go index 1ad38f828..7985005bc 100644 --- a/vendor/go.opentelemetry.io/otel/internal/global/state.go +++ b/vendor/go.opentelemetry.io/otel/internal/global/state.go @@ -19,6 +19,7 @@ import ( "sync" "sync/atomic" + "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) @@ -31,14 +32,20 @@ type ( propagatorsHolder struct { tm propagation.TextMapPropagator } + + meterProviderHolder struct { + mp metric.MeterProvider + } ) var ( - globalTracer = defaultTracerValue() - globalPropagators = defaultPropagatorsValue() + globalTracer = defaultTracerValue() + globalPropagators = defaultPropagatorsValue() + globalMeterProvider = defaultMeterProvider() delegateTraceOnce sync.Once delegateTextMapPropagatorOnce sync.Once + delegateMeterOnce sync.Once ) // TracerProvider is the internal implementation for global.TracerProvider. @@ -102,6 +109,34 @@ func SetTextMapPropagator(p propagation.TextMapPropagator) { globalPropagators.Store(propagatorsHolder{tm: p}) } +// MeterProvider is the internal implementation for global.MeterProvider. +func MeterProvider() metric.MeterProvider { + return globalMeterProvider.Load().(meterProviderHolder).mp +} + +// SetMeterProvider is the internal implementation for global.SetMeterProvider. +func SetMeterProvider(mp metric.MeterProvider) { + current := MeterProvider() + if _, cOk := current.(*meterProvider); cOk { + if _, mpOk := mp.(*meterProvider); mpOk && current == mp { + // Do not assign the default delegating MeterProvider to delegate + // to itself. + Error( + errors.New("no delegate configured in meter provider"), + "Setting meter provider to it's current value. No delegate will be configured", + ) + return + } + } + + delegateMeterOnce.Do(func() { + if def, ok := current.(*meterProvider); ok { + def.setDelegate(mp) + } + }) + globalMeterProvider.Store(meterProviderHolder{mp: mp}) +} + func defaultTracerValue() *atomic.Value { v := &atomic.Value{} v.Store(tracerProviderHolder{tp: &tracerProvider{}}) @@ -113,3 +148,9 @@ func defaultPropagatorsValue() *atomic.Value { v.Store(propagatorsHolder{tm: newTextMapPropagator()}) return v } + +func defaultMeterProvider() *atomic.Value { + v := &atomic.Value{} + v.Store(meterProviderHolder{mp: &meterProvider{}}) + return v +} diff --git a/vendor/go.opentelemetry.io/otel/metric.go b/vendor/go.opentelemetry.io/otel/metric.go new file mode 100644 index 000000000..f95517195 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/metric.go @@ -0,0 +1,53 @@ +// Copyright The OpenTelemetry 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 otel // import "go.opentelemetry.io/otel" + +import ( + "go.opentelemetry.io/otel/internal/global" + "go.opentelemetry.io/otel/metric" +) + +// Meter returns a Meter from the global MeterProvider. The name must be the +// name of the library providing instrumentation. This name may be the same as +// the instrumented code only if that code provides built-in instrumentation. +// If the name is empty, then a implementation defined default name will be +// used instead. +// +// If this is called before a global MeterProvider is registered the returned +// Meter will be a No-op implementation of a Meter. When a global MeterProvider +// is registered for the first time, the returned Meter, and all the +// instruments it has created or will create, are recreated automatically from +// the new MeterProvider. +// +// This is short for GetMeterProvider().Meter(name). +func Meter(name string, opts ...metric.MeterOption) metric.Meter { + return GetMeterProvider().Meter(name, opts...) +} + +// GetMeterProvider returns the registered global meter provider. +// +// If no global GetMeterProvider has been registered, a No-op GetMeterProvider +// implementation is returned. When a global GetMeterProvider is registered for +// the first time, the returned GetMeterProvider, and all the Meters it has +// created or will create, are recreated automatically from the new +// GetMeterProvider. +func GetMeterProvider() metric.MeterProvider { + return global.MeterProvider() +} + +// SetMeterProvider registers mp as the global MeterProvider. +func SetMeterProvider(mp metric.MeterProvider) { + global.SetMeterProvider(mp) +} diff --git a/vendor/go.opentelemetry.io/otel/metric/LICENSE b/vendor/go.opentelemetry.io/otel/metric/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/metric/LICENSE @@ -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. diff --git a/vendor/go.opentelemetry.io/otel/metric/asyncfloat64.go b/vendor/go.opentelemetry.io/otel/metric/asyncfloat64.go new file mode 100644 index 000000000..072baa8e8 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/metric/asyncfloat64.go @@ -0,0 +1,271 @@ +// Copyright The OpenTelemetry 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 metric // import "go.opentelemetry.io/otel/metric" + +import ( + "context" + + "go.opentelemetry.io/otel/metric/embedded" +) + +// Float64Observable describes a set of instruments used asynchronously to +// record float64 measurements once per collection cycle. Observations of +// these instruments are only made within a callback. +// +// Warning: Methods may be added to this interface in minor releases. +type Float64Observable interface { + Observable + + float64Observable() +} + +// Float64ObservableCounter is an instrument used to asynchronously record +// increasing float64 measurements once per collection cycle. Observations are +// only made within a callback for this instrument. The value observed is +// assumed the to be the cumulative sum of the count. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for +// unimplemented methods. +type Float64ObservableCounter interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Float64ObservableCounter + + Float64Observable +} + +// Float64ObservableCounterConfig contains options for asynchronous counter +// instruments that record int64 values. +type Float64ObservableCounterConfig struct { + description string + unit string + callbacks []Float64Callback +} + +// NewFloat64ObservableCounterConfig returns a new +// [Float64ObservableCounterConfig] with all opts applied. +func NewFloat64ObservableCounterConfig(opts ...Float64ObservableCounterOption) Float64ObservableCounterConfig { + var config Float64ObservableCounterConfig + for _, o := range opts { + config = o.applyFloat64ObservableCounter(config) + } + return config +} + +// Description returns the configured description. +func (c Float64ObservableCounterConfig) Description() string { + return c.description +} + +// Unit returns the configured unit. +func (c Float64ObservableCounterConfig) Unit() string { + return c.unit +} + +// Callbacks returns the configured callbacks. +func (c Float64ObservableCounterConfig) Callbacks() []Float64Callback { + return c.callbacks +} + +// Float64ObservableCounterOption applies options to a +// [Float64ObservableCounterConfig]. See [Float64ObservableOption] and +// [InstrumentOption] for other options that can be used as a +// Float64ObservableCounterOption. +type Float64ObservableCounterOption interface { + applyFloat64ObservableCounter(Float64ObservableCounterConfig) Float64ObservableCounterConfig +} + +// Float64ObservableUpDownCounter is an instrument used to asynchronously +// record float64 measurements once per collection cycle. Observations are only +// made within a callback for this instrument. The value observed is assumed +// the to be the cumulative sum of the count. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Float64ObservableUpDownCounter interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Float64ObservableUpDownCounter + + Float64Observable +} + +// Float64ObservableUpDownCounterConfig contains options for asynchronous +// counter instruments that record int64 values. +type Float64ObservableUpDownCounterConfig struct { + description string + unit string + callbacks []Float64Callback +} + +// NewFloat64ObservableUpDownCounterConfig returns a new +// [Float64ObservableUpDownCounterConfig] with all opts applied. +func NewFloat64ObservableUpDownCounterConfig(opts ...Float64ObservableUpDownCounterOption) Float64ObservableUpDownCounterConfig { + var config Float64ObservableUpDownCounterConfig + for _, o := range opts { + config = o.applyFloat64ObservableUpDownCounter(config) + } + return config +} + +// Description returns the configured description. +func (c Float64ObservableUpDownCounterConfig) Description() string { + return c.description +} + +// Unit returns the configured unit. +func (c Float64ObservableUpDownCounterConfig) Unit() string { + return c.unit +} + +// Callbacks returns the configured callbacks. +func (c Float64ObservableUpDownCounterConfig) Callbacks() []Float64Callback { + return c.callbacks +} + +// Float64ObservableUpDownCounterOption applies options to a +// [Float64ObservableUpDownCounterConfig]. See [Float64ObservableOption] and +// [InstrumentOption] for other options that can be used as a +// Float64ObservableUpDownCounterOption. +type Float64ObservableUpDownCounterOption interface { + applyFloat64ObservableUpDownCounter(Float64ObservableUpDownCounterConfig) Float64ObservableUpDownCounterConfig +} + +// Float64ObservableGauge is an instrument used to asynchronously record +// instantaneous float64 measurements once per collection cycle. Observations +// are only made within a callback for this instrument. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Float64ObservableGauge interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Float64ObservableGauge + + Float64Observable +} + +// Float64ObservableGaugeConfig contains options for asynchronous counter +// instruments that record int64 values. +type Float64ObservableGaugeConfig struct { + description string + unit string + callbacks []Float64Callback +} + +// NewFloat64ObservableGaugeConfig returns a new [Float64ObservableGaugeConfig] +// with all opts applied. +func NewFloat64ObservableGaugeConfig(opts ...Float64ObservableGaugeOption) Float64ObservableGaugeConfig { + var config Float64ObservableGaugeConfig + for _, o := range opts { + config = o.applyFloat64ObservableGauge(config) + } + return config +} + +// Description returns the configured description. +func (c Float64ObservableGaugeConfig) Description() string { + return c.description +} + +// Unit returns the configured unit. +func (c Float64ObservableGaugeConfig) Unit() string { + return c.unit +} + +// Callbacks returns the configured callbacks. +func (c Float64ObservableGaugeConfig) Callbacks() []Float64Callback { + return c.callbacks +} + +// Float64ObservableGaugeOption applies options to a +// [Float64ObservableGaugeConfig]. See [Float64ObservableOption] and +// [InstrumentOption] for other options that can be used as a +// Float64ObservableGaugeOption. +type Float64ObservableGaugeOption interface { + applyFloat64ObservableGauge(Float64ObservableGaugeConfig) Float64ObservableGaugeConfig +} + +// Float64Observer is a recorder of float64 measurements. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Float64Observer interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Float64Observer + + // Observe records the float64 value. + // + // Use the WithAttributeSet (or, if performance is not a concern, + // the WithAttributes) option to include measurement attributes. + Observe(value float64, options ...ObserveOption) +} + +// Float64Callback is a function registered with a Meter that makes +// observations for a Float64Observerable instrument it is registered with. +// Calls to the Float64Observer record measurement values for the +// Float64Observable. +// +// The function needs to complete in a finite amount of time and the deadline +// of the passed context is expected to be honored. +// +// The function needs to make unique observations across all registered +// Float64Callbacks. Meaning, it should not report measurements with the same +// attributes as another Float64Callbacks also registered for the same +// instrument. +// +// The function needs to be concurrent safe. +type Float64Callback func(context.Context, Float64Observer) error + +// Float64ObservableOption applies options to float64 Observer instruments. +type Float64ObservableOption interface { + Float64ObservableCounterOption + Float64ObservableUpDownCounterOption + Float64ObservableGaugeOption +} + +type float64CallbackOpt struct { + cback Float64Callback +} + +func (o float64CallbackOpt) applyFloat64ObservableCounter(cfg Float64ObservableCounterConfig) Float64ObservableCounterConfig { + cfg.callbacks = append(cfg.callbacks, o.cback) + return cfg +} + +func (o float64CallbackOpt) applyFloat64ObservableUpDownCounter(cfg Float64ObservableUpDownCounterConfig) Float64ObservableUpDownCounterConfig { + cfg.callbacks = append(cfg.callbacks, o.cback) + return cfg +} + +func (o float64CallbackOpt) applyFloat64ObservableGauge(cfg Float64ObservableGaugeConfig) Float64ObservableGaugeConfig { + cfg.callbacks = append(cfg.callbacks, o.cback) + return cfg +} + +// WithFloat64Callback adds callback to be called for an instrument. +func WithFloat64Callback(callback Float64Callback) Float64ObservableOption { + return float64CallbackOpt{callback} +} diff --git a/vendor/go.opentelemetry.io/otel/metric/asyncint64.go b/vendor/go.opentelemetry.io/otel/metric/asyncint64.go new file mode 100644 index 000000000..9bd6ebf02 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/metric/asyncint64.go @@ -0,0 +1,269 @@ +// Copyright The OpenTelemetry 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 metric // import "go.opentelemetry.io/otel/metric" + +import ( + "context" + + "go.opentelemetry.io/otel/metric/embedded" +) + +// Int64Observable describes a set of instruments used asynchronously to record +// int64 measurements once per collection cycle. Observations of these +// instruments are only made within a callback. +// +// Warning: Methods may be added to this interface in minor releases. +type Int64Observable interface { + Observable + + int64Observable() +} + +// Int64ObservableCounter is an instrument used to asynchronously record +// increasing int64 measurements once per collection cycle. Observations are +// only made within a callback for this instrument. The value observed is +// assumed the to be the cumulative sum of the count. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Int64ObservableCounter interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Int64ObservableCounter + + Int64Observable +} + +// Int64ObservableCounterConfig contains options for asynchronous counter +// instruments that record int64 values. +type Int64ObservableCounterConfig struct { + description string + unit string + callbacks []Int64Callback +} + +// NewInt64ObservableCounterConfig returns a new [Int64ObservableCounterConfig] +// with all opts applied. +func NewInt64ObservableCounterConfig(opts ...Int64ObservableCounterOption) Int64ObservableCounterConfig { + var config Int64ObservableCounterConfig + for _, o := range opts { + config = o.applyInt64ObservableCounter(config) + } + return config +} + +// Description returns the configured description. +func (c Int64ObservableCounterConfig) Description() string { + return c.description +} + +// Unit returns the configured unit. +func (c Int64ObservableCounterConfig) Unit() string { + return c.unit +} + +// Callbacks returns the configured callbacks. +func (c Int64ObservableCounterConfig) Callbacks() []Int64Callback { + return c.callbacks +} + +// Int64ObservableCounterOption applies options to a +// [Int64ObservableCounterConfig]. See [Int64ObservableOption] and +// [InstrumentOption] for other options that can be used as an +// Int64ObservableCounterOption. +type Int64ObservableCounterOption interface { + applyInt64ObservableCounter(Int64ObservableCounterConfig) Int64ObservableCounterConfig +} + +// Int64ObservableUpDownCounter is an instrument used to asynchronously record +// int64 measurements once per collection cycle. Observations are only made +// within a callback for this instrument. The value observed is assumed the to +// be the cumulative sum of the count. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Int64ObservableUpDownCounter interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Int64ObservableUpDownCounter + + Int64Observable +} + +// Int64ObservableUpDownCounterConfig contains options for asynchronous counter +// instruments that record int64 values. +type Int64ObservableUpDownCounterConfig struct { + description string + unit string + callbacks []Int64Callback +} + +// NewInt64ObservableUpDownCounterConfig returns a new +// [Int64ObservableUpDownCounterConfig] with all opts applied. +func NewInt64ObservableUpDownCounterConfig(opts ...Int64ObservableUpDownCounterOption) Int64ObservableUpDownCounterConfig { + var config Int64ObservableUpDownCounterConfig + for _, o := range opts { + config = o.applyInt64ObservableUpDownCounter(config) + } + return config +} + +// Description returns the configured description. +func (c Int64ObservableUpDownCounterConfig) Description() string { + return c.description +} + +// Unit returns the configured unit. +func (c Int64ObservableUpDownCounterConfig) Unit() string { + return c.unit +} + +// Callbacks returns the configured callbacks. +func (c Int64ObservableUpDownCounterConfig) Callbacks() []Int64Callback { + return c.callbacks +} + +// Int64ObservableUpDownCounterOption applies options to a +// [Int64ObservableUpDownCounterConfig]. See [Int64ObservableOption] and +// [InstrumentOption] for other options that can be used as an +// Int64ObservableUpDownCounterOption. +type Int64ObservableUpDownCounterOption interface { + applyInt64ObservableUpDownCounter(Int64ObservableUpDownCounterConfig) Int64ObservableUpDownCounterConfig +} + +// Int64ObservableGauge is an instrument used to asynchronously record +// instantaneous int64 measurements once per collection cycle. Observations are +// only made within a callback for this instrument. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Int64ObservableGauge interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Int64ObservableGauge + + Int64Observable +} + +// Int64ObservableGaugeConfig contains options for asynchronous counter +// instruments that record int64 values. +type Int64ObservableGaugeConfig struct { + description string + unit string + callbacks []Int64Callback +} + +// NewInt64ObservableGaugeConfig returns a new [Int64ObservableGaugeConfig] +// with all opts applied. +func NewInt64ObservableGaugeConfig(opts ...Int64ObservableGaugeOption) Int64ObservableGaugeConfig { + var config Int64ObservableGaugeConfig + for _, o := range opts { + config = o.applyInt64ObservableGauge(config) + } + return config +} + +// Description returns the configured description. +func (c Int64ObservableGaugeConfig) Description() string { + return c.description +} + +// Unit returns the configured unit. +func (c Int64ObservableGaugeConfig) Unit() string { + return c.unit +} + +// Callbacks returns the configured callbacks. +func (c Int64ObservableGaugeConfig) Callbacks() []Int64Callback { + return c.callbacks +} + +// Int64ObservableGaugeOption applies options to a +// [Int64ObservableGaugeConfig]. See [Int64ObservableOption] and +// [InstrumentOption] for other options that can be used as an +// Int64ObservableGaugeOption. +type Int64ObservableGaugeOption interface { + applyInt64ObservableGauge(Int64ObservableGaugeConfig) Int64ObservableGaugeConfig +} + +// Int64Observer is a recorder of int64 measurements. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Int64Observer interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Int64Observer + + // Observe records the int64 value. + // + // Use the WithAttributeSet (or, if performance is not a concern, + // the WithAttributes) option to include measurement attributes. + Observe(value int64, options ...ObserveOption) +} + +// Int64Callback is a function registered with a Meter that makes observations +// for an Int64Observerable instrument it is registered with. Calls to the +// Int64Observer record measurement values for the Int64Observable. +// +// The function needs to complete in a finite amount of time and the deadline +// of the passed context is expected to be honored. +// +// The function needs to make unique observations across all registered +// Int64Callbacks. Meaning, it should not report measurements with the same +// attributes as another Int64Callbacks also registered for the same +// instrument. +// +// The function needs to be concurrent safe. +type Int64Callback func(context.Context, Int64Observer) error + +// Int64ObservableOption applies options to int64 Observer instruments. +type Int64ObservableOption interface { + Int64ObservableCounterOption + Int64ObservableUpDownCounterOption + Int64ObservableGaugeOption +} + +type int64CallbackOpt struct { + cback Int64Callback +} + +func (o int64CallbackOpt) applyInt64ObservableCounter(cfg Int64ObservableCounterConfig) Int64ObservableCounterConfig { + cfg.callbacks = append(cfg.callbacks, o.cback) + return cfg +} + +func (o int64CallbackOpt) applyInt64ObservableUpDownCounter(cfg Int64ObservableUpDownCounterConfig) Int64ObservableUpDownCounterConfig { + cfg.callbacks = append(cfg.callbacks, o.cback) + return cfg +} + +func (o int64CallbackOpt) applyInt64ObservableGauge(cfg Int64ObservableGaugeConfig) Int64ObservableGaugeConfig { + cfg.callbacks = append(cfg.callbacks, o.cback) + return cfg +} + +// WithInt64Callback adds callback to be called for an instrument. +func WithInt64Callback(callback Int64Callback) Int64ObservableOption { + return int64CallbackOpt{callback} +} diff --git a/vendor/go.opentelemetry.io/otel/metric/config.go b/vendor/go.opentelemetry.io/otel/metric/config.go new file mode 100644 index 000000000..778ad2d74 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/metric/config.go @@ -0,0 +1,92 @@ +// Copyright The OpenTelemetry 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 metric // import "go.opentelemetry.io/otel/metric" + +import "go.opentelemetry.io/otel/attribute" + +// MeterConfig contains options for Meters. +type MeterConfig struct { + instrumentationVersion string + schemaURL string + attrs attribute.Set + + // Ensure forward compatibility by explicitly making this not comparable. + noCmp [0]func() //nolint: unused // This is indeed used. +} + +// InstrumentationVersion returns the version of the library providing +// instrumentation. +func (cfg MeterConfig) InstrumentationVersion() string { + return cfg.instrumentationVersion +} + +// InstrumentationAttributes returns the attributes associated with the library +// providing instrumentation. +func (cfg MeterConfig) InstrumentationAttributes() attribute.Set { + return cfg.attrs +} + +// SchemaURL is the schema_url of the library providing instrumentation. +func (cfg MeterConfig) SchemaURL() string { + return cfg.schemaURL +} + +// MeterOption is an interface for applying Meter options. +type MeterOption interface { + // applyMeter is used to set a MeterOption value of a MeterConfig. + applyMeter(MeterConfig) MeterConfig +} + +// NewMeterConfig creates a new MeterConfig and applies +// all the given options. +func NewMeterConfig(opts ...MeterOption) MeterConfig { + var config MeterConfig + for _, o := range opts { + config = o.applyMeter(config) + } + return config +} + +type meterOptionFunc func(MeterConfig) MeterConfig + +func (fn meterOptionFunc) applyMeter(cfg MeterConfig) MeterConfig { + return fn(cfg) +} + +// WithInstrumentationVersion sets the instrumentation version. +func WithInstrumentationVersion(version string) MeterOption { + return meterOptionFunc(func(config MeterConfig) MeterConfig { + config.instrumentationVersion = version + return config + }) +} + +// WithInstrumentationAttributes sets the instrumentation attributes. +// +// The passed attributes will be de-duplicated. +func WithInstrumentationAttributes(attr ...attribute.KeyValue) MeterOption { + return meterOptionFunc(func(config MeterConfig) MeterConfig { + config.attrs = attribute.NewSet(attr...) + return config + }) +} + +// WithSchemaURL sets the schema URL. +func WithSchemaURL(schemaURL string) MeterOption { + return meterOptionFunc(func(config MeterConfig) MeterConfig { + config.schemaURL = schemaURL + return config + }) +} diff --git a/vendor/go.opentelemetry.io/otel/metric/doc.go b/vendor/go.opentelemetry.io/otel/metric/doc.go new file mode 100644 index 000000000..ae24e448d --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/metric/doc.go @@ -0,0 +1,170 @@ +// Copyright The OpenTelemetry 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 metric provides the OpenTelemetry API used to measure metrics about +source code operation. + +This API is separate from its implementation so the instrumentation built from +it is reusable. See [go.opentelemetry.io/otel/sdk/metric] for the official +OpenTelemetry implementation of this API. + +All measurements made with this package are made via instruments. These +instruments are created by a [Meter] which itself is created by a +[MeterProvider]. Applications need to accept a [MeterProvider] implementation +as a starting point when instrumenting. This can be done directly, or by using +the OpenTelemetry global MeterProvider via [GetMeterProvider]. Using an +appropriately named [Meter] from the accepted [MeterProvider], instrumentation +can then be built from the [Meter]'s instruments. + +# Instruments + +Each instrument is designed to make measurements of a particular type. Broadly, +all instruments fall into two overlapping logical categories: asynchronous or +synchronous, and int64 or float64. + +All synchronous instruments ([Int64Counter], [Int64UpDownCounter], +[Int64Histogram], [Float64Counter], [Float64UpDownCounter], and +[Float64Histogram]) are used to measure the operation and performance of source +code during the source code execution. These instruments only make measurements +when the source code they instrument is run. + +All asynchronous instruments ([Int64ObservableCounter], +[Int64ObservableUpDownCounter], [Int64ObservableGauge], +[Float64ObservableCounter], [Float64ObservableUpDownCounter], and +[Float64ObservableGauge]) are used to measure metrics outside of the execution +of source code. They are said to make "observations" via a callback function +called once every measurement collection cycle. + +Each instrument is also grouped by the value type it measures. Either int64 or +float64. The value being measured will dictate which instrument in these +categories to use. + +Outside of these two broad categories, instruments are described by the +function they are designed to serve. All Counters ([Int64Counter], +[Float64Counter], [Int64ObservableCounter], and [Float64ObservableCounter]) are +designed to measure values that never decrease in value, but instead only +incrementally increase in value. UpDownCounters ([Int64UpDownCounter], +[Float64UpDownCounter], [Int64ObservableUpDownCounter], and +[Float64ObservableUpDownCounter]) on the other hand, are designed to measure +values that can increase and decrease. When more information needs to be +conveyed about all the synchronous measurements made during a collection cycle, +a Histogram ([Int64Histogram] and [Float64Histogram]) should be used. Finally, +when just the most recent measurement needs to be conveyed about an +asynchronous measurement, a Gauge ([Int64ObservableGauge] and +[Float64ObservableGauge]) should be used. + +See the [OpenTelemetry documentation] for more information about instruments +and their intended use. + +# Measurements + +Measurements are made by recording values and information about the values with +an instrument. How these measurements are recorded depends on the instrument. + +Measurements for synchronous instruments ([Int64Counter], [Int64UpDownCounter], +[Int64Histogram], [Float64Counter], [Float64UpDownCounter], and +[Float64Histogram]) are recorded using the instrument methods directly. All +counter instruments have an Add method that is used to measure an increment +value, and all histogram instruments have a Record method to measure a data +point. + +Asynchronous instruments ([Int64ObservableCounter], +[Int64ObservableUpDownCounter], [Int64ObservableGauge], +[Float64ObservableCounter], [Float64ObservableUpDownCounter], and +[Float64ObservableGauge]) record measurements within a callback function. The +callback is registered with the Meter which ensures the callback is called once +per collection cycle. A callback can be registered two ways: during the +instrument's creation using an option, or later using the RegisterCallback +method of the [Meter] that created the instrument. + +If the following criteria are met, an option ([WithInt64Callback] or +[WithFloat64Callback]) can be used during the asynchronous instrument's +creation to register a callback ([Int64Callback] or [Float64Callback], +respectively): + + - The measurement process is known when the instrument is created + - Only that instrument will make a measurement within the callback + - The callback never needs to be unregistered + +If the criteria are not met, use the RegisterCallback method of the [Meter] that +created the instrument to register a [Callback]. + +# API Implementations + +This package does not conform to the standard Go versioning policy, all of its +interfaces may have methods added to them without a package major version bump. +This non-standard API evolution could surprise an uninformed implementation +author. They could unknowingly build their implementation in a way that would +result in a runtime panic for their users that update to the new API. + +The API is designed to help inform an instrumentation author about this +non-standard API evolution. It requires them to choose a default behavior for +unimplemented interface methods. There are three behavior choices they can +make: + + - Compilation failure + - Panic + - Default to another implementation + +All interfaces in this API embed a corresponding interface from +[go.opentelemetry.io/otel/metric/embedded]. If an author wants the default +behavior of their implementations to be a compilation failure, signaling to +their users they need to update to the latest version of that implementation, +they need to embed the corresponding interface from +[go.opentelemetry.io/otel/metric/embedded] in their implementation. For +example, + + import "go.opentelemetry.io/otel/metric/embedded" + + type MeterProvider struct { + embedded.MeterProvider + // ... + } + +If an author wants the default behavior of their implementations to a panic, +they need to embed the API interface directly. + + import "go.opentelemetry.io/otel/metric" + + type MeterProvider struct { + metric.MeterProvider + // ... + } + +This is not a recommended behavior as it could lead to publishing packages that +contain runtime panics when users update other package that use newer versions +of [go.opentelemetry.io/otel/metric]. + +Finally, an author can embed another implementation in theirs. The embedded +implementation will be used for methods not defined by the author. For example, +an author who want to default to silently dropping the call can use +[go.opentelemetry.io/otel/metric/noop]: + + import "go.opentelemetry.io/otel/metric/noop" + + type MeterProvider struct { + noop.MeterProvider + // ... + } + +It is strongly recommended that authors only embed +[go.opentelemetry.io/otel/metric/noop] if they choose this default behavior. +That implementation is the only one OpenTelemetry authors can guarantee will +fully implement all the API interfaces when a user updates their API. + +[OpenTelemetry documentation]: https://opentelemetry.io/docs/concepts/signals/metrics/ +[GetMeterProvider]: https://pkg.go.dev/go.opentelemetry.io/otel#GetMeterProvider +*/ +package metric // import "go.opentelemetry.io/otel/metric" diff --git a/vendor/go.opentelemetry.io/otel/metric/embedded/embedded.go b/vendor/go.opentelemetry.io/otel/metric/embedded/embedded.go new file mode 100644 index 000000000..ae0bdbd2e --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/metric/embedded/embedded.go @@ -0,0 +1,234 @@ +// Copyright The OpenTelemetry 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 embedded provides interfaces embedded within the [OpenTelemetry +// metric API]. +// +// Implementers of the [OpenTelemetry metric API] can embed the relevant type +// from this package into their implementation directly. Doing so will result +// in a compilation error for users when the [OpenTelemetry metric API] is +// extended (which is something that can happen without a major version bump of +// the API package). +// +// [OpenTelemetry metric API]: https://pkg.go.dev/go.opentelemetry.io/otel/metric +package embedded // import "go.opentelemetry.io/otel/metric/embedded" + +// MeterProvider is embedded in +// [go.opentelemetry.io/otel/metric.MeterProvider]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.MeterProvider] if you want users to +// experience a compilation error, signaling they need to update to your latest +// implementation, when the [go.opentelemetry.io/otel/metric.MeterProvider] +// interface is extended (which is something that can happen without a major +// version bump of the API package). +type MeterProvider interface{ meterProvider() } + +// Meter is embedded in [go.opentelemetry.io/otel/metric.Meter]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Meter] if you want users to experience a +// compilation error, signaling they need to update to your latest +// implementation, when the [go.opentelemetry.io/otel/metric.Meter] interface +// is extended (which is something that can happen without a major version bump +// of the API package). +type Meter interface{ meter() } + +// Float64Observer is embedded in +// [go.opentelemetry.io/otel/metric.Float64Observer]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Float64Observer] if you want +// users to experience a compilation error, signaling they need to update to +// your latest implementation, when the +// [go.opentelemetry.io/otel/metric.Float64Observer] interface is +// extended (which is something that can happen without a major version bump of +// the API package). +type Float64Observer interface{ float64Observer() } + +// Int64Observer is embedded in +// [go.opentelemetry.io/otel/metric.Int64Observer]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Int64Observer] if you want users +// to experience a compilation error, signaling they need to update to your +// latest implementation, when the +// [go.opentelemetry.io/otel/metric.Int64Observer] interface is +// extended (which is something that can happen without a major version bump of +// the API package). +type Int64Observer interface{ int64Observer() } + +// Observer is embedded in [go.opentelemetry.io/otel/metric.Observer]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Observer] if you want users to experience a +// compilation error, signaling they need to update to your latest +// implementation, when the [go.opentelemetry.io/otel/metric.Observer] +// interface is extended (which is something that can happen without a major +// version bump of the API package). +type Observer interface{ observer() } + +// Registration is embedded in [go.opentelemetry.io/otel/metric.Registration]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Registration] if you want users to +// experience a compilation error, signaling they need to update to your latest +// implementation, when the [go.opentelemetry.io/otel/metric.Registration] +// interface is extended (which is something that can happen without a major +// version bump of the API package). +type Registration interface{ registration() } + +// Float64Counter is embedded in +// [go.opentelemetry.io/otel/metric.Float64Counter]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Float64Counter] if you want +// users to experience a compilation error, signaling they need to update to +// your latest implementation, when the +// [go.opentelemetry.io/otel/metric.Float64Counter] interface is +// extended (which is something that can happen without a major version bump of +// the API package). +type Float64Counter interface{ float64Counter() } + +// Float64Histogram is embedded in +// [go.opentelemetry.io/otel/metric.Float64Histogram]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Float64Histogram] if you want +// users to experience a compilation error, signaling they need to update to +// your latest implementation, when the +// [go.opentelemetry.io/otel/metric.Float64Histogram] interface is +// extended (which is something that can happen without a major version bump of +// the API package). +type Float64Histogram interface{ float64Histogram() } + +// Float64ObservableCounter is embedded in +// [go.opentelemetry.io/otel/metric.Float64ObservableCounter]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Float64ObservableCounter] if you +// want users to experience a compilation error, signaling they need to update +// to your latest implementation, when the +// [go.opentelemetry.io/otel/metric.Float64ObservableCounter] +// interface is extended (which is something that can happen without a major +// version bump of the API package). +type Float64ObservableCounter interface{ float64ObservableCounter() } + +// Float64ObservableGauge is embedded in +// [go.opentelemetry.io/otel/metric.Float64ObservableGauge]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Float64ObservableGauge] if you +// want users to experience a compilation error, signaling they need to update +// to your latest implementation, when the +// [go.opentelemetry.io/otel/metric.Float64ObservableGauge] +// interface is extended (which is something that can happen without a major +// version bump of the API package). +type Float64ObservableGauge interface{ float64ObservableGauge() } + +// Float64ObservableUpDownCounter is embedded in +// [go.opentelemetry.io/otel/metric.Float64ObservableUpDownCounter]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Float64ObservableUpDownCounter] +// if you want users to experience a compilation error, signaling they need to +// update to your latest implementation, when the +// [go.opentelemetry.io/otel/metric.Float64ObservableUpDownCounter] +// interface is extended (which is something that can happen without a major +// version bump of the API package). +type Float64ObservableUpDownCounter interface{ float64ObservableUpDownCounter() } + +// Float64UpDownCounter is embedded in +// [go.opentelemetry.io/otel/metric.Float64UpDownCounter]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Float64UpDownCounter] if you +// want users to experience a compilation error, signaling they need to update +// to your latest implementation, when the +// [go.opentelemetry.io/otel/metric.Float64UpDownCounter] interface +// is extended (which is something that can happen without a major version bump +// of the API package). +type Float64UpDownCounter interface{ float64UpDownCounter() } + +// Int64Counter is embedded in +// [go.opentelemetry.io/otel/metric.Int64Counter]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Int64Counter] if you want users +// to experience a compilation error, signaling they need to update to your +// latest implementation, when the +// [go.opentelemetry.io/otel/metric.Int64Counter] interface is +// extended (which is something that can happen without a major version bump of +// the API package). +type Int64Counter interface{ int64Counter() } + +// Int64Histogram is embedded in +// [go.opentelemetry.io/otel/metric.Int64Histogram]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Int64Histogram] if you want +// users to experience a compilation error, signaling they need to update to +// your latest implementation, when the +// [go.opentelemetry.io/otel/metric.Int64Histogram] interface is +// extended (which is something that can happen without a major version bump of +// the API package). +type Int64Histogram interface{ int64Histogram() } + +// Int64ObservableCounter is embedded in +// [go.opentelemetry.io/otel/metric.Int64ObservableCounter]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Int64ObservableCounter] if you +// want users to experience a compilation error, signaling they need to update +// to your latest implementation, when the +// [go.opentelemetry.io/otel/metric.Int64ObservableCounter] +// interface is extended (which is something that can happen without a major +// version bump of the API package). +type Int64ObservableCounter interface{ int64ObservableCounter() } + +// Int64ObservableGauge is embedded in +// [go.opentelemetry.io/otel/metric.Int64ObservableGauge]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Int64ObservableGauge] if you +// want users to experience a compilation error, signaling they need to update +// to your latest implementation, when the +// [go.opentelemetry.io/otel/metric.Int64ObservableGauge] interface +// is extended (which is something that can happen without a major version bump +// of the API package). +type Int64ObservableGauge interface{ int64ObservableGauge() } + +// Int64ObservableUpDownCounter is embedded in +// [go.opentelemetry.io/otel/metric.Int64ObservableUpDownCounter]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Int64ObservableUpDownCounter] if +// you want users to experience a compilation error, signaling they need to +// update to your latest implementation, when the +// [go.opentelemetry.io/otel/metric.Int64ObservableUpDownCounter] +// interface is extended (which is something that can happen without a major +// version bump of the API package). +type Int64ObservableUpDownCounter interface{ int64ObservableUpDownCounter() } + +// Int64UpDownCounter is embedded in +// [go.opentelemetry.io/otel/metric.Int64UpDownCounter]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Int64UpDownCounter] if you want +// users to experience a compilation error, signaling they need to update to +// your latest implementation, when the +// [go.opentelemetry.io/otel/metric.Int64UpDownCounter] interface is +// extended (which is something that can happen without a major version bump of +// the API package). +type Int64UpDownCounter interface{ int64UpDownCounter() } diff --git a/vendor/go.opentelemetry.io/otel/metric/instrument.go b/vendor/go.opentelemetry.io/otel/metric/instrument.go new file mode 100644 index 000000000..0033c1e12 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/metric/instrument.go @@ -0,0 +1,332 @@ +// Copyright The OpenTelemetry 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 metric // import "go.opentelemetry.io/otel/metric" + +import "go.opentelemetry.io/otel/attribute" + +// Observable is used as a grouping mechanism for all instruments that are +// updated within a Callback. +type Observable interface { + observable() +} + +// InstrumentOption applies options to all instruments. +type InstrumentOption interface { + Int64CounterOption + Int64UpDownCounterOption + Int64HistogramOption + Int64ObservableCounterOption + Int64ObservableUpDownCounterOption + Int64ObservableGaugeOption + + Float64CounterOption + Float64UpDownCounterOption + Float64HistogramOption + Float64ObservableCounterOption + Float64ObservableUpDownCounterOption + Float64ObservableGaugeOption +} + +type descOpt string + +func (o descOpt) applyFloat64Counter(c Float64CounterConfig) Float64CounterConfig { + c.description = string(o) + return c +} + +func (o descOpt) applyFloat64UpDownCounter(c Float64UpDownCounterConfig) Float64UpDownCounterConfig { + c.description = string(o) + return c +} + +func (o descOpt) applyFloat64Histogram(c Float64HistogramConfig) Float64HistogramConfig { + c.description = string(o) + return c +} + +func (o descOpt) applyFloat64ObservableCounter(c Float64ObservableCounterConfig) Float64ObservableCounterConfig { + c.description = string(o) + return c +} + +func (o descOpt) applyFloat64ObservableUpDownCounter(c Float64ObservableUpDownCounterConfig) Float64ObservableUpDownCounterConfig { + c.description = string(o) + return c +} + +func (o descOpt) applyFloat64ObservableGauge(c Float64ObservableGaugeConfig) Float64ObservableGaugeConfig { + c.description = string(o) + return c +} + +func (o descOpt) applyInt64Counter(c Int64CounterConfig) Int64CounterConfig { + c.description = string(o) + return c +} + +func (o descOpt) applyInt64UpDownCounter(c Int64UpDownCounterConfig) Int64UpDownCounterConfig { + c.description = string(o) + return c +} + +func (o descOpt) applyInt64Histogram(c Int64HistogramConfig) Int64HistogramConfig { + c.description = string(o) + return c +} + +func (o descOpt) applyInt64ObservableCounter(c Int64ObservableCounterConfig) Int64ObservableCounterConfig { + c.description = string(o) + return c +} + +func (o descOpt) applyInt64ObservableUpDownCounter(c Int64ObservableUpDownCounterConfig) Int64ObservableUpDownCounterConfig { + c.description = string(o) + return c +} + +func (o descOpt) applyInt64ObservableGauge(c Int64ObservableGaugeConfig) Int64ObservableGaugeConfig { + c.description = string(o) + return c +} + +// WithDescription sets the instrument description. +func WithDescription(desc string) InstrumentOption { return descOpt(desc) } + +type unitOpt string + +func (o unitOpt) applyFloat64Counter(c Float64CounterConfig) Float64CounterConfig { + c.unit = string(o) + return c +} + +func (o unitOpt) applyFloat64UpDownCounter(c Float64UpDownCounterConfig) Float64UpDownCounterConfig { + c.unit = string(o) + return c +} + +func (o unitOpt) applyFloat64Histogram(c Float64HistogramConfig) Float64HistogramConfig { + c.unit = string(o) + return c +} + +func (o unitOpt) applyFloat64ObservableCounter(c Float64ObservableCounterConfig) Float64ObservableCounterConfig { + c.unit = string(o) + return c +} + +func (o unitOpt) applyFloat64ObservableUpDownCounter(c Float64ObservableUpDownCounterConfig) Float64ObservableUpDownCounterConfig { + c.unit = string(o) + return c +} + +func (o unitOpt) applyFloat64ObservableGauge(c Float64ObservableGaugeConfig) Float64ObservableGaugeConfig { + c.unit = string(o) + return c +} + +func (o unitOpt) applyInt64Counter(c Int64CounterConfig) Int64CounterConfig { + c.unit = string(o) + return c +} + +func (o unitOpt) applyInt64UpDownCounter(c Int64UpDownCounterConfig) Int64UpDownCounterConfig { + c.unit = string(o) + return c +} + +func (o unitOpt) applyInt64Histogram(c Int64HistogramConfig) Int64HistogramConfig { + c.unit = string(o) + return c +} + +func (o unitOpt) applyInt64ObservableCounter(c Int64ObservableCounterConfig) Int64ObservableCounterConfig { + c.unit = string(o) + return c +} + +func (o unitOpt) applyInt64ObservableUpDownCounter(c Int64ObservableUpDownCounterConfig) Int64ObservableUpDownCounterConfig { + c.unit = string(o) + return c +} + +func (o unitOpt) applyInt64ObservableGauge(c Int64ObservableGaugeConfig) Int64ObservableGaugeConfig { + c.unit = string(o) + return c +} + +// WithUnit sets the instrument unit. +func WithUnit(u string) InstrumentOption { return unitOpt(u) } + +// AddOption applies options to an addition measurement. See +// [MeasurementOption] for other options that can be used as an AddOption. +type AddOption interface { + applyAdd(AddConfig) AddConfig +} + +// AddConfig contains options for an addition measurement. +type AddConfig struct { + attrs attribute.Set +} + +// NewAddConfig returns a new [AddConfig] with all opts applied. +func NewAddConfig(opts []AddOption) AddConfig { + config := AddConfig{attrs: *attribute.EmptySet()} + for _, o := range opts { + config = o.applyAdd(config) + } + return config +} + +// Attributes returns the configured attribute set. +func (c AddConfig) Attributes() attribute.Set { + return c.attrs +} + +// RecordOption applies options to an addition measurement. See +// [MeasurementOption] for other options that can be used as a RecordOption. +type RecordOption interface { + applyRecord(RecordConfig) RecordConfig +} + +// RecordConfig contains options for a recorded measurement. +type RecordConfig struct { + attrs attribute.Set +} + +// NewRecordConfig returns a new [RecordConfig] with all opts applied. +func NewRecordConfig(opts []RecordOption) RecordConfig { + config := RecordConfig{attrs: *attribute.EmptySet()} + for _, o := range opts { + config = o.applyRecord(config) + } + return config +} + +// Attributes returns the configured attribute set. +func (c RecordConfig) Attributes() attribute.Set { + return c.attrs +} + +// ObserveOption applies options to an addition measurement. See +// [MeasurementOption] for other options that can be used as a ObserveOption. +type ObserveOption interface { + applyObserve(ObserveConfig) ObserveConfig +} + +// ObserveConfig contains options for an observed measurement. +type ObserveConfig struct { + attrs attribute.Set +} + +// NewObserveConfig returns a new [ObserveConfig] with all opts applied. +func NewObserveConfig(opts []ObserveOption) ObserveConfig { + config := ObserveConfig{attrs: *attribute.EmptySet()} + for _, o := range opts { + config = o.applyObserve(config) + } + return config +} + +// Attributes returns the configured attribute set. +func (c ObserveConfig) Attributes() attribute.Set { + return c.attrs +} + +// MeasurementOption applies options to all instrument measurement. +type MeasurementOption interface { + AddOption + RecordOption + ObserveOption +} + +type attrOpt struct { + set attribute.Set +} + +// mergeSets returns the union of keys between a and b. Any duplicate keys will +// use the value associated with b. +func mergeSets(a, b attribute.Set) attribute.Set { + // NewMergeIterator uses the first value for any duplicates. + iter := attribute.NewMergeIterator(&b, &a) + merged := make([]attribute.KeyValue, 0, a.Len()+b.Len()) + for iter.Next() { + merged = append(merged, iter.Attribute()) + } + return attribute.NewSet(merged...) +} + +func (o attrOpt) applyAdd(c AddConfig) AddConfig { + switch { + case o.set.Len() == 0: + case c.attrs.Len() == 0: + c.attrs = o.set + default: + c.attrs = mergeSets(c.attrs, o.set) + } + return c +} + +func (o attrOpt) applyRecord(c RecordConfig) RecordConfig { + switch { + case o.set.Len() == 0: + case c.attrs.Len() == 0: + c.attrs = o.set + default: + c.attrs = mergeSets(c.attrs, o.set) + } + return c +} + +func (o attrOpt) applyObserve(c ObserveConfig) ObserveConfig { + switch { + case o.set.Len() == 0: + case c.attrs.Len() == 0: + c.attrs = o.set + default: + c.attrs = mergeSets(c.attrs, o.set) + } + return c +} + +// WithAttributeSet sets the attribute Set associated with a measurement is +// made with. +// +// If multiple WithAttributeSet or WithAttributes options are passed the +// attributes will be merged together in the order they are passed. Attributes +// with duplicate keys will use the last value passed. +func WithAttributeSet(attributes attribute.Set) MeasurementOption { + return attrOpt{set: attributes} +} + +// WithAttributes converts attributes into an attribute Set and sets the Set to +// be associated with a measurement. This is shorthand for: +// +// cp := make([]attribute.KeyValue, len(attributes)) +// copy(cp, attributes) +// WithAttributes(attribute.NewSet(cp...)) +// +// [attribute.NewSet] may modify the passed attributes so this will make a copy +// of attributes before creating a set in order to ensure this function is +// concurrent safe. This makes this option function less optimized in +// comparison to [WithAttributeSet]. Therefore, [WithAttributeSet] should be +// preferred for performance sensitive code. +// +// See [WithAttributeSet] for information about how multiple WithAttributes are +// merged. +func WithAttributes(attributes ...attribute.KeyValue) MeasurementOption { + cp := make([]attribute.KeyValue, len(attributes)) + copy(cp, attributes) + return attrOpt{set: attribute.NewSet(cp...)} +} diff --git a/vendor/go.opentelemetry.io/otel/metric/meter.go b/vendor/go.opentelemetry.io/otel/metric/meter.go new file mode 100644 index 000000000..8e1917c32 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/metric/meter.go @@ -0,0 +1,210 @@ +// Copyright The OpenTelemetry 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 metric // import "go.opentelemetry.io/otel/metric" + +import ( + "context" + + "go.opentelemetry.io/otel/metric/embedded" +) + +// MeterProvider provides access to named Meter instances, for instrumenting +// an application or package. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type MeterProvider interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.MeterProvider + + // Meter returns a new Meter with the provided name and configuration. + // + // A Meter should be scoped at most to a single package. The name needs to + // be unique so it does not collide with other names used by + // an application, nor other applications. To achieve this, the import path + // of the instrumentation package is recommended to be used as name. + // + // If the name is empty, then an implementation defined default name will + // be used instead. + Meter(name string, opts ...MeterOption) Meter +} + +// Meter provides access to instrument instances for recording metrics. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Meter interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Meter + + // Int64Counter returns a new Int64Counter instrument identified by name + // and configured with options. The instrument is used to synchronously + // record increasing int64 measurements during a computational operation. + Int64Counter(name string, options ...Int64CounterOption) (Int64Counter, error) + // Int64UpDownCounter returns a new Int64UpDownCounter instrument + // identified by name and configured with options. The instrument is used + // to synchronously record int64 measurements during a computational + // operation. + Int64UpDownCounter(name string, options ...Int64UpDownCounterOption) (Int64UpDownCounter, error) + // Int64Histogram returns a new Int64Histogram instrument identified by + // name and configured with options. The instrument is used to + // synchronously record the distribution of int64 measurements during a + // computational operation. + Int64Histogram(name string, options ...Int64HistogramOption) (Int64Histogram, error) + // Int64ObservableCounter returns a new Int64ObservableCounter identified + // by name and configured with options. The instrument is used to + // asynchronously record increasing int64 measurements once per a + // measurement collection cycle. + // + // Measurements for the returned instrument are made via a callback. Use + // the WithInt64Callback option to register the callback here, or use the + // RegisterCallback method of this Meter to register one later. See the + // Measurements section of the package documentation for more information. + Int64ObservableCounter(name string, options ...Int64ObservableCounterOption) (Int64ObservableCounter, error) + // Int64ObservableUpDownCounter returns a new Int64ObservableUpDownCounter + // instrument identified by name and configured with options. The + // instrument is used to asynchronously record int64 measurements once per + // a measurement collection cycle. + // + // Measurements for the returned instrument are made via a callback. Use + // the WithInt64Callback option to register the callback here, or use the + // RegisterCallback method of this Meter to register one later. See the + // Measurements section of the package documentation for more information. + Int64ObservableUpDownCounter(name string, options ...Int64ObservableUpDownCounterOption) (Int64ObservableUpDownCounter, error) + // Int64ObservableGauge returns a new Int64ObservableGauge instrument + // identified by name and configured with options. The instrument is used + // to asynchronously record instantaneous int64 measurements once per a + // measurement collection cycle. + // + // Measurements for the returned instrument are made via a callback. Use + // the WithInt64Callback option to register the callback here, or use the + // RegisterCallback method of this Meter to register one later. See the + // Measurements section of the package documentation for more information. + Int64ObservableGauge(name string, options ...Int64ObservableGaugeOption) (Int64ObservableGauge, error) + + // Float64Counter returns a new Float64Counter instrument identified by + // name and configured with options. The instrument is used to + // synchronously record increasing float64 measurements during a + // computational operation. + Float64Counter(name string, options ...Float64CounterOption) (Float64Counter, error) + // Float64UpDownCounter returns a new Float64UpDownCounter instrument + // identified by name and configured with options. The instrument is used + // to synchronously record float64 measurements during a computational + // operation. + Float64UpDownCounter(name string, options ...Float64UpDownCounterOption) (Float64UpDownCounter, error) + // Float64Histogram returns a new Float64Histogram instrument identified by + // name and configured with options. The instrument is used to + // synchronously record the distribution of float64 measurements during a + // computational operation. + Float64Histogram(name string, options ...Float64HistogramOption) (Float64Histogram, error) + // Float64ObservableCounter returns a new Float64ObservableCounter + // instrument identified by name and configured with options. The + // instrument is used to asynchronously record increasing float64 + // measurements once per a measurement collection cycle. + // + // Measurements for the returned instrument are made via a callback. Use + // the WithFloat64Callback option to register the callback here, or use the + // RegisterCallback method of this Meter to register one later. See the + // Measurements section of the package documentation for more information. + Float64ObservableCounter(name string, options ...Float64ObservableCounterOption) (Float64ObservableCounter, error) + // Float64ObservableUpDownCounter returns a new + // Float64ObservableUpDownCounter instrument identified by name and + // configured with options. The instrument is used to asynchronously record + // float64 measurements once per a measurement collection cycle. + // + // Measurements for the returned instrument are made via a callback. Use + // the WithFloat64Callback option to register the callback here, or use the + // RegisterCallback method of this Meter to register one later. See the + // Measurements section of the package documentation for more information. + Float64ObservableUpDownCounter(name string, options ...Float64ObservableUpDownCounterOption) (Float64ObservableUpDownCounter, error) + // Float64ObservableGauge returns a new Float64ObservableGauge instrument + // identified by name and configured with options. The instrument is used + // to asynchronously record instantaneous float64 measurements once per a + // measurement collection cycle. + // + // Measurements for the returned instrument are made via a callback. Use + // the WithFloat64Callback option to register the callback here, or use the + // RegisterCallback method of this Meter to register one later. See the + // Measurements section of the package documentation for more information. + Float64ObservableGauge(name string, options ...Float64ObservableGaugeOption) (Float64ObservableGauge, error) + + // RegisterCallback registers f to be called during the collection of a + // measurement cycle. + // + // If Unregister of the returned Registration is called, f needs to be + // unregistered and not called during collection. + // + // The instruments f is registered with are the only instruments that f may + // observe values for. + // + // If no instruments are passed, f should not be registered nor called + // during collection. + RegisterCallback(f Callback, instruments ...Observable) (Registration, error) +} + +// Callback is a function registered with a Meter that makes observations for +// the set of instruments it is registered with. The Observer parameter is used +// to record measurement observations for these instruments. +// +// The function needs to complete in a finite amount of time and the deadline +// of the passed context is expected to be honored. +// +// The function needs to make unique observations across all registered +// Callbacks. Meaning, it should not report measurements for an instrument with +// the same attributes as another Callback will report. +// +// The function needs to be concurrent safe. +type Callback func(context.Context, Observer) error + +// Observer records measurements for multiple instruments in a Callback. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Observer interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Observer + + // ObserveFloat64 records the float64 value for obsrv. + ObserveFloat64(obsrv Float64Observable, value float64, opts ...ObserveOption) + // ObserveInt64 records the int64 value for obsrv. + ObserveInt64(obsrv Int64Observable, value int64, opts ...ObserveOption) +} + +// Registration is an token representing the unique registration of a callback +// for a set of instruments with a Meter. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Registration interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Registration + + // Unregister removes the callback registration from a Meter. + // + // This method needs to be idempotent and concurrent safe. + Unregister() error +} diff --git a/vendor/go.opentelemetry.io/otel/metric/syncfloat64.go b/vendor/go.opentelemetry.io/otel/metric/syncfloat64.go new file mode 100644 index 000000000..f0b063721 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/metric/syncfloat64.go @@ -0,0 +1,179 @@ +// Copyright The OpenTelemetry 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 metric // import "go.opentelemetry.io/otel/metric" + +import ( + "context" + + "go.opentelemetry.io/otel/metric/embedded" +) + +// Float64Counter is an instrument that records increasing float64 values. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Float64Counter interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Float64Counter + + // Add records a change to the counter. + // + // Use the WithAttributeSet (or, if performance is not a concern, + // the WithAttributes) option to include measurement attributes. + Add(ctx context.Context, incr float64, options ...AddOption) +} + +// Float64CounterConfig contains options for synchronous counter instruments that +// record int64 values. +type Float64CounterConfig struct { + description string + unit string +} + +// NewFloat64CounterConfig returns a new [Float64CounterConfig] with all opts +// applied. +func NewFloat64CounterConfig(opts ...Float64CounterOption) Float64CounterConfig { + var config Float64CounterConfig + for _, o := range opts { + config = o.applyFloat64Counter(config) + } + return config +} + +// Description returns the configured description. +func (c Float64CounterConfig) Description() string { + return c.description +} + +// Unit returns the configured unit. +func (c Float64CounterConfig) Unit() string { + return c.unit +} + +// Float64CounterOption applies options to a [Float64CounterConfig]. See +// [InstrumentOption] for other options that can be used as a +// Float64CounterOption. +type Float64CounterOption interface { + applyFloat64Counter(Float64CounterConfig) Float64CounterConfig +} + +// Float64UpDownCounter is an instrument that records increasing or decreasing +// float64 values. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Float64UpDownCounter interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Float64UpDownCounter + + // Add records a change to the counter. + // + // Use the WithAttributeSet (or, if performance is not a concern, + // the WithAttributes) option to include measurement attributes. + Add(ctx context.Context, incr float64, options ...AddOption) +} + +// Float64UpDownCounterConfig contains options for synchronous counter +// instruments that record int64 values. +type Float64UpDownCounterConfig struct { + description string + unit string +} + +// NewFloat64UpDownCounterConfig returns a new [Float64UpDownCounterConfig] +// with all opts applied. +func NewFloat64UpDownCounterConfig(opts ...Float64UpDownCounterOption) Float64UpDownCounterConfig { + var config Float64UpDownCounterConfig + for _, o := range opts { + config = o.applyFloat64UpDownCounter(config) + } + return config +} + +// Description returns the configured description. +func (c Float64UpDownCounterConfig) Description() string { + return c.description +} + +// Unit returns the configured unit. +func (c Float64UpDownCounterConfig) Unit() string { + return c.unit +} + +// Float64UpDownCounterOption applies options to a +// [Float64UpDownCounterConfig]. See [InstrumentOption] for other options that +// can be used as a Float64UpDownCounterOption. +type Float64UpDownCounterOption interface { + applyFloat64UpDownCounter(Float64UpDownCounterConfig) Float64UpDownCounterConfig +} + +// Float64Histogram is an instrument that records a distribution of float64 +// values. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Float64Histogram interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Float64Histogram + + // Record adds an additional value to the distribution. + // + // Use the WithAttributeSet (or, if performance is not a concern, + // the WithAttributes) option to include measurement attributes. + Record(ctx context.Context, incr float64, options ...RecordOption) +} + +// Float64HistogramConfig contains options for synchronous counter instruments +// that record int64 values. +type Float64HistogramConfig struct { + description string + unit string +} + +// NewFloat64HistogramConfig returns a new [Float64HistogramConfig] with all +// opts applied. +func NewFloat64HistogramConfig(opts ...Float64HistogramOption) Float64HistogramConfig { + var config Float64HistogramConfig + for _, o := range opts { + config = o.applyFloat64Histogram(config) + } + return config +} + +// Description returns the configured description. +func (c Float64HistogramConfig) Description() string { + return c.description +} + +// Unit returns the configured unit. +func (c Float64HistogramConfig) Unit() string { + return c.unit +} + +// Float64HistogramOption applies options to a [Float64HistogramConfig]. See +// [InstrumentOption] for other options that can be used as a +// Float64HistogramOption. +type Float64HistogramOption interface { + applyFloat64Histogram(Float64HistogramConfig) Float64HistogramConfig +} diff --git a/vendor/go.opentelemetry.io/otel/metric/syncint64.go b/vendor/go.opentelemetry.io/otel/metric/syncint64.go new file mode 100644 index 000000000..6f508eb66 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/metric/syncint64.go @@ -0,0 +1,179 @@ +// Copyright The OpenTelemetry 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 metric // import "go.opentelemetry.io/otel/metric" + +import ( + "context" + + "go.opentelemetry.io/otel/metric/embedded" +) + +// Int64Counter is an instrument that records increasing int64 values. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Int64Counter interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Int64Counter + + // Add records a change to the counter. + // + // Use the WithAttributeSet (or, if performance is not a concern, + // the WithAttributes) option to include measurement attributes. + Add(ctx context.Context, incr int64, options ...AddOption) +} + +// Int64CounterConfig contains options for synchronous counter instruments that +// record int64 values. +type Int64CounterConfig struct { + description string + unit string +} + +// NewInt64CounterConfig returns a new [Int64CounterConfig] with all opts +// applied. +func NewInt64CounterConfig(opts ...Int64CounterOption) Int64CounterConfig { + var config Int64CounterConfig + for _, o := range opts { + config = o.applyInt64Counter(config) + } + return config +} + +// Description returns the configured description. +func (c Int64CounterConfig) Description() string { + return c.description +} + +// Unit returns the configured unit. +func (c Int64CounterConfig) Unit() string { + return c.unit +} + +// Int64CounterOption applies options to a [Int64CounterConfig]. See +// [InstrumentOption] for other options that can be used as an +// Int64CounterOption. +type Int64CounterOption interface { + applyInt64Counter(Int64CounterConfig) Int64CounterConfig +} + +// Int64UpDownCounter is an instrument that records increasing or decreasing +// int64 values. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Int64UpDownCounter interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Int64UpDownCounter + + // Add records a change to the counter. + // + // Use the WithAttributeSet (or, if performance is not a concern, + // the WithAttributes) option to include measurement attributes. + Add(ctx context.Context, incr int64, options ...AddOption) +} + +// Int64UpDownCounterConfig contains options for synchronous counter +// instruments that record int64 values. +type Int64UpDownCounterConfig struct { + description string + unit string +} + +// NewInt64UpDownCounterConfig returns a new [Int64UpDownCounterConfig] with +// all opts applied. +func NewInt64UpDownCounterConfig(opts ...Int64UpDownCounterOption) Int64UpDownCounterConfig { + var config Int64UpDownCounterConfig + for _, o := range opts { + config = o.applyInt64UpDownCounter(config) + } + return config +} + +// Description returns the configured description. +func (c Int64UpDownCounterConfig) Description() string { + return c.description +} + +// Unit returns the configured unit. +func (c Int64UpDownCounterConfig) Unit() string { + return c.unit +} + +// Int64UpDownCounterOption applies options to a [Int64UpDownCounterConfig]. +// See [InstrumentOption] for other options that can be used as an +// Int64UpDownCounterOption. +type Int64UpDownCounterOption interface { + applyInt64UpDownCounter(Int64UpDownCounterConfig) Int64UpDownCounterConfig +} + +// Int64Histogram is an instrument that records a distribution of int64 +// values. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Int64Histogram interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Int64Histogram + + // Record adds an additional value to the distribution. + // + // Use the WithAttributeSet (or, if performance is not a concern, + // the WithAttributes) option to include measurement attributes. + Record(ctx context.Context, incr int64, options ...RecordOption) +} + +// Int64HistogramConfig contains options for synchronous counter instruments +// that record int64 values. +type Int64HistogramConfig struct { + description string + unit string +} + +// NewInt64HistogramConfig returns a new [Int64HistogramConfig] with all opts +// applied. +func NewInt64HistogramConfig(opts ...Int64HistogramOption) Int64HistogramConfig { + var config Int64HistogramConfig + for _, o := range opts { + config = o.applyInt64Histogram(config) + } + return config +} + +// Description returns the configured description. +func (c Int64HistogramConfig) Description() string { + return c.description +} + +// Unit returns the configured unit. +func (c Int64HistogramConfig) Unit() string { + return c.unit +} + +// Int64HistogramOption applies options to a [Int64HistogramConfig]. See +// [InstrumentOption] for other options that can be used as an +// Int64HistogramOption. +type Int64HistogramOption interface { + applyInt64Histogram(Int64HistogramConfig) Int64HistogramConfig +} diff --git a/vendor/go.opentelemetry.io/otel/requirements.txt b/vendor/go.opentelemetry.io/otel/requirements.txt new file mode 100644 index 000000000..407f17489 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/requirements.txt @@ -0,0 +1 @@ +codespell==2.2.4 diff --git a/vendor/go.opentelemetry.io/otel/sdk/resource/doc.go b/vendor/go.opentelemetry.io/otel/sdk/resource/doc.go index 9aab3d839..d55a50b0d 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/resource/doc.go +++ b/vendor/go.opentelemetry.io/otel/sdk/resource/doc.go @@ -25,4 +25,7 @@ // OTEL_RESOURCE_ATTRIBUTES the FromEnv Detector can be used. It will interpret // the value as a list of comma delimited key/value pairs // (e.g. `=,=,...`). +// +// While this package provides a stable API, +// the attributes added by resource detectors may change. package resource // import "go.opentelemetry.io/otel/sdk/resource" diff --git a/vendor/go.opentelemetry.io/otel/sdk/resource/env.go b/vendor/go.opentelemetry.io/otel/sdk/resource/env.go index e32843cad..f09a78190 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/resource/env.go +++ b/vendor/go.opentelemetry.io/otel/sdk/resource/env.go @@ -82,23 +82,23 @@ func constructOTResources(s string) (*Resource, error) { return Empty(), nil } pairs := strings.Split(s, ",") - attrs := []attribute.KeyValue{} + var attrs []attribute.KeyValue var invalid []string for _, p := range pairs { - field := strings.SplitN(p, "=", 2) - if len(field) != 2 { + k, v, found := strings.Cut(p, "=") + if !found { invalid = append(invalid, p) continue } - k := strings.TrimSpace(field[0]) - v, err := url.QueryUnescape(strings.TrimSpace(field[1])) + key := strings.TrimSpace(k) + val, err := url.QueryUnescape(strings.TrimSpace(v)) if err != nil { // Retain original value if decoding fails, otherwise it will be // an empty string. - v = field[1] + val = v otel.Handle(err) } - attrs = append(attrs, attribute.String(k, v)) + attrs = append(attrs, attribute.String(key, val)) } var err error if len(invalid) > 0 { diff --git a/vendor/go.opentelemetry.io/otel/sdk/resource/host_id_exec.go b/vendor/go.opentelemetry.io/otel/sdk/resource/host_id_exec.go index 0080fc6b6..207acb0ed 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/resource/host_id_exec.go +++ b/vendor/go.opentelemetry.io/otel/sdk/resource/host_id_exec.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build bsd || darwin +//go:build darwin || dragonfly || freebsd || netbsd || openbsd || solaris package resource // import "go.opentelemetry.io/otel/sdk/resource" diff --git a/vendor/go.opentelemetry.io/otel/sdk/resource/host_id_readfile.go b/vendor/go.opentelemetry.io/otel/sdk/resource/host_id_readfile.go index df4dc92c1..f92c6dad0 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/resource/host_id_readfile.go +++ b/vendor/go.opentelemetry.io/otel/sdk/resource/host_id_readfile.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build bsd || linux +//go:build linux || dragonfly || freebsd || netbsd || openbsd || solaris package resource // import "go.opentelemetry.io/otel/sdk/resource" diff --git a/vendor/go.opentelemetry.io/otel/sdk/resource/os_release_unix.go b/vendor/go.opentelemetry.io/otel/sdk/resource/os_release_unix.go index fba6790e4..c771942de 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/resource/os_release_unix.go +++ b/vendor/go.opentelemetry.io/otel/sdk/resource/os_release_unix.go @@ -85,14 +85,14 @@ func skip(line string) bool { // parse attempts to split the provided line on the first '=' character, and then // sanitize each side of the split before returning them as a key-value pair. func parse(line string) (string, string, bool) { - parts := strings.SplitN(line, "=", 2) + k, v, found := strings.Cut(line, "=") - if len(parts) != 2 || len(parts[0]) == 0 { + if !found || len(k) == 0 { return "", "", false } - key := strings.TrimSpace(parts[0]) - value := unescape(unquote(strings.TrimSpace(parts[1]))) + key := strings.TrimSpace(k) + value := unescape(unquote(strings.TrimSpace(v))) return key, value, true } diff --git a/vendor/go.opentelemetry.io/otel/sdk/resource/resource.go b/vendor/go.opentelemetry.io/otel/sdk/resource/resource.go index 49958a9a6..139dc7e8f 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/resource/resource.go +++ b/vendor/go.opentelemetry.io/otel/sdk/resource/resource.go @@ -74,7 +74,7 @@ func NewSchemaless(attrs ...attribute.KeyValue) *Resource { } // Ensure attributes comply with the specification: - // https://github.com/open-telemetry/opentelemetry-specification/blob/v1.0.1/specification/common/common.md#attributes + // https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/common/README.md#attribute s, _ := attribute.NewSetWithFiltered(attrs, func(kv attribute.KeyValue) bool { return kv.Valid() }) @@ -154,7 +154,7 @@ func (r *Resource) Equal(eq *Resource) bool { // if resource b's value is empty. // // The SchemaURL of the resources will be merged according to the spec rules: -// https://github.com/open-telemetry/opentelemetry-specification/blob/bad49c714a62da5493f2d1d9bafd7ebe8c8ce7eb/specification/resource/sdk.md#merge +// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/resource/sdk.md#merge // If the resources have different non-empty schemaURL an empty resource and an error // will be returned. func Merge(a, b *Resource) (*Resource, error) { diff --git a/vendor/go.opentelemetry.io/otel/sdk/trace/version.go b/vendor/go.opentelemetry.io/otel/sdk/trace/version.go new file mode 100644 index 000000000..d3457ed13 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/trace/version.go @@ -0,0 +1,20 @@ +// Copyright The OpenTelemetry 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 trace // import "go.opentelemetry.io/otel/sdk/trace" + +// version is the current release version of the metric SDK in use. +func version() string { + return "1.16.0-rc.1" +} diff --git a/vendor/go.opentelemetry.io/otel/sdk/version.go b/vendor/go.opentelemetry.io/otel/sdk/version.go index 2adddad86..dbef90b0d 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/version.go +++ b/vendor/go.opentelemetry.io/otel/sdk/version.go @@ -16,5 +16,5 @@ package sdk // import "go.opentelemetry.io/otel/sdk" // Version is the current release version of the OpenTelemetry SDK in use. func Version() string { - return "1.15.1" + return "1.16.0" } diff --git a/vendor/go.opentelemetry.io/otel/semconv/internal/http.go b/vendor/go.opentelemetry.io/otel/semconv/internal/http.go index b580eedef..19c394c69 100644 --- a/vendor/go.opentelemetry.io/otel/semconv/internal/http.go +++ b/vendor/go.opentelemetry.io/otel/semconv/internal/http.go @@ -232,10 +232,12 @@ func (sc *SemanticConventions) HTTPServerAttributesFromHTTPRequest(serverName, r if route != "" { attrs = append(attrs, sc.HTTPRouteKey.String(route)) } - if values, ok := request.Header["X-Forwarded-For"]; ok && len(values) > 0 { - if addresses := strings.SplitN(values[0], ",", 2); len(addresses) > 0 { - attrs = append(attrs, sc.HTTPClientIPKey.String(addresses[0])) + if values := request.Header["X-Forwarded-For"]; len(values) > 0 { + addr := values[0] + if i := strings.Index(addr, ","); i > 0 { + addr = addr[:i] } + attrs = append(attrs, sc.HTTPClientIPKey.String(addr)) } return append(attrs, sc.httpCommonAttributesFromHTTPRequest(request)...) diff --git a/vendor/go.opentelemetry.io/otel/version.go b/vendor/go.opentelemetry.io/otel/version.go index 6c7fe114b..c2217a28d 100644 --- a/vendor/go.opentelemetry.io/otel/version.go +++ b/vendor/go.opentelemetry.io/otel/version.go @@ -16,5 +16,5 @@ package otel // import "go.opentelemetry.io/otel" // Version is the current release version of OpenTelemetry in use. func Version() string { - return "1.15.1" + return "1.16.0" } diff --git a/vendor/go.opentelemetry.io/otel/versions.yaml b/vendor/go.opentelemetry.io/otel/versions.yaml index b49ec1029..9dc47532b 100644 --- a/vendor/go.opentelemetry.io/otel/versions.yaml +++ b/vendor/go.opentelemetry.io/otel/versions.yaml @@ -14,7 +14,7 @@ module-sets: stable-v1: - version: v1.15.1 + version: v1.16.0 modules: - go.opentelemetry.io/otel - go.opentelemetry.io/otel/bridge/opentracing @@ -32,10 +32,11 @@ module-sets: - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp - go.opentelemetry.io/otel/exporters/stdout/stdouttrace - go.opentelemetry.io/otel/exporters/zipkin + - go.opentelemetry.io/otel/metric - go.opentelemetry.io/otel/sdk - go.opentelemetry.io/otel/trace experimental-metrics: - version: v0.38.1 + version: v0.39.0 modules: - go.opentelemetry.io/otel/example/opencensus - go.opentelemetry.io/otel/example/prometheus @@ -44,7 +45,6 @@ module-sets: - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp - go.opentelemetry.io/otel/exporters/prometheus - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric - - go.opentelemetry.io/otel/metric - go.opentelemetry.io/otel/sdk/metric - go.opentelemetry.io/otel/bridge/opencensus - go.opentelemetry.io/otel/bridge/opencensus/test diff --git a/vendor/golang.org/x/crypto/ssh/common.go b/vendor/golang.org/x/crypto/ssh/common.go index e6a77f26a..dc6f301de 100644 --- a/vendor/golang.org/x/crypto/ssh/common.go +++ b/vendor/golang.org/x/crypto/ssh/common.go @@ -85,7 +85,7 @@ var supportedHostKeyAlgos = []string{ // This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed // because they have reached the end of their useful life. var supportedMACs = []string{ - "hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1", "hmac-sha1-96", + "hmac-sha2-512-etm@openssh.com", "hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1", "hmac-sha1-96", } var supportedCompressions = []string{compressionNone} diff --git a/vendor/golang.org/x/crypto/ssh/mac.go b/vendor/golang.org/x/crypto/ssh/mac.go index c07a06285..0a21af47e 100644 --- a/vendor/golang.org/x/crypto/ssh/mac.go +++ b/vendor/golang.org/x/crypto/ssh/mac.go @@ -10,6 +10,7 @@ import ( "crypto/hmac" "crypto/sha1" "crypto/sha256" + "crypto/sha512" "hash" ) @@ -46,6 +47,9 @@ func (t truncatingMAC) Size() int { func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() } var macModes = map[string]*macMode{ + "hmac-sha2-512-etm@openssh.com": {64, true, func(key []byte) hash.Hash { + return hmac.New(sha512.New, key) + }}, "hmac-sha2-256-etm@openssh.com": {32, true, func(key []byte) hash.Hash { return hmac.New(sha256.New, key) }}, diff --git a/vendor/golang.org/x/net/http2/h2c/h2c.go b/vendor/golang.org/x/net/http2/h2c/h2c.go index a72bbed1b..2d6bf861b 100644 --- a/vendor/golang.org/x/net/http2/h2c/h2c.go +++ b/vendor/golang.org/x/net/http2/h2c/h2c.go @@ -44,7 +44,7 @@ func init() { // HTTP/1, but unlikely to occur in practice and (2) Upgrading from HTTP/1 to // h2c - this works by using the HTTP/1 Upgrade header to request an upgrade to // h2c. When either of those situations occur we hijack the HTTP/1 connection, -// convert it to a HTTP/2 connection and pass the net.Conn to http2.ServeConn. +// convert it to an HTTP/2 connection and pass the net.Conn to http2.ServeConn. type h2cHandler struct { Handler http.Handler s *http2.Server diff --git a/vendor/golang.org/x/net/http2/server.go b/vendor/golang.org/x/net/http2/server.go index cd057f398..033b6e6db 100644 --- a/vendor/golang.org/x/net/http2/server.go +++ b/vendor/golang.org/x/net/http2/server.go @@ -441,7 +441,7 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) { if s.NewWriteScheduler != nil { sc.writeSched = s.NewWriteScheduler() } else { - sc.writeSched = NewPriorityWriteScheduler(nil) + sc.writeSched = newRoundRobinWriteScheduler() } // These start at the RFC-specified defaults. If there is a higher @@ -2429,7 +2429,7 @@ type requestBody struct { conn *serverConn closeOnce sync.Once // for use by Close only sawEOF bool // for use by Read only - pipe *pipe // non-nil if we have a HTTP entity message body + pipe *pipe // non-nil if we have an HTTP entity message body needsContinue bool // need to send a 100-continue } @@ -2569,7 +2569,8 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) { clen = "" } } - if clen == "" && rws.handlerDone && bodyAllowedForStatus(rws.status) && (len(p) > 0 || !isHeadResp) { + _, hasContentLength := rws.snapHeader["Content-Length"] + if !hasContentLength && clen == "" && rws.handlerDone && bodyAllowedForStatus(rws.status) && (len(p) > 0 || !isHeadResp) { clen = strconv.Itoa(len(p)) } _, hasContentType := rws.snapHeader["Content-Type"] @@ -2774,7 +2775,7 @@ func (w *responseWriter) FlushError() error { err = rws.bw.Flush() } else { // The bufio.Writer won't call chunkWriter.Write - // (writeChunk with zero bytes, so we have to do it + // (writeChunk with zero bytes), so we have to do it // ourselves to force the HTTP response header and/or // final DATA frame (with END_STREAM) to be sent. _, err = chunkWriter{rws}.Write(nil) diff --git a/vendor/golang.org/x/net/http2/transport.go b/vendor/golang.org/x/net/http2/transport.go index ac90a2631..4f08ccba9 100644 --- a/vendor/golang.org/x/net/http2/transport.go +++ b/vendor/golang.org/x/net/http2/transport.go @@ -1268,8 +1268,8 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) { cancelRequest := func(cs *clientStream, err error) error { cs.cc.mu.Lock() - defer cs.cc.mu.Unlock() cs.abortStreamLocked(err) + bodyClosed := cs.reqBodyClosed if cs.ID != 0 { // This request may have failed because of a problem with the connection, // or for some unrelated reason. (For example, the user might have canceled @@ -1284,6 +1284,23 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) { // will not help. cs.cc.doNotReuse = true } + cs.cc.mu.Unlock() + // Wait for the request body to be closed. + // + // If nothing closed the body before now, abortStreamLocked + // will have started a goroutine to close it. + // + // Closing the body before returning avoids a race condition + // with net/http checking its readTrackingBody to see if the + // body was read from or closed. See golang/go#60041. + // + // The body is closed in a separate goroutine without the + // connection mutex held, but dropping the mutex before waiting + // will keep us from holding it indefinitely if the body + // close is slow for some reason. + if bodyClosed != nil { + <-bodyClosed + } return err } @@ -1899,7 +1916,7 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail // 8.1.2.3 Request Pseudo-Header Fields // The :path pseudo-header field includes the path and query parts of the // target URI (the path-absolute production and optionally a '?' character - // followed by the query production (see Sections 3.3 and 3.4 of + // followed by the query production, see Sections 3.3 and 3.4 of // [RFC3986]). f(":authority", host) m := req.Method diff --git a/vendor/golang.org/x/net/http2/writesched.go b/vendor/golang.org/x/net/http2/writesched.go index c7cd00173..cc893adc2 100644 --- a/vendor/golang.org/x/net/http2/writesched.go +++ b/vendor/golang.org/x/net/http2/writesched.go @@ -184,7 +184,8 @@ func (wr *FrameWriteRequest) replyToWriter(err error) { // writeQueue is used by implementations of WriteScheduler. type writeQueue struct { - s []FrameWriteRequest + s []FrameWriteRequest + prev, next *writeQueue } func (q *writeQueue) empty() bool { return len(q.s) == 0 } diff --git a/vendor/golang.org/x/net/http2/writesched_roundrobin.go b/vendor/golang.org/x/net/http2/writesched_roundrobin.go new file mode 100644 index 000000000..54fe86322 --- /dev/null +++ b/vendor/golang.org/x/net/http2/writesched_roundrobin.go @@ -0,0 +1,119 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http2 + +import ( + "fmt" + "math" +) + +type roundRobinWriteScheduler struct { + // control contains control frames (SETTINGS, PING, etc.). + control writeQueue + + // streams maps stream ID to a queue. + streams map[uint32]*writeQueue + + // stream queues are stored in a circular linked list. + // head is the next stream to write, or nil if there are no streams open. + head *writeQueue + + // pool of empty queues for reuse. + queuePool writeQueuePool +} + +// newRoundRobinWriteScheduler constructs a new write scheduler. +// The round robin scheduler priorizes control frames +// like SETTINGS and PING over DATA frames. +// When there are no control frames to send, it performs a round-robin +// selection from the ready streams. +func newRoundRobinWriteScheduler() WriteScheduler { + ws := &roundRobinWriteScheduler{ + streams: make(map[uint32]*writeQueue), + } + return ws +} + +func (ws *roundRobinWriteScheduler) OpenStream(streamID uint32, options OpenStreamOptions) { + if ws.streams[streamID] != nil { + panic(fmt.Errorf("stream %d already opened", streamID)) + } + q := ws.queuePool.get() + ws.streams[streamID] = q + if ws.head == nil { + ws.head = q + q.next = q + q.prev = q + } else { + // Queues are stored in a ring. + // Insert the new stream before ws.head, putting it at the end of the list. + q.prev = ws.head.prev + q.next = ws.head + q.prev.next = q + q.next.prev = q + } +} + +func (ws *roundRobinWriteScheduler) CloseStream(streamID uint32) { + q := ws.streams[streamID] + if q == nil { + return + } + if q.next == q { + // This was the only open stream. + ws.head = nil + } else { + q.prev.next = q.next + q.next.prev = q.prev + if ws.head == q { + ws.head = q.next + } + } + delete(ws.streams, streamID) + ws.queuePool.put(q) +} + +func (ws *roundRobinWriteScheduler) AdjustStream(streamID uint32, priority PriorityParam) {} + +func (ws *roundRobinWriteScheduler) Push(wr FrameWriteRequest) { + if wr.isControl() { + ws.control.push(wr) + return + } + q := ws.streams[wr.StreamID()] + if q == nil { + // This is a closed stream. + // wr should not be a HEADERS or DATA frame. + // We push the request onto the control queue. + if wr.DataSize() > 0 { + panic("add DATA on non-open stream") + } + ws.control.push(wr) + return + } + q.push(wr) +} + +func (ws *roundRobinWriteScheduler) Pop() (FrameWriteRequest, bool) { + // Control and RST_STREAM frames first. + if !ws.control.empty() { + return ws.control.shift(), true + } + if ws.head == nil { + return FrameWriteRequest{}, false + } + q := ws.head + for { + if wr, ok := q.consume(math.MaxInt32); ok { + ws.head = q.next + return wr, true + } + q = q.next + if q == ws.head { + break + } + } + return FrameWriteRequest{}, false +} diff --git a/vendor/golang.org/x/sys/cpu/endian_little.go b/vendor/golang.org/x/sys/cpu/endian_little.go index fe545966b..55db853ef 100644 --- a/vendor/golang.org/x/sys/cpu/endian_little.go +++ b/vendor/golang.org/x/sys/cpu/endian_little.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build 386 || amd64 || amd64p32 || alpha || arm || arm64 || loong64 || mipsle || mips64le || mips64p32le || nios2 || ppc64le || riscv || riscv64 || sh -// +build 386 amd64 amd64p32 alpha arm arm64 loong64 mipsle mips64le mips64p32le nios2 ppc64le riscv riscv64 sh +//go:build 386 || amd64 || amd64p32 || alpha || arm || arm64 || loong64 || mipsle || mips64le || mips64p32le || nios2 || ppc64le || riscv || riscv64 || sh || wasm +// +build 386 amd64 amd64p32 alpha arm arm64 loong64 mipsle mips64le mips64p32le nios2 ppc64le riscv riscv64 sh wasm package cpu diff --git a/vendor/golang.org/x/sys/unix/mkall.sh b/vendor/golang.org/x/sys/unix/mkall.sh index 8e3947c36..e6f31d374 100644 --- a/vendor/golang.org/x/sys/unix/mkall.sh +++ b/vendor/golang.org/x/sys/unix/mkall.sh @@ -50,7 +50,7 @@ if [[ "$GOOS" = "linux" ]]; then # Use the Docker-based build system # Files generated through docker (use $cmd so you can Ctl-C the build or run) $cmd docker build --tag generate:$GOOS $GOOS - $cmd docker run --interactive --tty --volume $(cd -- "$(dirname -- "$0")/.." && /bin/pwd):/build generate:$GOOS + $cmd docker run --interactive --tty --volume $(cd -- "$(dirname -- "$0")/.." && pwd):/build generate:$GOOS exit fi diff --git a/vendor/golang.org/x/sys/unix/mkerrors.sh b/vendor/golang.org/x/sys/unix/mkerrors.sh index be0423e68..315646271 100644 --- a/vendor/golang.org/x/sys/unix/mkerrors.sh +++ b/vendor/golang.org/x/sys/unix/mkerrors.sh @@ -741,7 +741,8 @@ main(void) e = errors[i].num; if(i > 0 && errors[i-1].num == e) continue; - strcpy(buf, strerror(e)); + strncpy(buf, strerror(e), sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; // lowercase first letter: Bad -> bad, but STREAM -> STREAM. if(A <= buf[0] && buf[0] <= Z && a <= buf[1] && buf[1] <= z) buf[0] += a - A; @@ -760,7 +761,8 @@ main(void) e = signals[i].num; if(i > 0 && signals[i-1].num == e) continue; - strcpy(buf, strsignal(e)); + strncpy(buf, strsignal(e), sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; // lowercase first letter: Bad -> bad, but STREAM -> STREAM. if(A <= buf[0] && buf[0] <= Z && a <= buf[1] && buf[1] <= z) buf[0] += a - A; diff --git a/vendor/golang.org/x/sys/unix/syscall_linux.go b/vendor/golang.org/x/sys/unix/syscall_linux.go index fbaeb5fff..6de486bef 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux.go @@ -1699,12 +1699,23 @@ func PtracePokeUser(pid int, addr uintptr, data []byte) (count int, err error) { return ptracePoke(PTRACE_POKEUSR, PTRACE_PEEKUSR, pid, addr, data) } +// elfNT_PRSTATUS is a copy of the debug/elf.NT_PRSTATUS constant so +// x/sys/unix doesn't need to depend on debug/elf and thus +// compress/zlib, debug/dwarf, and other packages. +const elfNT_PRSTATUS = 1 + func PtraceGetRegs(pid int, regsout *PtraceRegs) (err error) { - return ptracePtr(PTRACE_GETREGS, pid, 0, unsafe.Pointer(regsout)) + var iov Iovec + iov.Base = (*byte)(unsafe.Pointer(regsout)) + iov.SetLen(int(unsafe.Sizeof(*regsout))) + return ptracePtr(PTRACE_GETREGSET, pid, uintptr(elfNT_PRSTATUS), unsafe.Pointer(&iov)) } func PtraceSetRegs(pid int, regs *PtraceRegs) (err error) { - return ptracePtr(PTRACE_SETREGS, pid, 0, unsafe.Pointer(regs)) + var iov Iovec + iov.Base = (*byte)(unsafe.Pointer(regs)) + iov.SetLen(int(unsafe.Sizeof(*regs))) + return ptracePtr(PTRACE_SETREGSET, pid, uintptr(elfNT_PRSTATUS), unsafe.Pointer(&iov)) } func PtraceSetOptions(pid int, options int) (err error) { @@ -2420,6 +2431,21 @@ func PthreadSigmask(how int, set, oldset *Sigset_t) error { return rtSigprocmask(how, set, oldset, _C__NSIG/8) } +//sysnb getresuid(ruid *_C_int, euid *_C_int, suid *_C_int) +//sysnb getresgid(rgid *_C_int, egid *_C_int, sgid *_C_int) + +func Getresuid() (ruid, euid, suid int) { + var r, e, s _C_int + getresuid(&r, &e, &s) + return int(r), int(e), int(s) +} + +func Getresgid() (rgid, egid, sgid int) { + var r, e, s _C_int + getresgid(&r, &e, &s) + return int(r), int(e), int(s) +} + /* * Unimplemented */ diff --git a/vendor/golang.org/x/sys/unix/syscall_openbsd.go b/vendor/golang.org/x/sys/unix/syscall_openbsd.go index f9c7a9663..c5f166a11 100644 --- a/vendor/golang.org/x/sys/unix/syscall_openbsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_openbsd.go @@ -151,6 +151,21 @@ func Getfsstat(buf []Statfs_t, flags int) (n int, err error) { return } +//sysnb getresuid(ruid *_C_int, euid *_C_int, suid *_C_int) +//sysnb getresgid(rgid *_C_int, egid *_C_int, sgid *_C_int) + +func Getresuid() (ruid, euid, suid int) { + var r, e, s _C_int + getresuid(&r, &e, &s) + return int(r), int(e), int(s) +} + +func Getresgid() (rgid, egid, sgid int) { + var r, e, s _C_int + getresgid(&r, &e, &s) + return int(r), int(e), int(s) +} + //sys ioctl(fd int, req uint, arg uintptr) (err error) //sys ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) = SYS_IOCTL @@ -338,8 +353,6 @@ func Uname(uname *Utsname) error { // getgid // getitimer // getlogin -// getresgid -// getresuid // getthrid // ktrace // lfs_bmapv diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go index f61925269..48984202c 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go @@ -329,6 +329,54 @@ const ( SCM_WIFI_STATUS = 0x25 SFD_CLOEXEC = 0x400000 SFD_NONBLOCK = 0x4000 + SF_FP = 0x38 + SF_I0 = 0x20 + SF_I1 = 0x24 + SF_I2 = 0x28 + SF_I3 = 0x2c + SF_I4 = 0x30 + SF_I5 = 0x34 + SF_L0 = 0x0 + SF_L1 = 0x4 + SF_L2 = 0x8 + SF_L3 = 0xc + SF_L4 = 0x10 + SF_L5 = 0x14 + SF_L6 = 0x18 + SF_L7 = 0x1c + SF_PC = 0x3c + SF_RETP = 0x40 + SF_V9_FP = 0x70 + SF_V9_I0 = 0x40 + SF_V9_I1 = 0x48 + SF_V9_I2 = 0x50 + SF_V9_I3 = 0x58 + SF_V9_I4 = 0x60 + SF_V9_I5 = 0x68 + SF_V9_L0 = 0x0 + SF_V9_L1 = 0x8 + SF_V9_L2 = 0x10 + SF_V9_L3 = 0x18 + SF_V9_L4 = 0x20 + SF_V9_L5 = 0x28 + SF_V9_L6 = 0x30 + SF_V9_L7 = 0x38 + SF_V9_PC = 0x78 + SF_V9_RETP = 0x80 + SF_V9_XARG0 = 0x88 + SF_V9_XARG1 = 0x90 + SF_V9_XARG2 = 0x98 + SF_V9_XARG3 = 0xa0 + SF_V9_XARG4 = 0xa8 + SF_V9_XARG5 = 0xb0 + SF_V9_XXARG = 0xb8 + SF_XARG0 = 0x44 + SF_XARG1 = 0x48 + SF_XARG2 = 0x4c + SF_XARG3 = 0x50 + SF_XARG4 = 0x54 + SF_XARG5 = 0x58 + SF_XXARG = 0x5c SIOCATMARK = 0x8905 SIOCGPGRP = 0x8904 SIOCGSTAMPNS_NEW = 0x40108907 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux.go b/vendor/golang.org/x/sys/unix/zsyscall_linux.go index da63d9d78..722c29a00 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux.go @@ -2172,3 +2172,17 @@ func rtSigprocmask(how int, set *Sigset_t, oldset *Sigset_t, sigsetsize uintptr) } return } + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func getresuid(ruid *_C_int, euid *_C_int, suid *_C_int) { + RawSyscallNoError(SYS_GETRESUID, uintptr(unsafe.Pointer(ruid)), uintptr(unsafe.Pointer(euid)), uintptr(unsafe.Pointer(suid))) + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func getresgid(rgid *_C_int, egid *_C_int, sgid *_C_int) { + RawSyscallNoError(SYS_GETRESGID, uintptr(unsafe.Pointer(rgid)), uintptr(unsafe.Pointer(egid)), uintptr(unsafe.Pointer(sgid))) + return +} diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go index 6699a783e..9ab9abf72 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go @@ -519,6 +519,28 @@ var libc_getcwd_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func getresuid(ruid *_C_int, euid *_C_int, suid *_C_int) { + syscall_rawSyscall(libc_getresuid_trampoline_addr, uintptr(unsafe.Pointer(ruid)), uintptr(unsafe.Pointer(euid)), uintptr(unsafe.Pointer(suid))) + return +} + +var libc_getresuid_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_getresuid getresuid "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func getresgid(rgid *_C_int, egid *_C_int, sgid *_C_int) { + syscall_rawSyscall(libc_getresgid_trampoline_addr, uintptr(unsafe.Pointer(rgid)), uintptr(unsafe.Pointer(egid)), uintptr(unsafe.Pointer(sgid))) + return +} + +var libc_getresgid_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_getresgid getresgid "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func ioctl(fd int, req uint, arg uintptr) (err error) { _, _, e1 := syscall_syscall(libc_ioctl_trampoline_addr, uintptr(fd), uintptr(req), uintptr(arg)) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.s b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.s index 04f0de34b..3dcacd30d 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.s @@ -158,6 +158,16 @@ TEXT libc_getcwd_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_getcwd_trampoline_addr(SB), RODATA, $4 DATA ·libc_getcwd_trampoline_addr(SB)/4, $libc_getcwd_trampoline<>(SB) +TEXT libc_getresuid_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_getresuid(SB) +GLOBL ·libc_getresuid_trampoline_addr(SB), RODATA, $4 +DATA ·libc_getresuid_trampoline_addr(SB)/4, $libc_getresuid_trampoline<>(SB) + +TEXT libc_getresgid_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_getresgid(SB) +GLOBL ·libc_getresgid_trampoline_addr(SB), RODATA, $4 +DATA ·libc_getresgid_trampoline_addr(SB)/4, $libc_getresgid_trampoline<>(SB) + TEXT libc_ioctl_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_ioctl(SB) GLOBL ·libc_ioctl_trampoline_addr(SB), RODATA, $4 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go index 1e775fe05..915761eab 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go @@ -519,15 +519,29 @@ var libc_getcwd_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func ioctl(fd int, req uint, arg uintptr) (err error) { - _, _, e1 := syscall_syscall(libc_ioctl_trampoline_addr, uintptr(fd), uintptr(req), uintptr(arg)) - if e1 != 0 { - err = errnoErr(e1) - } +func getresuid(ruid *_C_int, euid *_C_int, suid *_C_int) { + syscall_rawSyscall(libc_getresuid_trampoline_addr, uintptr(unsafe.Pointer(ruid)), uintptr(unsafe.Pointer(euid)), uintptr(unsafe.Pointer(suid))) return } -func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { +var libc_getresuid_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_getresuid getresuid "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func getresgid(rgid *_C_int, egid *_C_int, sgid *_C_int) { + syscall_rawSyscall(libc_getresgid_trampoline_addr, uintptr(unsafe.Pointer(rgid)), uintptr(unsafe.Pointer(egid)), uintptr(unsafe.Pointer(sgid))) + return +} + +var libc_getresgid_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_getresgid getresgid "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ioctl(fd int, req uint, arg uintptr) (err error) { _, _, e1 := syscall_syscall(libc_ioctl_trampoline_addr, uintptr(fd), uintptr(req), uintptr(arg)) if e1 != 0 { err = errnoErr(e1) @@ -541,6 +555,16 @@ var libc_ioctl_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { + _, _, e1 := syscall_syscall(libc_ioctl_trampoline_addr, uintptr(fd), uintptr(req), uintptr(arg)) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { var _p0 unsafe.Pointer if len(mib) > 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.s b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.s index 27b6f4df7..2763620b0 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.s @@ -158,6 +158,16 @@ TEXT libc_getcwd_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_getcwd_trampoline_addr(SB), RODATA, $8 DATA ·libc_getcwd_trampoline_addr(SB)/8, $libc_getcwd_trampoline<>(SB) +TEXT libc_getresuid_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_getresuid(SB) +GLOBL ·libc_getresuid_trampoline_addr(SB), RODATA, $8 +DATA ·libc_getresuid_trampoline_addr(SB)/8, $libc_getresuid_trampoline<>(SB) + +TEXT libc_getresgid_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_getresgid(SB) +GLOBL ·libc_getresgid_trampoline_addr(SB), RODATA, $8 +DATA ·libc_getresgid_trampoline_addr(SB)/8, $libc_getresgid_trampoline<>(SB) + TEXT libc_ioctl_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_ioctl(SB) GLOBL ·libc_ioctl_trampoline_addr(SB), RODATA, $8 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go index 7f6427899..8e87fdf15 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go @@ -519,6 +519,28 @@ var libc_getcwd_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func getresuid(ruid *_C_int, euid *_C_int, suid *_C_int) { + syscall_rawSyscall(libc_getresuid_trampoline_addr, uintptr(unsafe.Pointer(ruid)), uintptr(unsafe.Pointer(euid)), uintptr(unsafe.Pointer(suid))) + return +} + +var libc_getresuid_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_getresuid getresuid "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func getresgid(rgid *_C_int, egid *_C_int, sgid *_C_int) { + syscall_rawSyscall(libc_getresgid_trampoline_addr, uintptr(unsafe.Pointer(rgid)), uintptr(unsafe.Pointer(egid)), uintptr(unsafe.Pointer(sgid))) + return +} + +var libc_getresgid_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_getresgid getresgid "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func ioctl(fd int, req uint, arg uintptr) (err error) { _, _, e1 := syscall_syscall(libc_ioctl_trampoline_addr, uintptr(fd), uintptr(req), uintptr(arg)) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.s b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.s index b797045fd..c92231404 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.s @@ -158,6 +158,16 @@ TEXT libc_getcwd_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_getcwd_trampoline_addr(SB), RODATA, $4 DATA ·libc_getcwd_trampoline_addr(SB)/4, $libc_getcwd_trampoline<>(SB) +TEXT libc_getresuid_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_getresuid(SB) +GLOBL ·libc_getresuid_trampoline_addr(SB), RODATA, $4 +DATA ·libc_getresuid_trampoline_addr(SB)/4, $libc_getresuid_trampoline<>(SB) + +TEXT libc_getresgid_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_getresgid(SB) +GLOBL ·libc_getresgid_trampoline_addr(SB), RODATA, $4 +DATA ·libc_getresgid_trampoline_addr(SB)/4, $libc_getresgid_trampoline<>(SB) + TEXT libc_ioctl_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_ioctl(SB) GLOBL ·libc_ioctl_trampoline_addr(SB), RODATA, $4 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go index 756ef7b17..12a7a2160 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go @@ -519,6 +519,28 @@ var libc_getcwd_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func getresuid(ruid *_C_int, euid *_C_int, suid *_C_int) { + syscall_rawSyscall(libc_getresuid_trampoline_addr, uintptr(unsafe.Pointer(ruid)), uintptr(unsafe.Pointer(euid)), uintptr(unsafe.Pointer(suid))) + return +} + +var libc_getresuid_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_getresuid getresuid "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func getresgid(rgid *_C_int, egid *_C_int, sgid *_C_int) { + syscall_rawSyscall(libc_getresgid_trampoline_addr, uintptr(unsafe.Pointer(rgid)), uintptr(unsafe.Pointer(egid)), uintptr(unsafe.Pointer(sgid))) + return +} + +var libc_getresgid_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_getresgid getresgid "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func ioctl(fd int, req uint, arg uintptr) (err error) { _, _, e1 := syscall_syscall(libc_ioctl_trampoline_addr, uintptr(fd), uintptr(req), uintptr(arg)) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.s b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.s index a87126622..a6bc32c92 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.s @@ -158,6 +158,16 @@ TEXT libc_getcwd_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_getcwd_trampoline_addr(SB), RODATA, $8 DATA ·libc_getcwd_trampoline_addr(SB)/8, $libc_getcwd_trampoline<>(SB) +TEXT libc_getresuid_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_getresuid(SB) +GLOBL ·libc_getresuid_trampoline_addr(SB), RODATA, $8 +DATA ·libc_getresuid_trampoline_addr(SB)/8, $libc_getresuid_trampoline<>(SB) + +TEXT libc_getresgid_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_getresgid(SB) +GLOBL ·libc_getresgid_trampoline_addr(SB), RODATA, $8 +DATA ·libc_getresgid_trampoline_addr(SB)/8, $libc_getresgid_trampoline<>(SB) + TEXT libc_ioctl_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_ioctl(SB) GLOBL ·libc_ioctl_trampoline_addr(SB), RODATA, $8 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go index 7bc2e24eb..b19e8aa03 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go @@ -519,6 +519,28 @@ var libc_getcwd_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func getresuid(ruid *_C_int, euid *_C_int, suid *_C_int) { + syscall_rawSyscall(libc_getresuid_trampoline_addr, uintptr(unsafe.Pointer(ruid)), uintptr(unsafe.Pointer(euid)), uintptr(unsafe.Pointer(suid))) + return +} + +var libc_getresuid_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_getresuid getresuid "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func getresgid(rgid *_C_int, egid *_C_int, sgid *_C_int) { + syscall_rawSyscall(libc_getresgid_trampoline_addr, uintptr(unsafe.Pointer(rgid)), uintptr(unsafe.Pointer(egid)), uintptr(unsafe.Pointer(sgid))) + return +} + +var libc_getresgid_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_getresgid getresgid "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func ioctl(fd int, req uint, arg uintptr) (err error) { _, _, e1 := syscall_syscall(libc_ioctl_trampoline_addr, uintptr(fd), uintptr(req), uintptr(arg)) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.s b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.s index 05d4bffd7..b4e7bceab 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.s @@ -158,6 +158,16 @@ TEXT libc_getcwd_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_getcwd_trampoline_addr(SB), RODATA, $8 DATA ·libc_getcwd_trampoline_addr(SB)/8, $libc_getcwd_trampoline<>(SB) +TEXT libc_getresuid_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_getresuid(SB) +GLOBL ·libc_getresuid_trampoline_addr(SB), RODATA, $8 +DATA ·libc_getresuid_trampoline_addr(SB)/8, $libc_getresuid_trampoline<>(SB) + +TEXT libc_getresgid_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_getresgid(SB) +GLOBL ·libc_getresgid_trampoline_addr(SB), RODATA, $8 +DATA ·libc_getresgid_trampoline_addr(SB)/8, $libc_getresgid_trampoline<>(SB) + TEXT libc_ioctl_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_ioctl(SB) GLOBL ·libc_ioctl_trampoline_addr(SB), RODATA, $8 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go index 739be6217..fb99594c9 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go @@ -519,6 +519,28 @@ var libc_getcwd_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func getresuid(ruid *_C_int, euid *_C_int, suid *_C_int) { + syscall_rawSyscall(libc_getresuid_trampoline_addr, uintptr(unsafe.Pointer(ruid)), uintptr(unsafe.Pointer(euid)), uintptr(unsafe.Pointer(suid))) + return +} + +var libc_getresuid_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_getresuid getresuid "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func getresgid(rgid *_C_int, egid *_C_int, sgid *_C_int) { + syscall_rawSyscall(libc_getresgid_trampoline_addr, uintptr(unsafe.Pointer(rgid)), uintptr(unsafe.Pointer(egid)), uintptr(unsafe.Pointer(sgid))) + return +} + +var libc_getresgid_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_getresgid getresgid "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func ioctl(fd int, req uint, arg uintptr) (err error) { _, _, e1 := syscall_syscall(libc_ioctl_trampoline_addr, uintptr(fd), uintptr(req), uintptr(arg)) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.s b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.s index 74a25f8d6..ca3f76600 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.s @@ -189,6 +189,18 @@ TEXT libc_getcwd_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_getcwd_trampoline_addr(SB), RODATA, $8 DATA ·libc_getcwd_trampoline_addr(SB)/8, $libc_getcwd_trampoline<>(SB) +TEXT libc_getresuid_trampoline<>(SB),NOSPLIT,$0-0 + CALL libc_getresuid(SB) + RET +GLOBL ·libc_getresuid_trampoline_addr(SB), RODATA, $8 +DATA ·libc_getresuid_trampoline_addr(SB)/8, $libc_getresuid_trampoline<>(SB) + +TEXT libc_getresgid_trampoline<>(SB),NOSPLIT,$0-0 + CALL libc_getresgid(SB) + RET +GLOBL ·libc_getresgid_trampoline_addr(SB), RODATA, $8 +DATA ·libc_getresgid_trampoline_addr(SB)/8, $libc_getresgid_trampoline<>(SB) + TEXT libc_ioctl_trampoline<>(SB),NOSPLIT,$0-0 CALL libc_ioctl(SB) RET diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go index 7d95a1978..32cbbbc52 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go @@ -519,6 +519,28 @@ var libc_getcwd_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func getresuid(ruid *_C_int, euid *_C_int, suid *_C_int) { + syscall_rawSyscall(libc_getresuid_trampoline_addr, uintptr(unsafe.Pointer(ruid)), uintptr(unsafe.Pointer(euid)), uintptr(unsafe.Pointer(suid))) + return +} + +var libc_getresuid_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_getresuid getresuid "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func getresgid(rgid *_C_int, egid *_C_int, sgid *_C_int) { + syscall_rawSyscall(libc_getresgid_trampoline_addr, uintptr(unsafe.Pointer(rgid)), uintptr(unsafe.Pointer(egid)), uintptr(unsafe.Pointer(sgid))) + return +} + +var libc_getresgid_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_getresgid getresgid "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func ioctl(fd int, req uint, arg uintptr) (err error) { _, _, e1 := syscall_syscall(libc_ioctl_trampoline_addr, uintptr(fd), uintptr(req), uintptr(arg)) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.s b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.s index 990be2457..477a7d5b2 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.s @@ -158,6 +158,16 @@ TEXT libc_getcwd_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_getcwd_trampoline_addr(SB), RODATA, $8 DATA ·libc_getcwd_trampoline_addr(SB)/8, $libc_getcwd_trampoline<>(SB) +TEXT libc_getresuid_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_getresuid(SB) +GLOBL ·libc_getresuid_trampoline_addr(SB), RODATA, $8 +DATA ·libc_getresuid_trampoline_addr(SB)/8, $libc_getresuid_trampoline<>(SB) + +TEXT libc_getresgid_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_getresgid(SB) +GLOBL ·libc_getresgid_trampoline_addr(SB), RODATA, $8 +DATA ·libc_getresgid_trampoline_addr(SB)/8, $libc_getresgid_trampoline<>(SB) + TEXT libc_ioctl_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_ioctl(SB) GLOBL ·libc_ioctl_trampoline_addr(SB), RODATA, $8 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go index ca84727cf..00c3b8c20 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go @@ -2555,6 +2555,11 @@ const ( BPF_REG_8 = 0x8 BPF_REG_9 = 0x9 BPF_REG_10 = 0xa + BPF_CGROUP_ITER_ORDER_UNSPEC = 0x0 + BPF_CGROUP_ITER_SELF_ONLY = 0x1 + BPF_CGROUP_ITER_DESCENDANTS_PRE = 0x2 + BPF_CGROUP_ITER_DESCENDANTS_POST = 0x3 + BPF_CGROUP_ITER_ANCESTORS_UP = 0x4 BPF_MAP_CREATE = 0x0 BPF_MAP_LOOKUP_ELEM = 0x1 BPF_MAP_UPDATE_ELEM = 0x2 @@ -2566,6 +2571,7 @@ const ( BPF_PROG_ATTACH = 0x8 BPF_PROG_DETACH = 0x9 BPF_PROG_TEST_RUN = 0xa + BPF_PROG_RUN = 0xa BPF_PROG_GET_NEXT_ID = 0xb BPF_MAP_GET_NEXT_ID = 0xc BPF_PROG_GET_FD_BY_ID = 0xd @@ -2610,6 +2616,7 @@ const ( BPF_MAP_TYPE_CPUMAP = 0x10 BPF_MAP_TYPE_XSKMAP = 0x11 BPF_MAP_TYPE_SOCKHASH = 0x12 + BPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATED = 0x13 BPF_MAP_TYPE_CGROUP_STORAGE = 0x13 BPF_MAP_TYPE_REUSEPORT_SOCKARRAY = 0x14 BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE = 0x15 @@ -2620,6 +2627,10 @@ const ( BPF_MAP_TYPE_STRUCT_OPS = 0x1a BPF_MAP_TYPE_RINGBUF = 0x1b BPF_MAP_TYPE_INODE_STORAGE = 0x1c + BPF_MAP_TYPE_TASK_STORAGE = 0x1d + BPF_MAP_TYPE_BLOOM_FILTER = 0x1e + BPF_MAP_TYPE_USER_RINGBUF = 0x1f + BPF_MAP_TYPE_CGRP_STORAGE = 0x20 BPF_PROG_TYPE_UNSPEC = 0x0 BPF_PROG_TYPE_SOCKET_FILTER = 0x1 BPF_PROG_TYPE_KPROBE = 0x2 @@ -2651,6 +2662,7 @@ const ( BPF_PROG_TYPE_EXT = 0x1c BPF_PROG_TYPE_LSM = 0x1d BPF_PROG_TYPE_SK_LOOKUP = 0x1e + BPF_PROG_TYPE_SYSCALL = 0x1f BPF_CGROUP_INET_INGRESS = 0x0 BPF_CGROUP_INET_EGRESS = 0x1 BPF_CGROUP_INET_SOCK_CREATE = 0x2 @@ -2689,6 +2701,12 @@ const ( BPF_XDP_CPUMAP = 0x23 BPF_SK_LOOKUP = 0x24 BPF_XDP = 0x25 + BPF_SK_SKB_VERDICT = 0x26 + BPF_SK_REUSEPORT_SELECT = 0x27 + BPF_SK_REUSEPORT_SELECT_OR_MIGRATE = 0x28 + BPF_PERF_EVENT = 0x29 + BPF_TRACE_KPROBE_MULTI = 0x2a + BPF_LSM_CGROUP = 0x2b BPF_LINK_TYPE_UNSPEC = 0x0 BPF_LINK_TYPE_RAW_TRACEPOINT = 0x1 BPF_LINK_TYPE_TRACING = 0x2 @@ -2696,6 +2714,9 @@ const ( BPF_LINK_TYPE_ITER = 0x4 BPF_LINK_TYPE_NETNS = 0x5 BPF_LINK_TYPE_XDP = 0x6 + BPF_LINK_TYPE_PERF_EVENT = 0x7 + BPF_LINK_TYPE_KPROBE_MULTI = 0x8 + BPF_LINK_TYPE_STRUCT_OPS = 0x9 BPF_ANY = 0x0 BPF_NOEXIST = 0x1 BPF_EXIST = 0x2 @@ -2733,6 +2754,7 @@ const ( BPF_F_ZERO_CSUM_TX = 0x2 BPF_F_DONT_FRAGMENT = 0x4 BPF_F_SEQ_NUMBER = 0x8 + BPF_F_TUNINFO_FLAGS = 0x10 BPF_F_INDEX_MASK = 0xffffffff BPF_F_CURRENT_CPU = 0xffffffff BPF_F_CTXLEN_MASK = 0xfffff00000000 @@ -2747,6 +2769,7 @@ const ( BPF_F_ADJ_ROOM_ENCAP_L4_GRE = 0x8 BPF_F_ADJ_ROOM_ENCAP_L4_UDP = 0x10 BPF_F_ADJ_ROOM_NO_CSUM_RESET = 0x20 + BPF_F_ADJ_ROOM_ENCAP_L2_ETH = 0x40 BPF_ADJ_ROOM_ENCAP_L2_MASK = 0xff BPF_ADJ_ROOM_ENCAP_L2_SHIFT = 0x38 BPF_F_SYSCTL_BASE_NAME = 0x1 @@ -2771,10 +2794,16 @@ const ( BPF_LWT_ENCAP_SEG6 = 0x0 BPF_LWT_ENCAP_SEG6_INLINE = 0x1 BPF_LWT_ENCAP_IP = 0x2 + BPF_F_BPRM_SECUREEXEC = 0x1 + BPF_F_BROADCAST = 0x8 + BPF_F_EXCLUDE_INGRESS = 0x10 + BPF_SKB_TSTAMP_UNSPEC = 0x0 + BPF_SKB_TSTAMP_DELIVERY_MONO = 0x1 BPF_OK = 0x0 BPF_DROP = 0x2 BPF_REDIRECT = 0x7 BPF_LWT_REROUTE = 0x80 + BPF_FLOW_DISSECTOR_CONTINUE = 0x81 BPF_SOCK_OPS_RTO_CB_FLAG = 0x1 BPF_SOCK_OPS_RETRANS_CB_FLAG = 0x2 BPF_SOCK_OPS_STATE_CB_FLAG = 0x4 @@ -2838,6 +2867,10 @@ const ( BPF_FIB_LKUP_RET_UNSUPP_LWT = 0x6 BPF_FIB_LKUP_RET_NO_NEIGH = 0x7 BPF_FIB_LKUP_RET_FRAG_NEEDED = 0x8 + BPF_MTU_CHK_SEGS = 0x1 + BPF_MTU_CHK_RET_SUCCESS = 0x0 + BPF_MTU_CHK_RET_FRAG_NEEDED = 0x1 + BPF_MTU_CHK_RET_SEGS_TOOBIG = 0x2 BPF_FD_TYPE_RAW_TRACEPOINT = 0x0 BPF_FD_TYPE_TRACEPOINT = 0x1 BPF_FD_TYPE_KPROBE = 0x2 @@ -2847,6 +2880,19 @@ const ( BPF_FLOW_DISSECTOR_F_PARSE_1ST_FRAG = 0x1 BPF_FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL = 0x2 BPF_FLOW_DISSECTOR_F_STOP_AT_ENCAP = 0x4 + BPF_CORE_FIELD_BYTE_OFFSET = 0x0 + BPF_CORE_FIELD_BYTE_SIZE = 0x1 + BPF_CORE_FIELD_EXISTS = 0x2 + BPF_CORE_FIELD_SIGNED = 0x3 + BPF_CORE_FIELD_LSHIFT_U64 = 0x4 + BPF_CORE_FIELD_RSHIFT_U64 = 0x5 + BPF_CORE_TYPE_ID_LOCAL = 0x6 + BPF_CORE_TYPE_ID_TARGET = 0x7 + BPF_CORE_TYPE_EXISTS = 0x8 + BPF_CORE_TYPE_SIZE = 0x9 + BPF_CORE_ENUMVAL_EXISTS = 0xa + BPF_CORE_ENUMVAL_VALUE = 0xb + BPF_CORE_TYPE_MATCHES = 0xc ) const ( diff --git a/vendor/golang.org/x/sys/windows/syscall_windows.go b/vendor/golang.org/x/sys/windows/syscall_windows.go index 3723b2c22..964590075 100644 --- a/vendor/golang.org/x/sys/windows/syscall_windows.go +++ b/vendor/golang.org/x/sys/windows/syscall_windows.go @@ -405,7 +405,7 @@ func NewCallbackCDecl(fn interface{}) uintptr { //sys VerQueryValue(block unsafe.Pointer, subBlock string, pointerToBufferPointer unsafe.Pointer, bufSize *uint32) (err error) = version.VerQueryValueW // Process Status API (PSAPI) -//sys EnumProcesses(processIds []uint32, bytesReturned *uint32) (err error) = psapi.EnumProcesses +//sys enumProcesses(processIds *uint32, nSize uint32, bytesReturned *uint32) (err error) = psapi.EnumProcesses //sys EnumProcessModules(process Handle, module *Handle, cb uint32, cbNeeded *uint32) (err error) = psapi.EnumProcessModules //sys EnumProcessModulesEx(process Handle, module *Handle, cb uint32, cbNeeded *uint32, filterFlag uint32) (err error) = psapi.EnumProcessModulesEx //sys GetModuleInformation(process Handle, module Handle, modinfo *ModuleInfo, cb uint32) (err error) = psapi.GetModuleInformation @@ -1354,6 +1354,17 @@ func SetsockoptIPv6Mreq(fd Handle, level, opt int, mreq *IPv6Mreq) (err error) { return syscall.EWINDOWS } +func EnumProcesses(processIds []uint32, bytesReturned *uint32) error { + // EnumProcesses syscall expects the size parameter to be in bytes, but the code generated with mksyscall uses + // the length of the processIds slice instead. Hence, this wrapper function is added to fix the discrepancy. + var p *uint32 + if len(processIds) > 0 { + p = &processIds[0] + } + size := uint32(len(processIds) * 4) + return enumProcesses(p, size, bytesReturned) +} + func Getpid() (pid int) { return int(GetCurrentProcessId()) } func FindFirstFile(name *uint16, data *Win32finddata) (handle Handle, err error) { diff --git a/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/zsyscall_windows.go index a81ea2c70..566dd3e31 100644 --- a/vendor/golang.org/x/sys/windows/zsyscall_windows.go +++ b/vendor/golang.org/x/sys/windows/zsyscall_windows.go @@ -3516,12 +3516,8 @@ func EnumProcessModulesEx(process Handle, module *Handle, cb uint32, cbNeeded *u return } -func EnumProcesses(processIds []uint32, bytesReturned *uint32) (err error) { - var _p0 *uint32 - if len(processIds) > 0 { - _p0 = &processIds[0] - } - r1, _, e1 := syscall.Syscall(procEnumProcesses.Addr(), 3, uintptr(unsafe.Pointer(_p0)), uintptr(len(processIds)), uintptr(unsafe.Pointer(bytesReturned))) +func enumProcesses(processIds *uint32, nSize uint32, bytesReturned *uint32) (err error) { + r1, _, e1 := syscall.Syscall(procEnumProcesses.Addr(), 3, uintptr(unsafe.Pointer(processIds)), uintptr(nSize), uintptr(unsafe.Pointer(bytesReturned))) if r1 == 0 { err = errnoErr(e1) } diff --git a/vendor/golang.org/x/xerrors/LICENSE b/vendor/golang.org/x/xerrors/LICENSE new file mode 100644 index 000000000..e4a47e17f --- /dev/null +++ b/vendor/golang.org/x/xerrors/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2019 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/xerrors/PATENTS b/vendor/golang.org/x/xerrors/PATENTS new file mode 100644 index 000000000..733099041 --- /dev/null +++ b/vendor/golang.org/x/xerrors/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google 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, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/xerrors/README b/vendor/golang.org/x/xerrors/README new file mode 100644 index 000000000..aac7867a5 --- /dev/null +++ b/vendor/golang.org/x/xerrors/README @@ -0,0 +1,2 @@ +This repository holds the transition packages for the new Go 1.13 error values. +See golang.org/design/29934-error-values. diff --git a/vendor/golang.org/x/xerrors/adaptor.go b/vendor/golang.org/x/xerrors/adaptor.go new file mode 100644 index 000000000..4317f2483 --- /dev/null +++ b/vendor/golang.org/x/xerrors/adaptor.go @@ -0,0 +1,193 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import ( + "bytes" + "fmt" + "io" + "reflect" + "strconv" +) + +// FormatError calls the FormatError method of f with an errors.Printer +// configured according to s and verb, and writes the result to s. +func FormatError(f Formatter, s fmt.State, verb rune) { + // Assuming this function is only called from the Format method, and given + // that FormatError takes precedence over Format, it cannot be called from + // any package that supports errors.Formatter. It is therefore safe to + // disregard that State may be a specific printer implementation and use one + // of our choice instead. + + // limitations: does not support printing error as Go struct. + + var ( + sep = " " // separator before next error + p = &state{State: s} + direct = true + ) + + var err error = f + + switch verb { + // Note that this switch must match the preference order + // for ordinary string printing (%#v before %+v, and so on). + + case 'v': + if s.Flag('#') { + if stringer, ok := err.(fmt.GoStringer); ok { + io.WriteString(&p.buf, stringer.GoString()) + goto exit + } + // proceed as if it were %v + } else if s.Flag('+') { + p.printDetail = true + sep = "\n - " + } + case 's': + case 'q', 'x', 'X': + // Use an intermediate buffer in the rare cases that precision, + // truncation, or one of the alternative verbs (q, x, and X) are + // specified. + direct = false + + default: + p.buf.WriteString("%!") + p.buf.WriteRune(verb) + p.buf.WriteByte('(') + switch { + case err != nil: + p.buf.WriteString(reflect.TypeOf(f).String()) + default: + p.buf.WriteString("") + } + p.buf.WriteByte(')') + io.Copy(s, &p.buf) + return + } + +loop: + for { + switch v := err.(type) { + case Formatter: + err = v.FormatError((*printer)(p)) + case fmt.Formatter: + v.Format(p, 'v') + break loop + default: + io.WriteString(&p.buf, v.Error()) + break loop + } + if err == nil { + break + } + if p.needColon || !p.printDetail { + p.buf.WriteByte(':') + p.needColon = false + } + p.buf.WriteString(sep) + p.inDetail = false + p.needNewline = false + } + +exit: + width, okW := s.Width() + prec, okP := s.Precision() + + if !direct || (okW && width > 0) || okP { + // Construct format string from State s. + format := []byte{'%'} + if s.Flag('-') { + format = append(format, '-') + } + if s.Flag('+') { + format = append(format, '+') + } + if s.Flag(' ') { + format = append(format, ' ') + } + if okW { + format = strconv.AppendInt(format, int64(width), 10) + } + if okP { + format = append(format, '.') + format = strconv.AppendInt(format, int64(prec), 10) + } + format = append(format, string(verb)...) + fmt.Fprintf(s, string(format), p.buf.String()) + } else { + io.Copy(s, &p.buf) + } +} + +var detailSep = []byte("\n ") + +// state tracks error printing state. It implements fmt.State. +type state struct { + fmt.State + buf bytes.Buffer + + printDetail bool + inDetail bool + needColon bool + needNewline bool +} + +func (s *state) Write(b []byte) (n int, err error) { + if s.printDetail { + if len(b) == 0 { + return 0, nil + } + if s.inDetail && s.needColon { + s.needNewline = true + if b[0] == '\n' { + b = b[1:] + } + } + k := 0 + for i, c := range b { + if s.needNewline { + if s.inDetail && s.needColon { + s.buf.WriteByte(':') + s.needColon = false + } + s.buf.Write(detailSep) + s.needNewline = false + } + if c == '\n' { + s.buf.Write(b[k:i]) + k = i + 1 + s.needNewline = true + } + } + s.buf.Write(b[k:]) + if !s.inDetail { + s.needColon = true + } + } else if !s.inDetail { + s.buf.Write(b) + } + return len(b), nil +} + +// printer wraps a state to implement an xerrors.Printer. +type printer state + +func (s *printer) Print(args ...interface{}) { + if !s.inDetail || s.printDetail { + fmt.Fprint((*state)(s), args...) + } +} + +func (s *printer) Printf(format string, args ...interface{}) { + if !s.inDetail || s.printDetail { + fmt.Fprintf((*state)(s), format, args...) + } +} + +func (s *printer) Detail() bool { + s.inDetail = true + return s.printDetail +} diff --git a/vendor/golang.org/x/xerrors/codereview.cfg b/vendor/golang.org/x/xerrors/codereview.cfg new file mode 100644 index 000000000..3f8b14b64 --- /dev/null +++ b/vendor/golang.org/x/xerrors/codereview.cfg @@ -0,0 +1 @@ +issuerepo: golang/go diff --git a/vendor/golang.org/x/xerrors/doc.go b/vendor/golang.org/x/xerrors/doc.go new file mode 100644 index 000000000..2ef99f5a8 --- /dev/null +++ b/vendor/golang.org/x/xerrors/doc.go @@ -0,0 +1,23 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package xerrors implements functions to manipulate errors. +// +// This package is based on the Go 2 proposal for error values: +// +// https://golang.org/design/29934-error-values +// +// These functions were incorporated into the standard library's errors package +// in Go 1.13: +// - Is +// - As +// - Unwrap +// +// Also, Errorf's %w verb was incorporated into fmt.Errorf. +// +// Use this package to get equivalent behavior in all supported Go versions. +// +// No other features of this package were included in Go 1.13, and at present +// there are no plans to include any of them. +package xerrors // import "golang.org/x/xerrors" diff --git a/vendor/golang.org/x/xerrors/errors.go b/vendor/golang.org/x/xerrors/errors.go new file mode 100644 index 000000000..e88d3772d --- /dev/null +++ b/vendor/golang.org/x/xerrors/errors.go @@ -0,0 +1,33 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import "fmt" + +// errorString is a trivial implementation of error. +type errorString struct { + s string + frame Frame +} + +// New returns an error that formats as the given text. +// +// The returned error contains a Frame set to the caller's location and +// implements Formatter to show this information when printed with details. +func New(text string) error { + return &errorString{text, Caller(1)} +} + +func (e *errorString) Error() string { + return e.s +} + +func (e *errorString) Format(s fmt.State, v rune) { FormatError(e, s, v) } + +func (e *errorString) FormatError(p Printer) (next error) { + p.Print(e.s) + e.frame.Format(p) + return nil +} diff --git a/vendor/golang.org/x/xerrors/fmt.go b/vendor/golang.org/x/xerrors/fmt.go new file mode 100644 index 000000000..27a5d70bd --- /dev/null +++ b/vendor/golang.org/x/xerrors/fmt.go @@ -0,0 +1,190 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import ( + "fmt" + "strings" + "unicode" + "unicode/utf8" + + "golang.org/x/xerrors/internal" +) + +const percentBangString = "%!" + +// Errorf formats according to a format specifier and returns the string as a +// value that satisfies error. +// +// The returned error includes the file and line number of the caller when +// formatted with additional detail enabled. If the last argument is an error +// the returned error's Format method will return it if the format string ends +// with ": %s", ": %v", or ": %w". If the last argument is an error and the +// format string ends with ": %w", the returned error implements an Unwrap +// method returning it. +// +// If the format specifier includes a %w verb with an error operand in a +// position other than at the end, the returned error will still implement an +// Unwrap method returning the operand, but the error's Format method will not +// return the wrapped error. +// +// It is invalid to include more than one %w verb or to supply it with an +// operand that does not implement the error interface. The %w verb is otherwise +// a synonym for %v. +// +// Note that as of Go 1.13, the fmt.Errorf function will do error formatting, +// but it will not capture a stack backtrace. +func Errorf(format string, a ...interface{}) error { + format = formatPlusW(format) + // Support a ": %[wsv]" suffix, which works well with xerrors.Formatter. + wrap := strings.HasSuffix(format, ": %w") + idx, format2, ok := parsePercentW(format) + percentWElsewhere := !wrap && idx >= 0 + if !percentWElsewhere && (wrap || strings.HasSuffix(format, ": %s") || strings.HasSuffix(format, ": %v")) { + err := errorAt(a, len(a)-1) + if err == nil { + return &noWrapError{fmt.Sprintf(format, a...), nil, Caller(1)} + } + // TODO: this is not entirely correct. The error value could be + // printed elsewhere in format if it mixes numbered with unnumbered + // substitutions. With relatively small changes to doPrintf we can + // have it optionally ignore extra arguments and pass the argument + // list in its entirety. + msg := fmt.Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...) + frame := Frame{} + if internal.EnableTrace { + frame = Caller(1) + } + if wrap { + return &wrapError{msg, err, frame} + } + return &noWrapError{msg, err, frame} + } + // Support %w anywhere. + // TODO: don't repeat the wrapped error's message when %w occurs in the middle. + msg := fmt.Sprintf(format2, a...) + if idx < 0 { + return &noWrapError{msg, nil, Caller(1)} + } + err := errorAt(a, idx) + if !ok || err == nil { + // Too many %ws or argument of %w is not an error. Approximate the Go + // 1.13 fmt.Errorf message. + return &noWrapError{fmt.Sprintf("%sw(%s)", percentBangString, msg), nil, Caller(1)} + } + frame := Frame{} + if internal.EnableTrace { + frame = Caller(1) + } + return &wrapError{msg, err, frame} +} + +func errorAt(args []interface{}, i int) error { + if i < 0 || i >= len(args) { + return nil + } + err, ok := args[i].(error) + if !ok { + return nil + } + return err +} + +// formatPlusW is used to avoid the vet check that will barf at %w. +func formatPlusW(s string) string { + return s +} + +// Return the index of the only %w in format, or -1 if none. +// Also return a rewritten format string with %w replaced by %v, and +// false if there is more than one %w. +// TODO: handle "%[N]w". +func parsePercentW(format string) (idx int, newFormat string, ok bool) { + // Loosely copied from golang.org/x/tools/go/analysis/passes/printf/printf.go. + idx = -1 + ok = true + n := 0 + sz := 0 + var isW bool + for i := 0; i < len(format); i += sz { + if format[i] != '%' { + sz = 1 + continue + } + // "%%" is not a format directive. + if i+1 < len(format) && format[i+1] == '%' { + sz = 2 + continue + } + sz, isW = parsePrintfVerb(format[i:]) + if isW { + if idx >= 0 { + ok = false + } else { + idx = n + } + // "Replace" the last character, the 'w', with a 'v'. + p := i + sz - 1 + format = format[:p] + "v" + format[p+1:] + } + n++ + } + return idx, format, ok +} + +// Parse the printf verb starting with a % at s[0]. +// Return how many bytes it occupies and whether the verb is 'w'. +func parsePrintfVerb(s string) (int, bool) { + // Assume only that the directive is a sequence of non-letters followed by a single letter. + sz := 0 + var r rune + for i := 1; i < len(s); i += sz { + r, sz = utf8.DecodeRuneInString(s[i:]) + if unicode.IsLetter(r) { + return i + sz, r == 'w' + } + } + return len(s), false +} + +type noWrapError struct { + msg string + err error + frame Frame +} + +func (e *noWrapError) Error() string { + return fmt.Sprint(e) +} + +func (e *noWrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) } + +func (e *noWrapError) FormatError(p Printer) (next error) { + p.Print(e.msg) + e.frame.Format(p) + return e.err +} + +type wrapError struct { + msg string + err error + frame Frame +} + +func (e *wrapError) Error() string { + return fmt.Sprint(e) +} + +func (e *wrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) } + +func (e *wrapError) FormatError(p Printer) (next error) { + p.Print(e.msg) + e.frame.Format(p) + return e.err +} + +func (e *wrapError) Unwrap() error { + return e.err +} diff --git a/vendor/golang.org/x/xerrors/format.go b/vendor/golang.org/x/xerrors/format.go new file mode 100644 index 000000000..1bc9c26b9 --- /dev/null +++ b/vendor/golang.org/x/xerrors/format.go @@ -0,0 +1,34 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +// A Formatter formats error messages. +type Formatter interface { + error + + // FormatError prints the receiver's first error and returns the next error in + // the error chain, if any. + FormatError(p Printer) (next error) +} + +// A Printer formats error messages. +// +// The most common implementation of Printer is the one provided by package fmt +// during Printf (as of Go 1.13). Localization packages such as golang.org/x/text/message +// typically provide their own implementations. +type Printer interface { + // Print appends args to the message output. + Print(args ...interface{}) + + // Printf writes a formatted string. + Printf(format string, args ...interface{}) + + // Detail reports whether error detail is requested. + // After the first call to Detail, all text written to the Printer + // is formatted as additional detail, or ignored when + // detail has not been requested. + // If Detail returns false, the caller can avoid printing the detail at all. + Detail() bool +} diff --git a/vendor/golang.org/x/xerrors/frame.go b/vendor/golang.org/x/xerrors/frame.go new file mode 100644 index 000000000..0de628ec5 --- /dev/null +++ b/vendor/golang.org/x/xerrors/frame.go @@ -0,0 +1,56 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import ( + "runtime" +) + +// A Frame contains part of a call stack. +type Frame struct { + // Make room for three PCs: the one we were asked for, what it called, + // and possibly a PC for skipPleaseUseCallersFrames. See: + // https://go.googlesource.com/go/+/032678e0fb/src/runtime/extern.go#169 + frames [3]uintptr +} + +// Caller returns a Frame that describes a frame on the caller's stack. +// The argument skip is the number of frames to skip over. +// Caller(0) returns the frame for the caller of Caller. +func Caller(skip int) Frame { + var s Frame + runtime.Callers(skip+1, s.frames[:]) + return s +} + +// location reports the file, line, and function of a frame. +// +// The returned function may be "" even if file and line are not. +func (f Frame) location() (function, file string, line int) { + frames := runtime.CallersFrames(f.frames[:]) + if _, ok := frames.Next(); !ok { + return "", "", 0 + } + fr, ok := frames.Next() + if !ok { + return "", "", 0 + } + return fr.Function, fr.File, fr.Line +} + +// Format prints the stack as error detail. +// It should be called from an error's Format implementation +// after printing any other error detail. +func (f Frame) Format(p Printer) { + if p.Detail() { + function, file, line := f.location() + if function != "" { + p.Printf("%s\n ", function) + } + if file != "" { + p.Printf("%s:%d\n", file, line) + } + } +} diff --git a/vendor/golang.org/x/xerrors/internal/internal.go b/vendor/golang.org/x/xerrors/internal/internal.go new file mode 100644 index 000000000..89f4eca5d --- /dev/null +++ b/vendor/golang.org/x/xerrors/internal/internal.go @@ -0,0 +1,8 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package internal + +// EnableTrace indicates whether stack information should be recorded in errors. +var EnableTrace = true diff --git a/vendor/golang.org/x/xerrors/wrap.go b/vendor/golang.org/x/xerrors/wrap.go new file mode 100644 index 000000000..9842758ca --- /dev/null +++ b/vendor/golang.org/x/xerrors/wrap.go @@ -0,0 +1,112 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import ( + "reflect" +) + +// A Wrapper provides context around another error. +type Wrapper interface { + // Unwrap returns the next error in the error chain. + // If there is no next error, Unwrap returns nil. + Unwrap() error +} + +// Opaque returns an error with the same error formatting as err +// but that does not match err and cannot be unwrapped. +func Opaque(err error) error { + return noWrapper{err} +} + +type noWrapper struct { + error +} + +func (e noWrapper) FormatError(p Printer) (next error) { + if f, ok := e.error.(Formatter); ok { + return f.FormatError(p) + } + p.Print(e.error) + return nil +} + +// Unwrap returns the result of calling the Unwrap method on err, if err implements +// Unwrap. Otherwise, Unwrap returns nil. +// +// Deprecated: As of Go 1.13, use errors.Unwrap instead. +func Unwrap(err error) error { + u, ok := err.(Wrapper) + if !ok { + return nil + } + return u.Unwrap() +} + +// Is reports whether any error in err's chain matches target. +// +// An error is considered to match a target if it is equal to that target or if +// it implements a method Is(error) bool such that Is(target) returns true. +// +// Deprecated: As of Go 1.13, use errors.Is instead. +func Is(err, target error) bool { + if target == nil { + return err == target + } + + isComparable := reflect.TypeOf(target).Comparable() + for { + if isComparable && err == target { + return true + } + if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) { + return true + } + // TODO: consider supporing target.Is(err). This would allow + // user-definable predicates, but also may allow for coping with sloppy + // APIs, thereby making it easier to get away with them. + if err = Unwrap(err); err == nil { + return false + } + } +} + +// As finds the first error in err's chain that matches the type to which target +// points, and if so, sets the target to its value and returns true. An error +// matches a type if it is assignable to the target type, or if it has a method +// As(interface{}) bool such that As(target) returns true. As will panic if target +// is not a non-nil pointer to a type which implements error or is of interface type. +// +// The As method should set the target to its value and return true if err +// matches the type to which target points. +// +// Deprecated: As of Go 1.13, use errors.As instead. +func As(err error, target interface{}) bool { + if target == nil { + panic("errors: target cannot be nil") + } + val := reflect.ValueOf(target) + typ := val.Type() + if typ.Kind() != reflect.Ptr || val.IsNil() { + panic("errors: target must be a non-nil pointer") + } + if e := typ.Elem(); e.Kind() != reflect.Interface && !e.Implements(errorType) { + panic("errors: *target must be interface or implement error") + } + targetType := typ.Elem() + for err != nil { + if reflect.TypeOf(err).AssignableTo(targetType) { + val.Elem().Set(reflect.ValueOf(err)) + return true + } + if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) { + return true + } + err = Unwrap(err) + } + return false +} + +var errorType = reflect.TypeOf((*error)(nil)).Elem() diff --git a/vendor/google.golang.org/grpc/CONTRIBUTING.md b/vendor/google.golang.org/grpc/CONTRIBUTING.md index 8e001134d..608aa6e1a 100644 --- a/vendor/google.golang.org/grpc/CONTRIBUTING.md +++ b/vendor/google.golang.org/grpc/CONTRIBUTING.md @@ -20,10 +20,6 @@ How to get your contributions merged smoothly and quickly. both author's & review's time is wasted. Create more PRs to address different concerns and everyone will be happy. -- For speculative changes, consider opening an issue and discussing it first. If - you are suggesting a behavioral or API change, consider starting with a [gRFC - proposal](https://github.com/grpc/proposal). - - If you are searching for features to work on, issues labeled [Status: Help Wanted](https://github.com/grpc/grpc-go/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22Status%3A+Help+Wanted%22) is a great place to start. These issues are well-documented and usually can be diff --git a/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go b/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go index 8cd89dab9..ec2c2fa14 100644 --- a/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go +++ b/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go @@ -18,7 +18,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 +// protoc-gen-go v1.30.0 // protoc v4.22.0 // source: grpc/binlog/v1/binarylog.proto diff --git a/vendor/google.golang.org/grpc/clientconn.go b/vendor/google.golang.org/grpc/clientconn.go index b9cc05507..3a7614242 100644 --- a/vendor/google.golang.org/grpc/clientconn.go +++ b/vendor/google.golang.org/grpc/clientconn.go @@ -244,19 +244,6 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * } }() - scSet := false - if cc.dopts.scChan != nil { - // Try to get an initial service config. - select { - case sc, ok := <-cc.dopts.scChan: - if ok { - cc.sc = &sc - cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{&sc}) - scSet = true - } - default: - } - } if cc.dopts.bs == nil { cc.dopts.bs = backoff.DefaultExponential } @@ -272,7 +259,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * } channelz.Infof(logger, cc.channelzID, "Channel authority set to %q", cc.authority) - if cc.dopts.scChan != nil && !scSet { + if cc.dopts.scChan != nil { // Blocking wait for the initial service config. select { case sc, ok := <-cc.dopts.scChan: diff --git a/vendor/google.golang.org/grpc/dialoptions.go b/vendor/google.golang.org/grpc/dialoptions.go index e9d6852fd..cdc8263bd 100644 --- a/vendor/google.golang.org/grpc/dialoptions.go +++ b/vendor/google.golang.org/grpc/dialoptions.go @@ -295,6 +295,9 @@ func withBackoff(bs internalbackoff.Strategy) DialOption { // WithBlock returns a DialOption which makes callers of Dial block until the // underlying connection is up. Without this, Dial returns immediately and // connecting the server happens in background. +// +// Use of this feature is not recommended. For more information, please see: +// https://github.com/grpc/grpc-go/blob/master/Documentation/anti-patterns.md func WithBlock() DialOption { return newFuncDialOption(func(o *dialOptions) { o.block = true @@ -306,6 +309,9 @@ func WithBlock() DialOption { // the context.DeadlineExceeded error. // Implies WithBlock() // +// Use of this feature is not recommended. For more information, please see: +// https://github.com/grpc/grpc-go/blob/master/Documentation/anti-patterns.md +// // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a @@ -448,6 +454,9 @@ func withBinaryLogger(bl binarylog.Logger) DialOption { // FailOnNonTempDialError only affects the initial dial, and does not do // anything useful unless you are also using WithBlock(). // +// Use of this feature is not recommended. For more information, please see: +// https://github.com/grpc/grpc-go/blob/master/Documentation/anti-patterns.md +// // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a diff --git a/vendor/google.golang.org/grpc/health/grpc_health_v1/health.pb.go b/vendor/google.golang.org/grpc/health/grpc_health_v1/health.pb.go index 0b1abc646..142d35f75 100644 --- a/vendor/google.golang.org/grpc/health/grpc_health_v1/health.pb.go +++ b/vendor/google.golang.org/grpc/health/grpc_health_v1/health.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 +// protoc-gen-go v1.30.0 // protoc v4.22.0 // source: grpc/health/v1/health.proto diff --git a/vendor/google.golang.org/grpc/internal/envconfig/xds.go b/vendor/google.golang.org/grpc/internal/envconfig/xds.go index 04136882c..3b17705ba 100644 --- a/vendor/google.golang.org/grpc/internal/envconfig/xds.go +++ b/vendor/google.golang.org/grpc/internal/envconfig/xds.go @@ -79,7 +79,7 @@ var ( // XDSFederation indicates whether federation support is enabled, which can // be enabled by setting the environment variable // "GRPC_EXPERIMENTAL_XDS_FEDERATION" to "true". - XDSFederation = boolFromEnv("GRPC_EXPERIMENTAL_XDS_FEDERATION", false) + XDSFederation = boolFromEnv("GRPC_EXPERIMENTAL_XDS_FEDERATION", true) // XDSRLS indicates whether processing of Cluster Specifier plugins and // support for the RLS CLuster Specifier is enabled, which can be enabled by diff --git a/vendor/google.golang.org/grpc/internal/grpcsync/callback_serializer.go b/vendor/google.golang.org/grpc/internal/grpcsync/callback_serializer.go new file mode 100644 index 000000000..79993d343 --- /dev/null +++ b/vendor/google.golang.org/grpc/internal/grpcsync/callback_serializer.go @@ -0,0 +1,65 @@ +/* + * + * Copyright 2022 gRPC 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 grpcsync + +import ( + "context" + + "google.golang.org/grpc/internal/buffer" +) + +// CallbackSerializer provides a mechanism to schedule callbacks in a +// synchronized manner. It provides a FIFO guarantee on the order of execution +// of scheduled callbacks. New callbacks can be scheduled by invoking the +// Schedule() method. +// +// This type is safe for concurrent access. +type CallbackSerializer struct { + callbacks *buffer.Unbounded +} + +// NewCallbackSerializer returns a new CallbackSerializer instance. The provided +// context will be passed to the scheduled callbacks. Users should cancel the +// provided context to shutdown the CallbackSerializer. It is guaranteed that no +// callbacks will be executed once this context is canceled. +func NewCallbackSerializer(ctx context.Context) *CallbackSerializer { + t := &CallbackSerializer{callbacks: buffer.NewUnbounded()} + go t.run(ctx) + return t +} + +// Schedule adds a callback to be scheduled after existing callbacks are run. +// +// Callbacks are expected to honor the context when performing any blocking +// operations, and should return early when the context is canceled. +func (t *CallbackSerializer) Schedule(f func(ctx context.Context)) { + t.callbacks.Put(f) +} + +func (t *CallbackSerializer) run(ctx context.Context) { + for ctx.Err() == nil { + select { + case <-ctx.Done(): + return + case callback := <-t.callbacks.Get(): + t.callbacks.Load() + callback.(func(ctx context.Context))(ctx) + } + } +} diff --git a/vendor/google.golang.org/grpc/internal/transport/controlbuf.go b/vendor/google.golang.org/grpc/internal/transport/controlbuf.go index c343c23a5..be5a9c81e 100644 --- a/vendor/google.golang.org/grpc/internal/transport/controlbuf.go +++ b/vendor/google.golang.org/grpc/internal/transport/controlbuf.go @@ -30,6 +30,7 @@ import ( "golang.org/x/net/http2" "golang.org/x/net/http2/hpack" + "google.golang.org/grpc/internal/grpclog" "google.golang.org/grpc/internal/grpcutil" "google.golang.org/grpc/status" ) @@ -488,12 +489,13 @@ type loopyWriter struct { bdpEst *bdpEstimator draining bool conn net.Conn + logger *grpclog.PrefixLogger // Side-specific handlers ssGoAwayHandler func(*goAway) (bool, error) } -func newLoopyWriter(s side, fr *framer, cbuf *controlBuffer, bdpEst *bdpEstimator, conn net.Conn) *loopyWriter { +func newLoopyWriter(s side, fr *framer, cbuf *controlBuffer, bdpEst *bdpEstimator, conn net.Conn, logger *grpclog.PrefixLogger) *loopyWriter { var buf bytes.Buffer l := &loopyWriter{ side: s, @@ -507,6 +509,7 @@ func newLoopyWriter(s side, fr *framer, cbuf *controlBuffer, bdpEst *bdpEstimato hEnc: hpack.NewEncoder(&buf), bdpEst: bdpEst, conn: conn, + logger: logger, } return l } @@ -536,8 +539,8 @@ const minBatchSize = 1000 // left open to allow the I/O error to be encountered by the reader instead. func (l *loopyWriter) run() (err error) { defer func() { - if logger.V(logLevel) { - logger.Infof("transport: loopyWriter exiting with error: %v", err) + if l.logger.V(logLevel) { + l.logger.Infof("loopyWriter exiting with error: %v", err) } if !isIOError(err) { l.framer.writer.Flush() @@ -636,8 +639,8 @@ func (l *loopyWriter) headerHandler(h *headerFrame) error { if l.side == serverSide { str, ok := l.estdStreams[h.streamID] if !ok { - if logger.V(logLevel) { - logger.Warningf("transport: loopy doesn't recognize the stream: %d", h.streamID) + if l.logger.V(logLevel) { + l.logger.Infof("Unrecognized streamID %d in loopyWriter", h.streamID) } return nil } @@ -692,8 +695,8 @@ func (l *loopyWriter) writeHeader(streamID uint32, endStream bool, hf []hpack.He l.hBuf.Reset() for _, f := range hf { if err := l.hEnc.WriteField(f); err != nil { - if logger.V(logLevel) { - logger.Warningf("transport: loopyWriter.writeHeader encountered error while encoding headers: %v", err) + if l.logger.V(logLevel) { + l.logger.Warningf("Encountered error while encoding headers: %v", err) } } } diff --git a/vendor/google.golang.org/grpc/internal/transport/handler_server.go b/vendor/google.golang.org/grpc/internal/transport/handler_server.go index e6626bf96..fbee581b8 100644 --- a/vendor/google.golang.org/grpc/internal/transport/handler_server.go +++ b/vendor/google.golang.org/grpc/internal/transport/handler_server.go @@ -39,6 +39,7 @@ import ( "golang.org/x/net/http2" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" + "google.golang.org/grpc/internal/grpclog" "google.golang.org/grpc/internal/grpcutil" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" @@ -83,6 +84,7 @@ func NewServerHandlerTransport(w http.ResponseWriter, r *http.Request, stats []s contentSubtype: contentSubtype, stats: stats, } + st.logger = prefixLoggerForServerHandlerTransport(st) if v := r.Header.Get("grpc-timeout"); v != "" { to, err := decodeTimeout(v) @@ -150,13 +152,14 @@ type serverHandlerTransport struct { // TODO make sure this is consistent across handler_server and http2_server contentSubtype string - stats []stats.Handler + stats []stats.Handler + logger *grpclog.PrefixLogger } func (ht *serverHandlerTransport) Close(err error) { ht.closeOnce.Do(func() { - if logger.V(logLevel) { - logger.Infof("Closing serverHandlerTransport: %v", err) + if ht.logger.V(logLevel) { + ht.logger.Infof("Closing: %v", err) } close(ht.closedCh) }) diff --git a/vendor/google.golang.org/grpc/internal/transport/http2_client.go b/vendor/google.golang.org/grpc/internal/transport/http2_client.go index 9826feb8c..5216998a8 100644 --- a/vendor/google.golang.org/grpc/internal/transport/http2_client.go +++ b/vendor/google.golang.org/grpc/internal/transport/http2_client.go @@ -38,6 +38,7 @@ import ( "google.golang.org/grpc/credentials" "google.golang.org/grpc/internal/channelz" icredentials "google.golang.org/grpc/internal/credentials" + "google.golang.org/grpc/internal/grpclog" "google.golang.org/grpc/internal/grpcsync" "google.golang.org/grpc/internal/grpcutil" imetadata "google.golang.org/grpc/internal/metadata" @@ -145,6 +146,7 @@ type http2Client struct { bufferPool *bufferPool connectionID uint64 + logger *grpclog.PrefixLogger } func dial(ctx context.Context, fn func(context.Context, string) (net.Conn, error), addr resolver.Address, useProxy bool, grpcUA string) (net.Conn, error) { @@ -244,7 +246,7 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts if err := connectCtx.Err(); err != nil { // connectCtx expired before exiting the function. Hard close the connection. if logger.V(logLevel) { - logger.Infof("newClientTransport: aborting due to connectCtx: %v", err) + logger.Infof("Aborting due to connect deadline expiring: %v", err) } conn.Close() } @@ -346,6 +348,7 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts bufferPool: newBufferPool(), onClose: onClose, } + t.logger = prefixLoggerForClientTransport(t) // Add peer information to the http2client context. t.ctx = peer.NewContext(t.ctx, t.getPeer()) @@ -444,7 +447,7 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts return nil, err } go func() { - t.loopy = newLoopyWriter(clientSide, t.framer, t.controlBuf, t.bdpEst, t.conn) + t.loopy = newLoopyWriter(clientSide, t.framer, t.controlBuf, t.bdpEst, t.conn, t.logger) t.loopy.run() close(t.writerDone) }() @@ -782,7 +785,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (*Stream, s.id = h.streamID s.fc = &inFlow{limit: uint32(t.initialWindowSize)} t.mu.Lock() - if t.activeStreams == nil { // Can be niled from Close(). + if t.state == draining || t.activeStreams == nil { // Can be niled from Close(). t.mu.Unlock() return false // Don't create a stream if the transport is already closed. } @@ -859,8 +862,8 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (*Stream, } } if transportDrainRequired { - if logger.V(logLevel) { - logger.Infof("transport: t.nextID > MaxStreamID. Draining") + if t.logger.V(logLevel) { + t.logger.Infof("Draining transport: t.nextID > MaxStreamID") } t.GracefulClose() } @@ -952,8 +955,8 @@ func (t *http2Client) Close(err error) { t.mu.Unlock() return } - if logger.V(logLevel) { - logger.Infof("transport: closing: %v", err) + if t.logger.V(logLevel) { + t.logger.Infof("Closing: %v", err) } // Call t.onClose ASAP to prevent the client from attempting to create new // streams. @@ -1009,8 +1012,8 @@ func (t *http2Client) GracefulClose() { t.mu.Unlock() return } - if logger.V(logLevel) { - logger.Infof("transport: GracefulClose called") + if t.logger.V(logLevel) { + t.logger.Infof("GracefulClose called") } t.onClose(GoAwayInvalid) t.state = draining @@ -1174,8 +1177,8 @@ func (t *http2Client) handleRSTStream(f *http2.RSTStreamFrame) { } statusCode, ok := http2ErrConvTab[f.ErrCode] if !ok { - if logger.V(logLevel) { - logger.Warningf("transport: http2Client.handleRSTStream found no mapped gRPC status for the received http2 error: %v", f.ErrCode) + if t.logger.V(logLevel) { + t.logger.Infof("Received a RST_STREAM frame with code %q, but found no mapped gRPC status", f.ErrCode) } statusCode = codes.Unknown } diff --git a/vendor/google.golang.org/grpc/internal/transport/http2_server.go b/vendor/google.golang.org/grpc/internal/transport/http2_server.go index 99ae1a737..4b406b8cb 100644 --- a/vendor/google.golang.org/grpc/internal/transport/http2_server.go +++ b/vendor/google.golang.org/grpc/internal/transport/http2_server.go @@ -35,7 +35,9 @@ import ( "github.com/golang/protobuf/proto" "golang.org/x/net/http2" "golang.org/x/net/http2/hpack" + "google.golang.org/grpc/internal/grpclog" "google.golang.org/grpc/internal/grpcutil" + "google.golang.org/grpc/internal/pretty" "google.golang.org/grpc/internal/syscall" "google.golang.org/grpc/codes" @@ -129,6 +131,8 @@ type http2Server struct { // This lock may not be taken if mu is already held. maxStreamMu sync.Mutex maxStreamID uint32 // max stream ID ever seen + + logger *grpclog.PrefixLogger } // NewServerTransport creates a http2 transport with conn and configuration @@ -267,6 +271,7 @@ func NewServerTransport(conn net.Conn, config *ServerConfig) (_ ServerTransport, czData: new(channelzData), bufferPool: newBufferPool(), } + t.logger = prefixLoggerForServerTransport(t) // Add peer information to the http2server context. t.ctx = peer.NewContext(t.ctx, t.getPeer()) @@ -331,7 +336,7 @@ func NewServerTransport(conn net.Conn, config *ServerConfig) (_ ServerTransport, t.handleSettings(sf) go func() { - t.loopy = newLoopyWriter(serverSide, t.framer, t.controlBuf, t.bdpEst, t.conn) + t.loopy = newLoopyWriter(serverSide, t.framer, t.controlBuf, t.bdpEst, t.conn, t.logger) t.loopy.ssGoAwayHandler = t.outgoingGoAwayHandler t.loopy.run() close(t.writerDone) @@ -425,8 +430,8 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( // "Transports must consider requests containing the Connection header // as malformed." - A41 case "connection": - if logger.V(logLevel) { - logger.Errorf("transport: http2Server.operateHeaders parsed a :connection header which makes a request malformed as per the HTTP/2 spec") + if t.logger.V(logLevel) { + t.logger.Infof("Received a HEADERS frame with a :connection header which makes the request malformed, as per the HTTP/2 spec") } protocolError = true default: @@ -436,7 +441,7 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( v, err := decodeMetadataHeader(hf.Name, hf.Value) if err != nil { headerError = status.Newf(codes.Internal, "malformed binary metadata %q in header %q: %v", hf.Value, hf.Name, err) - logger.Warningf("Failed to decode metadata header (%q, %q): %v", hf.Name, hf.Value, err) + t.logger.Warningf("Failed to decode metadata header (%q, %q): %v", hf.Name, hf.Value, err) break } mdata[hf.Name] = append(mdata[hf.Name], v) @@ -450,8 +455,8 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( // error, this takes precedence over a client not speaking gRPC. if len(mdata[":authority"]) > 1 || len(mdata["host"]) > 1 { errMsg := fmt.Sprintf("num values of :authority: %v, num values of host: %v, both must only have 1 value as per HTTP/2 spec", len(mdata[":authority"]), len(mdata["host"])) - if logger.V(logLevel) { - logger.Errorf("transport: %v", errMsg) + if t.logger.V(logLevel) { + t.logger.Infof("Aborting the stream early: %v", errMsg) } t.controlBuf.put(&earlyAbortStream{ httpStatus: http.StatusBadRequest, @@ -545,9 +550,9 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( } if httpMethod != http.MethodPost { t.mu.Unlock() - errMsg := fmt.Sprintf("http2Server.operateHeaders parsed a :method field: %v which should be POST", httpMethod) - if logger.V(logLevel) { - logger.Infof("transport: %v", errMsg) + errMsg := fmt.Sprintf("Received a HEADERS frame with :method %q which should be POST", httpMethod) + if t.logger.V(logLevel) { + t.logger.Infof("Aborting the stream early: %v", errMsg) } t.controlBuf.put(&earlyAbortStream{ httpStatus: 405, @@ -563,8 +568,8 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( var err error if s.ctx, err = t.inTapHandle(s.ctx, &tap.Info{FullMethodName: s.method}); err != nil { t.mu.Unlock() - if logger.V(logLevel) { - logger.Infof("transport: http2Server.operateHeaders got an error from InTapHandle: %v", err) + if t.logger.V(logLevel) { + t.logger.Infof("Aborting the stream early due to InTapHandle failure: %v", err) } stat, ok := status.FromError(err) if !ok { @@ -638,8 +643,8 @@ func (t *http2Server) HandleStreams(handle func(*Stream), traceCtx func(context. atomic.StoreInt64(&t.lastRead, time.Now().UnixNano()) if err != nil { if se, ok := err.(http2.StreamError); ok { - if logger.V(logLevel) { - logger.Warningf("transport: http2Server.HandleStreams encountered http2.StreamError: %v", se) + if t.logger.V(logLevel) { + t.logger.Warningf("Encountered http2.StreamError: %v", se) } t.mu.Lock() s := t.activeStreams[se.StreamID] @@ -682,8 +687,8 @@ func (t *http2Server) HandleStreams(handle func(*Stream), traceCtx func(context. case *http2.GoAwayFrame: // TODO: Handle GoAway from the client appropriately. default: - if logger.V(logLevel) { - logger.Errorf("transport: http2Server.HandleStreams found unhandled frame type %v.", frame) + if t.logger.V(logLevel) { + t.logger.Infof("Received unsupported frame type %T", frame) } } } @@ -942,8 +947,8 @@ func (t *http2Server) checkForHeaderListSize(it interface{}) bool { var sz int64 for _, f := range hdrFrame.hf { if sz += int64(f.Size()); sz > int64(*t.maxSendHeaderListSize) { - if logger.V(logLevel) { - logger.Errorf("header list size to send violates the maximum size (%d bytes) set by client", *t.maxSendHeaderListSize) + if t.logger.V(logLevel) { + t.logger.Infof("Header list size to send violates the maximum size (%d bytes) set by client", *t.maxSendHeaderListSize) } return false } @@ -1056,7 +1061,7 @@ func (t *http2Server) WriteStatus(s *Stream, st *status.Status) error { stBytes, err := proto.Marshal(p) if err != nil { // TODO: return error instead, when callers are able to handle it. - logger.Errorf("transport: failed to marshal rpc status: %v, error: %v", p, err) + t.logger.Errorf("Failed to marshal rpc status: %s, error: %v", pretty.ToJSON(p), err) } else { headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-status-details-bin", Value: encodeBinHeader(stBytes)}) } @@ -1171,8 +1176,8 @@ func (t *http2Server) keepalive() { select { case <-ageTimer.C: // Close the connection after grace period. - if logger.V(logLevel) { - logger.Infof("transport: closing server transport due to maximum connection age.") + if t.logger.V(logLevel) { + t.logger.Infof("Closing server transport due to maximum connection age") } t.controlBuf.put(closeConnection{}) case <-t.done: @@ -1223,8 +1228,8 @@ func (t *http2Server) Close(err error) { t.mu.Unlock() return } - if logger.V(logLevel) { - logger.Infof("transport: closing: %v", err) + if t.logger.V(logLevel) { + t.logger.Infof("Closing: %v", err) } t.state = closing streams := t.activeStreams @@ -1232,8 +1237,8 @@ func (t *http2Server) Close(err error) { t.mu.Unlock() t.controlBuf.finish() close(t.done) - if err := t.conn.Close(); err != nil && logger.V(logLevel) { - logger.Infof("transport: error closing conn during Close: %v", err) + if err := t.conn.Close(); err != nil && t.logger.V(logLevel) { + t.logger.Infof("Error closing underlying net.Conn during Close: %v", err) } channelz.RemoveEntry(t.channelzID) // Cancel all active streams. diff --git a/vendor/google.golang.org/grpc/internal/transport/http_util.go b/vendor/google.golang.org/grpc/internal/transport/http_util.go index 8fcae4f4d..19cbb18f5 100644 --- a/vendor/google.golang.org/grpc/internal/transport/http_util.go +++ b/vendor/google.golang.org/grpc/internal/transport/http_util.go @@ -38,7 +38,6 @@ import ( "golang.org/x/net/http2/hpack" spb "google.golang.org/genproto/googleapis/rpc/status" "google.golang.org/grpc/codes" - "google.golang.org/grpc/grpclog" "google.golang.org/grpc/status" ) @@ -86,7 +85,6 @@ var ( // 504 Gateway timeout - UNAVAILABLE. http.StatusGatewayTimeout: codes.Unavailable, } - logger = grpclog.Component("transport") ) // isReservedHeader checks whether hdr belongs to HTTP2 headers diff --git a/vendor/google.golang.org/grpc/internal/transport/logging.go b/vendor/google.golang.org/grpc/internal/transport/logging.go new file mode 100644 index 000000000..42ed2b07a --- /dev/null +++ b/vendor/google.golang.org/grpc/internal/transport/logging.go @@ -0,0 +1,40 @@ +/* + * + * Copyright 2023 gRPC 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 transport + +import ( + "fmt" + + "google.golang.org/grpc/grpclog" + internalgrpclog "google.golang.org/grpc/internal/grpclog" +) + +var logger = grpclog.Component("transport") + +func prefixLoggerForServerTransport(p *http2Server) *internalgrpclog.PrefixLogger { + return internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf("[server-transport %p] ", p)) +} + +func prefixLoggerForServerHandlerTransport(p *serverHandlerTransport) *internalgrpclog.PrefixLogger { + return internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf("[server-handler-transport %p] ", p)) +} + +func prefixLoggerForClientTransport(p *http2Client) *internalgrpclog.PrefixLogger { + return internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf("[client-transport %p] ", p)) +} diff --git a/vendor/google.golang.org/grpc/reflection/grpc_reflection_v1alpha/reflection.pb.go b/vendor/google.golang.org/grpc/reflection/grpc_reflection_v1alpha/reflection.pb.go index 444f2a6f4..d54c07676 100644 --- a/vendor/google.golang.org/grpc/reflection/grpc_reflection_v1alpha/reflection.pb.go +++ b/vendor/google.golang.org/grpc/reflection/grpc_reflection_v1alpha/reflection.pb.go @@ -18,7 +18,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 +// protoc-gen-go v1.30.0 // protoc v4.22.0 // grpc/reflection/v1alpha/reflection.proto is a deprecated file. @@ -39,11 +39,14 @@ const ( ) // The message sent by the client when calling ServerReflectionInfo method. +// +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. type ServerReflectionRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` // To use reflection service, the client should set one of the following // fields in message_request. The server distinguishes requests by their @@ -91,6 +94,7 @@ func (*ServerReflectionRequest) Descriptor() ([]byte, []int) { return file_grpc_reflection_v1alpha_reflection_proto_rawDescGZIP(), []int{0} } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ServerReflectionRequest) GetHost() string { if x != nil { return x.Host @@ -105,6 +109,7 @@ func (m *ServerReflectionRequest) GetMessageRequest() isServerReflectionRequest_ return nil } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ServerReflectionRequest) GetFileByFilename() string { if x, ok := x.GetMessageRequest().(*ServerReflectionRequest_FileByFilename); ok { return x.FileByFilename @@ -112,6 +117,7 @@ func (x *ServerReflectionRequest) GetFileByFilename() string { return "" } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ServerReflectionRequest) GetFileContainingSymbol() string { if x, ok := x.GetMessageRequest().(*ServerReflectionRequest_FileContainingSymbol); ok { return x.FileContainingSymbol @@ -119,6 +125,7 @@ func (x *ServerReflectionRequest) GetFileContainingSymbol() string { return "" } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ServerReflectionRequest) GetFileContainingExtension() *ExtensionRequest { if x, ok := x.GetMessageRequest().(*ServerReflectionRequest_FileContainingExtension); ok { return x.FileContainingExtension @@ -126,6 +133,7 @@ func (x *ServerReflectionRequest) GetFileContainingExtension() *ExtensionRequest return nil } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ServerReflectionRequest) GetAllExtensionNumbersOfType() string { if x, ok := x.GetMessageRequest().(*ServerReflectionRequest_AllExtensionNumbersOfType); ok { return x.AllExtensionNumbersOfType @@ -133,6 +141,7 @@ func (x *ServerReflectionRequest) GetAllExtensionNumbersOfType() string { return "" } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ServerReflectionRequest) GetListServices() string { if x, ok := x.GetMessageRequest().(*ServerReflectionRequest_ListServices); ok { return x.ListServices @@ -146,6 +155,8 @@ type isServerReflectionRequest_MessageRequest interface { type ServerReflectionRequest_FileByFilename struct { // Find a proto file by the file name. + // + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. FileByFilename string `protobuf:"bytes,3,opt,name=file_by_filename,json=fileByFilename,proto3,oneof"` } @@ -153,12 +164,16 @@ type ServerReflectionRequest_FileContainingSymbol struct { // Find the proto file that declares the given fully-qualified symbol name. // This field should be a fully-qualified symbol name // (e.g. .[.] or .). + // + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. FileContainingSymbol string `protobuf:"bytes,4,opt,name=file_containing_symbol,json=fileContainingSymbol,proto3,oneof"` } type ServerReflectionRequest_FileContainingExtension struct { // Find the proto file which defines an extension extending the given // message type with the given field number. + // + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. FileContainingExtension *ExtensionRequest `protobuf:"bytes,5,opt,name=file_containing_extension,json=fileContainingExtension,proto3,oneof"` } @@ -171,12 +186,16 @@ type ServerReflectionRequest_AllExtensionNumbersOfType struct { // StatusCode::UNIMPLEMENTED if it's not implemented. // This field should be a fully-qualified type name. The format is // . + // + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. AllExtensionNumbersOfType string `protobuf:"bytes,6,opt,name=all_extension_numbers_of_type,json=allExtensionNumbersOfType,proto3,oneof"` } type ServerReflectionRequest_ListServices struct { // List the full names of registered services. The content will not be // checked. + // + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. ListServices string `protobuf:"bytes,7,opt,name=list_services,json=listServices,proto3,oneof"` } @@ -193,14 +212,19 @@ func (*ServerReflectionRequest_ListServices) isServerReflectionRequest_MessageRe // The type name and extension number sent by the client when requesting // file_containing_extension. +// +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. type ExtensionRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Fully-qualified type name. The format should be . - ContainingType string `protobuf:"bytes,1,opt,name=containing_type,json=containingType,proto3" json:"containing_type,omitempty"` - ExtensionNumber int32 `protobuf:"varint,2,opt,name=extension_number,json=extensionNumber,proto3" json:"extension_number,omitempty"` + // + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. + ContainingType string `protobuf:"bytes,1,opt,name=containing_type,json=containingType,proto3" json:"containing_type,omitempty"` + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. + ExtensionNumber int32 `protobuf:"varint,2,opt,name=extension_number,json=extensionNumber,proto3" json:"extension_number,omitempty"` } func (x *ExtensionRequest) Reset() { @@ -235,6 +259,7 @@ func (*ExtensionRequest) Descriptor() ([]byte, []int) { return file_grpc_reflection_v1alpha_reflection_proto_rawDescGZIP(), []int{1} } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ExtensionRequest) GetContainingType() string { if x != nil { return x.ContainingType @@ -242,6 +267,7 @@ func (x *ExtensionRequest) GetContainingType() string { return "" } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ExtensionRequest) GetExtensionNumber() int32 { if x != nil { return x.ExtensionNumber @@ -250,12 +276,16 @@ func (x *ExtensionRequest) GetExtensionNumber() int32 { } // The message sent by the server to answer ServerReflectionInfo method. +// +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. type ServerReflectionResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - ValidHost string `protobuf:"bytes,1,opt,name=valid_host,json=validHost,proto3" json:"valid_host,omitempty"` + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. + ValidHost string `protobuf:"bytes,1,opt,name=valid_host,json=validHost,proto3" json:"valid_host,omitempty"` + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. OriginalRequest *ServerReflectionRequest `protobuf:"bytes,2,opt,name=original_request,json=originalRequest,proto3" json:"original_request,omitempty"` // The server set one of the following fields according to the message_request // in the request. @@ -301,6 +331,7 @@ func (*ServerReflectionResponse) Descriptor() ([]byte, []int) { return file_grpc_reflection_v1alpha_reflection_proto_rawDescGZIP(), []int{2} } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ServerReflectionResponse) GetValidHost() string { if x != nil { return x.ValidHost @@ -308,6 +339,7 @@ func (x *ServerReflectionResponse) GetValidHost() string { return "" } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ServerReflectionResponse) GetOriginalRequest() *ServerReflectionRequest { if x != nil { return x.OriginalRequest @@ -322,6 +354,7 @@ func (m *ServerReflectionResponse) GetMessageResponse() isServerReflectionRespon return nil } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ServerReflectionResponse) GetFileDescriptorResponse() *FileDescriptorResponse { if x, ok := x.GetMessageResponse().(*ServerReflectionResponse_FileDescriptorResponse); ok { return x.FileDescriptorResponse @@ -329,6 +362,7 @@ func (x *ServerReflectionResponse) GetFileDescriptorResponse() *FileDescriptorRe return nil } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ServerReflectionResponse) GetAllExtensionNumbersResponse() *ExtensionNumberResponse { if x, ok := x.GetMessageResponse().(*ServerReflectionResponse_AllExtensionNumbersResponse); ok { return x.AllExtensionNumbersResponse @@ -336,6 +370,7 @@ func (x *ServerReflectionResponse) GetAllExtensionNumbersResponse() *ExtensionNu return nil } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ServerReflectionResponse) GetListServicesResponse() *ListServiceResponse { if x, ok := x.GetMessageResponse().(*ServerReflectionResponse_ListServicesResponse); ok { return x.ListServicesResponse @@ -343,6 +378,7 @@ func (x *ServerReflectionResponse) GetListServicesResponse() *ListServiceRespons return nil } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ServerReflectionResponse) GetErrorResponse() *ErrorResponse { if x, ok := x.GetMessageResponse().(*ServerReflectionResponse_ErrorResponse); ok { return x.ErrorResponse @@ -361,21 +397,29 @@ type ServerReflectionResponse_FileDescriptorResponse struct { // FileDescriptorResponse message to encapsulate the repeated fields. // The reflection service is allowed to avoid sending FileDescriptorProtos // that were previously sent in response to earlier requests in the stream. + // + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. FileDescriptorResponse *FileDescriptorResponse `protobuf:"bytes,4,opt,name=file_descriptor_response,json=fileDescriptorResponse,proto3,oneof"` } type ServerReflectionResponse_AllExtensionNumbersResponse struct { // This message is used to answer all_extension_numbers_of_type requst. + // + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. AllExtensionNumbersResponse *ExtensionNumberResponse `protobuf:"bytes,5,opt,name=all_extension_numbers_response,json=allExtensionNumbersResponse,proto3,oneof"` } type ServerReflectionResponse_ListServicesResponse struct { // This message is used to answer list_services request. + // + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. ListServicesResponse *ListServiceResponse `protobuf:"bytes,6,opt,name=list_services_response,json=listServicesResponse,proto3,oneof"` } type ServerReflectionResponse_ErrorResponse struct { // This message is used when an error occurs. + // + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. ErrorResponse *ErrorResponse `protobuf:"bytes,7,opt,name=error_response,json=errorResponse,proto3,oneof"` } @@ -392,6 +436,8 @@ func (*ServerReflectionResponse_ErrorResponse) isServerReflectionResponse_Messag // Serialized FileDescriptorProto messages sent by the server answering // a file_by_filename, file_containing_symbol, or file_containing_extension // request. +// +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. type FileDescriptorResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -400,6 +446,8 @@ type FileDescriptorResponse struct { // Serialized FileDescriptorProto messages. We avoid taking a dependency on // descriptor.proto, which uses proto2 only features, by making them opaque // bytes instead. + // + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. FileDescriptorProto [][]byte `protobuf:"bytes,1,rep,name=file_descriptor_proto,json=fileDescriptorProto,proto3" json:"file_descriptor_proto,omitempty"` } @@ -435,6 +483,7 @@ func (*FileDescriptorResponse) Descriptor() ([]byte, []int) { return file_grpc_reflection_v1alpha_reflection_proto_rawDescGZIP(), []int{3} } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *FileDescriptorResponse) GetFileDescriptorProto() [][]byte { if x != nil { return x.FileDescriptorProto @@ -444,6 +493,8 @@ func (x *FileDescriptorResponse) GetFileDescriptorProto() [][]byte { // A list of extension numbers sent by the server answering // all_extension_numbers_of_type request. +// +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. type ExtensionNumberResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -451,7 +502,10 @@ type ExtensionNumberResponse struct { // Full name of the base type, including the package name. The format // is . - BaseTypeName string `protobuf:"bytes,1,opt,name=base_type_name,json=baseTypeName,proto3" json:"base_type_name,omitempty"` + // + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. + BaseTypeName string `protobuf:"bytes,1,opt,name=base_type_name,json=baseTypeName,proto3" json:"base_type_name,omitempty"` + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. ExtensionNumber []int32 `protobuf:"varint,2,rep,packed,name=extension_number,json=extensionNumber,proto3" json:"extension_number,omitempty"` } @@ -487,6 +541,7 @@ func (*ExtensionNumberResponse) Descriptor() ([]byte, []int) { return file_grpc_reflection_v1alpha_reflection_proto_rawDescGZIP(), []int{4} } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ExtensionNumberResponse) GetBaseTypeName() string { if x != nil { return x.BaseTypeName @@ -494,6 +549,7 @@ func (x *ExtensionNumberResponse) GetBaseTypeName() string { return "" } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ExtensionNumberResponse) GetExtensionNumber() []int32 { if x != nil { return x.ExtensionNumber @@ -502,6 +558,8 @@ func (x *ExtensionNumberResponse) GetExtensionNumber() []int32 { } // A list of ServiceResponse sent by the server answering list_services request. +// +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. type ListServiceResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -509,6 +567,8 @@ type ListServiceResponse struct { // The information of each service may be expanded in the future, so we use // ServiceResponse message to encapsulate it. + // + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. Service []*ServiceResponse `protobuf:"bytes,1,rep,name=service,proto3" json:"service,omitempty"` } @@ -544,6 +604,7 @@ func (*ListServiceResponse) Descriptor() ([]byte, []int) { return file_grpc_reflection_v1alpha_reflection_proto_rawDescGZIP(), []int{5} } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ListServiceResponse) GetService() []*ServiceResponse { if x != nil { return x.Service @@ -553,6 +614,8 @@ func (x *ListServiceResponse) GetService() []*ServiceResponse { // The information of a single service used by ListServiceResponse to answer // list_services request. +// +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. type ServiceResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -560,6 +623,8 @@ type ServiceResponse struct { // Full name of a registered service, including its package name. The format // is . + // + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` } @@ -595,6 +660,7 @@ func (*ServiceResponse) Descriptor() ([]byte, []int) { return file_grpc_reflection_v1alpha_reflection_proto_rawDescGZIP(), []int{6} } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ServiceResponse) GetName() string { if x != nil { return x.Name @@ -603,13 +669,18 @@ func (x *ServiceResponse) GetName() string { } // The error code and error message sent by the server when an error occurs. +// +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. type ErrorResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // This field uses the error codes defined in grpc::StatusCode. - ErrorCode int32 `protobuf:"varint,1,opt,name=error_code,json=errorCode,proto3" json:"error_code,omitempty"` + // + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. + ErrorCode int32 `protobuf:"varint,1,opt,name=error_code,json=errorCode,proto3" json:"error_code,omitempty"` + // Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` } @@ -645,6 +716,7 @@ func (*ErrorResponse) Descriptor() ([]byte, []int) { return file_grpc_reflection_v1alpha_reflection_proto_rawDescGZIP(), []int{7} } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ErrorResponse) GetErrorCode() int32 { if x != nil { return x.ErrorCode @@ -652,6 +724,7 @@ func (x *ErrorResponse) GetErrorCode() int32 { return 0 } +// Deprecated: The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated. func (x *ErrorResponse) GetErrorMessage() string { if x != nil { return x.ErrorMessage diff --git a/vendor/google.golang.org/grpc/server.go b/vendor/google.golang.org/grpc/server.go index 087b9ad7c..76d152a69 100644 --- a/vendor/google.golang.org/grpc/server.go +++ b/vendor/google.golang.org/grpc/server.go @@ -43,7 +43,6 @@ import ( "google.golang.org/grpc/internal" "google.golang.org/grpc/internal/binarylog" "google.golang.org/grpc/internal/channelz" - "google.golang.org/grpc/internal/grpcrand" "google.golang.org/grpc/internal/grpcsync" "google.golang.org/grpc/internal/grpcutil" "google.golang.org/grpc/internal/transport" @@ -146,7 +145,7 @@ type Server struct { channelzID *channelz.Identifier czData *channelzData - serverWorkerChannels []chan *serverWorkerData + serverWorkerChannel chan *serverWorkerData } type serverOptions struct { @@ -561,40 +560,38 @@ func NumStreamWorkers(numServerWorkers uint32) ServerOption { const serverWorkerResetThreshold = 1 << 16 // serverWorkers blocks on a *transport.Stream channel forever and waits for -// data to be fed by serveStreams. This allows different requests to be +// data to be fed by serveStreams. This allows multiple requests to be // processed by the same goroutine, removing the need for expensive stack // re-allocations (see the runtime.morestack problem [1]). // // [1] https://github.com/golang/go/issues/18138 -func (s *Server) serverWorker(ch chan *serverWorkerData) { - // To make sure all server workers don't reset at the same time, choose a - // random number of iterations before resetting. - threshold := serverWorkerResetThreshold + grpcrand.Intn(serverWorkerResetThreshold) - for completed := 0; completed < threshold; completed++ { - data, ok := <-ch +func (s *Server) serverWorker() { + for completed := 0; completed < serverWorkerResetThreshold; completed++ { + data, ok := <-s.serverWorkerChannel if !ok { return } - s.handleStream(data.st, data.stream, s.traceInfo(data.st, data.stream)) - data.wg.Done() + s.handleSingleStream(data) } - go s.serverWorker(ch) + go s.serverWorker() } -// initServerWorkers creates worker goroutines and channels to process incoming +func (s *Server) handleSingleStream(data *serverWorkerData) { + defer data.wg.Done() + s.handleStream(data.st, data.stream, s.traceInfo(data.st, data.stream)) +} + +// initServerWorkers creates worker goroutines and a channel to process incoming // connections to reduce the time spent overall on runtime.morestack. func (s *Server) initServerWorkers() { - s.serverWorkerChannels = make([]chan *serverWorkerData, s.opts.numServerWorkers) + s.serverWorkerChannel = make(chan *serverWorkerData) for i := uint32(0); i < s.opts.numServerWorkers; i++ { - s.serverWorkerChannels[i] = make(chan *serverWorkerData) - go s.serverWorker(s.serverWorkerChannels[i]) + go s.serverWorker() } } func (s *Server) stopServerWorkers() { - for i := uint32(0); i < s.opts.numServerWorkers; i++ { - close(s.serverWorkerChannels[i]) - } + close(s.serverWorkerChannel) } // NewServer creates a gRPC server which has no service registered and has not @@ -946,26 +943,21 @@ func (s *Server) serveStreams(st transport.ServerTransport) { defer st.Close(errors.New("finished serving streams for the server transport")) var wg sync.WaitGroup - var roundRobinCounter uint32 st.HandleStreams(func(stream *transport.Stream) { wg.Add(1) if s.opts.numServerWorkers > 0 { data := &serverWorkerData{st: st, wg: &wg, stream: stream} select { - case s.serverWorkerChannels[atomic.AddUint32(&roundRobinCounter, 1)%s.opts.numServerWorkers] <- data: + case s.serverWorkerChannel <- data: + return default: // If all stream workers are busy, fallback to the default code path. - go func() { - s.handleStream(st, stream, s.traceInfo(st, stream)) - wg.Done() - }() } - } else { - go func() { - defer wg.Done() - s.handleStream(st, stream, s.traceInfo(st, stream)) - }() } + go func() { + defer wg.Done() + s.handleStream(st, stream, s.traceInfo(st, stream)) + }() }, func(ctx context.Context, method string) context.Context { if !EnableTracing { return ctx diff --git a/vendor/google.golang.org/grpc/status/status.go b/vendor/google.golang.org/grpc/status/status.go index 623be39f2..53910fb7c 100644 --- a/vendor/google.golang.org/grpc/status/status.go +++ b/vendor/google.golang.org/grpc/status/status.go @@ -77,7 +77,9 @@ func FromProto(s *spb.Status) *Status { // FromError returns a Status representation of err. // // - If err was produced by this package or implements the method `GRPCStatus() -// *Status`, the appropriate Status is returned. +// *Status`, or if err wraps a type satisfying this, the appropriate Status is +// returned. For wrapped errors, the message returned contains the entire +// err.Error() text and not just the wrapped status. // // - If err is nil, a Status is returned with codes.OK and no message. // @@ -88,10 +90,15 @@ func FromError(err error) (s *Status, ok bool) { if err == nil { return nil, true } - if se, ok := err.(interface { - GRPCStatus() *Status - }); ok { - return se.GRPCStatus(), true + type grpcstatus interface{ GRPCStatus() *Status } + if gs, ok := err.(grpcstatus); ok { + return gs.GRPCStatus(), true + } + var gs grpcstatus + if errors.As(err, &gs) { + p := gs.GRPCStatus().Proto() + p.Message = err.Error() + return status.FromProto(p), true } return New(codes.Unknown, err.Error()), false } @@ -103,19 +110,16 @@ func Convert(err error) *Status { return s } -// Code returns the Code of the error if it is a Status error, codes.OK if err -// is nil, or codes.Unknown otherwise. +// Code returns the Code of the error if it is a Status error or if it wraps a +// Status error. If that is not the case, it returns codes.OK if err is nil, or +// codes.Unknown otherwise. func Code(err error) codes.Code { // Don't use FromError to avoid allocation of OK status. if err == nil { return codes.OK } - if se, ok := err.(interface { - GRPCStatus() *Status - }); ok { - return se.GRPCStatus().Code() - } - return codes.Unknown + + return Convert(err).Code() } // FromContextError converts a context error or wrapped context error into a diff --git a/vendor/google.golang.org/grpc/version.go b/vendor/google.golang.org/grpc/version.go index 3c6e3c911..853ce0e30 100644 --- a/vendor/google.golang.org/grpc/version.go +++ b/vendor/google.golang.org/grpc/version.go @@ -19,4 +19,4 @@ package grpc // Version is the current grpc version. -const Version = "1.54.0" +const Version = "1.55.0" diff --git a/vendor/gopkg.in/cenkalti/backoff.v1/.gitignore b/vendor/gopkg.in/cenkalti/backoff.v1/.gitignore new file mode 100644 index 000000000..00268614f --- /dev/null +++ b/vendor/gopkg.in/cenkalti/backoff.v1/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe diff --git a/vendor/gopkg.in/cenkalti/backoff.v1/.travis.yml b/vendor/gopkg.in/cenkalti/backoff.v1/.travis.yml new file mode 100644 index 000000000..1040404bf --- /dev/null +++ b/vendor/gopkg.in/cenkalti/backoff.v1/.travis.yml @@ -0,0 +1,9 @@ +language: go +go: + - 1.3.3 + - tip +before_install: + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover +script: + - $HOME/gopath/bin/goveralls -service=travis-ci diff --git a/vendor/gopkg.in/cenkalti/backoff.v1/LICENSE b/vendor/gopkg.in/cenkalti/backoff.v1/LICENSE new file mode 100644 index 000000000..89b817996 --- /dev/null +++ b/vendor/gopkg.in/cenkalti/backoff.v1/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Cenk Altı + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/gopkg.in/cenkalti/backoff.v1/README.md b/vendor/gopkg.in/cenkalti/backoff.v1/README.md new file mode 100644 index 000000000..13b347fb9 --- /dev/null +++ b/vendor/gopkg.in/cenkalti/backoff.v1/README.md @@ -0,0 +1,30 @@ +# Exponential Backoff [![GoDoc][godoc image]][godoc] [![Build Status][travis image]][travis] [![Coverage Status][coveralls image]][coveralls] + +This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client]. + +[Exponential backoff][exponential backoff wiki] +is an algorithm that uses feedback to multiplicatively decrease the rate of some process, +in order to gradually find an acceptable rate. +The retries exponentially increase and stop increasing when a certain threshold is met. + +## Usage + +See https://godoc.org/github.com/cenkalti/backoff#pkg-examples + +## Contributing + +* I would like to keep this library as small as possible. +* Please don't send a PR without opening an issue and discussing it first. +* If proposed change is not a common use case, I will probably not accept it. + +[godoc]: https://godoc.org/github.com/cenkalti/backoff +[godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png +[travis]: https://travis-ci.org/cenkalti/backoff +[travis image]: https://travis-ci.org/cenkalti/backoff.png?branch=master +[coveralls]: https://coveralls.io/github/cenkalti/backoff?branch=master +[coveralls image]: https://coveralls.io/repos/github/cenkalti/backoff/badge.svg?branch=master + +[google-http-java-client]: https://github.com/google/google-http-java-client +[exponential backoff wiki]: http://en.wikipedia.org/wiki/Exponential_backoff + +[advanced example]: https://godoc.org/github.com/cenkalti/backoff#example_ diff --git a/vendor/gopkg.in/cenkalti/backoff.v1/backoff.go b/vendor/gopkg.in/cenkalti/backoff.v1/backoff.go new file mode 100644 index 000000000..2102c5f2d --- /dev/null +++ b/vendor/gopkg.in/cenkalti/backoff.v1/backoff.go @@ -0,0 +1,66 @@ +// Package backoff implements backoff algorithms for retrying operations. +// +// Use Retry function for retrying operations that may fail. +// If Retry does not meet your needs, +// copy/paste the function into your project and modify as you wish. +// +// There is also Ticker type similar to time.Ticker. +// You can use it if you need to work with channels. +// +// See Examples section below for usage examples. +package backoff + +import "time" + +// BackOff is a backoff policy for retrying an operation. +type BackOff interface { + // NextBackOff returns the duration to wait before retrying the operation, + // or backoff.Stop to indicate that no more retries should be made. + // + // Example usage: + // + // duration := backoff.NextBackOff(); + // if (duration == backoff.Stop) { + // // Do not retry operation. + // } else { + // // Sleep for duration and retry operation. + // } + // + NextBackOff() time.Duration + + // Reset to initial state. + Reset() +} + +// Stop indicates that no more retries should be made for use in NextBackOff(). +const Stop time.Duration = -1 + +// ZeroBackOff is a fixed backoff policy whose backoff time is always zero, +// meaning that the operation is retried immediately without waiting, indefinitely. +type ZeroBackOff struct{} + +func (b *ZeroBackOff) Reset() {} + +func (b *ZeroBackOff) NextBackOff() time.Duration { return 0 } + +// StopBackOff is a fixed backoff policy that always returns backoff.Stop for +// NextBackOff(), meaning that the operation should never be retried. +type StopBackOff struct{} + +func (b *StopBackOff) Reset() {} + +func (b *StopBackOff) NextBackOff() time.Duration { return Stop } + +// ConstantBackOff is a backoff policy that always returns the same backoff delay. +// This is in contrast to an exponential backoff policy, +// which returns a delay that grows longer as you call NextBackOff() over and over again. +type ConstantBackOff struct { + Interval time.Duration +} + +func (b *ConstantBackOff) Reset() {} +func (b *ConstantBackOff) NextBackOff() time.Duration { return b.Interval } + +func NewConstantBackOff(d time.Duration) *ConstantBackOff { + return &ConstantBackOff{Interval: d} +} diff --git a/vendor/gopkg.in/cenkalti/backoff.v1/context.go b/vendor/gopkg.in/cenkalti/backoff.v1/context.go new file mode 100644 index 000000000..5d1570925 --- /dev/null +++ b/vendor/gopkg.in/cenkalti/backoff.v1/context.go @@ -0,0 +1,60 @@ +package backoff + +import ( + "time" + + "golang.org/x/net/context" +) + +// BackOffContext is a backoff policy that stops retrying after the context +// is canceled. +type BackOffContext interface { + BackOff + Context() context.Context +} + +type backOffContext struct { + BackOff + ctx context.Context +} + +// WithContext returns a BackOffContext with context ctx +// +// ctx must not be nil +func WithContext(b BackOff, ctx context.Context) BackOffContext { + if ctx == nil { + panic("nil context") + } + + if b, ok := b.(*backOffContext); ok { + return &backOffContext{ + BackOff: b.BackOff, + ctx: ctx, + } + } + + return &backOffContext{ + BackOff: b, + ctx: ctx, + } +} + +func ensureContext(b BackOff) BackOffContext { + if cb, ok := b.(BackOffContext); ok { + return cb + } + return WithContext(b, context.Background()) +} + +func (b *backOffContext) Context() context.Context { + return b.ctx +} + +func (b *backOffContext) NextBackOff() time.Duration { + select { + case <-b.Context().Done(): + return Stop + default: + return b.BackOff.NextBackOff() + } +} diff --git a/vendor/gopkg.in/cenkalti/backoff.v1/exponential.go b/vendor/gopkg.in/cenkalti/backoff.v1/exponential.go new file mode 100644 index 000000000..9a6addf07 --- /dev/null +++ b/vendor/gopkg.in/cenkalti/backoff.v1/exponential.go @@ -0,0 +1,156 @@ +package backoff + +import ( + "math/rand" + "time" +) + +/* +ExponentialBackOff is a backoff implementation that increases the backoff +period for each retry attempt using a randomization function that grows exponentially. + +NextBackOff() is calculated using the following formula: + + randomized interval = + RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor]) + +In other words NextBackOff() will range between the randomization factor +percentage below and above the retry interval. + +For example, given the following parameters: + + RetryInterval = 2 + RandomizationFactor = 0.5 + Multiplier = 2 + +the actual backoff period used in the next retry attempt will range between 1 and 3 seconds, +multiplied by the exponential, that is, between 2 and 6 seconds. + +Note: MaxInterval caps the RetryInterval and not the randomized interval. + +If the time elapsed since an ExponentialBackOff instance is created goes past the +MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop. + +The elapsed time can be reset by calling Reset(). + +Example: Given the following default arguments, for 10 tries the sequence will be, +and assuming we go over the MaxElapsedTime on the 10th try: + + Request # RetryInterval (seconds) Randomized Interval (seconds) + + 1 0.5 [0.25, 0.75] + 2 0.75 [0.375, 1.125] + 3 1.125 [0.562, 1.687] + 4 1.687 [0.8435, 2.53] + 5 2.53 [1.265, 3.795] + 6 3.795 [1.897, 5.692] + 7 5.692 [2.846, 8.538] + 8 8.538 [4.269, 12.807] + 9 12.807 [6.403, 19.210] + 10 19.210 backoff.Stop + +Note: Implementation is not thread-safe. +*/ +type ExponentialBackOff struct { + InitialInterval time.Duration + RandomizationFactor float64 + Multiplier float64 + MaxInterval time.Duration + // After MaxElapsedTime the ExponentialBackOff stops. + // It never stops if MaxElapsedTime == 0. + MaxElapsedTime time.Duration + Clock Clock + + currentInterval time.Duration + startTime time.Time + random *rand.Rand +} + +// Clock is an interface that returns current time for BackOff. +type Clock interface { + Now() time.Time +} + +// Default values for ExponentialBackOff. +const ( + DefaultInitialInterval = 500 * time.Millisecond + DefaultRandomizationFactor = 0.5 + DefaultMultiplier = 1.5 + DefaultMaxInterval = 60 * time.Second + DefaultMaxElapsedTime = 15 * time.Minute +) + +// NewExponentialBackOff creates an instance of ExponentialBackOff using default values. +func NewExponentialBackOff() *ExponentialBackOff { + b := &ExponentialBackOff{ + InitialInterval: DefaultInitialInterval, + RandomizationFactor: DefaultRandomizationFactor, + Multiplier: DefaultMultiplier, + MaxInterval: DefaultMaxInterval, + MaxElapsedTime: DefaultMaxElapsedTime, + Clock: SystemClock, + random: rand.New(rand.NewSource(time.Now().UnixNano())), + } + b.Reset() + return b +} + +type systemClock struct{} + +func (t systemClock) Now() time.Time { + return time.Now() +} + +// SystemClock implements Clock interface that uses time.Now(). +var SystemClock = systemClock{} + +// Reset the interval back to the initial retry interval and restarts the timer. +func (b *ExponentialBackOff) Reset() { + b.currentInterval = b.InitialInterval + b.startTime = b.Clock.Now() +} + +// NextBackOff calculates the next backoff interval using the formula: +// Randomized interval = RetryInterval +/- (RandomizationFactor * RetryInterval) +func (b *ExponentialBackOff) NextBackOff() time.Duration { + // Make sure we have not gone over the maximum elapsed time. + if b.MaxElapsedTime != 0 && b.GetElapsedTime() > b.MaxElapsedTime { + return Stop + } + defer b.incrementCurrentInterval() + if b.random == nil { + b.random = rand.New(rand.NewSource(time.Now().UnixNano())) + } + return getRandomValueFromInterval(b.RandomizationFactor, b.random.Float64(), b.currentInterval) +} + +// GetElapsedTime returns the elapsed time since an ExponentialBackOff instance +// is created and is reset when Reset() is called. +// +// The elapsed time is computed using time.Now().UnixNano(). +func (b *ExponentialBackOff) GetElapsedTime() time.Duration { + return b.Clock.Now().Sub(b.startTime) +} + +// Increments the current interval by multiplying it with the multiplier. +func (b *ExponentialBackOff) incrementCurrentInterval() { + // Check for overflow, if overflow is detected set the current interval to the max interval. + if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier { + b.currentInterval = b.MaxInterval + } else { + b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier) + } +} + +// Returns a random value from the following interval: +// [randomizationFactor * currentInterval, randomizationFactor * currentInterval]. +func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration { + var delta = randomizationFactor * float64(currentInterval) + var minInterval = float64(currentInterval) - delta + var maxInterval = float64(currentInterval) + delta + + // Get a random value from the range [minInterval, maxInterval]. + // The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then + // we want a 33% chance for selecting either 1, 2 or 3. + return time.Duration(minInterval + (random * (maxInterval - minInterval + 1))) +} diff --git a/vendor/gopkg.in/cenkalti/backoff.v1/retry.go b/vendor/gopkg.in/cenkalti/backoff.v1/retry.go new file mode 100644 index 000000000..5dbd825b5 --- /dev/null +++ b/vendor/gopkg.in/cenkalti/backoff.v1/retry.go @@ -0,0 +1,78 @@ +package backoff + +import "time" + +// An Operation is executing by Retry() or RetryNotify(). +// The operation will be retried using a backoff policy if it returns an error. +type Operation func() error + +// Notify is a notify-on-error function. It receives an operation error and +// backoff delay if the operation failed (with an error). +// +// NOTE that if the backoff policy stated to stop retrying, +// the notify function isn't called. +type Notify func(error, time.Duration) + +// Retry the operation o until it does not return error or BackOff stops. +// o is guaranteed to be run at least once. +// It is the caller's responsibility to reset b after Retry returns. +// +// If o returns a *PermanentError, the operation is not retried, and the +// wrapped error is returned. +// +// Retry sleeps the goroutine for the duration returned by BackOff after a +// failed operation returns. +func Retry(o Operation, b BackOff) error { return RetryNotify(o, b, nil) } + +// RetryNotify calls notify function with the error and wait duration +// for each failed attempt before sleep. +func RetryNotify(operation Operation, b BackOff, notify Notify) error { + var err error + var next time.Duration + + cb := ensureContext(b) + + b.Reset() + for { + if err = operation(); err == nil { + return nil + } + + if permanent, ok := err.(*PermanentError); ok { + return permanent.Err + } + + if next = b.NextBackOff(); next == Stop { + return err + } + + if notify != nil { + notify(err, next) + } + + t := time.NewTimer(next) + + select { + case <-cb.Context().Done(): + t.Stop() + return err + case <-t.C: + } + } +} + +// PermanentError signals that the operation should not be retried. +type PermanentError struct { + Err error +} + +func (e *PermanentError) Error() string { + return e.Err.Error() +} + +// Permanent wraps the given err in a *PermanentError. +func Permanent(err error) *PermanentError { + return &PermanentError{ + Err: err, + } +} diff --git a/vendor/gopkg.in/cenkalti/backoff.v1/ticker.go b/vendor/gopkg.in/cenkalti/backoff.v1/ticker.go new file mode 100644 index 000000000..49a99718d --- /dev/null +++ b/vendor/gopkg.in/cenkalti/backoff.v1/ticker.go @@ -0,0 +1,81 @@ +package backoff + +import ( + "runtime" + "sync" + "time" +) + +// Ticker holds a channel that delivers `ticks' of a clock at times reported by a BackOff. +// +// Ticks will continue to arrive when the previous operation is still running, +// so operations that take a while to fail could run in quick succession. +type Ticker struct { + C <-chan time.Time + c chan time.Time + b BackOffContext + stop chan struct{} + stopOnce sync.Once +} + +// NewTicker returns a new Ticker containing a channel that will send the time at times +// specified by the BackOff argument. Ticker is guaranteed to tick at least once. +// The channel is closed when Stop method is called or BackOff stops. +func NewTicker(b BackOff) *Ticker { + c := make(chan time.Time) + t := &Ticker{ + C: c, + c: c, + b: ensureContext(b), + stop: make(chan struct{}), + } + go t.run() + runtime.SetFinalizer(t, (*Ticker).Stop) + return t +} + +// Stop turns off a ticker. After Stop, no more ticks will be sent. +func (t *Ticker) Stop() { + t.stopOnce.Do(func() { close(t.stop) }) +} + +func (t *Ticker) run() { + c := t.c + defer close(c) + t.b.Reset() + + // Ticker is guaranteed to tick at least once. + afterC := t.send(time.Now()) + + for { + if afterC == nil { + return + } + + select { + case tick := <-afterC: + afterC = t.send(tick) + case <-t.stop: + t.c = nil // Prevent future ticks from being sent to the channel. + return + case <-t.b.Context().Done(): + return + } + } +} + +func (t *Ticker) send(tick time.Time) <-chan time.Time { + select { + case t.c <- tick: + case <-t.stop: + return nil + } + + next := t.b.NextBackOff() + if next == Stop { + t.Stop() + return nil + } + + return time.After(next) +} diff --git a/vendor/gopkg.in/cenkalti/backoff.v1/tries.go b/vendor/gopkg.in/cenkalti/backoff.v1/tries.go new file mode 100644 index 000000000..d2da7308b --- /dev/null +++ b/vendor/gopkg.in/cenkalti/backoff.v1/tries.go @@ -0,0 +1,35 @@ +package backoff + +import "time" + +/* +WithMaxTries creates a wrapper around another BackOff, which will +return Stop if NextBackOff() has been called too many times since +the last time Reset() was called + +Note: Implementation is not thread-safe. +*/ +func WithMaxTries(b BackOff, max uint64) BackOff { + return &backOffTries{delegate: b, maxTries: max} +} + +type backOffTries struct { + delegate BackOff + maxTries uint64 + numTries uint64 +} + +func (b *backOffTries) NextBackOff() time.Duration { + if b.maxTries > 0 { + if b.maxTries <= b.numTries { + return Stop + } + b.numTries++ + } + return b.delegate.NextBackOff() +} + +func (b *backOffTries) Reset() { + b.numTries = 0 + b.delegate.Reset() +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 6e20a8ff4..e83a4df80 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -4,7 +4,7 @@ contrib.go.opencensus.io/exporter/prometheus # github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 ## explicit github.com/Azure/go-ntlmssp -# github.com/BurntSushi/toml v1.2.1 +# github.com/BurntSushi/toml v1.3.2 ## explicit; go 1.16 github.com/BurntSushi/toml github.com/BurntSushi/toml/internal @@ -352,8 +352,8 @@ github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1 github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1 github.com/cs3org/go-cs3apis/cs3/tx/v1beta1 github.com/cs3org/go-cs3apis/cs3/types/v1beta1 -# github.com/cs3org/reva/v2 v2.13.4-0.20230526111208-d62d1497abb9 -## explicit; go 1.19 +# github.com/cs3org/reva/v2 v2.14.1-0.20230623085734-919a9585f147 => github.com/micbar/reva/v2 v2.0.0-20230626125956-c381fe19a108 +## explicit; go 1.20 github.com/cs3org/reva/v2/cmd/revad/internal/grace github.com/cs3org/reva/v2/cmd/revad/runtime github.com/cs3org/reva/v2/internal/grpc/interceptors/appctx @@ -568,7 +568,6 @@ github.com/cs3org/reva/v2/pkg/publicshare/manager/memory github.com/cs3org/reva/v2/pkg/publicshare/manager/owncloudsql github.com/cs3org/reva/v2/pkg/publicshare/manager/registry github.com/cs3org/reva/v2/pkg/registry -github.com/cs3org/reva/v2/pkg/registry/memory github.com/cs3org/reva/v2/pkg/rgrpc github.com/cs3org/reva/v2/pkg/rgrpc/status github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool @@ -656,6 +655,7 @@ github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/migrator +github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/mtimesyncedcache github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree @@ -754,8 +754,8 @@ github.com/emvi/iso-639-1 # github.com/evanphx/json-patch/v5 v5.5.0 ## explicit; go 1.12 github.com/evanphx/json-patch/v5 -# github.com/fatih/color v1.13.0 -## explicit; go 1.13 +# github.com/fatih/color v1.14.1 +## explicit; go 1.17 github.com/fatih/color # github.com/felixge/httpsnoop v1.0.3 ## explicit; go 1.13 @@ -893,7 +893,7 @@ github.com/go-micro/plugins/v4/registry/consul # github.com/go-micro/plugins/v4/registry/etcd v1.2.0 ## explicit; go 1.17 github.com/go-micro/plugins/v4/registry/etcd -# github.com/go-micro/plugins/v4/registry/kubernetes v1.1.1 +# github.com/go-micro/plugins/v4/registry/kubernetes v1.1.2-0.20230605104008-a179a6b8f8e6 ## explicit; go 1.18 github.com/go-micro/plugins/v4/registry/kubernetes github.com/go-micro/plugins/v4/registry/kubernetes/client @@ -952,6 +952,8 @@ github.com/go-sql-driver/mysql # github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 ## explicit; go 1.13 github.com/go-task/slim-sprig +# github.com/go-test/deep v1.1.0 +## explicit; go 1.16 # github.com/gobwas/glob v0.2.3 ## explicit github.com/gobwas/glob @@ -975,6 +977,16 @@ github.com/gobwas/pool/pbytes ## explicit github.com/gobwas/ws github.com/gobwas/ws/wsutil +# github.com/goccy/go-yaml v1.11.0 +## explicit; go 1.18 +github.com/goccy/go-yaml +github.com/goccy/go-yaml/ast +github.com/goccy/go-yaml/internal/errors +github.com/goccy/go-yaml/lexer +github.com/goccy/go-yaml/parser +github.com/goccy/go-yaml/printer +github.com/goccy/go-yaml/scanner +github.com/goccy/go-yaml/token # github.com/godbus/dbus/v5 v5.0.6 ## explicit; go 1.12 github.com/godbus/dbus/v5 @@ -1001,9 +1013,11 @@ github.com/golang/geo/r1 github.com/golang/geo/r2 github.com/golang/geo/r3 github.com/golang/geo/s1 -# github.com/golang/glog v1.0.0 -## explicit; go 1.11 +# github.com/golang/glog v1.1.0 +## explicit; go 1.18 github.com/golang/glog +github.com/golang/glog/internal/logsink +github.com/golang/glog/internal/stackdump # github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da ## explicit github.com/golang/groupcache/lru @@ -1041,22 +1055,23 @@ github.com/google/pprof/profile # github.com/google/uuid v1.3.0 ## explicit github.com/google/uuid -# github.com/gookit/color v1.5.2 -## explicit; go 1.16 +# github.com/gookit/color v1.5.3 +## explicit; go 1.18 github.com/gookit/color -# github.com/gookit/config/v2 v2.1.8 -## explicit; go 1.16 +# github.com/gookit/config/v2 v2.2.2 +## explicit; go 1.18 github.com/gookit/config/v2 github.com/gookit/config/v2/yaml -# github.com/gookit/goutil v0.6.0 +# github.com/gookit/goutil v0.6.9 ## explicit; go 1.18 github.com/gookit/goutil github.com/gookit/goutil/arrutil +github.com/gookit/goutil/basefn +github.com/gookit/goutil/byteutil github.com/gookit/goutil/cliutil/cmdline github.com/gookit/goutil/comdef github.com/gookit/goutil/envutil github.com/gookit/goutil/errorx -github.com/gookit/goutil/fmtutil github.com/gookit/goutil/fsutil github.com/gookit/goutil/internal/comfunc github.com/gookit/goutil/jsonutil @@ -1067,9 +1082,9 @@ github.com/gookit/goutil/stdio github.com/gookit/goutil/stdutil github.com/gookit/goutil/structs github.com/gookit/goutil/strutil +github.com/gookit/goutil/strutil/textutil github.com/gookit/goutil/sysutil github.com/gookit/goutil/sysutil/cmdr -github.com/gookit/goutil/sysutil/process # github.com/gorilla/handlers v1.5.1 ## explicit; go 1.14 github.com/gorilla/handlers @@ -1128,7 +1143,7 @@ github.com/huandu/xstrings # github.com/iancoleman/strcase v0.2.0 ## explicit; go 1.16 github.com/iancoleman/strcase -# github.com/imdario/mergo v0.3.13 +# github.com/imdario/mergo v0.3.15 ## explicit; go 1.13 github.com/imdario/mergo # github.com/inconshreveable/mousetrap v1.1.0 @@ -1230,7 +1245,7 @@ github.com/mattermost/xml-roundtrip-validator # github.com/mattn/go-colorable v0.1.13 ## explicit; go 1.15 github.com/mattn/go-colorable -# github.com/mattn/go-isatty v0.0.16 +# github.com/mattn/go-isatty v0.0.17 ## explicit; go 1.15 github.com/mattn/go-isatty # github.com/mattn/go-runewidth v0.0.13 @@ -1310,7 +1325,7 @@ github.com/mschoch/smat # github.com/nats-io/jwt/v2 v2.4.1 ## explicit; go 1.18 github.com/nats-io/jwt/v2 -# github.com/nats-io/nats-server/v2 v2.9.17 +# github.com/nats-io/nats-server/v2 v2.9.19 ## explicit; go 1.19 github.com/nats-io/nats-server/v2/conf github.com/nats-io/nats-server/v2/internal/ldap @@ -1318,10 +1333,11 @@ github.com/nats-io/nats-server/v2/logger github.com/nats-io/nats-server/v2/server github.com/nats-io/nats-server/v2/server/pse github.com/nats-io/nats-server/v2/server/sysmem -# github.com/nats-io/nats.go v1.24.0 +# github.com/nats-io/nats.go v1.27.0 ## explicit; go 1.19 github.com/nats-io/nats.go github.com/nats-io/nats.go/encoders/builtin +github.com/nats-io/nats.go/internal/parser github.com/nats-io/nats.go/util # github.com/nats-io/nkeys v0.4.4 ## explicit; go 1.19 @@ -1515,7 +1531,7 @@ github.com/prometheus/alertmanager/asset github.com/prometheus/alertmanager/pkg/labels github.com/prometheus/alertmanager/template github.com/prometheus/alertmanager/types -# github.com/prometheus/client_golang v1.15.1 +# github.com/prometheus/client_golang v1.16.0 ## explicit; go 1.17 github.com/prometheus/client_golang/prometheus github.com/prometheus/client_golang/prometheus/internal @@ -1529,8 +1545,8 @@ github.com/prometheus/client_model/go github.com/prometheus/common/expfmt github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg github.com/prometheus/common/model -# github.com/prometheus/procfs v0.9.0 -## explicit; go 1.18 +# github.com/prometheus/procfs v0.10.1 +## explicit; go 1.19 github.com/prometheus/procfs github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/util @@ -1539,9 +1555,15 @@ github.com/prometheus/procfs/internal/util github.com/prometheus/statsd_exporter/pkg/level github.com/prometheus/statsd_exporter/pkg/mapper github.com/prometheus/statsd_exporter/pkg/mapper/fsm +# github.com/r3labs/sse/v2 v2.10.0 +## explicit; go 1.13 +github.com/r3labs/sse/v2 # github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 ## explicit github.com/rcrowley/go-metrics +# github.com/riandyrn/otelchi v0.5.1 +## explicit; go 1.15 +github.com/riandyrn/otelchi # github.com/rivo/uniseg v0.4.2 ## explicit; go 1.18 github.com/rivo/uniseg @@ -1557,7 +1579,7 @@ github.com/rs/cors # github.com/rs/xid v1.4.0 ## explicit; go 1.12 github.com/rs/xid -# github.com/rs/zerolog v1.29.0 +# github.com/rs/zerolog v1.29.1 ## explicit; go 1.15 github.com/rs/zerolog github.com/rs/zerolog/internal/cbor @@ -1619,19 +1641,14 @@ github.com/spf13/pflag # github.com/stretchr/objx v0.5.0 ## explicit; go 1.12 github.com/stretchr/objx -# github.com/stretchr/testify v1.8.2 -## explicit; go 1.13 +# github.com/stretchr/testify v1.8.4 +## explicit; go 1.20 github.com/stretchr/testify/assert github.com/stretchr/testify/mock github.com/stretchr/testify/require # github.com/tchap/go-patricia/v2 v2.3.1 ## explicit; go 1.16 github.com/tchap/go-patricia/v2/patricia -# github.com/test-go/testify v1.1.4 -## explicit -github.com/test-go/testify/assert -github.com/test-go/testify/mock -github.com/test-go/testify/require # github.com/thejerf/suture/v4 v4.0.2 ## explicit; go 1.9 github.com/thejerf/suture/v4 @@ -1650,7 +1667,7 @@ github.com/trustelem/zxcvbn/scoring # github.com/tus/tusd v1.10.1 ## explicit; go 1.16 github.com/tus/tusd/pkg/handler -# github.com/urfave/cli/v2 v2.25.1 +# github.com/urfave/cli/v2 v2.25.7 ## explicit; go 1.18 github.com/urfave/cli/v2 # github.com/wk8/go-ordered-map v1.0.0 @@ -1668,8 +1685,8 @@ github.com/xeipuuv/gojsonreference # github.com/xhit/go-simple-mail/v2 v2.13.0 ## explicit; go 1.13 github.com/xhit/go-simple-mail/v2 -# github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 -## explicit; go 1.15 +# github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e +## explicit; go 1.19 github.com/xo/terminfo # github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 ## explicit @@ -1792,15 +1809,22 @@ go.opencensus.io/trace go.opencensus.io/trace/internal go.opencensus.io/trace/propagation go.opencensus.io/trace/tracestate +# go.opentelemetry.io/contrib v1.0.0 +## explicit; go 1.15 +go.opentelemetry.io/contrib # go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.4 ## explicit; go 1.18 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal -# go.opentelemetry.io/contrib/zpages v0.41.1 +# go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.1-0.20230608065455-5cc3715df180 +## explicit; go 1.19 +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil +# go.opentelemetry.io/contrib/zpages v0.42.0 ## explicit; go 1.19 go.opentelemetry.io/contrib/zpages go.opentelemetry.io/contrib/zpages/internal -# go.opentelemetry.io/otel v1.15.1 +# go.opentelemetry.io/otel v1.16.0 ## explicit; go 1.19 go.opentelemetry.io/otel go.opentelemetry.io/otel/attribute @@ -1825,19 +1849,23 @@ go.opentelemetry.io/otel/exporters/jaeger/internal/gen-go/agent go.opentelemetry.io/otel/exporters/jaeger/internal/gen-go/jaeger go.opentelemetry.io/otel/exporters/jaeger/internal/gen-go/zipkincore go.opentelemetry.io/otel/exporters/jaeger/internal/third_party/thrift/lib/go/thrift -# go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.15.1 +# go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 ## explicit; go 1.19 go.opentelemetry.io/otel/exporters/otlp/internal/retry -# go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.1 +# go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 ## explicit; go 1.19 go.opentelemetry.io/otel/exporters/otlp/otlptrace go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/otlpconfig go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform -# go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.15.1 +# go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0 ## explicit; go 1.19 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc -# go.opentelemetry.io/otel/sdk v1.15.1 +# go.opentelemetry.io/otel/metric v1.16.0 +## explicit; go 1.19 +go.opentelemetry.io/otel/metric +go.opentelemetry.io/otel/metric/embedded +# go.opentelemetry.io/otel/sdk v1.16.0 ## explicit; go 1.19 go.opentelemetry.io/otel/sdk go.opentelemetry.io/otel/sdk/instrumentation @@ -1845,7 +1873,7 @@ go.opentelemetry.io/otel/sdk/internal go.opentelemetry.io/otel/sdk/internal/env go.opentelemetry.io/otel/sdk/resource go.opentelemetry.io/otel/sdk/trace -# go.opentelemetry.io/otel/trace v1.15.1 +# go.opentelemetry.io/otel/trace v1.16.0 ## explicit; go 1.19 go.opentelemetry.io/otel/trace # go.opentelemetry.io/proto/otlp v0.19.0 @@ -1870,7 +1898,7 @@ go.uber.org/zap/internal/color go.uber.org/zap/internal/exit go.uber.org/zap/zapcore go.uber.org/zap/zapgrpc -# golang.org/x/crypto v0.9.0 +# golang.org/x/crypto v0.10.0 ## explicit; go 1.17 golang.org/x/crypto/argon2 golang.org/x/crypto/bcrypt @@ -1916,7 +1944,7 @@ golang.org/x/image/vector # golang.org/x/mod v0.9.0 ## explicit; go 1.17 golang.org/x/mod/semver -# golang.org/x/net v0.10.0 +# golang.org/x/net v0.11.0 ## explicit; go 1.17 golang.org/x/net/bpf golang.org/x/net/context @@ -1943,12 +1971,12 @@ golang.org/x/net/trace ## explicit; go 1.17 golang.org/x/oauth2 golang.org/x/oauth2/internal -# golang.org/x/sync v0.1.0 +# golang.org/x/sync v0.2.0 ## explicit golang.org/x/sync/errgroup golang.org/x/sync/semaphore golang.org/x/sync/singleflight -# golang.org/x/sys v0.8.0 +# golang.org/x/sys v0.9.0 ## explicit; go 1.17 golang.org/x/sys/cpu golang.org/x/sys/execabs @@ -1960,10 +1988,10 @@ golang.org/x/sys/windows/registry golang.org/x/sys/windows/svc golang.org/x/sys/windows/svc/eventlog golang.org/x/sys/windows/svc/mgr -# golang.org/x/term v0.8.0 +# golang.org/x/term v0.9.0 ## explicit; go 1.17 golang.org/x/term -# golang.org/x/text v0.9.0 +# golang.org/x/text v0.10.0 ## explicit; go 1.17 golang.org/x/text/cases golang.org/x/text/encoding @@ -2010,6 +2038,10 @@ golang.org/x/tools/internal/pkgbits golang.org/x/tools/internal/tokeninternal golang.org/x/tools/internal/typeparams golang.org/x/tools/internal/typesinternal +# golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 +## explicit; go 1.17 +golang.org/x/xerrors +golang.org/x/xerrors/internal # google.golang.org/appengine v1.6.7 ## explicit; go 1.11 google.golang.org/appengine/internal @@ -2019,7 +2051,7 @@ google.golang.org/appengine/internal/log google.golang.org/appengine/internal/remote_api google.golang.org/appengine/internal/urlfetch google.golang.org/appengine/urlfetch -# google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f +# google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 ## explicit; go 1.19 google.golang.org/genproto/googleapis/api google.golang.org/genproto/googleapis/api/annotations @@ -2027,7 +2059,7 @@ google.golang.org/genproto/googleapis/api/httpbody google.golang.org/genproto/googleapis/rpc/errdetails google.golang.org/genproto/googleapis/rpc/status google.golang.org/genproto/protobuf/field_mask -# google.golang.org/grpc v1.54.0 +# google.golang.org/grpc v1.55.0 ## explicit; go 1.17 google.golang.org/grpc google.golang.org/grpc/attributes @@ -2121,6 +2153,9 @@ google.golang.org/protobuf/types/known/fieldmaskpb google.golang.org/protobuf/types/known/structpb google.golang.org/protobuf/types/known/timestamppb google.golang.org/protobuf/types/known/wrapperspb +# gopkg.in/cenkalti/backoff.v1 v1.1.0 +## explicit +gopkg.in/cenkalti/backoff.v1 # gopkg.in/ini.v1 v1.67.0 ## explicit gopkg.in/ini.v1 @@ -2160,3 +2195,4 @@ stash.kopano.io/kgol/oidc-go ## explicit; go 1.13 stash.kopano.io/kgol/rndm # github.com/cs3org/go-cs3apis => github.com/2403905/go-cs3apis v0.0.0-20230517122726-727045414fd1 +# github.com/cs3org/reva/v2 => github.com/micbar/reva/v2 v2.0.0-20230626125956-c381fe19a108