diff --git a/maven/.gitignore b/maven/.gitignore new file mode 100644 index 0000000..773338b --- /dev/null +++ b/maven/.gitignore @@ -0,0 +1,5 @@ +/dagger.gen.go +/internal/dagger +/internal/querybuilder +/internal/telemetry +/.env diff --git a/maven/dagger.json b/maven/dagger.json index 9fa003f..5a73e65 100644 --- a/maven/dagger.json +++ b/maven/dagger.json @@ -1,6 +1,6 @@ { "name": "maven", - "engineVersion": "v0.18.9", + "engineVersion": "v0.18.19", "sdk": { "source": "go" } diff --git a/maven/go.mod b/maven/go.mod index ac4f8d0..41346f5 100644 --- a/maven/go.mod +++ b/maven/go.mod @@ -3,48 +3,48 @@ module dagger/maven go 1.24.3 require ( - github.com/99designs/gqlgen v0.17.73 + github.com/99designs/gqlgen v0.17.79 github.com/Khan/genqlient v0.8.1 - github.com/vektah/gqlparser/v2 v2.5.27 - go.opentelemetry.io/otel v1.34.0 - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 - go.opentelemetry.io/otel/log v0.8.0 - go.opentelemetry.io/otel/metric v1.34.0 - go.opentelemetry.io/otel/sdk v1.34.0 - go.opentelemetry.io/otel/sdk/log v0.8.0 - go.opentelemetry.io/otel/sdk/metric v1.34.0 - go.opentelemetry.io/otel/trace v1.34.0 - go.opentelemetry.io/proto/otlp v1.3.1 - golang.org/x/sync v0.14.0 - google.golang.org/grpc v1.72.1 + github.com/vektah/gqlparser/v2 v2.5.30 + go.opentelemetry.io/otel v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 + go.opentelemetry.io/otel/log v0.14.0 + go.opentelemetry.io/otel/metric v1.38.0 + go.opentelemetry.io/otel/sdk v1.38.0 + go.opentelemetry.io/otel/sdk/log v0.14.0 + go.opentelemetry.io/otel/sdk/metric v1.38.0 + go.opentelemetry.io/otel/trace v1.38.0 + go.opentelemetry.io/proto/otlp v1.8.0 + golang.org/x/sync v0.17.0 + google.golang.org/grpc v1.75.1 ) require ( - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/sosodev/duration v1.3.1 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect - golang.org/x/net v0.40.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.25.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect - google.golang.org/protobuf v1.36.6 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + golang.org/x/net v0.44.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/protobuf v1.36.9 // indirect ) -replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 -replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 -replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.8.0 +replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.14.0 -replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.8.0 +replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.14.0 diff --git a/maven/go.sum b/maven/go.sum index 73a2898..4cf9d6c 100644 --- a/maven/go.sum +++ b/maven/go.sum @@ -1,85 +1,89 @@ -github.com/99designs/gqlgen v0.17.73 h1:A3Ki+rHWqKbAOlg5fxiZBnz6OjW3nwupDHEG15gEsrg= -github.com/99designs/gqlgen v0.17.73/go.mod h1:2RyGWjy2k7W9jxrs8MOQthXGkD3L3oGr0jXW3Pu8lGg= +github.com/99designs/gqlgen v0.17.79 h1:RTsJZtdzcfROeWdt42NGMIabIbiBn69YyVmLEAuxtnA= +github.com/99designs/gqlgen v0.17.79/go.mod h1:vgNcZlLwemsUhYim4dC1pvFP5FX0pr2Y+uYUoHFb1ig= github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs= github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTdwFp0s= -github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/vektah/gqlparser/v2 v2.5.30 h1:EqLwGAFLIzt1wpx1IPpY67DwUujF1OfzgEyDsLrN6kE= +github.com/vektah/gqlparser/v2 v2.5.30/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= -go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= -go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= -go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= +go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= +go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE= +go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= -google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= -google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= -google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/maven/main.go b/maven/main.go index feedd3b..ff316f7 100644 --- a/maven/main.go +++ b/maven/main.go @@ -1,4 +1,4 @@ -// Dagger module to build maven Projects +// Dagger module to build Maven projects. package main import ( @@ -7,103 +7,184 @@ import ( "fmt" ) -var DefaultMvnCiOptions = []string{"--batch-mode", "--errors", "-Dmaven.test.failure.ignore=true"} - -// Maven Module -type Maven struct { - // image for executing Builds - Image string - // Use Maven Wrapper - UseMvnw bool - // Use Cache for Maven repository - UseCache bool - // Use default maven parameters for CI builds - UseDefaultCiOptions bool - Dir *dagger.Directory - MavenContainer *dagger.Container +// FullBuildModules orchestrates build, test, Sonar analysis, and image publishing for each module in order. +func (m *Maven) FullBuildModules( + ctx context.Context, + source *dagger.Directory, + modules []string, + // +optional + sonarConfig *SonarConfig, + // +optional + dockerConfig *DockerBuildConfig) ([]*ModuleBuildResult, error) { + results := make([]*ModuleBuildResult, 0) + for _, module := range modules { + result, err := m.FullBuild(ctx, source, module, sonarConfig, dockerConfig) + if err != nil { + return nil, err + } + results = append(results, result) + } + return results, nil } -// New creates a new Maven Module -func New( -// Directory for maven goals execution -// +optional +// FullBuild executes the three-stage pipeline (build/test, Sonar, Docker publish) for a single Maven module. +func (m *Maven) FullBuild(ctx context.Context, source *dagger.Directory, -// image for executing Builds -// +default="maven:3.9.9-eclipse-temurin-21-alpine" - buildImage string, -// Use Maven Wrapper -// +default=false - useMvnw bool, -// Use Cache for Maven repository (true recommended) -// +default=true - useCache bool, -// Use default maven parameters for CI builds -// +default=true - useDefaultCiOptions bool) *Maven { - return &Maven{ - Image: buildImage, - UseMvnw: useMvnw, - UseCache: useCache, - UseDefaultCiOptions: useDefaultCiOptions, - Dir: source, + module string, + sonarConfig *SonarConfig, + dockerConfig *DockerBuildConfig) (*ModuleBuildResult, error) { + + stages := []PipelineStage{ + { + DisplayName: "Build and Test", + Goals: []string{"clean", "verify"}, + }, } -} -// MvnVerify runs mvn verify goal to build the application and run UT and IT -func (m *Maven) MvnVerify(cleanFirst bool) *dagger.Directory { - var args []string - if cleanFirst { - args = append(args, "clean") + if sonarConfig != nil { + sonarStage, err := m.configureSonar(ctx, sonarConfig, module) + if err != nil { + return nil, err + } + stages = append(stages, sonarStage) + } + + imageUrl := "" + if dockerConfig != nil { + dockerStage, image, err := m.configureDockerPublish(ctx, source, module, dockerConfig) + if err != nil { + return nil, err + } + stages = append(stages, dockerStage) + imageUrl = image } - args = append(args, "verify") - return m.MavenBuild(args).GetBuildDir() -} -// PublishWithJib runs JIB through mvn jib:build to build and publish a Docker Image -func (m *Maven) PublishWithJib(ctx context.Context, - registry string, - image string, - username string, - password *dagger.Secret) (*Maven, error) { - plaintextPwd, err := password.Plaintext(ctx) + buildResult, err := m.executeStages(ctx, source.Directory(module), module, stages) if err != nil { return nil, err } - return m.MavenBuild([]string{"jib:build", - fmt.Sprintf("-Djib.to.image=%s/%s", registry, image), - fmt.Sprintf("-Djib.to.auth.username=%s", username), - fmt.Sprintf("-Djib.to.auth.password=%s", plaintextPwd)}), nil + buildResult.ImageUrl = imageUrl + return buildResult, nil } -// MvnVerifyPublishWithJib runs mvn clean verify to build the application then publish the imagem with Jib -func (m *Maven) MvnVerifyPublishWithJib( +func (m *Maven) configureDockerPublish( ctx context.Context, -// Docker registry for image publishing - registry string, -// Image name with tag (can contain groups, i.e.: a/b/c:1.0) - image string, -// Username for login to the registry - username string, -// Password for login to the registry - password *dagger.Secret) (*dagger.Directory, error) { - _, err := m.MvnVerify(true).Entries(ctx) + source *dagger.Directory, + module string, + dockerConfig *DockerBuildConfig) (PipelineStage, string, error) { + moduleDockerConfig := *dockerConfig + // TODO Vamos ter que passar + moduleDockerConfig.Image = module + // TODO Vamos ter que passar o commitSha aqui + moduleDockerConfig.Tag = m.GetVersionOrDefault(ctx, source, "latest") + dockerOptions, err := m.buildDockerOptions(ctx, &moduleDockerConfig) if err != nil { - return nil, err + return PipelineStage{}, "", err } - _, err = m.PublishWithJib(ctx, registry, image, username, password) + return PipelineStage{ + DisplayName: "Docker Build and Push", + Goals: []string{"jib:build"}, + Options: dockerOptions, + }, moduleDockerConfig.imageReference("latest"), nil +} + +func (m *Maven) configureSonar(ctx context.Context, sonarConfig *SonarConfig, module string) (PipelineStage, error) { + moduleSonarConfig := *sonarConfig + moduleSonarConfig.ProjectKey = module + sonarOptions, err := m.buildSonarOptions(ctx, &moduleSonarConfig) if err != nil { - return nil, err + return PipelineStage{}, err } - return m.GetBuildDir(), nil + return PipelineStage{ + DisplayName: "SonarQube Analysis", + Goals: []string{"sonar:sonar"}, + Options: sonarOptions, + }, nil } -// Run Sonar Analysis using Sonar Maven Plugin -func (m *Maven) MvnSonarAnalysis(ctx context.Context, sonarHostUrl string, token *dagger.Secret) (*Maven, error) { - plaintextToken, err := token.Plaintext(ctx) +// executeStages runs the provided pipeline stages sequentially using a shared container. +func (m *Maven) executeStages( + ctx context.Context, + source *dagger.Directory, + module string, + stages []PipelineStage) (*ModuleBuildResult, error) { + stageContainer := m.Container() + result := &ModuleBuildResult{} + for _, stage := range stages { + buildResultStage, err := m.executeStage(ctx, source, module, stage, stageContainer) + if err != nil { + return nil, err + } + stageContainer = buildResultStage.Container + result.Stdout = append(result.Stdout, buildResultStage.Stdout) + result.Stderr = append(result.Stderr, buildResultStage.Stderr) + result.ExecutedStages = append(result.ExecutedStages, stage.DisplayName) + result.Artifacts = buildResultStage.Artifacts + } + return result, nil +} + +// executeStage mounts the module source, runs the Maven goals for a stage, and captures outputs. +func (m *Maven) executeStage( + ctx context.Context, + source *dagger.Directory, + module string, + stage PipelineStage, + stageContainer *dagger.Container) (*StageBuildResult, error) { + moduleDir := fmt.Sprintf("%s/%s", BaseWorkdir, module) + stageContainer = stageContainer. + WithDirectory(moduleDir, source, dagger.ContainerWithDirectoryOpts{Exclude: []string{"target"}}). + WithWorkdir(moduleDir). + WithExec(m.getFullMvnModuleCommand(stage.Options, stage.Goals)) + stdout, err := stageContainer.Stdout(ctx) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get stdout: %w", err) + } + stderr, err := stageContainer.Stderr(ctx) + if err != nil { + // Non-fatal, stderr might be empty + stderr = "" + } + artifactsDir := stageContainer.Directory(fmt.Sprintf("%s/target", moduleDir)) + return &StageBuildResult{ + Container: stageContainer, + Artifacts: artifactsDir, + Stdout: stdout, + Stderr: stderr, + }, nil +} + +// buildDockerOptions materializes the Maven command-line arguments needed to run the Jib plugin. +func (m *Maven) buildDockerOptions(ctx context.Context, config *DockerBuildConfig) ([]string, error) { + if config == nil { + return nil, nil + } + + var options []string + + imageURL := config.imageReference("latest") + + if imageURL != "" { + options = append(options, fmt.Sprintf("-Djib.to.image=%s", imageURL)) + // Separar Tag + options = append(options, fmt.Sprintf("-Djib.to.tag=%s", config.Tag)) } - return m.MavenBuild([]string{"org.sonarsource.scanner.maven:sonar-maven-plugin:sonar", - fmt.Sprintf("-Dsonar.token=%s", plaintextToken), - fmt.Sprintf("-Dsonar.host.url=%s", sonarHostUrl)}), nil + + if config.Username != "" { + options = append(options, fmt.Sprintf("-Djib.to.auth.username=%s", config.Username)) + } + + if config.PasswordSecret != nil { + password, err := config.PasswordSecret.Plaintext(ctx) + if err != nil { + return nil, fmt.Errorf("error getting registry password: %w", err) + } + options = append(options, fmt.Sprintf("-Djib.to.auth.password=%s", password)) + } + + if config.Options != nil { + options = append(options, config.Options...) + } + + return options, nil } diff --git a/maven/maven.go b/maven/maven.go index 81b87a1..fbde5be 100644 --- a/maven/maven.go +++ b/maven/maven.go @@ -1,3 +1,4 @@ +// Dagger module to build maven Projects package main import ( @@ -7,8 +8,14 @@ import ( "fmt" ) -const DefaultMavenCacheName = "maven-cache" -const DefaultWorkdir = "/app" +// DefaultMavenCacheName identifies the cache volume used for the Maven local repository. +const ( + DefaultMavenCacheName = "maven-cache" + // BaseWorkdir is the working directory inside the container where builds are executed. + BaseWorkdir = "/app" +) + +var DefaultDockerHosts = []string{"registry-1.docker.io", "registry.hub.docker.com", "quay.io", "ghcr.io"} type pomProject struct { // XMLName is used to ensure we're parsing the element. @@ -17,89 +24,146 @@ type pomProject struct { Version string `xml:"version"` } -func (mc *Maven) WithImage(image string) *Maven { - mc.Image = image - return mc -} - -func (mc *Maven) WithUseMvnw(mvnw bool) *Maven { - mc.UseMvnw = mvnw - return mc +// Maven models the configuration and reusable container used to run Maven-based builds. +type Maven struct { + // image for executing Builds + Image string + // Use Maven Wrapper + UseMvnw bool + // Use Cache for Maven repository + UseCache bool + // Use default maven parameters for CI builds + UseDefaultCiOptions bool + // Extra maven options, for example properties: "-Dmyprop=1" + ExtraOptions []string + // Optional Parent POM for multi-modules buils + ParentPom *dagger.File + // Container used to run the builds + baseContainer *dagger.Container + UseJib bool } -func (mc *Maven) WithUseCache(useCache bool) *Maven { - mc.UseCache = useCache - return mc -} - -func (mc *Maven) WithUseDefaultCiOptions(useDefaultCiOptions bool) *Maven { - mc.UseDefaultCiOptions = useDefaultCiOptions - return mc -} +// DefaultMvnCiOptions lists the flags automatically added when UseDefaultCiOptions is enabled. +var DefaultMvnCiOptions = []string{"--batch-mode", "--errors", "-Dmaven.test.failure.ignore=true"} -func (mc *Maven) WithDir(dir *dagger.Directory) *Maven { - mc.Dir = dir - return mc +// New constructs a Maven helper ready to execute builds with the provided base options. +func New( + // image for executing Builds + // +default="maven:3.9.9-eclipse-temurin-21-alpine" + buildImage string, + // Use Maven Wrapper + // +default=false + useMvnw bool, + // Use Cache for Maven repository (true recommended) + // +default=true + useCache bool, + // Use default maven parameters for CI builds + // +default=true + useDefaultCiOptions bool, + // Extra maven options, for example properties: "-Dmyprop=1" + // +optional + extraOptions []string, + // Parent Pom if multi-module project + // +optional + parentPom *dagger.File, + // +default=true + useJib bool) *Maven { + m := &Maven{ + Image: buildImage, + UseMvnw: useMvnw, + UseCache: useCache, + UseDefaultCiOptions: useDefaultCiOptions, + ExtraOptions: extraOptions, + ParentPom: parentPom, + UseJib: useJib, + } + return m } -func (mc *Maven) NewContainer() *dagger.Container { - container := dag.Container().From(mc.Image).WithWorkdir(DefaultWorkdir) - if mc.UseCache { +// NewBaseContainer initializes the base container with caches and optional parent POM installation. +func (m *Maven) NewBaseContainer() *dagger.Container { + container := dag.Container().From(m.Image).WithWorkdir(BaseWorkdir) + if m.UseCache { container = container.WithMountedCache("/root/.m2", dag.CacheVolume(DefaultMavenCacheName)) } - if mc.Dir != nil { - container = container.WithMountedDirectory(DefaultWorkdir, mc.Dir) + if m.ParentPom != nil { + container = container. + WithFile(fmt.Sprintf("%s/%s", BaseWorkdir, "pom.xml"), m.ParentPom). + WithWorkdir(BaseWorkdir). + WithExec(m.getFullMvnModuleCommand([]string{"-N"}, []string{"install"})) } return container } -func (mc *Maven) Container() *dagger.Container { - if mc.MavenContainer == nil { - mc.MavenContainer = mc.NewContainer() +// Container ensures a base container exists and returns it for further customization. +func (m *Maven) Container() *dagger.Container { + if m.baseContainer == nil { + m.baseContainer = m.NewBaseContainer() } - return mc.MavenContainer + return m.baseContainer +} + +func (m *Maven) getFullMvnCommand(goals []string) []string { + return m.getFullMvnModuleCommand(nil, goals) } -func (mc *Maven) MavenBuild(args []string) *Maven { - container := mc.Container() +func (m *Maven) getFullMvnModuleCommand(moduleOptions []string, goals []string) []string { var execCmd []string - if mc.UseMvnw { + if m.UseMvnw { execCmd = append(execCmd, "./mvnw") } else { execCmd = append(execCmd, "mvn") } - if mc.UseDefaultCiOptions { + if m.UseDefaultCiOptions { execCmd = append(execCmd, DefaultMvnCiOptions...) } - mc.MavenContainer = container.WithExec(append(execCmd, args...)) - return mc + if m.ExtraOptions != nil { + execCmd = append(execCmd, m.ExtraOptions...) + } + if moduleOptions != nil { + execCmd = append(execCmd, moduleOptions...) + } + return append(execCmd, goals...) } -// GetGeneratedArtifact returns an artifact from the target directory -func (mc *Maven) GetGeneratedArtifact(jarName string) *dagger.File { - return mc.Container().File(fmt.Sprintf("%s/target/%s", DefaultWorkdir, jarName)) +// GetVersion reads the module pom.xml (falling back to the parent POM) and returns the version value. +func (m *Maven) GetVersion(ctx context.Context, moduleDir *dagger.Directory) (string, error) { + if moduleDir == nil { + return "", fmt.Errorf("cannot get version: maven directory is not set") + } + project, err := m.readProject(ctx, moduleDir.File("pom.xml")) + if err != nil { + return "", err + } + if project.Version == "" { + if m.ParentPom != nil { + project, err = m.readProject(ctx, m.ParentPom) + } + if err != nil || project.Version == "" { + return "", fmt.Errorf("could not find a tag inside the tag in pom.xml or Parent") + } + } + return project.Version, nil } -// GetBuildDir returns the output build Directory -func (mc *Maven) GetBuildDir() *dagger.Directory { - return mc.Container().Directory(DefaultWorkdir) +// GetVersionOrDefault returns the module version or the provided default when it cannot be resolved. +func (m *Maven) GetVersionOrDefault(ctx context.Context, moduleDir *dagger.Directory, defaultVersion string) string { + version, err := m.GetVersion(ctx, moduleDir) + if err != nil { + version = defaultVersion + } + return version } -// GetVersion returns the project version from pom.xml -func (m *Maven) GetVersion(ctx context.Context) (string, error) { - if m.Dir == nil { - return "", fmt.Errorf("cannot get version: maven directory is not set") - } - pomXML, err := m.Dir.File("pom.xml").Contents(ctx) +// readProject reads and unmarshals a pom.xml file into a lightweight pomProject representation. +func (m *Maven) readProject(ctx context.Context, pomFile *dagger.File) (*pomProject, error) { + pomXML, err := pomFile.Contents(ctx) if err != nil { - return "", fmt.Errorf("failed to read pom.xml from directory: %w", err) + return nil, fmt.Errorf("failed to read pom.xml from directory: %w", err) } var project pomProject if err := xml.Unmarshal([]byte(pomXML), &project); err != nil { - return "", fmt.Errorf("failed to parse pom.xml: %w", err) + return nil, fmt.Errorf("failed to parse pom.xml: %w", err) } - if project.Version == "" { - return "", fmt.Errorf("could not find a tag inside the tag in pom.xml") - } - return project.Version, nil + return &project, nil } diff --git a/maven/models.go b/maven/models.go new file mode 100644 index 0000000..80ac584 --- /dev/null +++ b/maven/models.go @@ -0,0 +1,102 @@ +package main + +import ( + "fmt" + + "dagger/maven/internal/dagger" +) + +// ModuleBuildResult represents the aggregated outcome of a Maven module build. +type ModuleBuildResult struct { + Artifacts *dagger.Directory + Container *dagger.Container + ImageUrl string + ExecutedStages []string + Stdout []string + Stderr []string +} + +// StageBuildResult captures the container state, logs, and artifacts produced by a single stage. +type StageBuildResult struct { + Container *dagger.Container + Artifacts *dagger.Directory + Stdout string + Stderr string +} + +// PipelineStage represents a single set of Maven goals executed within the pipeline. +type PipelineStage struct { + DisplayName string + Command []string + Goals []string + Options []string +} + +// DockerBuildConfig contains the information required to execute the Jib Maven plugin and push images. +type DockerBuildConfig struct { + // Remover + Registry string + // Remover + Group string + // Vai conter nome base sem tag: registry/group/name + Image string + // Pode ser um array de string + Tag string + Username string + PasswordSecret *dagger.Secret + Options []string + Labels map[string]string +} + +// SonarConfig stores the data required to invoke SonarQube analysis for a module. +type SonarConfig struct { + Host string + TokenSecret *dagger.Secret + ProjectKey string + WaitForQualityGate bool + ExtraOptions []string +} + +// NewDockerBuildConfig creates a DockerBuildConfig tailored for Maven builds. +func (m *Maven) NewDockerBuildConfig(registry, group, username string, passwordSecret *dagger.Secret, options []string) DockerBuildConfig { + return DockerBuildConfig{ + Registry: registry, + Group: group, + Username: username, + PasswordSecret: passwordSecret, + Options: options, + } +} + +// NewSonarConfig validates Maven Sonar settings and returns a reusable configuration struct. +func (m *Maven) NewSonarConfig(host string, tokenSecret *dagger.Secret, waitForQualityGate bool, extraOptions []string) (*SonarConfig, error) { + if host == "" { + return nil, fmt.Errorf("host is empty") + } + if tokenSecret == nil { + return nil, fmt.Errorf("token secret is empty") + } + return &SonarConfig{ + Host: host, + TokenSecret: tokenSecret, + WaitForQualityGate: waitForQualityGate, + ExtraOptions: extraOptions, + }, nil +} + +func (c *DockerBuildConfig) imageReference(defaultTag string) string { + ref := c.Registry + if c.Group != "" { + ref = fmt.Sprintf("%s/%s", ref, c.Group) + } + if c.Image != "" { + ref = fmt.Sprintf("%s/%s", ref, c.Image) + } + tag := c.Tag + if tag == "" { + tag = defaultTag + } + return fmt.Sprintf("%s:%s", ref, tag) +} + +//type WithContainerFunc func(r *Container) *Container diff --git a/maven/sonar.go b/maven/sonar.go new file mode 100644 index 0000000..900f393 --- /dev/null +++ b/maven/sonar.go @@ -0,0 +1,39 @@ +package main + +import ( + "context" + "fmt" +) + +// buildSonarOptions renders the Maven command-line options required to execute Sonar analysis. +func (m *Maven) buildSonarOptions(ctx context.Context, config *SonarConfig) ([]string, error) { + if config == nil { + return nil, nil + } + + var options []string + + if config.Host != "" { + options = append(options, fmt.Sprintf("-Dsonar.host.url=%s", config.Host)) + } + + token, err := config.TokenSecret.Plaintext(ctx) + if err != nil { + return nil, fmt.Errorf("get sonar token: %w", err) + } + options = append(options, fmt.Sprintf("-Dsonar.token=%s", token)) + + if config.ProjectKey != "" { + options = append(options, fmt.Sprintf("-Dsonar.projectKey=%s", config.ProjectKey)) + } + + if config.WaitForQualityGate { + options = append(options, "-Dsonar.qualitygate.wait=true") + } + + if config.ExtraOptions != nil { + options = append(options, config.ExtraOptions...) + } + + return options, nil +} diff --git a/npm/.gitignore b/npm/.gitignore new file mode 100644 index 0000000..773338b --- /dev/null +++ b/npm/.gitignore @@ -0,0 +1,5 @@ +/dagger.gen.go +/internal/dagger +/internal/querybuilder +/internal/telemetry +/.env diff --git a/npm/dagger.json b/npm/dagger.json index 275ba3c..951088c 100644 --- a/npm/dagger.json +++ b/npm/dagger.json @@ -1,6 +1,6 @@ { "name": "npm", - "engineVersion": "v0.18.9", + "engineVersion": "v0.18.19", "sdk": { "source": "go" } diff --git a/npm/go.mod b/npm/go.mod index 6559efb..62d2935 100644 --- a/npm/go.mod +++ b/npm/go.mod @@ -3,48 +3,48 @@ module dagger/npm go 1.24.3 require ( - github.com/99designs/gqlgen v0.17.73 + github.com/99designs/gqlgen v0.17.79 github.com/Khan/genqlient v0.8.1 - github.com/vektah/gqlparser/v2 v2.5.27 - go.opentelemetry.io/otel v1.34.0 - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 - go.opentelemetry.io/otel/log v0.8.0 - go.opentelemetry.io/otel/metric v1.34.0 - go.opentelemetry.io/otel/sdk v1.34.0 - go.opentelemetry.io/otel/sdk/log v0.8.0 - go.opentelemetry.io/otel/sdk/metric v1.34.0 - go.opentelemetry.io/otel/trace v1.34.0 - go.opentelemetry.io/proto/otlp v1.3.1 - golang.org/x/sync v0.14.0 - google.golang.org/grpc v1.72.1 + github.com/vektah/gqlparser/v2 v2.5.30 + go.opentelemetry.io/otel v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 + go.opentelemetry.io/otel/log v0.14.0 + go.opentelemetry.io/otel/metric v1.38.0 + go.opentelemetry.io/otel/sdk v1.38.0 + go.opentelemetry.io/otel/sdk/log v0.14.0 + go.opentelemetry.io/otel/sdk/metric v1.38.0 + go.opentelemetry.io/otel/trace v1.38.0 + go.opentelemetry.io/proto/otlp v1.8.0 + golang.org/x/sync v0.17.0 + google.golang.org/grpc v1.75.1 ) require ( - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/sosodev/duration v1.3.1 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect - golang.org/x/net v0.40.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.25.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect - google.golang.org/protobuf v1.36.6 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + golang.org/x/net v0.44.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/protobuf v1.36.9 // indirect ) -replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 -replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 -replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.8.0 +replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.14.0 -replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.8.0 +replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.14.0 diff --git a/npm/go.sum b/npm/go.sum index 73a2898..4cf9d6c 100644 --- a/npm/go.sum +++ b/npm/go.sum @@ -1,85 +1,89 @@ -github.com/99designs/gqlgen v0.17.73 h1:A3Ki+rHWqKbAOlg5fxiZBnz6OjW3nwupDHEG15gEsrg= -github.com/99designs/gqlgen v0.17.73/go.mod h1:2RyGWjy2k7W9jxrs8MOQthXGkD3L3oGr0jXW3Pu8lGg= +github.com/99designs/gqlgen v0.17.79 h1:RTsJZtdzcfROeWdt42NGMIabIbiBn69YyVmLEAuxtnA= +github.com/99designs/gqlgen v0.17.79/go.mod h1:vgNcZlLwemsUhYim4dC1pvFP5FX0pr2Y+uYUoHFb1ig= github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs= github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTdwFp0s= -github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/vektah/gqlparser/v2 v2.5.30 h1:EqLwGAFLIzt1wpx1IPpY67DwUujF1OfzgEyDsLrN6kE= +github.com/vektah/gqlparser/v2 v2.5.30/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= -go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= -go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= -go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= +go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= +go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE= +go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= -google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= -google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= -google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/npm/main.go b/npm/main.go index f9abc3c..b5302ea 100644 --- a/npm/main.go +++ b/npm/main.go @@ -9,84 +9,223 @@ import ( const DefaultNpmCacheName = "npm-cache" const DefaultWorkdir = "/app" +const DefaultBuildDir = "dist" var NpmRumCmd = []string{"npm", "run"} +// Npm represents an npm project and provides helpers to run common build steps inside a Dagger container. type Npm struct { - Image string + BuildImage string + RunImage string UseCache bool - Dir *dagger.Directory + Source *dagger.Directory NodeContainer *dagger.Container + // TODO: put BuildResultDir here (example: "dist") } type packageJSON struct { Version string `json:"version"` } +// New constructs an npm helper bound to the provided source directory and runtime configuration. func New( // Directory with node/npm source code source *dagger.Directory, - // Image name for building application + // BuildImage name for building application // +default="node:22.16.0-alpine3.22" buildImage string, + // BuildImage name for building application + // +default="nginx:1.27.3-alpine-slim" + runImage string, // Use Npm Cache // +default=true useCache bool) *Npm { return &Npm{ - Image: buildImage, - UseCache: useCache, - Dir: source, + BuildImage: buildImage, + RunImage: runImage, + UseCache: useCache, + Source: source, } } -func (nc *Npm) WithImagem(image string) *Npm { - nc.Image = image - return nc +// NewContainer creates a fresh container primed with the project source and optional cache. +func (n *Npm) NewContainer() *dagger.Container { + container := dag.Container().From(n.BuildImage).WithWorkdir(DefaultWorkdir) + if n.UseCache { + container = container.WithMountedCache("/root/.npm", dag.CacheVolume(DefaultNpmCacheName)) + } + if n.Source != nil { + container = container.WithDirectory(DefaultWorkdir, n.Source, dagger.ContainerWithDirectoryOpts{Exclude: []string{"node_modules", "dist"}}) + } + return container } -func (nc *Npm) WithUseCache(useCache bool) *Npm { - nc.UseCache = useCache - return nc +// Container returns the memoised container, initialising it if needed. +func (n *Npm) Container() *dagger.Container { + if n.NodeContainer == nil { + n.NodeContainer = n.NewContainer() + } + return n.NodeContainer } -func (nc *Npm) WithDir(dir *dagger.Directory) *Npm { - nc.Dir = dir - return nc +// GetAngularDistDir returns the Angular distribution directory produced by the build. +func (n *Npm) GetAngularDistDir() *dagger.Directory { + return n.Container().Directory(fmt.Sprintf("%s/dist/browser", DefaultWorkdir)) } -func (nc *Npm) NewContainer() *dagger.Container { - container := dag.Container().From(nc.Image).WithWorkdir(DefaultWorkdir) - if nc.UseCache { - container = container.WithMountedCache("/root/.npm", dag.CacheVolume(DefaultNpmCacheName)) +// FullBuild executes a typical npm pipeline: install dependencies, run tests, build assets, optional Sonar analysis, and image metadata preparation. +func (n *Npm) FullBuild(ctx context.Context, sonarConfig *SonarConfig, dockerConfig *DockerBuildConfig) (*BuildResult, error) { + if n.Source == nil { + return nil, fmt.Errorf("source is nil") } - if nc.Dir != nil { - container = container.WithDirectory(DefaultWorkdir, nc.Dir) + + stages := []PipelineStage{ + {DisplayName: "Install Dependencies", Command: []string{"npm", "ci"}}, + {DisplayName: "Build Production Bundle", Command: []string{"npm", "run", "build"}}, } - return container + + if sonarConfig != nil { + sonarOptions, err := n.buildSonarOptions(ctx, sonarConfig) + if err != nil { + return nil, err + } + stages = append(stages, + PipelineStage{ + DisplayName: "Run SonarQube Analysis", + Command: []string{"sonar-scanner"}, + Options: sonarOptions, + Image: sonarConfig.Image, + Owner: "scanner-cli", + }) + } + result, err := n.executeStages(ctx, stages) + if err != nil { + return nil, err + } + + if dockerConfig != nil { + version := n.GetVersionOrDefault(ctx, "latest") + container := result.Container + container = container.From(n.RunImage). + WithLabel("version", version). + WithDirectory("/usr/share/nginx/html/", result.Artifacts). + WithRegistryAuth(dockerConfig.Registry, dockerConfig.Username, dockerConfig.PasswordSecret) + publishedImage, err := container.Publish(ctx, dockerConfig.imageReference(version)) + if err != nil { + return nil, err + } + result.ImageUrl = publishedImage + } + + return result, nil } -func (nc *Npm) Container() *dagger.Container { - if nc.NodeContainer == nil { - nc.NodeContainer = nc.NewContainer() +// executeStages runs each pipeline stage sequentially while collecting logs and artifacts. +func (n *Npm) executeStages(ctx context.Context, stages []PipelineStage) (*BuildResult, error) { + stageContainer := n.Container() + result := &BuildResult{} + for _, stage := range stages { + stageResult, err := n.executeStage(ctx, stageContainer, stage) + if err != nil { + return nil, err + } + stageContainer = stageResult.Container + result.Stdout = append(result.Stdout, stageResult.Stdout) + result.Stderr = append(result.Stderr, stageResult.Stderr) + result.ExecutedStages = append(result.ExecutedStages, stage.DisplayName) + result.Artifacts = stageContainer.Directory(fmt.Sprintf("%s/%s", DefaultWorkdir, DefaultBuildDir)) } - return nc.NodeContainer + result.Container = stageContainer + return result, nil } -func (nc *Npm) NpmRun(args []string) *Npm { - container := nc.Container() - nc.NodeContainer = container.WithExec(append(NpmRumCmd, args...)) - return nc +// executeStage mounts sources, runs the stage command, and captures logs plus dist artifacts. +func (n *Npm) executeStage(ctx context.Context, container *dagger.Container, stage PipelineStage) (*StageResult, error) { + cmd := stage.Command + if len(cmd) == 0 { + cmd = append([]string{"npm"}, stage.Goals...) + } + if len(stage.Options) > 0 { + cmd = append(cmd, stage.Options...) + } + var stageContainer *dagger.Container + var returnContainer *dagger.Container + containerOpts := dagger.ContainerWithDirectoryOpts{Exclude: []string{"node_modules", DefaultBuildDir}} + if stage.Owner != "" { + containerOpts.Owner = stage.Owner + } + // If the PipelineStage has its own image, use it, but, return the original image + if stage.Image != "" { + returnContainer = container + stageContainer = dag.Container().From(stage.Image) + } else { + stageContainer = container + } + stageContainer = stageContainer. + WithDirectory(DefaultWorkdir, n.Source, containerOpts). + WithWorkdir(DefaultWorkdir). + WithExec(cmd) + + stdout, err := stageContainer.Stdout(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get stdout: %w", err) + } + stderr, err := stageContainer.Stderr(ctx) + if err != nil { + stderr = "" + } + + if returnContainer == nil { + returnContainer = stageContainer + } + artifactsDir := stageContainer.Directory(fmt.Sprintf("%s/", DefaultWorkdir, DefaultBuildDir)) + return &StageResult{ + Container: returnContainer, + Stdout: stdout, + Stderr: stderr, + Artifacts: artifactsDir, + }, nil } -func (nc *Npm) GetAngularDistDir() *dagger.Directory { - return nc.Container().Directory(fmt.Sprintf("%s/dist/browser", DefaultWorkdir)) +// buildSonarOptions converts the Sonar configuration into CLI arguments for the scanner. +func (n *Npm) buildSonarOptions(ctx context.Context, config *SonarConfig) ([]string, error) { + // TODO see: sonar.projectName and sonar.projectVersion + if config == nil { + return nil, nil + } + + var options []string + + if config.Host != "" { + options = append(options, fmt.Sprintf("-Dsonar.host.url=%s", config.Host)) + } + + token, err := config.TokenSecret.Plaintext(ctx) + if err != nil { + return nil, fmt.Errorf("get sonar token: %w", err) + } + options = append(options, fmt.Sprintf("-Dsonar.token=%s", token)) + + if config.ProjectKey != "" { + options = append(options, fmt.Sprintf("-Dsonar.projectKey=%s", config.ProjectKey)) + } + + if config.WaitForQualityGate { + options = append(options, "-Dsonar.qualitygate.wait=true") + } + + if config.ExtraOptions != nil { + options = append(options, config.ExtraOptions...) + } + + return options, nil } func (n *Npm) GetVersion(ctx context.Context) (string, error) { - if n.Dir == nil { + if n.Source == nil { return "", fmt.Errorf("cannot get NPM version: npm directory is not set") } - pkgJSON, err := n.Dir.File("package.json").Contents(ctx) + pkgJSON, err := n.Source.File("package.json").Contents(ctx) if err != nil { return "", fmt.Errorf("failed to read package.json from directory: %w", err) } @@ -99,3 +238,11 @@ func (n *Npm) GetVersion(ctx context.Context) (string, error) { } return pkg.Version, nil } + +func (n *Npm) GetVersionOrDefault(ctx context.Context, defaultVersion string) string { + version, err := n.GetVersion(ctx) + if err != nil { + version = defaultVersion + } + return version +} diff --git a/npm/types.go b/npm/types.go new file mode 100644 index 0000000..aeb432f --- /dev/null +++ b/npm/types.go @@ -0,0 +1,108 @@ +package main + +import ( + "dagger/npm/internal/dagger" + "fmt" +) + +// BuildResult aggregates the outcome of the npm pipeline for a single project. +type BuildResult struct { + Artifacts *dagger.Directory + Container *dagger.Container + ImageUrl string + ExecutedStages []string + Stdout []string + Stderr []string +} + +// StageResult captures the intermediate state produced by a single pipeline stage. +type StageResult struct { + Container *dagger.Container + Artifacts *dagger.Directory + Stdout string + Stderr string +} + +// PipelineStage describes a command (or legacy goals/options) executed in sequence. +type PipelineStage struct { + DisplayName string + Command []string + Owner string + Goals []string + Options []string + Image string +} + +// DockerBuildConfig stores the data needed to construct an image reference and authenticate pushes. +type DockerBuildConfig struct { + Registry string + Group string + Image string + Tag string + Username string + PasswordSecret *dagger.Secret + Options []string +} + +// SonarConfig encapsulates the parameters required to run SonarQube analysis from npm. +type SonarConfig struct { + Host string + TokenSecret *dagger.Secret + ProjectKey string + WaitForQualityGate bool + ExtraOptions []string + Image string +} + +// NewDockerBuildConfig returns a DockerBuildConfig initialised with registry metadata and credentials. +func (n *Npm) NewDockerBuildConfig(registry string, group string, image string, username string, passwordSecret *dagger.Secret) DockerBuildConfig { + return DockerBuildConfig{ + Registry: registry, + Group: group, + Image: image, + Username: username, + PasswordSecret: passwordSecret, + } +} + +// NewSonarConfig validates the provided inputs and returns an npm-specific Sonar configuration. +func (n *Npm) NewSonarConfig(host string, + tokenSecret *dagger.Secret, + projectKey string, + // +default=true + waitForQualityGate bool, + // +optional + extraOptions []string, + // +default="sonarsource/sonar-scanner-cli:11.4.0.2044_7.2.0" + image string) (*SonarConfig, error) { + if host == "" { + return nil, fmt.Errorf("host is empty") + } + if tokenSecret == nil { + return nil, fmt.Errorf("token secret is empty") + } + return &SonarConfig{ + Host: host, + TokenSecret: tokenSecret, + ProjectKey: projectKey, + WaitForQualityGate: waitForQualityGate, + ExtraOptions: extraOptions, + Image: image, + }, nil +} + +// imageReference formats a fully-qualified image reference, defaulting the tag when absent. +func (c *DockerBuildConfig) imageReference(defaultTag string) string { + ref := c.Registry + if c.Group != "" { + ref = fmt.Sprintf("%s/%s", ref, c.Group) + } + if c.Image != "" { + ref = fmt.Sprintf("%s/%s", ref, c.Image) + } + tag := c.Tag + if tag == "" { + tag = defaultTag + } + return fmt.Sprintf("%s:%s", ref, tag) +} diff --git a/uv/.gitattributes b/uv/.gitattributes new file mode 100644 index 0000000..3a45493 --- /dev/null +++ b/uv/.gitattributes @@ -0,0 +1,4 @@ +/dagger.gen.go linguist-generated +/internal/dagger/** linguist-generated +/internal/querybuilder/** linguist-generated +/internal/telemetry/** linguist-generated diff --git a/uv/.gitignore b/uv/.gitignore new file mode 100644 index 0000000..773338b --- /dev/null +++ b/uv/.gitignore @@ -0,0 +1,5 @@ +/dagger.gen.go +/internal/dagger +/internal/querybuilder +/internal/telemetry +/.env diff --git a/uv/LICENSE b/uv/LICENSE new file mode 100644 index 0000000..137069b --- /dev/null +++ b/uv/LICENSE @@ -0,0 +1,73 @@ +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/uv/dagger.json b/uv/dagger.json new file mode 100644 index 0000000..123e136 --- /dev/null +++ b/uv/dagger.json @@ -0,0 +1,7 @@ +{ + "name": "uv", + "engineVersion": "v0.19.7", + "sdk": { + "source": "go" + } +} diff --git a/uv/go.mod b/uv/go.mod new file mode 100644 index 0000000..1b52d4e --- /dev/null +++ b/uv/go.mod @@ -0,0 +1,51 @@ +module dagger/uv + +go 1.25.1 + +require ( + github.com/99designs/gqlgen v0.17.81 + github.com/Khan/genqlient v0.8.1 + github.com/pelletier/go-toml/v2 v2.2.4 + github.com/vektah/gqlparser/v2 v2.5.30 + go.opentelemetry.io/otel v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 + go.opentelemetry.io/otel/log v0.14.0 + go.opentelemetry.io/otel/metric v1.38.0 + go.opentelemetry.io/otel/sdk v1.38.0 + go.opentelemetry.io/otel/sdk/log v0.14.0 + go.opentelemetry.io/otel/sdk/metric v1.38.0 + go.opentelemetry.io/otel/trace v1.38.0 + go.opentelemetry.io/proto/otlp v1.8.0 + golang.org/x/sync v0.17.0 + google.golang.org/grpc v1.76.0 +) + +require ( + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect + github.com/sosodev/duration v1.3.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + golang.org/x/net v0.44.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/protobuf v1.36.9 // indirect +) + +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 + +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 + +replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.14.0 + +replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.14.0 diff --git a/uv/go.sum b/uv/go.sum new file mode 100644 index 0000000..369b4ff --- /dev/null +++ b/uv/go.sum @@ -0,0 +1,91 @@ +github.com/99designs/gqlgen v0.17.81 h1:kCkN/xVyRb5rEQpuwOHRTYq83i0IuTQg9vdIiwEerTs= +github.com/99designs/gqlgen v0.17.81/go.mod h1:vgNcZlLwemsUhYim4dC1pvFP5FX0pr2Y+uYUoHFb1ig= +github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs= +github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= +github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/vektah/gqlparser/v2 v2.5.30 h1:EqLwGAFLIzt1wpx1IPpY67DwUujF1OfzgEyDsLrN6kE= +github.com/vektah/gqlparser/v2 v2.5.30/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= +go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= +go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE= +go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/uv/main.go b/uv/main.go new file mode 100644 index 0000000..1164975 --- /dev/null +++ b/uv/main.go @@ -0,0 +1,234 @@ +package main + +import ( + "context" + "dagger/uv/internal/dagger" + "fmt" + + "github.com/pelletier/go-toml/v2" +) + +type PyProject struct { + Project struct { + Version string `toml:"version"` + Name string `toml:"name"` + } `toml:"project"` +} + +type Uv struct { + BuildImage string + RunImage string + UseCache bool + Source *dagger.Directory + RunSubdir string + // Customizations list of Build Container customizations that will be executed. Valid values: "dbt" and "dlt" + Customizations []string + buildContainer *dagger.Container +} + +func New( + source *dagger.Directory, +// +default="ghcr.io/astral-sh/uv:0.8.19-python3.12-bookworm-slim" + buildImage string, +// +default="python:3.12-slim-bookworm" + runImage string, +// +optional + runSubdir string, +// +default=true + useCache bool) *Uv { + return &Uv{ + BuildImage: buildImage, + RunImage: runImage, + RunSubdir: runSubdir, + UseCache: useCache, + Source: source, + } +} + +// func (n *Npm) FullBuild(ctx context.Context, sonarConfig *SonarConfig, dockerConfig *DockerBuildConfig) (*BuildResult, error) { + +const ( + DefaultUvCacheName = "uv-cache" + DefaultUvCachePath = "/root/.cache/uv" + WorkDir = "/app" +) + +func (u *Uv) FullBuild( + ctx context.Context, + sonarConfig *SonarConfig, + dockerConfig *DockerBuildConfig) (*BuildResult, error) { + + buildResult := BuildResult{} + + pyProject, err := GetPythonVersion(ctx, u.Source) + if err != nil { + return nil, err + } + builder := u.BuildContainer() + buildDirectory := builder.Directory(WorkDir) + buildResult.Artifacts = buildDirectory + path := fmt.Sprintf("%s/%s", WorkDir, u.RunSubdir) + appContainer := dag.Container(). + From(u.RunImage). + WithDirectory(WorkDir, buildDirectory). + WithWorkdir(path). + WithEnvVariable("PATH", "/app/.venv/bin:$PATH", dagger.ContainerWithEnvVariableOpts{Expand: true}). + WithEntrypoint([]string{"python", fmt.Sprintf("%s/%s", path, "main.py")}) + if sonarConfig != nil { + result, err := u.runSonarAnalysis(ctx, sonarConfig, buildDirectory, buildResult) + if err != nil { + return result, err + } + } + + if dockerConfig != nil { + dockerConfig.Tag = pyProject.Project.Version + result, err := u.publishDockerImage(ctx, dockerConfig, appContainer, buildResult) + if err != nil { + return result, err + } + } + + return &buildResult, nil +} + +func (u *Uv) publishDockerImage( + ctx context.Context, + dockerConfig *DockerBuildConfig, + appContainer *dagger.Container, + buildResult BuildResult) (*BuildResult, error) { + publishedImage, err := appContainer. + WithRegistryAuth(dockerConfig.Registry, dockerConfig.Username, dockerConfig.PasswordSecret). + Publish(ctx, dockerConfig.imageReference("latest")) + if err != nil { + return nil, err + } + buildResult.ImageUrl = publishedImage + return nil, nil +} + +func (u *Uv) runSonarAnalysis(ctx context.Context, sonarConfig *SonarConfig, buildDirectory *dagger.Directory, buildResult BuildResult) (*BuildResult, error) { + sonarContainer, err := u.createSonarContainer(ctx, sonarConfig, buildDirectory) + if err != nil { + fmt.Println("Failed to create sonar container") + } else { + stdout, err := sonarContainer.Stdout(ctx) + if err != nil { + return nil, err + } + buildResult.Stdout = append(buildResult.Stdout, stdout) + stderr, _ := sonarContainer.Stderr(ctx) + buildResult.Stderr = append(buildResult.Stderr, stderr) + } + return nil, nil +} + +func (u *Uv) BuildContainer() *dagger.Container { + if u.buildContainer == nil { + u.buildContainer = u.NewContainer() + } + return u.buildContainer +} + +func (u *Uv) NewContainer() *dagger.Container { + buildContainer := u.buildContainer + if buildContainer == nil { + buildContainer = dag.Container().From(u.BuildImage). + WithWorkdir(WorkDir). + WithEnvVariable("UV_COMPILE_BYTECODE", "1"). + WithEnvVariable("UV_LINK_MODE", "copy"). + WithEnvVariable("PYTHONUNBUFFERED", "1"). + WithEnvVariable("UV_PYTHON_DOWNLOADS", "0"). + WithFiles(WorkDir, []*dagger.File{u.Source.File("uv.lock"), u.Source.File("pyproject.toml")}). + WithExec([]string{"uv", "sync", "--frozen", "--no-install-project", "--no-dev"}). + WithDirectory(WorkDir, u.Source) + for _, name := range u.Customizations { + fn, ok := buildCustomizations[name] + if ok { + buildContainer = buildContainer.With(fn) + } + } + buildContainer = buildContainer.WithExec([]string{"uv", "sync", "--no-dev"}) + if u.UseCache { + buildContainer = buildContainer.WithMountedCache(DefaultUvCachePath, dag.CacheVolume(DefaultUvCacheName)) + } + } + return buildContainer +} + +func (u Uv) createSonarContainer( + context context.Context, + config *SonarConfig, + source *dagger.Directory) (*dagger.Container, error) { + sonarContainer := dag.Container(). + From(config.AnalysisImage). + WithDirectory(config.WorkDir, source, dagger.ContainerWithDirectoryOpts{Owner: "scanner-cli"}). + WithWorkdir(config.WorkDir) + if config.UseCache { + sonarContainer = sonarContainer.WithMountedCache( + "/opt/sonar-scanner/.sonar/cache", + dag.CacheVolume(config.CacheKey), + dagger.ContainerWithMountedCacheOpts{Owner: "scanner-cli"}) + } + sonarCommand, err := u.buildSonarCommand(context, config) + if err != nil { + return nil, err + } + return sonarContainer.WithExec(sonarCommand), nil +} + +func GetPythonVersion(ctx context.Context, source *dagger.Directory) (*PyProject, error) { + pyproject := source.File("pyproject.toml") + if pyproject == nil { + pathname, _ := source.Name(ctx) + return nil, fmt.Errorf("pyproject.toml not found in %s", pathname) + } + data, _ := pyproject.Contents(ctx) + var cfg PyProject + if err := toml.Unmarshal([]byte(data), &cfg); err != nil { + panic(err) + } + return &cfg, nil +} + +func (u *Uv) buildSonarCommand(ctx context.Context, config *SonarConfig) ([]string, error) { + options, err := u.buildSonarOptions(ctx, config) + if err != nil { + return nil, err + } + command := []string{"sonar-scanner"} + return append(command, options...), nil +} + +// buildSonarOptions converts the Sonar configuration into CLI arguments for the scanner. +func (u *Uv) buildSonarOptions(ctx context.Context, config *SonarConfig) ([]string, error) { + if config == nil { + return nil, fmt.Errorf("sonar configuration is nil") + } + + var options []string + + if config.Host != "" { + options = append(options, fmt.Sprintf("-Dsonar.host.url=%s", config.Host)) + } + + token, err := config.TokenSecret.Plaintext(ctx) + if err != nil { + return nil, fmt.Errorf("get sonar token: %w", err) + } + options = append(options, fmt.Sprintf("-Dsonar.token=%s", token)) + + if config.ProjectKey != "" { + options = append(options, fmt.Sprintf("-Dsonar.projectKey=%s", config.ProjectKey)) + } + + if config.WaitForQualityGate { + options = append(options, "-Dsonar.qualitygate.wait=true") + } + + if config.ExtraOptions != nil { + options = append(options, config.ExtraOptions...) + } + + return options, nil +} diff --git a/uv/types.go b/uv/types.go new file mode 100644 index 0000000..47e4950 --- /dev/null +++ b/uv/types.go @@ -0,0 +1,126 @@ +package main + +import ( + "dagger/uv/internal/dagger" + "fmt" +) + +// BuildResult aggregates the outcome of the npm pipeline for a single project. +type BuildResult struct { + Artifacts *dagger.Directory + Container *dagger.Container + ImageUrl string + ExecutedStages []string + Stdout []string + Stderr []string +} + +// StageResult captures the intermediate state produced by a single pipeline stage. +type StageResult struct { + Container *dagger.Container + Artifacts *dagger.Directory + Stdout string + Stderr string +} + +// PipelineStage describes a command (or legacy goals/options) executed in sequence. +type PipelineStage struct { + DisplayName string + Command []string + Goals []string + Options []string +} + +// DockerBuildConfig stores the data needed to construct an image reference and authenticate pushes. +type DockerBuildConfig struct { + Registry string + Group string + Image string + Tag string + Username string + PasswordSecret *dagger.Secret + Options []string +} + +// SonarConfig encapsulates the parameters required to run SonarQube analysis from npm. +type SonarConfig struct { + AnalysisImage string + Host string + TokenSecret *dagger.Secret + ProjectKey string + CacheKey string + UseCache bool + WaitForQualityGate bool + WorkDir string + ExtraOptions []string +} + +// NewDockerBuildConfig returns a DockerBuildConfig initialised with registry metadata and credentials. +func (u *Uv) NewDockerBuildConfig(registry string, group string, image string, username string, passwordSecret *dagger.Secret) DockerBuildConfig { + return DockerBuildConfig{ + Registry: registry, + Group: group, + Image: image, + Username: username, + PasswordSecret: passwordSecret, + } +} + +// NewSonarConfig validates the provided inputs and returns an npm-specific Sonar configuration. +func (u *Uv) NewSonarConfig(host string, + tokenSecret *dagger.Secret, + projectKey string, + // +default="sonarsource/sonar-scanner-cli:11.4.0.2044_7.2.0" + analysisImage string, + // +default="SONARQUBE-CACHE" + cacheKey string, + // +default=true + useCache bool, + // +default=true + waitForQualityGate bool, + // +default="/usr/src" + workDir string, + // +optional + extraOptions []string) (*SonarConfig, error) { + if host == "" { + return nil, fmt.Errorf("host is empty") + } + if tokenSecret == nil { + return nil, fmt.Errorf("token secret is empty") + } + return &SonarConfig{ + Host: host, + TokenSecret: tokenSecret, + AnalysisImage: analysisImage, + ProjectKey: projectKey, + CacheKey: cacheKey, + UseCache: useCache, + WaitForQualityGate: waitForQualityGate, + ExtraOptions: extraOptions, + }, nil +} + +// imageReference formats a fully-qualified image reference, defaulting the tag when absent. +func (c *DockerBuildConfig) imageReference(defaultTag string) string { + ref := c.Registry + if c.Group != "" { + ref = fmt.Sprintf("%s/%s", ref, c.Group) + } + if c.Image != "" { + ref = fmt.Sprintf("%s/%s", ref, c.Image) + } + tag := c.Tag + if tag == "" { + tag = defaultTag + } + return fmt.Sprintf("%s:%s", ref, tag) +} + +var buildCustomizations = map[string]dagger.WithContainerFunc{ + "dlt": func(c *dagger.Container) *dagger.Container { + return c.WithoutDirectory(".dlt") + }, + "dbt": func(c *dagger.Container) *dagger.Container { + return c.WithoutDirectory(".env") + }, +}