From eb6daa29f0a20325f10cccad1625dc2e29d39da0 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Sat, 28 Nov 2020 11:26:38 -0500 Subject: [PATCH] API: Implement mutation { createArtifact } Can you believe that this function worked on the first try? --- api/go.mod | 1 + api/go.sum | 24 ++++++ api/graph/api/generated.go | 140 +++++++++++++++++++++++++++++++ api/graph/model/artifact.go | 5 +- api/graph/model/models_gen.go | 5 ++ api/graph/schema.graphqls | 9 ++ api/graph/schema.resolvers.go | 153 +++++++++++++++++++++++++++++++++- 7 files changed, 334 insertions(+), 3 deletions(-) diff --git a/api/go.mod b/api/go.mod index f7cd0eb..e091c33 100644 --- a/api/go.mod +++ b/api/go.mod @@ -21,6 +21,7 @@ require ( github.com/lib/pq v1.8.0 github.com/martinlindhe/base36 v1.1.0 github.com/matryer/moq v0.0.0-20200310130814-7721994d1b54 // indirect + github.com/minio/minio-go/v7 v7.0.5 github.com/mitchellh/mapstructure v1.3.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.8.0 // indirect diff --git a/api/go.sum b/api/go.sum index 3979385..7ddef85 100644 --- a/api/go.sum +++ b/api/go.sum @@ -13,6 +13,7 @@ git.sr.ht/~sircmpwn/dowork v0.0.0-20201121170652-c2a771442daf/go.mod h1:8neHEO35 git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3 h1:4wDp4BKF7NQqoh73VXpZsB/t1OEhDpz/zEpmdQfbjDk= git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw= git.sr.ht/~sircmpwn/git.sr.ht v0.0.0-20201126161813-85c7338ae566 h1:4MRSyIEfnNfGsLl/R0IjZV7JgYJctGacBMwYJV5RkZE= +git.sr.ht/~sircmpwn/git.sr.ht v0.0.0-20201128022450-379557dc4278 h1:sC44HSgIiwjZOouW07tYRkokvPbswTQ7cUn6IbEeOEU= git.sr.ht/~sircmpwn/go-bare v0.0.0-20200623145341-debb068b456a h1:bqT/ygbVtD6b/B/skCQ+hZI4OtwuyKFQJxwS3z1lY3g= git.sr.ht/~sircmpwn/go-bare v0.0.0-20200623145341-debb068b456a/go.mod h1:BVJwbDfVjCjoFiKrhkei6NdGcZYpkDkdyCdg1ukytRA= git.sr.ht/~sircmpwn/go-bare v0.0.0-20200812160916-d2c72e1a5018 h1:89QMorzx6ML69PKPoayL3HuSfb7WqAlxD1dZ7DyzD0k= @@ -174,6 +175,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= @@ -241,6 +244,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/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 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= @@ -251,6 +255,9 @@ github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= +github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= 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.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -289,6 +296,14 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4= +github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= +github.com/minio/minio-go v1.0.0 h1:ooSujki+Z1PRGZsYffJw5jnF5eMBvzMVV86TLAlM0UM= +github.com/minio/minio-go v3.0.2+incompatible h1:TgjYEriSwSpcawLVPw4LpkENIJdqqtVwkyMEnNapZ8w= +github.com/minio/minio-go/v7 v7.0.5 h1:I2NIJ2ojwJqD/YByemC1M59e1b4FW9kS7NlOar7HPV4= +github.com/minio/minio-go/v7 v7.0.5/go.mod h1:TA0CQCjJZHM5SJj9IjqR0NmpmQJ6bCbXifAJ3mUU6Hw= +github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= +github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -303,8 +318,10 @@ github.com/mitchellh/mapstructure v1.3.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -392,6 +409,8 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -408,6 +427,7 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= @@ -558,6 +578,7 @@ golang.org/x/sys v0.0.0-20201126144705-a4b67b81d3d2 h1:WFCmm2Hi9I2gYf1kv7LQ8ajKA golang.org/x/sys v0.0.0-20201126144705-a4b67b81d3d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4-0.20201021145329-22f1617af38e h1:0kyKOEC0chG7FKmnf/1uNwvDLc3NtNTRip2rXAN9nwI= golang.org/x/text v0.3.4-0.20201021145329-22f1617af38e/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -629,6 +650,8 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= @@ -645,6 +668,7 @@ 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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/api/graph/api/generated.go b/api/graph/api/generated.go index 978c005..1019585 100644 --- a/api/graph/api/generated.go +++ b/api/graph/api/generated.go @@ -108,6 +108,10 @@ type ComplexityRoot struct { Results func(childComplexity int) int } + Features struct { + Artifacts func(childComplexity int) int + } + Mutation struct { CreateRepository func(childComplexity int, name string, visibility model.Visibility, description *string) int DeleteACL func(childComplexity int, id int) int @@ -225,6 +229,7 @@ type ComplexityRoot struct { Version struct { DeprecationDate func(childComplexity int) int + Features func(childComplexity int) int Major func(childComplexity int) int Minor func(childComplexity int) int Patch func(childComplexity int) int @@ -519,6 +524,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.CommitCursor.Results(childComplexity), true + case "Features.artifacts": + if e.complexity.Features.Artifacts == nil { + break + } + + return e.complexity.Features.Artifacts(childComplexity), true + case "Mutation.createRepository": if e.complexity.Mutation.CreateRepository == nil { break @@ -1168,6 +1180,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Version.DeprecationDate(childComplexity), true + case "Version.features": + if e.complexity.Version.Features == nil { + break + } + + return e.complexity.Version.Features(childComplexity), true + case "Version.major": if e.complexity.Version.Major == nil { break @@ -1283,10 +1302,19 @@ type Version { major: Int! minor: Int! patch: Int! + # If this API version is scheduled for deprecation, this is the date on which # it will stop working; or null if this API version is not scheduled for # deprecation. deprecationDate: Time + + # Optional features + features: Features! +} + +# Describes the status of optional features +type Features { + artifacts: Boolean! } enum AccessMode { @@ -3301,6 +3329,41 @@ func (ec *executionContext) _CommitCursor_cursor(ctx context.Context, field grap return ec.marshalOCursor2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋcoreᚑgoᚋmodelᚐCursor(ctx, field.Selections, res) } +func (ec *executionContext) _Features_artifacts(ctx context.Context, field graphql.CollectedField, obj *model.Features) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Features", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Artifacts, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + func (ec *executionContext) _Mutation_createRepository(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -6879,6 +6942,41 @@ func (ec *executionContext) _Version_deprecationDate(ctx context.Context, field return ec.marshalOTime2ᚖtimeᚐTime(ctx, field.Selections, res) } +func (ec *executionContext) _Version_features(ctx context.Context, field graphql.CollectedField, obj *model.Version) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Version", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Features, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Features) + fc.Result = res + return ec.marshalNFeatures2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐFeatures(ctx, field.Selections, res) +} + func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -8427,6 +8525,33 @@ func (ec *executionContext) _CommitCursor(ctx context.Context, sel ast.Selection return out } +var featuresImplementors = []string{"Features"} + +func (ec *executionContext) _Features(ctx context.Context, sel ast.SelectionSet, obj *model.Features) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, featuresImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Features") + case "artifacts": + out.Values[i] = ec._Features_artifacts(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var mutationImplementors = []string{"Mutation"} func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler { @@ -9226,6 +9351,11 @@ func (ec *executionContext) _Version(ctx context.Context, sel ast.SelectionSet, } case "deprecationDate": out.Values[i] = ec._Version_deprecationDate(ctx, field, obj) + case "features": + out.Values[i] = ec._Version_features(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -9765,6 +9895,16 @@ func (ec *executionContext) marshalNEntity2gitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsr return ec._Entity(ctx, sel, v) } +func (ec *executionContext) marshalNFeatures2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐFeatures(ctx context.Context, sel ast.SelectionSet, v *model.Features) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._Features(ctx, sel, v) +} + func (ec *executionContext) unmarshalNID2string(ctx context.Context, v interface{}) (string, error) { res, err := graphql.UnmarshalID(v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/api/graph/model/artifact.go b/api/graph/model/artifact.go index 33579f9..d080500 100644 --- a/api/graph/model/artifact.go +++ b/api/graph/model/artifact.go @@ -19,8 +19,9 @@ type Artifact struct { Checksum string `json:"checksum"` Size int `json:"size"` + Commit string + alias string - commit string fields *database.ModelFields } @@ -51,7 +52,7 @@ func (a *Artifact) Fields() *database.ModelFields { // Always fetch: { "id", "", &a.ID }, - { "commit", "", &a.commit }, + { "commit", "", &a.Commit }, { "filename", "", &a.Filename }, }, } diff --git a/api/graph/model/models_gen.go b/api/graph/model/models_gen.go index 54a9a53..baf9936 100644 --- a/api/graph/model/models_gen.go +++ b/api/graph/model/models_gen.go @@ -34,6 +34,10 @@ type CommitCursor struct { Cursor *model.Cursor `json:"cursor"` } +type Features struct { + Artifacts bool `json:"artifacts"` +} + type ReferenceCursor struct { Results []*Reference `json:"results"` Cursor *model.Cursor `json:"cursor"` @@ -73,6 +77,7 @@ type Version struct { Minor int `json:"minor"` Patch int `json:"patch"` DeprecationDate *time.Time `json:"deprecationDate"` + Features *Features `json:"features"` } type AccessKind string diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls index 0167270..2ef2669 100644 --- a/api/graph/schema.graphqls +++ b/api/graph/schema.graphqls @@ -28,10 +28,19 @@ type Version { major: Int! minor: Int! patch: Int! + # If this API version is scheduled for deprecation, this is the date on which # it will stop working; or null if this API version is not scheduled for # deprecation. deprecationDate: Time + + # Optional features + features: Features! +} + +# Describes the status of optional features +type Features { + artifacts: Boolean! } enum AccessMode { diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index b094879..55c2e8f 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -4,10 +4,16 @@ package graph // will be copied through when generating and any unknown code will be moved to the end. import ( + "bytes" "context" + "crypto/md5" + "crypto/sha256" "database/sql" + "encoding/base64" + "encoding/hex" "errors" "fmt" + "io" "os" "path" "sort" @@ -28,6 +34,8 @@ import ( "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/storer" + minio "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" ) func (r *aCLResolver) Repository(ctx context.Context, obj *model.ACL) (*model.Repository, error) { @@ -385,7 +393,139 @@ func (r *mutationResolver) DeleteACL(ctx context.Context, id int) (*model.ACL, e } func (r *mutationResolver) UploadArtifact(ctx context.Context, repoID int, revspec string, file graphql.Upload) (*model.Artifact, error) { - panic(fmt.Errorf("uploadArtifact: not implemented")) + conf := config.ForContext(ctx) + upstream, _ := conf.Get("objects", "s3-upstream") + accessKey, _ := conf.Get("objects", "s3-access-key") + secretKey, _ := conf.Get("objects", "s3-secret-key") + bucket, _ := conf.Get("git.sr.ht", "s3-bucket") + prefix, _ := conf.Get("git.sr.ht", "s3-prefix") + if upstream == "" || accessKey == "" || secretKey == "" || bucket == "" { + return nil, fmt.Errorf("Object storage is not enabled for this server") + } + + mc, err := minio.New(upstream, &minio.Options{ + Creds: credentials.NewStaticV4(accessKey, secretKey, ""), + Secure: true, + }) + if err != nil { + panic(err) + } + + repo, err := loaders.ForContext(ctx).RepositoriesByID.Load(repoID) + if err != nil { + return nil, fmt.Errorf("Repository %d not found", repoID) + } + if repo.OwnerID != auth.ForContext(ctx).UserID { + return nil, fmt.Errorf("Access denied for repo %d", repoID) + } + + gitRepo := repo.Repo() + gitRepo.Lock() + hash, err := gitRepo.ResolveRevision(plumbing.Revision(revspec)) + gitRepo.Unlock() + if err != nil { + return nil, err + } + + s3path := path.Join(prefix, "artifacts", + "~" + auth.ForContext(ctx).Username, repo.Name, file.Filename) + + err = mc.MakeBucket(ctx, bucket, minio.MakeBucketOptions{}) + if s3err, ok := err.(minio.ErrorResponse); !ok || + (s3err.Code != "BucketAlreadyExists" && s3err.Code != "BucketAlreadyOwnedByYou") { + panic(err) + } + + core := minio.Core{mc} + uid, err := core.NewMultipartUpload(ctx, bucket, + s3path, minio.PutObjectOptions{ + ContentType: "application/octet-stream", + }) + if err != nil { + return nil, err + } + + defer func() { + if err := recover(); err != nil { + core.AbortMultipartUpload(context.Background(), bucket, s3path, uid) + panic(err) + } + }() + + var artifact model.Artifact + if err := database.WithTx(ctx, nil, func(tx *sql.Tx) error { + // To guarantee atomicity, we do the following: + // 1. Upload all parts as an S3 multipart upload, but don't complete + // it. This computes the SHA-256 as a side-effect. + // 2. Insert row into the database and let PostgreSQL prevent + // duplicates with constraints. Once this finishes we know we are the + // exclusive writer of this artifact. + // 3. Complete the S3 multi-part upload. + sha := sha256.New() + reader := io.TeeReader(file.File, sha) + + var ( + partNum int = 1 + parts []minio.CompletePart + ) + for { + var data [16777216]byte // 16 MiB + n, err := reader.Read(data[:]) + if errors.Is(err, io.EOF) { + if n == 0 { + break + } + // Write data[:n] and go for another loop before quitting + } else if err != nil { + return err + } + + md5sum := md5.Sum(data[:n]) + md5b64 := base64.StdEncoding.EncodeToString(md5sum[:]) + shasum := sha256.Sum256(data[:n]) + shahex := hex.EncodeToString(shasum[:]) + + opart, err := core.PutObjectPart(ctx, bucket, s3path, + uid, partNum, bytes.NewReader(data[:n]), int64(n), + md5b64, shahex, nil) + if err != nil { + return err + } + parts = append(parts, minio.CompletePart{ + PartNumber: partNum, + ETag: opart.ETag, + }) + partNum++ + } + + checksum := "sha256:" + hex.EncodeToString(sha.Sum(nil)) + row := tx.QueryRowContext(ctx, ` + INSERT INTO artifacts ( + created, user_id, repo_id, commit, filename, checksum, size + ) VALUES ( + NOW() at time zone 'utc', + $1, $2, $3, $4, $5, $6 + ) RETURNING id, created, filename, checksum, size, commit`, + repo.OwnerID, repo.ID, hash.String(), file.Filename, + checksum, file.Size) + if err := row.Scan(&artifact.ID, &artifact.Created, &artifact.Filename, + &artifact.Checksum, &artifact.Size, &artifact.Commit); err != nil { + if err.Error() == "pq: duplicate key value violates unique constraint \"repo_artifact_filename_unique\"" { + return fmt.Errorf("An artifact by this name already exists for this repository (artifact names must be unique for within each repository)") + } + return err + } + + _, err := core.CompleteMultipartUpload(ctx, bucket, s3path, uid, parts) + return err + }); err != nil { + if err != nil { + core.AbortMultipartUpload(context.Background(), bucket, s3path, uid) + } + return nil, err + } + + return &artifact, nil } func (r *mutationResolver) DeleteArtifact(ctx context.Context, id int) (*model.Artifact, error) { @@ -393,11 +533,22 @@ func (r *mutationResolver) DeleteArtifact(ctx context.Context, id int) (*model.A } func (r *queryResolver) Version(ctx context.Context) (*model.Version, error) { + conf := config.ForContext(ctx) + upstream, _ := conf.Get("objects", "s3-upstream") + accessKey, _ := conf.Get("objects", "s3-access-key") + secretKey, _ := conf.Get("objects", "s3-secret-key") + bucket, _ := conf.Get("git.sr.ht", "s3-bucket") + artifacts := upstream != "" && accessKey != "" && secretKey != "" && bucket != "" + return &model.Version{ Major: 0, Minor: 0, Patch: 0, DeprecationDate: nil, + + Features: &model.Features{ + Artifacts: artifacts, + }, }, nil } -- 2.38.4