Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs
This commit is contained in:
@@ -137,17 +137,17 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
|
||||
golang.org/x/mod v0.26.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/term v0.33.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/term v0.34.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.35.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
|
||||
|
||||
+14
-14
@@ -403,8 +403,8 @@ go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
|
||||
@@ -414,8 +414,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -425,8 +425,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
@@ -451,14 +451,14 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -472,8 +472,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -43,11 +43,11 @@ require (
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/grpc v1.74.2 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
|
||||
@@ -96,8 +96,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
@@ -110,8 +110,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -129,8 +129,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -142,8 +142,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
|
||||
@@ -82,15 +82,15 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/term v0.33.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/term v0.34.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.35.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
|
||||
|
||||
+12
-12
@@ -240,8 +240,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
@@ -264,8 +264,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
@@ -291,23 +291,23 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -322,8 +322,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
package build
|
||||
@@ -1,127 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/config"
|
||||
"github.com/grafana/grafana/pkg/build/docker"
|
||||
"github.com/grafana/grafana/pkg/build/gcloud"
|
||||
)
|
||||
|
||||
const (
|
||||
alpine = "alpine"
|
||||
ubuntu = "ubuntu"
|
||||
)
|
||||
|
||||
// GetImageFiles returns the list of image (.img, but should be .tar because they are tar archives) files that are
|
||||
// created in the 'tag' process and stored in the prerelease bucket, waiting to be released.
|
||||
func GetImageFiles(grafana string, version string, architectures []config.Architecture) []string {
|
||||
bases := []string{alpine, ubuntu}
|
||||
images := []string{}
|
||||
for _, base := range bases {
|
||||
for _, arch := range architectures {
|
||||
image := fmt.Sprintf("%s-%s-%s.img", grafana, version, arch)
|
||||
if base == "ubuntu" {
|
||||
image = fmt.Sprintf("%s-%s-ubuntu-%s.img", grafana, version, arch)
|
||||
}
|
||||
|
||||
images = append(images, image)
|
||||
}
|
||||
}
|
||||
|
||||
return images
|
||||
}
|
||||
|
||||
func FetchImages(c *cli.Context) error {
|
||||
if c.NArg() > 0 {
|
||||
if err := cli.ShowSubcommandHelp(c); err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
return cli.Exit("", 1)
|
||||
}
|
||||
|
||||
metadata, err := config.GenerateMetadata(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buildConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg := docker.Config{
|
||||
Archs: buildConfig.Docker.Architectures,
|
||||
Distribution: buildConfig.Docker.Distribution,
|
||||
Bucket: buildConfig.Docker.PrereleaseBucket,
|
||||
Edition: c.String("edition"),
|
||||
Tag: metadata.GrafanaVersion,
|
||||
}
|
||||
|
||||
grafana := "grafana"
|
||||
if cfg.Edition == "enterprise" {
|
||||
grafana = "grafana-enterprise"
|
||||
}
|
||||
if cfg.Edition == "enterprise2" {
|
||||
grafana = "grafana-enterprise2"
|
||||
}
|
||||
if cfg.Edition == "grafana" || cfg.Edition == "oss" {
|
||||
grafana = "grafana-oss"
|
||||
}
|
||||
|
||||
baseURL := fmt.Sprintf("gs://%s/%s/", cfg.Bucket, cfg.Tag)
|
||||
images := GetImageFiles(grafana, cfg.Tag, cfg.Archs)
|
||||
|
||||
log.Printf("Fetching images [%v]", images)
|
||||
|
||||
if err := gcloud.ActivateServiceAccount(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := DownloadImages(baseURL, images, "."); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := LoadImages(images, "."); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadImages uses the `docker load -i` command to load the image tar file into the docker daemon so that it can be
|
||||
// tagged and pushed.
|
||||
func LoadImages(images []string, source string) error {
|
||||
p := filepath.Clean(source)
|
||||
for _, image := range images {
|
||||
image := filepath.Join(p, image)
|
||||
log.Println("Loading image", image)
|
||||
//nolint:gosec
|
||||
cmd := exec.Command("docker", "load", "-i", image)
|
||||
cmd.Dir = "."
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("out: %s\n", out)
|
||||
return fmt.Errorf("error loading image: %q", err)
|
||||
}
|
||||
log.Println("Loaded image", image)
|
||||
}
|
||||
log.Println("Images successfully loaded!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func DownloadImages(baseURL string, images []string, destination string) error {
|
||||
for _, image := range images {
|
||||
p := baseURL + image
|
||||
log.Println("Downloading image", p)
|
||||
//nolint:gosec
|
||||
cmd := exec.Command("gsutil", "-m", "cp", "-r", p, destination)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download: %w\n%s", err, out)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/config"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetImageFiles(t *testing.T) {
|
||||
var (
|
||||
architectures = []config.Architecture{
|
||||
config.ArchAMD64,
|
||||
config.ArchARM64,
|
||||
config.ArchARMv7,
|
||||
}
|
||||
)
|
||||
|
||||
t.Run("1.2.3", func(t *testing.T) {
|
||||
expect := []string{
|
||||
"grafana-oss-1.2.3-amd64.img",
|
||||
"grafana-oss-1.2.3-arm64.img",
|
||||
"grafana-oss-1.2.3-armv7.img",
|
||||
"grafana-oss-1.2.3-ubuntu-amd64.img",
|
||||
"grafana-oss-1.2.3-ubuntu-arm64.img",
|
||||
"grafana-oss-1.2.3-ubuntu-armv7.img",
|
||||
}
|
||||
|
||||
res := GetImageFiles("grafana-oss", "1.2.3", architectures)
|
||||
|
||||
require.Equal(t, expect, res)
|
||||
})
|
||||
|
||||
t.Run("1.2.3+example-01", func(t *testing.T) {
|
||||
expect := []string{
|
||||
"grafana-oss-1.2.3+example-01-amd64.img",
|
||||
"grafana-oss-1.2.3+example-01-arm64.img",
|
||||
"grafana-oss-1.2.3+example-01-armv7.img",
|
||||
"grafana-oss-1.2.3+example-01-ubuntu-amd64.img",
|
||||
"grafana-oss-1.2.3+example-01-ubuntu-arm64.img",
|
||||
"grafana-oss-1.2.3+example-01-ubuntu-armv7.img",
|
||||
}
|
||||
|
||||
res := GetImageFiles("grafana-oss", "1.2.3+example-01", architectures)
|
||||
|
||||
require.Equal(t, expect, res)
|
||||
})
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package main
|
||||
|
||||
import "github.com/urfave/cli/v2"
|
||||
|
||||
var (
|
||||
jobsFlag = cli.IntFlag{
|
||||
Name: "jobs",
|
||||
Usage: "Number of parallel jobs",
|
||||
}
|
||||
buildIDFlag = cli.StringFlag{
|
||||
Name: "build-id",
|
||||
Usage: "Optionally supply a build ID to be part of the version",
|
||||
}
|
||||
editionFlag = cli.StringFlag{
|
||||
Name: "edition",
|
||||
Usage: "The edition of Grafana to build (oss or enterprise)",
|
||||
Value: "oss",
|
||||
}
|
||||
triesFlag = cli.IntFlag{
|
||||
Name: "tries",
|
||||
Usage: "Specify number of tries before failing",
|
||||
Value: 1,
|
||||
}
|
||||
tagFlag = cli.StringFlag{
|
||||
Name: "tag",
|
||||
Usage: "Grafana version tag",
|
||||
}
|
||||
securityFlag = cli.BoolFlag{
|
||||
Name: "security",
|
||||
Usage: "Security release",
|
||||
}
|
||||
srcFlag = cli.StringFlag{
|
||||
Name: "src-bucket",
|
||||
Value: "grafana-prerelease",
|
||||
Usage: "Google Cloud Storage bucket",
|
||||
}
|
||||
securityDestBucketFlag = cli.StringFlag{
|
||||
Name: "security-dest-bucket",
|
||||
Usage: "Google Cloud Storage bucket for security packages (or $SECURITY_DEST_BUCKET)",
|
||||
}
|
||||
destFlag = cli.StringFlag{
|
||||
Name: "dest-bucket",
|
||||
Value: "grafana-downloads",
|
||||
Usage: "Google Cloud Storage bucket for published packages",
|
||||
}
|
||||
)
|
||||
@@ -1,406 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/config"
|
||||
"github.com/grafana/grafana/pkg/build/gcloud"
|
||||
"github.com/grafana/grafana/pkg/build/gcloud/storage"
|
||||
"github.com/grafana/grafana/pkg/build/gcom"
|
||||
"github.com/grafana/grafana/pkg/build/packaging"
|
||||
"github.com/grafana/grafana/pkg/build/versions"
|
||||
)
|
||||
|
||||
const grafanaAPI = "https://grafana.com/api"
|
||||
|
||||
var httpClient = http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: func(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) {
|
||||
return dialer.DialContext
|
||||
}(&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}),
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
// GrafanaCom implements the sub-command "grafana-com".
|
||||
func GrafanaCom(c *cli.Context) error {
|
||||
bucketStr := c.String("src-bucket")
|
||||
edition := config.Edition(c.String("edition"))
|
||||
|
||||
if err := gcloud.ActivateServiceAccount(); err != nil {
|
||||
return fmt.Errorf("couldn't activate service account, err: %w", err)
|
||||
}
|
||||
|
||||
metadata, err := config.GenerateMetadata(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
releaseMode, err := metadata.GetReleaseMode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
version := metadata.GrafanaVersion
|
||||
semver := versions.ParseSemver(version)
|
||||
if releaseMode.Mode == config.Cronjob {
|
||||
gcs, err := storage.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bucket := gcs.Bucket(bucketStr)
|
||||
latestMainVersion, err := storage.GetLatestMainBuild(c.Context, bucket, filepath.Join(string(edition), "main"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
version = latestMainVersion
|
||||
}
|
||||
|
||||
dryRun := c.Bool("dry-run")
|
||||
simulateRelease := c.Bool("simulate-release")
|
||||
// Test release mode and dryRun imply simulateRelease
|
||||
if releaseMode.IsTest || dryRun {
|
||||
simulateRelease = true
|
||||
}
|
||||
|
||||
grafanaAPIKey := strings.TrimSpace(os.Getenv("GRAFANA_COM_API_KEY"))
|
||||
if grafanaAPIKey == "" {
|
||||
return cli.Exit("the environment variable GRAFANA_COM_API_KEY must be set", 1)
|
||||
}
|
||||
|
||||
pkgjson, err := getPackageJSON()
|
||||
if err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
|
||||
whatsNewURL, releaseNotesURL, err := getReleaseURLs(semver, pkgjson)
|
||||
if err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
|
||||
// TODO: Verify config values
|
||||
cfg := packaging.PublishConfig{
|
||||
Config: config.Config{
|
||||
Version: version,
|
||||
},
|
||||
Edition: edition,
|
||||
ReleaseMode: releaseMode,
|
||||
GrafanaAPIKey: grafanaAPIKey,
|
||||
WhatsNewURL: whatsNewURL,
|
||||
ReleaseNotesURL: releaseNotesURL,
|
||||
DryRun: dryRun,
|
||||
TTL: c.String("ttl"),
|
||||
SimulateRelease: simulateRelease,
|
||||
}
|
||||
|
||||
if err := publishPackages(cfg); err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
|
||||
log.Println("Successfully published packages to grafana.com!")
|
||||
return nil
|
||||
}
|
||||
|
||||
type grafanaConf struct {
|
||||
WhatsNewURL string `json:"whatsNewUrl"`
|
||||
ReleaseNotesURL string `json:"releaseNotesUrl"`
|
||||
}
|
||||
|
||||
type packageConf struct {
|
||||
Grafana grafanaConf `json:"grafana"`
|
||||
}
|
||||
|
||||
func getPackageJSON() (*packageConf, error) {
|
||||
pkgB, err := os.ReadFile("package.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read package.json: %w", err)
|
||||
}
|
||||
|
||||
var pconf packageConf
|
||||
if err := json.Unmarshal(pkgB, &pconf); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode package.json: %w", err)
|
||||
}
|
||||
|
||||
return &pconf, nil
|
||||
}
|
||||
|
||||
func getReleaseURLs(semver versions.Semver, pconf *packageConf) (string, string, error) {
|
||||
u := fmt.Sprintf(pconf.Grafana.WhatsNewURL, semver.Major, semver.Minor, semver.Patch)
|
||||
if _, err := url.ParseRequestURI(u); err != nil {
|
||||
return "", "", fmt.Errorf("grafana.whatsNewUrl is invalid in package.json: %q", pconf.Grafana.WhatsNewURL)
|
||||
}
|
||||
if _, err := url.ParseRequestURI(pconf.Grafana.ReleaseNotesURL); err != nil {
|
||||
return "", "", fmt.Errorf("grafana.releaseNotesUrl is invalid in package.json: %q",
|
||||
pconf.Grafana.ReleaseNotesURL)
|
||||
}
|
||||
|
||||
return u, pconf.Grafana.ReleaseNotesURL, nil
|
||||
}
|
||||
|
||||
func Builds(baseURL *url.URL, grafana, version string, packages []packaging.BuildArtifact) ([]GCOMPackage, error) {
|
||||
builds := make([]GCOMPackage, len(packages))
|
||||
for i, v := range packages {
|
||||
var (
|
||||
os = v.Distro
|
||||
arch = v.Arch
|
||||
)
|
||||
|
||||
if v.Distro == "windows" {
|
||||
os = "win"
|
||||
if v.Ext == "msi" {
|
||||
os = "win-installer"
|
||||
}
|
||||
}
|
||||
|
||||
if v.Distro == "rhel" {
|
||||
if arch == "aarch64" {
|
||||
arch = "arm64"
|
||||
}
|
||||
if arch == "x86_64" {
|
||||
arch = "amd64"
|
||||
}
|
||||
}
|
||||
|
||||
if v.Distro == "deb" {
|
||||
if arch == "armhf" {
|
||||
arch = "armv7"
|
||||
if v.RaspberryPi {
|
||||
log.Println(v.Distro, arch, "raspberrypi == true")
|
||||
arch = "armv6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u := gcom.GetURL(baseURL, version, grafana, v.Distro, v.Arch, v.Ext, v.Musl, v.RaspberryPi)
|
||||
builds[i] = GCOMPackage{
|
||||
OS: os,
|
||||
URL: u.String(),
|
||||
Arch: arch,
|
||||
}
|
||||
}
|
||||
|
||||
return builds, nil
|
||||
}
|
||||
|
||||
// publishPackages publishes packages to grafana.com.
|
||||
func publishPackages(cfg packaging.PublishConfig) error {
|
||||
log.Printf("Publishing Grafana packages, version %s, %s edition, %s mode, dryRun: %v, simulating: %v...\n",
|
||||
cfg.Version, cfg.Edition, cfg.ReleaseMode.Mode, cfg.DryRun, cfg.SimulateRelease)
|
||||
|
||||
versionStr := fmt.Sprintf("v%s", cfg.Version)
|
||||
log.Printf("Creating release %s at grafana.com...\n", versionStr)
|
||||
|
||||
var (
|
||||
pth string
|
||||
grafana = "grafana"
|
||||
)
|
||||
|
||||
switch cfg.Edition {
|
||||
case config.EditionOSS:
|
||||
pth = "oss"
|
||||
case config.EditionEnterprise:
|
||||
grafana = "grafana-enterprise"
|
||||
pth = "enterprise"
|
||||
default:
|
||||
return fmt.Errorf("unrecognized edition %q", cfg.Edition)
|
||||
}
|
||||
|
||||
switch cfg.ReleaseMode.Mode {
|
||||
case config.MainMode, config.DownstreamMode, config.CronjobMode:
|
||||
pth = path.Join(pth, packaging.MainFolder)
|
||||
default:
|
||||
pth = path.Join(pth, packaging.ReleaseFolder)
|
||||
}
|
||||
|
||||
pth = path.Join(pth)
|
||||
baseArchiveURL := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "dl.grafana.com",
|
||||
Path: pth,
|
||||
}
|
||||
|
||||
builds, err := Builds(baseArchiveURL, grafana, cfg.Version, packaging.ArtifactConfigs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := Release{
|
||||
Version: cfg.Version,
|
||||
ReleaseDate: time.Now().UTC(),
|
||||
Builds: builds,
|
||||
Stable: cfg.ReleaseMode.Mode == config.TagMode && !cfg.ReleaseMode.IsPreview && !cfg.ReleaseMode.IsTest,
|
||||
Beta: cfg.ReleaseMode.IsPreview,
|
||||
Nightly: cfg.ReleaseMode.Mode == config.CronjobMode,
|
||||
}
|
||||
if cfg.ReleaseMode.Mode == config.TagMode || r.Beta {
|
||||
r.WhatsNewURL = cfg.WhatsNewURL
|
||||
r.ReleaseNotesURL = cfg.ReleaseNotesURL
|
||||
}
|
||||
|
||||
if err := postRequest(cfg, "versions", r, fmt.Sprintf("create release %s", r.Version)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := postRequest(cfg, fmt.Sprintf("versions/%s", cfg.Version), r,
|
||||
fmt.Sprintf("update release %s", cfg.Version)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, v := range r.Builds {
|
||||
sha, err := getSHA256(v.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Builds[i].SHA256 = string(sha)
|
||||
}
|
||||
|
||||
for _, b := range r.Builds {
|
||||
if err := postRequest(cfg, fmt.Sprintf("versions/%s/packages", cfg.Version), b,
|
||||
fmt.Sprintf("create build %s %s", b.OS, b.Arch)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := postRequest(cfg, fmt.Sprintf("versions/%s/packages/%s/%s", cfg.Version, b.Arch, b.OS), b,
|
||||
fmt.Sprintf("update build %s %s", b.OS, b.Arch)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSHA256(u string) ([]byte, error) {
|
||||
shaURL := fmt.Sprintf("%s.sha256", u)
|
||||
|
||||
// nolint:gosec
|
||||
resp, err := http.Get(shaURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Println("failed to close response body, err: %w", err)
|
||||
}
|
||||
}()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return nil, fmt.Errorf("failed downloading %s: %s", u, resp.Status)
|
||||
}
|
||||
|
||||
sha256, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sha256, nil
|
||||
}
|
||||
|
||||
func postRequest(cfg packaging.PublishConfig, pth string, body any, descr string) error {
|
||||
var sfx string
|
||||
switch cfg.Edition {
|
||||
case config.EditionOSS:
|
||||
case config.EditionEnterprise:
|
||||
sfx = packaging.EnterpriseSfx
|
||||
default:
|
||||
return fmt.Errorf("unrecognized edition %q", cfg.Edition)
|
||||
}
|
||||
product := fmt.Sprintf("grafana%s", sfx)
|
||||
|
||||
jsonB, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to JSON encode release: %w", err)
|
||||
}
|
||||
|
||||
u, err := constructURL(product, pth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodPost, u, bytes.NewReader(jsonB))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", cfg.GrafanaAPIKey))
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
log.Printf("Posting to grafana.com API, %s - JSON: %s\n", u, string(jsonB))
|
||||
if cfg.SimulateRelease {
|
||||
log.Println("Only simulating request")
|
||||
return nil
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed posting to %s (%s): %s", u, descr, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Println("failed to close response body, err: %w", err)
|
||||
}
|
||||
}()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.Contains(string(body), "already exists") || strings.Contains(string(body), "Nothing to update") {
|
||||
log.Printf("Already exists: %s\n", descr)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed posting to %s (%s): %s", u, descr, resp.Status)
|
||||
}
|
||||
|
||||
log.Printf("Successfully posted to grafana.com API, %s\n", u)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func constructURL(product string, pth string) (string, error) {
|
||||
productPath := filepath.Clean(filepath.Join("/", product, pth))
|
||||
u, err := url.Parse(grafanaAPI)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
u.Path = path.Join(u.Path, productPath)
|
||||
return u.String(), err
|
||||
}
|
||||
|
||||
type GCOMPackage struct {
|
||||
OS string `json:"os"`
|
||||
URL string `json:"url"`
|
||||
SHA256 string `json:"sha256"`
|
||||
Arch string `json:"arch"`
|
||||
}
|
||||
|
||||
type Release struct {
|
||||
Version string `json:"version"`
|
||||
ReleaseDate time.Time `json:"releaseDate"`
|
||||
Stable bool `json:"stable"`
|
||||
Beta bool `json:"beta"`
|
||||
Nightly bool `json:"nightly"`
|
||||
WhatsNewURL string `json:"whatsNewUrl"`
|
||||
ReleaseNotesURL string `json:"releaseNotesUrl"`
|
||||
Builds []GCOMPackage `json:"-"`
|
||||
}
|
||||
@@ -1,284 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/packaging"
|
||||
"github.com/grafana/grafana/pkg/build/versions"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_constructURL(t *testing.T) {
|
||||
type args struct {
|
||||
product string
|
||||
pth string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "cleans .. sequence", args: args{"..", ".."}, want: "https://grafana.com/api", wantErr: false},
|
||||
{name: "doesn't clean anything - non malicious url", args: args{"foo", "bar"}, want: "https://grafana.com/api/foo/bar", wantErr: false},
|
||||
{name: "doesn't clean anything - three dots", args: args{"...", "..."}, want: "https://grafana.com/api/.../...", wantErr: false},
|
||||
{name: "cleans .", args: args{"..", ".."}, want: "https://grafana.com/api", wantErr: false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := constructURL(tt.args.product, tt.args.pth)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("constructURL() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("constructURL() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilds(t *testing.T) {
|
||||
baseURL := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "dl.example.com",
|
||||
Path: path.Join("oss", "release"),
|
||||
}
|
||||
|
||||
version := "1.2.3"
|
||||
grafana := "grafana"
|
||||
packages := []packaging.BuildArtifact{
|
||||
{
|
||||
Distro: "deb",
|
||||
Arch: "arm64",
|
||||
Ext: "deb",
|
||||
},
|
||||
{
|
||||
Distro: "rhel",
|
||||
Arch: "aarch64",
|
||||
Ext: "rpm",
|
||||
},
|
||||
{
|
||||
Distro: "linux",
|
||||
Arch: "arm64",
|
||||
Ext: "tar.gz",
|
||||
},
|
||||
{
|
||||
Distro: "deb",
|
||||
Arch: "armhf",
|
||||
Ext: "deb",
|
||||
RaspberryPi: true,
|
||||
},
|
||||
{
|
||||
Distro: "deb",
|
||||
Arch: "armhf",
|
||||
Ext: "deb",
|
||||
},
|
||||
{
|
||||
Distro: "linux",
|
||||
Arch: "armv7",
|
||||
Ext: "tar.gz",
|
||||
},
|
||||
{
|
||||
Distro: "windows",
|
||||
Arch: "amd64",
|
||||
Ext: "zip",
|
||||
},
|
||||
{
|
||||
Distro: "windows",
|
||||
Arch: "amd64",
|
||||
Ext: "msi",
|
||||
},
|
||||
}
|
||||
|
||||
expect := []GCOMPackage{
|
||||
{
|
||||
URL: "https://dl.example.com/oss/release/grafana_1.2.3_arm64.deb",
|
||||
OS: "deb",
|
||||
Arch: "arm64",
|
||||
},
|
||||
{
|
||||
URL: "https://dl.example.com/oss/release/grafana-1.2.3-1.aarch64.rpm",
|
||||
OS: "rhel",
|
||||
Arch: "arm64",
|
||||
},
|
||||
{
|
||||
URL: "https://dl.example.com/oss/release/grafana-1.2.3.linux-arm64.tar.gz",
|
||||
OS: "linux",
|
||||
Arch: "arm64",
|
||||
},
|
||||
{
|
||||
URL: "https://dl.example.com/oss/release/grafana-rpi_1.2.3_armhf.deb",
|
||||
OS: "deb",
|
||||
Arch: "armv6",
|
||||
},
|
||||
{
|
||||
URL: "https://dl.example.com/oss/release/grafana_1.2.3_armhf.deb",
|
||||
OS: "deb",
|
||||
Arch: "armv7",
|
||||
},
|
||||
{
|
||||
URL: "https://dl.example.com/oss/release/grafana-1.2.3.linux-armv7.tar.gz",
|
||||
OS: "linux",
|
||||
Arch: "armv7",
|
||||
},
|
||||
{
|
||||
URL: "https://dl.example.com/oss/release/grafana-1.2.3.windows-amd64.zip",
|
||||
OS: "win",
|
||||
Arch: "amd64",
|
||||
},
|
||||
{
|
||||
URL: "https://dl.example.com/oss/release/grafana-1.2.3.windows-amd64.msi",
|
||||
OS: "win-installer",
|
||||
Arch: "amd64",
|
||||
},
|
||||
}
|
||||
|
||||
builds, err := Builds(baseURL, grafana, version, packages)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(expect), len(builds))
|
||||
|
||||
for i := range builds {
|
||||
t.Run(fmt.Sprintf("[%d/%d] %s", i+1, len(builds), expect[i].URL), func(t *testing.T) {
|
||||
assert.Equal(t, expect[i].URL, builds[i].URL)
|
||||
assert.Equal(t, expect[i].OS, builds[i].OS)
|
||||
assert.Equal(t, expect[i].Arch, builds[i].Arch)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildsWithPlus(t *testing.T) {
|
||||
baseURL := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "dl.example.com",
|
||||
Path: path.Join("oss", "release"),
|
||||
}
|
||||
|
||||
version := "1.2.3+example-01"
|
||||
grafana := "grafana"
|
||||
packages := []packaging.BuildArtifact{
|
||||
{
|
||||
Distro: "deb",
|
||||
Arch: "arm64",
|
||||
Ext: "deb",
|
||||
},
|
||||
{
|
||||
Distro: "rhel",
|
||||
Arch: "aarch64",
|
||||
Ext: "rpm",
|
||||
},
|
||||
{
|
||||
Distro: "linux",
|
||||
Arch: "arm64",
|
||||
Ext: "tar.gz",
|
||||
},
|
||||
{
|
||||
Distro: "deb",
|
||||
Arch: "armhf",
|
||||
Ext: "deb",
|
||||
RaspberryPi: true,
|
||||
},
|
||||
{
|
||||
Distro: "deb",
|
||||
Arch: "armhf",
|
||||
Ext: "deb",
|
||||
},
|
||||
{
|
||||
Distro: "linux",
|
||||
Arch: "armv7",
|
||||
Ext: "tar.gz",
|
||||
},
|
||||
{
|
||||
Distro: "windows",
|
||||
Arch: "amd64",
|
||||
Ext: "zip",
|
||||
},
|
||||
{
|
||||
Distro: "windows",
|
||||
Arch: "amd64",
|
||||
Ext: "msi",
|
||||
},
|
||||
}
|
||||
|
||||
expect := []GCOMPackage{
|
||||
{
|
||||
URL: "https://dl.example.com/oss/release/grafana_1.2.3+example~01_arm64.deb",
|
||||
OS: "deb",
|
||||
Arch: "arm64",
|
||||
},
|
||||
{
|
||||
URL: "https://dl.example.com/oss/release/grafana-1.2.3+example~01-1.aarch64.rpm",
|
||||
OS: "rhel",
|
||||
Arch: "arm64",
|
||||
},
|
||||
{
|
||||
URL: "https://dl.example.com/oss/release/grafana-1.2.3+example-01.linux-arm64.tar.gz",
|
||||
OS: "linux",
|
||||
Arch: "arm64",
|
||||
},
|
||||
{
|
||||
URL: "https://dl.example.com/oss/release/grafana-rpi_1.2.3+example~01_armhf.deb",
|
||||
OS: "deb",
|
||||
Arch: "armv6",
|
||||
},
|
||||
{
|
||||
URL: "https://dl.example.com/oss/release/grafana_1.2.3+example~01_armhf.deb",
|
||||
OS: "deb",
|
||||
Arch: "armv7",
|
||||
},
|
||||
{
|
||||
URL: "https://dl.example.com/oss/release/grafana-1.2.3+example-01.linux-armv7.tar.gz",
|
||||
OS: "linux",
|
||||
Arch: "armv7",
|
||||
},
|
||||
{
|
||||
URL: "https://dl.example.com/oss/release/grafana-1.2.3+example-01.windows-amd64.zip",
|
||||
OS: "win",
|
||||
Arch: "amd64",
|
||||
},
|
||||
{
|
||||
URL: "https://dl.example.com/oss/release/grafana-1.2.3+example-01.windows-amd64.msi",
|
||||
OS: "win-installer",
|
||||
Arch: "amd64",
|
||||
},
|
||||
}
|
||||
|
||||
builds, err := Builds(baseURL, grafana, version, packages)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(expect), len(builds))
|
||||
|
||||
for i := range builds {
|
||||
t.Run(fmt.Sprintf("[%d/%d] %s", i+1, len(builds), expect[i].URL), func(t *testing.T) {
|
||||
assert.Equal(t, expect[i].URL, builds[i].URL)
|
||||
assert.Equal(t, expect[i].OS, builds[i].OS)
|
||||
assert.Equal(t, expect[i].Arch, builds[i].Arch)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseURLs(t *testing.T) {
|
||||
f := "https://grafana.com/whats-new-in-v%[1]s-%[2]s"
|
||||
|
||||
smv := versions.Semver{
|
||||
Major: "1",
|
||||
Minor: "2",
|
||||
Patch: "3",
|
||||
}
|
||||
|
||||
conf := packageConf{
|
||||
Grafana: grafanaConf{
|
||||
WhatsNewURL: f,
|
||||
ReleaseNotesURL: "https://example.com",
|
||||
},
|
||||
}
|
||||
|
||||
expect := "https://grafana.com/whats-new-in-v1-2"
|
||||
|
||||
a, _, err := getReleaseURLs(smv, &conf)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expect, a)
|
||||
}
|
||||
@@ -4,18 +4,9 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/cmd/util"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/cmd"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var additionalCommands []*cli.Command = make([]*cli.Command, 0, 5)
|
||||
|
||||
//nolint:unused
|
||||
func registerAppCommand(c *cli.Command) {
|
||||
additionalCommands = append(additionalCommands, c)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// TODO change the registerer if the user is running using a JSON file etc
|
||||
for k, v := range cmd.Artifacts {
|
||||
@@ -25,272 +16,6 @@ func main() {
|
||||
}
|
||||
|
||||
app := cmd.GlobalCLI.App()
|
||||
artifactsCommand := cmd.GlobalCLI.ArtifactsCommand()
|
||||
artifactsCommand.Subcommands = cli.Commands{
|
||||
{
|
||||
Name: "storybook",
|
||||
Usage: "[ARCHIVED] Publish Grafana storybook",
|
||||
Action: PublishStorybookAction,
|
||||
Flags: []cli.Flag{
|
||||
&editionFlag,
|
||||
&tagFlag,
|
||||
&srcFlag,
|
||||
&cli.StringFlag{
|
||||
Name: "storybook-bucket",
|
||||
Value: "grafana-storybook",
|
||||
Usage: "Google Cloud Storage bucket for storybooks",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "static-assets",
|
||||
Usage: "[ARCHIVED] Publish Grafana static assets",
|
||||
Action: PublishStaticAssetsAction,
|
||||
Flags: []cli.Flag{
|
||||
&editionFlag,
|
||||
&securityFlag,
|
||||
&securityDestBucketFlag,
|
||||
&tagFlag,
|
||||
&srcFlag,
|
||||
&destFlag,
|
||||
&cli.StringFlag{
|
||||
Name: "static-assets-bucket",
|
||||
Value: "grafana-static-assets",
|
||||
Usage: "Google Cloud Storage bucket for static assets",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "static-asset-editions",
|
||||
Usage: "All the editions of the static assets (or $STATIC_ASSET_EDITIONS)",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "packages",
|
||||
Usage: "[ARCHIVED] Publish Grafana packages",
|
||||
Action: PublishArtifactsAction,
|
||||
Flags: []cli.Flag{
|
||||
&editionFlag,
|
||||
&securityFlag,
|
||||
&securityDestBucketFlag,
|
||||
&tagFlag,
|
||||
&srcFlag,
|
||||
&destFlag,
|
||||
&cli.StringSliceFlag{
|
||||
Name: "artifacts-editions",
|
||||
Value: cli.NewStringSlice("oss", "enterprise", "enterprise2"),
|
||||
Usage: "Editions for which the artifacts should be delivered (oss,enterprise,enterprise2), (or $ARTIFACTS_EDITIONS)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "enterprise2-dest-bucket",
|
||||
Value: "grafana-downloads-enterprise2",
|
||||
Usage: "Google Cloud Storage bucket for published packages",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "enterprise2-security-prefix",
|
||||
Usage: "Bucket path prefix for enterprise2 security releases (or $ENTERPRISE2_SECURITY_PREFIX)",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "docker",
|
||||
Usage: "[ARCHIVED] Handle Grafana Docker images",
|
||||
Subcommands: cli.Commands{
|
||||
{
|
||||
Name: "fetch",
|
||||
Usage: "Fetch Grafana Docker images",
|
||||
ArgsUsage: "[version]",
|
||||
Action: util.MaxArgCountWrapper(1, FetchImages),
|
||||
Flags: []cli.Flag{
|
||||
&editionFlag,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "npm",
|
||||
Usage: "[ARCHIVED] Handle Grafana npm packages",
|
||||
Subcommands: cli.Commands{
|
||||
{
|
||||
Name: "release",
|
||||
Usage: "Release npm packages",
|
||||
ArgsUsage: "[version]",
|
||||
Action: NpmReleaseAction,
|
||||
Flags: []cli.Flag{
|
||||
&tagFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "store",
|
||||
Usage: "Store npm packages tarball",
|
||||
Action: NpmStoreAction,
|
||||
Flags: []cli.Flag{
|
||||
&tagFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "retrieve",
|
||||
Usage: "Retrieve npm packages tarball",
|
||||
Action: NpmRetrieveAction,
|
||||
Flags: []cli.Flag{
|
||||
&tagFlag,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
app.Commands = append(app.Commands, []*cli.Command{
|
||||
{
|
||||
Name: "e2e-tests",
|
||||
Usage: "[ARCHIVED] Run Grafana e2e tests",
|
||||
Action: EndToEndTests,
|
||||
Flags: []cli.Flag{
|
||||
&triesFlag,
|
||||
&cli.IntFlag{
|
||||
Name: "port",
|
||||
Value: 3001,
|
||||
Usage: "Specify the server port",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "suite",
|
||||
Usage: "Specify the end-to-end tests suite to be used",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "host",
|
||||
Value: "grafana-server",
|
||||
Usage: "Specify the server host",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "video",
|
||||
Value: "true",
|
||||
Usage: "Specify if videos should be recorded",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "upload-cdn",
|
||||
Usage: "[ARCHIVED] Upload public/* to a cdn bucket",
|
||||
Action: UploadCDN,
|
||||
Flags: []cli.Flag{
|
||||
&editionFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "publish-metrics",
|
||||
Usage: "[ARCHIVED] Publish a set of metrics from stdin",
|
||||
ArgsUsage: "<api-key>",
|
||||
Action: util.MaxArgCountWrapper(1, PublishMetrics),
|
||||
},
|
||||
{
|
||||
Name: "verify-drone",
|
||||
Usage: "[ARCHIVED] Verify Drone configuration",
|
||||
Action: VerifyDrone,
|
||||
},
|
||||
{
|
||||
Name: "store-storybook",
|
||||
Usage: "[ARCHIVED] Stores storybook to GCS buckets",
|
||||
Action: StoreStorybook,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "deployment",
|
||||
Usage: "Kind of deployment (e.g. canary/latest)",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "verify-storybook",
|
||||
Usage: "[ARCHIVED] Integrity check for storybook build",
|
||||
Action: VerifyStorybook,
|
||||
},
|
||||
{
|
||||
Name: "upload-packages",
|
||||
Usage: "[ARCHIVED] Upload Grafana packages",
|
||||
Action: UploadPackages,
|
||||
Flags: []cli.Flag{
|
||||
&jobsFlag,
|
||||
&editionFlag,
|
||||
&cli.BoolFlag{
|
||||
Name: "enterprise2",
|
||||
Usage: "Declare if the edition is enterprise2",
|
||||
},
|
||||
},
|
||||
},
|
||||
artifactsCommand,
|
||||
{
|
||||
Name: "publish",
|
||||
Usage: "[ARCHIVED] Publish packages to Grafana com and repositories",
|
||||
Subcommands: cli.Commands{
|
||||
{
|
||||
Name: "grafana-com",
|
||||
Usage: "Publish packages to grafana.com",
|
||||
Action: GrafanaCom,
|
||||
Flags: []cli.Flag{
|
||||
&editionFlag,
|
||||
&buildIDFlag,
|
||||
&util.DryRunFlag,
|
||||
&cli.StringFlag{
|
||||
Name: "src-bucket",
|
||||
Value: "grafana-downloads",
|
||||
Usage: "Google Cloud Storage bucket",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "github",
|
||||
Usage: "Publish packages to GitHub releases",
|
||||
Action: PublishGithub,
|
||||
Flags: []cli.Flag{
|
||||
&util.DryRunFlag,
|
||||
&cli.StringFlag{
|
||||
Name: "path",
|
||||
Usage: "Path to the asset to be published",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "repo",
|
||||
Required: true,
|
||||
Usage: "GitHub repository",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "tag",
|
||||
Usage: "Release tag (default from metadata)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "create",
|
||||
Usage: "Create release if it doesn't exist",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "aws",
|
||||
Usage: "Publish image to AWS Marketplace releases",
|
||||
Action: PublishAwsMarketplace,
|
||||
Flags: []cli.Flag{
|
||||
&util.DryRunFlag,
|
||||
&cli.StringFlag{
|
||||
Name: "version",
|
||||
Usage: "Release version (default from metadata)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "image",
|
||||
Required: true,
|
||||
Usage: "Name of the image to be released",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "repo",
|
||||
Required: true,
|
||||
Usage: "AWS Marketplace ECR repository",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "product",
|
||||
Required: true,
|
||||
Usage: "AWS Marketplace product identifier",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}...)
|
||||
|
||||
app.Commands = append(app.Commands, additionalCommands...)
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatalln(err)
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/npm"
|
||||
)
|
||||
|
||||
func NpmRetrieveAction(c *cli.Context) error {
|
||||
if c.NArg() > 0 {
|
||||
if err := cli.ShowSubcommandHelp(c); err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
return cli.Exit("", 1)
|
||||
}
|
||||
|
||||
tag := c.String("tag")
|
||||
if tag == "" {
|
||||
return fmt.Errorf("no tag version specified, exitting")
|
||||
}
|
||||
|
||||
if strings.Contains(tag, "security") {
|
||||
log.Printf("skipping npm publish because version '%s' has 'security'", tag)
|
||||
return nil
|
||||
}
|
||||
|
||||
prereleaseBucket := strings.TrimSpace(os.Getenv("PRERELEASE_BUCKET"))
|
||||
if prereleaseBucket == "" {
|
||||
return cli.Exit("the environment variable PRERELEASE_BUCKET must be set", 1)
|
||||
}
|
||||
|
||||
err := npm.FetchNpmPackages(c.Context, tag, prereleaseBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NpmStoreAction(c *cli.Context) error {
|
||||
if c.NArg() > 0 {
|
||||
if err := cli.ShowSubcommandHelp(c); err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
return cli.Exit("", 1)
|
||||
}
|
||||
|
||||
tag := c.String("tag")
|
||||
if tag == "" {
|
||||
return fmt.Errorf("no tag version specified, exiting")
|
||||
}
|
||||
|
||||
if strings.Contains(tag, "security") {
|
||||
log.Printf("skipping npm publish because version '%s' has 'security'", tag)
|
||||
return nil
|
||||
}
|
||||
|
||||
prereleaseBucket := strings.TrimSpace(os.Getenv("PRERELEASE_BUCKET"))
|
||||
if prereleaseBucket == "" {
|
||||
return cli.Exit("the environment variable PRERELEASE_BUCKET must be set", 1)
|
||||
}
|
||||
|
||||
err := npm.StoreNpmPackages(c.Context, tag, prereleaseBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NpmReleaseAction(c *cli.Context) error {
|
||||
if c.NArg() > 0 {
|
||||
if err := cli.ShowSubcommandHelp(c); err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
return cli.Exit("", 1)
|
||||
}
|
||||
|
||||
tag := c.String("tag")
|
||||
if tag == "" {
|
||||
return fmt.Errorf("no tag version specified, exitting")
|
||||
}
|
||||
|
||||
if strings.Contains(tag, "security") {
|
||||
log.Printf("skipping npm publish because version '%s' has 'security'", tag)
|
||||
return nil
|
||||
}
|
||||
|
||||
err := npm.PublishNpmPackages(c.Context, tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/env"
|
||||
"github.com/grafana/grafana/pkg/build/gcloud"
|
||||
"github.com/grafana/grafana/pkg/build/gcloud/storage"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// PublishArtifactsAction Action implements the sub-command "publish-artifacts".
|
||||
func PublishArtifactsAction(c *cli.Context) error {
|
||||
if c.NArg() > 0 {
|
||||
if err := cli.ShowSubcommandHelp(c); err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
return cli.Exit("", 1)
|
||||
}
|
||||
|
||||
security := c.Bool("security")
|
||||
var securityDestBucket, enterprise2SecurityPrefix string
|
||||
|
||||
artifactsEditions, err := env.RequireListWithEnvFallback(c, "artifacts-editions", "ARTIFACTS_EDITIONS")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if security {
|
||||
securityDestBucket, err = env.RequireStringWithEnvFallback(c, "security-dest-bucket", "SECURITY_DEST_BUCKET")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
enterprise2SecurityPrefix, err = env.RequireStringWithEnvFallback(c, "enterprise2-security-prefix", "ENTERPRISE2_SECURITY_PREFIX")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := gcloud.ActivateServiceAccount(); err != nil {
|
||||
return fmt.Errorf("error connecting to gcp, %q", err)
|
||||
}
|
||||
|
||||
cfg := publishConfig{
|
||||
srcBucket: c.String("src-bucket"),
|
||||
destBucket: c.String("dest-bucket"),
|
||||
enterprise2DestBucket: c.String("enterprise2-dest-bucket"),
|
||||
enterprise2SecurityPrefix: enterprise2SecurityPrefix,
|
||||
security: security,
|
||||
tag: strings.TrimPrefix(c.String("tag"), "v"),
|
||||
}
|
||||
|
||||
if cfg.security {
|
||||
cfg.destBucket = securityDestBucket
|
||||
}
|
||||
|
||||
gcs, err := storage.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, edition := range artifactsEditions {
|
||||
switch edition {
|
||||
case "oss", "enterprise":
|
||||
err = copyArtifacts(c, gcs, cfg, edition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "enterprise2":
|
||||
err = copyEnterprise2Artifacts(c, gcs, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
log.Printf("unrecognised artifacts edition: %s\n", edition)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyArtifacts(c *cli.Context, gcs *storage.Client, cfg publishConfig, edition string) error {
|
||||
bucket := gcs.Bucket(cfg.destBucket)
|
||||
destURL := edition
|
||||
if !cfg.security {
|
||||
destURL = filepath.Join(destURL, "release")
|
||||
}
|
||||
log.Printf("Copying downloads for %s, from %s bucket to %s bucket", edition, cfg.srcBucket, destURL)
|
||||
if err := gcs.CopyRemoteDir(c.Context, gcs.Bucket(cfg.srcBucket), fmt.Sprintf("artifacts/downloads/v%s/%s/release", cfg.tag, edition), bucket, destURL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Successfully copied downloads.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyEnterprise2Artifacts(c *cli.Context, gcs *storage.Client, cfg publishConfig) error {
|
||||
bucket := gcs.Bucket(cfg.enterprise2DestBucket)
|
||||
var prefix string
|
||||
if cfg.security {
|
||||
prefix = cfg.enterprise2SecurityPrefix
|
||||
}
|
||||
destURL := fmt.Sprintf("enterprise2/%srelease", prefix)
|
||||
log.Printf("Copying downloads for enterprise2, from %s bucket to %s bucket", cfg.srcBucket, destURL)
|
||||
if err := gcs.CopyRemoteDir(c.Context, gcs.Bucket(cfg.srcBucket), fmt.Sprintf("artifacts/downloads-enterprise2/v%s/enterprise2/release", cfg.tag), bucket, destURL); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ecr"
|
||||
"github.com/aws/aws-sdk-go/service/marketplacecatalog"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/config"
|
||||
)
|
||||
|
||||
const (
|
||||
marketplaceChangeSetName = "Add new version"
|
||||
marketplaceCatalogId = "AWSMarketplace"
|
||||
marketplaceRegistryId = "709825985650"
|
||||
marketplaceRegistryRegion = "us-east-1"
|
||||
marketplaceRegistryUrl = "709825985650.dkr.ecr.us-east-1.amazonaws.com"
|
||||
marketplaceRequestsUrl = "https://aws.amazon.com/marketplace/management/requests/"
|
||||
releaseNotesTemplateUrl = "https://grafana.com/docs/grafana/latest/release-notes/release-notes-${TAG}/"
|
||||
helmChartsUrl = "https://grafana.github.io/helm-charts/"
|
||||
docsUrl = "https://grafana.com/docs/grafana/latest/enterprise/license/"
|
||||
imagePlatform = "linux/amd64"
|
||||
|
||||
publishAwsMarketplaceTestKey publishAwsMarketplaceTestKeyType = "test-client"
|
||||
)
|
||||
|
||||
var (
|
||||
errEmptyVersion = errors.New(`failed to retrieve release version from metadata, use "--version" to set it manually`)
|
||||
)
|
||||
|
||||
type publishAwsMarketplaceTestKeyType string
|
||||
|
||||
type publishAwsMarketplaceFlags struct {
|
||||
dryRun bool
|
||||
version string
|
||||
repo string
|
||||
image string
|
||||
product string
|
||||
}
|
||||
|
||||
type AwsMarketplacePublishingService struct {
|
||||
auth string
|
||||
docker AwsMarketplaceDocker
|
||||
ecr AwsMarketplaceRegistry
|
||||
mkt AwsMarketplaceCatalog
|
||||
}
|
||||
|
||||
type AwsMarketplaceDocker interface {
|
||||
ImagePull(ctx context.Context, refStr string, options image.PullOptions) (io.ReadCloser, error)
|
||||
ImageTag(ctx context.Context, source string, target string) error
|
||||
ImagePush(ctx context.Context, image string, options image.PushOptions) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
type AwsMarketplaceRegistry interface {
|
||||
GetAuthorizationTokenWithContext(ctx context.Context, input *ecr.GetAuthorizationTokenInput, opts ...request.Option) (*ecr.GetAuthorizationTokenOutput, error)
|
||||
}
|
||||
|
||||
type AwsMarketplaceCatalog interface {
|
||||
DescribeEntityWithContext(ctx context.Context, input *marketplacecatalog.DescribeEntityInput, opts ...request.Option) (*marketplacecatalog.DescribeEntityOutput, error)
|
||||
StartChangeSetWithContext(ctx context.Context, input *marketplacecatalog.StartChangeSetInput, opts ...request.Option) (*marketplacecatalog.StartChangeSetOutput, error)
|
||||
}
|
||||
|
||||
func PublishAwsMarketplace(ctx *cli.Context) error {
|
||||
f, err := getPublishAwsMarketplaceFlags(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.version == "" {
|
||||
return errEmptyVersion
|
||||
}
|
||||
|
||||
svc, err := getAwsMarketplacePublishingService()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctx.Context.Value(publishAwsMarketplaceTestKey) != nil {
|
||||
svc = ctx.Context.Value(publishAwsMarketplaceTestKey).(*AwsMarketplacePublishingService)
|
||||
}
|
||||
|
||||
fmt.Println("Logging in to AWS Marketplace registry")
|
||||
err = svc.Login(ctx.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Retrieving image '%s:%s' from Docker Hub\n", f.image, f.version)
|
||||
err = svc.PullImage(ctx.Context, f.image, f.version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Renaming image '%s:%s' to '%s/%s:%s'\n", f.image, f.version, marketplaceRegistryUrl, f.repo, f.version)
|
||||
err = svc.TagImage(ctx.Context, f.image, f.repo, f.version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !f.dryRun {
|
||||
fmt.Printf("Pushing image '%s/%s:%s' to the AWS Marketplace ECR\n", marketplaceRegistryUrl, f.repo, f.version)
|
||||
err = svc.PushToMarketplace(ctx.Context, f.repo, f.version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Dry-Run: Pushing image '%s/%s:%s' to the AWS Marketplace ECR\n", marketplaceRegistryUrl, f.repo, f.version)
|
||||
}
|
||||
|
||||
fmt.Printf("Retrieving product identifier for product '%s'\n", f.product)
|
||||
pid, err := svc.GetProductIdentifier(ctx.Context, f.product)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !f.dryRun {
|
||||
fmt.Printf("Releasing to product, you can view the progress of the release on %s\n", marketplaceRequestsUrl)
|
||||
return svc.ReleaseToProduct(ctx.Context, pid, f.repo, f.version)
|
||||
} else {
|
||||
fmt.Printf("Dry-Run: Releasing to product, you can view the progress of the release on %s\n", marketplaceRequestsUrl)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAwsMarketplacePublishingService() (*AwsMarketplacePublishingService, error) {
|
||||
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mySession := session.Must(session.NewSession())
|
||||
ecr := ecr.New(mySession, aws.NewConfig().WithRegion(marketplaceRegistryRegion))
|
||||
mkt := marketplacecatalog.New(mySession, aws.NewConfig().WithRegion(marketplaceRegistryRegion))
|
||||
return &AwsMarketplacePublishingService{
|
||||
docker: cli,
|
||||
ecr: ecr,
|
||||
mkt: mkt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *AwsMarketplacePublishingService) Login(ctx context.Context) error {
|
||||
out, err := s.ecr.GetAuthorizationTokenWithContext(ctx, &ecr.GetAuthorizationTokenInput{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.auth = *out.AuthorizationData[0].AuthorizationToken
|
||||
authData, err := base64.StdEncoding.DecodeString(s.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authString := strings.Split(string(authData), ":")
|
||||
authData, err = json.Marshal(registry.AuthConfig{
|
||||
Username: authString[0],
|
||||
Password: authString[1],
|
||||
})
|
||||
s.auth = base64.StdEncoding.EncodeToString(authData)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *AwsMarketplacePublishingService) PullImage(ctx context.Context, img string, version string) error {
|
||||
reader, err := s.docker.ImagePull(ctx, fmt.Sprintf("%s:%s", img, version), image.PullOptions{
|
||||
Platform: imagePlatform,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(os.Stdout, reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = reader.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *AwsMarketplacePublishingService) TagImage(ctx context.Context, image string, repo string, version string) error {
|
||||
err := s.docker.ImageTag(ctx, fmt.Sprintf("%s:%s", image, version), fmt.Sprintf("%s/%s:%s", marketplaceRegistryUrl, repo, version))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *AwsMarketplacePublishingService) PushToMarketplace(ctx context.Context, repo string, version string) error {
|
||||
reader, err := s.docker.ImagePush(ctx, fmt.Sprintf("%s/%s:%s", marketplaceRegistryUrl, repo, version), image.PushOptions{
|
||||
RegistryAuth: s.auth,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(os.Stdout, reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = reader.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *AwsMarketplacePublishingService) GetProductIdentifier(ctx context.Context, product string) (string, error) {
|
||||
out, err := s.mkt.DescribeEntityWithContext(ctx, &marketplacecatalog.DescribeEntityInput{
|
||||
EntityId: aws.String(product),
|
||||
Catalog: aws.String(marketplaceCatalogId),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return *out.EntityIdentifier, nil
|
||||
}
|
||||
|
||||
func (s *AwsMarketplacePublishingService) ReleaseToProduct(ctx context.Context, pid string, repo string, version string) error {
|
||||
_, err := s.mkt.StartChangeSetWithContext(ctx, &marketplacecatalog.StartChangeSetInput{
|
||||
Catalog: aws.String(marketplaceCatalogId),
|
||||
ChangeSetName: aws.String(marketplaceChangeSetName),
|
||||
ChangeSet: []*marketplacecatalog.Change{
|
||||
buildAwsMarketplaceChangeSet(pid, repo, version),
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func getPublishAwsMarketplaceFlags(ctx *cli.Context) (*publishAwsMarketplaceFlags, error) {
|
||||
metadata, err := config.GenerateMetadata(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
version := ctx.String("version")
|
||||
if version == "" && metadata.GrafanaVersion != "" {
|
||||
version = metadata.GrafanaVersion
|
||||
}
|
||||
image := ctx.String("image")
|
||||
repo := ctx.String("repo")
|
||||
product := ctx.String("product")
|
||||
dryRun := ctx.Bool("dry-run")
|
||||
return &publishAwsMarketplaceFlags{
|
||||
dryRun: dryRun,
|
||||
version: version,
|
||||
image: image,
|
||||
repo: repo,
|
||||
product: product,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildAwsMarketplaceReleaseNotesUrl(version string) string {
|
||||
sanitizedVersion := strings.ReplaceAll(version, ".", "-")
|
||||
return strings.ReplaceAll(releaseNotesTemplateUrl, "${TAG}", sanitizedVersion)
|
||||
}
|
||||
|
||||
func buildAwsMarketplaceChangeSet(entityId string, repo string, version string) *marketplacecatalog.Change {
|
||||
return &marketplacecatalog.Change{
|
||||
ChangeType: aws.String("AddDeliveryOptions"),
|
||||
Entity: &marketplacecatalog.Entity{
|
||||
Type: aws.String("ContainerProduct@1.0"),
|
||||
Identifier: aws.String(entityId),
|
||||
},
|
||||
Details: aws.String(buildAwsMarketplaceVersionDetails(repo, version)),
|
||||
}
|
||||
}
|
||||
|
||||
func buildAwsMarketplaceVersionDetails(repo string, version string) string {
|
||||
releaseNotesUrl := buildAwsMarketplaceReleaseNotesUrl(version)
|
||||
return fmt.Sprintf(`{
|
||||
"Version": {
|
||||
"ReleaseNotes": "Release notes are available on the website %s",
|
||||
"VersionTitle": "v%s"
|
||||
},
|
||||
"DeliveryOptions": [
|
||||
{
|
||||
"Details": {
|
||||
"EcrDeliveryOptionDetails": {
|
||||
"DeploymentResources": [
|
||||
{
|
||||
"Name": "Helm Charts",
|
||||
"Url": "%s"
|
||||
}
|
||||
],
|
||||
"CompatibleServices": ["EKS", "ECS", "ECS-Anywhere", "EKS-Anywhere"],
|
||||
"ContainerImages": ["%s/%s:%s"],
|
||||
"Description": "Grafana Enterprise can be installed using the official Grafana Helm chart repository. The repository is available on Github: %s",
|
||||
"UsageInstructions": "You can apply your Grafana Enterprise license to a new or existing Grafana Enterprise deployment by updating a configuration setting or environment variable. Your Grafana instance must be deployed on AWS, or have network access to AWS. For more information, see %s"
|
||||
}
|
||||
},
|
||||
"DeliveryOptionTitle": "Helm Chart"
|
||||
}
|
||||
]
|
||||
}`, releaseNotesUrl, version, helmChartsUrl, marketplaceRegistryUrl, repo, version, helmChartsUrl, docsUrl)
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/service/ecr"
|
||||
"github.com/aws/aws-sdk-go/service/marketplacecatalog"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/grafana/grafana/pkg/build/cmd/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
type awsPublishTestCase struct {
|
||||
name string
|
||||
args []string
|
||||
expectedError error
|
||||
errorContains string
|
||||
expectedOutput string
|
||||
mockedService *AwsMarketplacePublishingService
|
||||
}
|
||||
|
||||
func TestPublishAwsMarketplace(t *testing.T) {
|
||||
t.Setenv("DRONE_BUILD_EVENT", "promote")
|
||||
t.Setenv("DRONE_TAG", "v1.0.0")
|
||||
t.Setenv("DRONE_COMMIT", "abcdefgh")
|
||||
testApp := setupPublishAwsMarketplaceTests(t)
|
||||
errShouldNotCallMock := errors.New("shouldn't call")
|
||||
|
||||
testCases := []awsPublishTestCase{
|
||||
{
|
||||
name: "try to publish without required flags",
|
||||
errorContains: `Required flags "image, repo, product" not set`,
|
||||
},
|
||||
{
|
||||
name: "try to publish without credentials",
|
||||
args: []string{"--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"},
|
||||
mockedService: &AwsMarketplacePublishingService{
|
||||
ecr: &mockAwsMarketplaceRegistry{
|
||||
GetAuthorizationTokenWithContextError: credentials.ErrNoValidProvidersFoundInChain,
|
||||
},
|
||||
},
|
||||
expectedError: credentials.ErrNoValidProvidersFoundInChain,
|
||||
},
|
||||
{
|
||||
name: "try to publish with valid credentials and nonexisting version",
|
||||
args: []string{"--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"},
|
||||
mockedService: &AwsMarketplacePublishingService{
|
||||
ecr: &mockAwsMarketplaceRegistry{},
|
||||
docker: &mockAwsMarketplaceDocker{},
|
||||
mkt: &mockAwsMarketplaceCatalog{},
|
||||
},
|
||||
expectedOutput: "Releasing to product",
|
||||
},
|
||||
{
|
||||
name: "try to publish with valid credentials and existing version",
|
||||
args: []string{"--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"},
|
||||
mockedService: &AwsMarketplacePublishingService{
|
||||
ecr: &mockAwsMarketplaceRegistry{},
|
||||
docker: &mockAwsMarketplaceDocker{},
|
||||
mkt: &mockAwsMarketplaceCatalog{},
|
||||
},
|
||||
expectedOutput: "Releasing to product",
|
||||
},
|
||||
{
|
||||
name: "dry run with invalid credentials",
|
||||
args: []string{"--dry-run", "--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"},
|
||||
mockedService: &AwsMarketplacePublishingService{
|
||||
ecr: &mockAwsMarketplaceRegistry{
|
||||
GetAuthorizationTokenWithContextError: credentials.ErrNoValidProvidersFoundInChain,
|
||||
},
|
||||
},
|
||||
expectedError: credentials.ErrNoValidProvidersFoundInChain,
|
||||
},
|
||||
{
|
||||
name: "dry run with valid credentials",
|
||||
args: []string{"--dry-run", "--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"},
|
||||
mockedService: &AwsMarketplacePublishingService{
|
||||
ecr: &mockAwsMarketplaceRegistry{},
|
||||
docker: &mockAwsMarketplaceDocker{
|
||||
ImagePushError: errShouldNotCallMock,
|
||||
},
|
||||
mkt: &mockAwsMarketplaceCatalog{
|
||||
StartChangeSetWithContextError: errShouldNotCallMock,
|
||||
},
|
||||
},
|
||||
expectedOutput: "Dry-Run: Releasing to product",
|
||||
},
|
||||
}
|
||||
|
||||
if os.Getenv("DRONE_COMMIT") == "" {
|
||||
// this test only works locally due to Drone environment
|
||||
testCases = append(testCases,
|
||||
awsPublishTestCase{
|
||||
name: "try to publish without version",
|
||||
args: []string{"--image", "test/test", "--repo", "test/test", "--product", "test"},
|
||||
expectedError: errEmptyVersion,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), publishAwsMarketplaceTestKey, test.mockedService)
|
||||
args := []string{"run"}
|
||||
args = append(args, test.args...)
|
||||
out, err := captureStdout(t, func() error {
|
||||
return testApp.RunContext(ctx, args)
|
||||
})
|
||||
if test.expectedOutput != "" {
|
||||
assert.Contains(t, out, test.expectedOutput)
|
||||
}
|
||||
if test.expectedError != nil || test.errorContains != "" {
|
||||
assert.Error(t, err)
|
||||
if test.expectedError != nil {
|
||||
assert.ErrorIs(t, err, test.expectedError)
|
||||
}
|
||||
if test.errorContains != "" {
|
||||
assert.ErrorContains(t, err, test.errorContains)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setupPublishAwsMarketplaceTests(t *testing.T) *cli.App {
|
||||
t.Helper()
|
||||
testApp := cli.NewApp()
|
||||
testApp.Action = PublishAwsMarketplace
|
||||
testApp.Flags = []cli.Flag{
|
||||
&util.DryRunFlag,
|
||||
&cli.StringFlag{
|
||||
Name: "version",
|
||||
Usage: "Release version (default from metadata)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "image",
|
||||
Required: true,
|
||||
Usage: "Name of the image to be released",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "repo",
|
||||
Required: true,
|
||||
Usage: "AWS Marketplace ECR repository",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "product",
|
||||
Required: true,
|
||||
Usage: "AWS Marketplace product identifier",
|
||||
},
|
||||
}
|
||||
return testApp
|
||||
}
|
||||
|
||||
type mockAwsMarketplaceDocker struct {
|
||||
ImagePullError error
|
||||
ImageTagError error
|
||||
ImagePushError error
|
||||
}
|
||||
|
||||
func (m *mockAwsMarketplaceDocker) ImagePull(ctx context.Context, refStr string, options image.PullOptions) (io.ReadCloser, error) {
|
||||
return io.NopCloser(bytes.NewReader([]byte(""))), m.ImagePullError
|
||||
}
|
||||
|
||||
func (m *mockAwsMarketplaceDocker) ImageTag(ctx context.Context, source string, target string) error {
|
||||
return m.ImageTagError
|
||||
}
|
||||
|
||||
func (m *mockAwsMarketplaceDocker) ImagePush(ctx context.Context, image string, options image.PushOptions) (io.ReadCloser, error) {
|
||||
return io.NopCloser(bytes.NewReader([]byte(""))), m.ImagePushError
|
||||
}
|
||||
|
||||
type mockAwsMarketplaceRegistry struct {
|
||||
GetAuthorizationTokenWithContextError error
|
||||
}
|
||||
|
||||
func (m *mockAwsMarketplaceRegistry) GetAuthorizationTokenWithContext(ctx context.Context, input *ecr.GetAuthorizationTokenInput, opts ...request.Option) (*ecr.GetAuthorizationTokenOutput, error) {
|
||||
return &ecr.GetAuthorizationTokenOutput{
|
||||
AuthorizationData: []*ecr.AuthorizationData{
|
||||
{
|
||||
AuthorizationToken: aws.String(base64.StdEncoding.EncodeToString([]byte("username:password"))),
|
||||
},
|
||||
},
|
||||
}, m.GetAuthorizationTokenWithContextError
|
||||
}
|
||||
|
||||
type mockAwsMarketplaceCatalog struct {
|
||||
DescribeEntityWithContextError error
|
||||
StartChangeSetWithContextError error
|
||||
}
|
||||
|
||||
func (m *mockAwsMarketplaceCatalog) DescribeEntityWithContext(ctx context.Context, input *marketplacecatalog.DescribeEntityInput, opts ...request.Option) (*marketplacecatalog.DescribeEntityOutput, error) {
|
||||
return &marketplacecatalog.DescribeEntityOutput{
|
||||
EntityIdentifier: aws.String("productid"),
|
||||
}, m.DescribeEntityWithContextError
|
||||
}
|
||||
|
||||
func (m *mockAwsMarketplaceCatalog) StartChangeSetWithContext(ctx context.Context, input *marketplacecatalog.StartChangeSetInput, opts ...request.Option) (*marketplacecatalog.StartChangeSetOutput, error) {
|
||||
return &marketplacecatalog.StartChangeSetOutput{}, m.StartChangeSetWithContextError
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package main
|
||||
|
||||
type publishConfig struct {
|
||||
tag string
|
||||
srcBucket string
|
||||
destBucket string
|
||||
enterprise2DestBucket string
|
||||
enterprise2SecurityPrefix string
|
||||
staticAssetsBucket string
|
||||
staticAssetEditions []string
|
||||
storybookBucket string
|
||||
security bool
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-github/v70/github"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/config"
|
||||
)
|
||||
|
||||
type githubRepositoryService interface {
|
||||
GetReleaseByTag(ctx context.Context, owner string, repo string, tag string) (*github.RepositoryRelease, *github.Response, error)
|
||||
CreateRelease(ctx context.Context, owner string, repo string, release *github.RepositoryRelease) (*github.RepositoryRelease, *github.Response, error)
|
||||
UploadReleaseAsset(ctx context.Context, owner string, repo string, id int64, opt *github.UploadOptions, file *os.File) (*github.ReleaseAsset, *github.Response, error)
|
||||
DeleteReleaseAsset(ctx context.Context, owner string, repo string, id int64) (*github.Response, error)
|
||||
ListReleaseAssets(ctx context.Context, owner string, repo string, id int64, opt *github.ListOptions) ([]*github.ReleaseAsset, *github.Response, error)
|
||||
}
|
||||
|
||||
type githubRepo struct {
|
||||
owner string
|
||||
name string
|
||||
}
|
||||
|
||||
type publishGithubFlags struct {
|
||||
create bool
|
||||
dryRun bool
|
||||
tag string
|
||||
repo *githubRepo
|
||||
artifactPath string
|
||||
}
|
||||
|
||||
var (
|
||||
newGithubClient = githubRepositoryClient
|
||||
errTokenIsEmpty = errors.New("the environment variable GH_TOKEN must be set")
|
||||
errTagIsEmpty = errors.New(`failed to retrieve release tag from metadata, use "--tag" to set it manually`)
|
||||
errReleaseNotFound = errors.New(`release not found, use "--create" to create the release`)
|
||||
)
|
||||
|
||||
func PublishGithub(c *cli.Context) error {
|
||||
ctx := c.Context
|
||||
token := os.Getenv("GH_TOKEN")
|
||||
f, err := getPublishGithubFlags(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.tag == "" {
|
||||
return errTagIsEmpty
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
return errTokenIsEmpty
|
||||
}
|
||||
|
||||
if f.dryRun {
|
||||
return runPublishGithubDryRun(f, token, c)
|
||||
}
|
||||
|
||||
client := newGithubClient(ctx, token)
|
||||
release, res, err := client.GetReleaseByTag(ctx, f.repo.owner, f.repo.name, f.tag)
|
||||
if err != nil && res.StatusCode != 404 {
|
||||
return err
|
||||
}
|
||||
|
||||
if release == nil {
|
||||
if f.create {
|
||||
release, _, err = client.CreateRelease(ctx, f.repo.owner, f.repo.name, &github.RepositoryRelease{TagName: &f.tag})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return errReleaseNotFound
|
||||
}
|
||||
}
|
||||
|
||||
artifactName := path.Base(f.artifactPath)
|
||||
file, err := os.Open(f.artifactPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
assetsPage := 1
|
||||
foundAsset := false
|
||||
for {
|
||||
assets, resp, err := client.ListReleaseAssets(ctx, f.repo.owner, f.repo.name, *release.ID, &github.ListOptions{
|
||||
Page: assetsPage,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, asset := range assets {
|
||||
if asset.GetName() == artifactName {
|
||||
fmt.Printf("Found existing artifact with the name '%s'. Deleting that now.\n", artifactName)
|
||||
if _, err := client.DeleteReleaseAsset(ctx, f.repo.owner, f.repo.name, asset.GetID()); err != nil {
|
||||
return fmt.Errorf("failed to delete already existing asset: %w", err)
|
||||
}
|
||||
foundAsset = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if resp.NextPage <= assetsPage || foundAsset {
|
||||
break
|
||||
}
|
||||
assetsPage = resp.NextPage
|
||||
}
|
||||
|
||||
asset, _, err := client.UploadReleaseAsset(ctx, f.repo.owner, f.repo.name, *release.ID, &github.UploadOptions{Name: artifactName}, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Asset '%s' uploaded to release '%s' on repository '%s/%s'\nDownload: %s\n", *asset.Name, f.tag, f.repo.owner, f.repo.name, *asset.BrowserDownloadURL)
|
||||
return nil
|
||||
}
|
||||
|
||||
func githubRepositoryClient(ctx context.Context, token string) githubRepositoryService {
|
||||
ts := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: token},
|
||||
)
|
||||
tc := oauth2.NewClient(ctx, ts)
|
||||
|
||||
client := github.NewClient(tc)
|
||||
return client.Repositories
|
||||
}
|
||||
|
||||
func getPublishGithubFlags(c *cli.Context) (*publishGithubFlags, error) {
|
||||
metadata, err := config.GenerateMetadata(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tag := c.Value("tag").(string)
|
||||
if tag == "" && metadata.GrafanaVersion != "" {
|
||||
tag = fmt.Sprintf("v%s", metadata.GrafanaVersion)
|
||||
}
|
||||
fullRepo := c.Value("repo").(string)
|
||||
dryRun := c.Value("dry-run").(bool)
|
||||
owner := strings.Split(fullRepo, "/")[0]
|
||||
name := strings.Split(fullRepo, "/")[1]
|
||||
create := c.Value("create").(bool)
|
||||
artifactPath := c.Value("path").(string)
|
||||
if artifactPath == "" {
|
||||
artifactPath = fmt.Sprintf("grafana-enterprise2-%s-amd64.img", metadata.GrafanaVersion)
|
||||
fmt.Printf("path argument is not provided, resolving to default %s...\n", artifactPath)
|
||||
}
|
||||
return &publishGithubFlags{
|
||||
artifactPath: artifactPath,
|
||||
create: create,
|
||||
dryRun: dryRun,
|
||||
tag: tag,
|
||||
repo: &githubRepo{
|
||||
owner: owner,
|
||||
name: name,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func runPublishGithubDryRun(f *publishGithubFlags, token string, c *cli.Context) error {
|
||||
client := newGithubClient(c.Context, token)
|
||||
fmt.Println("Dry-Run: Retrieving release on repository by tag")
|
||||
release, res, err := client.GetReleaseByTag(c.Context, f.repo.owner, f.repo.name, f.tag)
|
||||
if err != nil && res.StatusCode != 404 {
|
||||
fmt.Println("Dry-Run: Github communication error:\n", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if release == nil {
|
||||
if f.create {
|
||||
fmt.Println("Dry-Run: Release doesn't exist and --create is enabled, so it would try to create the release")
|
||||
} else {
|
||||
fmt.Println("Dry-Run: Release doesn't exist and --create is disabled, so it would fail with error")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
artifactName := path.Base(f.artifactPath)
|
||||
fmt.Printf("Dry-Run: Opening file for release: %s\n", f.artifactPath)
|
||||
_, err = os.Open(f.artifactPath)
|
||||
if err != nil {
|
||||
fmt.Println("Dry-Run: Error opening file\n", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Dry-Run: Would upload asset '%s' to release '%s' on repo '%s/%s' and return download URL if successful\n", artifactName, f.tag, f.repo.owner, f.repo.name)
|
||||
return nil
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-github/v70/github"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/cmd/util"
|
||||
)
|
||||
|
||||
type githubPublishTestCases struct {
|
||||
name string
|
||||
args []string
|
||||
token string
|
||||
expectedError error
|
||||
errorContains string
|
||||
expectedOutput string
|
||||
mockedService *mockGithubRepositoryServiceImpl
|
||||
}
|
||||
|
||||
var mockGithubRepositoryService = &mockGithubRepositoryServiceImpl{}
|
||||
|
||||
func mockGithubRepositoryClient(context.Context, string) githubRepositoryService {
|
||||
return mockGithubRepositoryService
|
||||
}
|
||||
|
||||
func TestPublishGithub(t *testing.T) {
|
||||
t.Setenv("DRONE_BUILD_EVENT", "promote")
|
||||
t.Setenv("DRONE_TAG", "v1.0.0")
|
||||
t.Setenv("DRONE_COMMIT", "abcdefgh")
|
||||
testApp, testPath := setupPublishGithubTests(t)
|
||||
mockErrUnauthorized := errors.New("401")
|
||||
|
||||
testCases := []githubPublishTestCases{
|
||||
{
|
||||
name: "try to publish without required flags",
|
||||
errorContains: `Required flags "path, repo" not set`,
|
||||
},
|
||||
{
|
||||
name: "try to publish without token",
|
||||
args: []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"},
|
||||
expectedError: errTokenIsEmpty,
|
||||
},
|
||||
{
|
||||
name: "try to publish with invalid token",
|
||||
token: "invalid",
|
||||
args: []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"},
|
||||
mockedService: &mockGithubRepositoryServiceImpl{tagErr: mockErrUnauthorized},
|
||||
expectedError: mockErrUnauthorized,
|
||||
},
|
||||
{
|
||||
name: "try to publish with valid token and nonexisting tag with create disabled",
|
||||
token: "valid",
|
||||
args: []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"},
|
||||
mockedService: &mockGithubRepositoryServiceImpl{tagErr: errReleaseNotFound},
|
||||
expectedError: errReleaseNotFound,
|
||||
},
|
||||
{
|
||||
name: "try to publish with valid token and nonexisting tag with create enabled",
|
||||
token: "valid",
|
||||
args: []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0", "--create"},
|
||||
mockedService: &mockGithubRepositoryServiceImpl{tagErr: errReleaseNotFound},
|
||||
},
|
||||
{
|
||||
name: "try to publish with valid token and existing tag",
|
||||
token: "valid",
|
||||
args: []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"},
|
||||
},
|
||||
{
|
||||
name: "dry run with invalid token",
|
||||
token: "invalid",
|
||||
args: []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"},
|
||||
mockedService: &mockGithubRepositoryServiceImpl{tagErr: mockErrUnauthorized},
|
||||
expectedOutput: "Github communication error",
|
||||
},
|
||||
{
|
||||
name: "dry run with valid token and nonexisting tag with create disabled",
|
||||
token: "valid",
|
||||
args: []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"},
|
||||
mockedService: &mockGithubRepositoryServiceImpl{tagErr: errReleaseNotFound},
|
||||
expectedOutput: "Release doesn't exist",
|
||||
},
|
||||
{
|
||||
name: "dry run with valid token and nonexisting tag with create enabled",
|
||||
token: "valid",
|
||||
args: []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0", "--create"},
|
||||
mockedService: &mockGithubRepositoryServiceImpl{tagErr: errReleaseNotFound},
|
||||
expectedOutput: "Would upload asset",
|
||||
},
|
||||
{
|
||||
name: "dry run with valid token and existing tag",
|
||||
token: "valid",
|
||||
args: []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"},
|
||||
expectedOutput: "Would upload asset",
|
||||
},
|
||||
}
|
||||
|
||||
if os.Getenv("DRONE_COMMIT") == "" {
|
||||
// this test only works locally due to Drone environment
|
||||
testCases = append(testCases,
|
||||
githubPublishTestCases{
|
||||
name: "try to publish without tag",
|
||||
args: []string{"--path", testPath, "--repo", "test/test"},
|
||||
expectedError: errTagIsEmpty,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if test.token != "" {
|
||||
t.Setenv("GH_TOKEN", test.token)
|
||||
}
|
||||
if test.mockedService != nil {
|
||||
mockGithubRepositoryService = test.mockedService
|
||||
} else {
|
||||
mockGithubRepositoryService = &mockGithubRepositoryServiceImpl{}
|
||||
}
|
||||
args := []string{"run"}
|
||||
args = append(args, test.args...)
|
||||
out, err := captureStdout(t, func() error {
|
||||
return testApp.Run(args)
|
||||
})
|
||||
if test.expectedOutput != "" {
|
||||
assert.Contains(t, out, test.expectedOutput)
|
||||
}
|
||||
if test.expectedError != nil || test.errorContains != "" {
|
||||
assert.Error(t, err)
|
||||
if test.expectedError != nil {
|
||||
assert.ErrorIs(t, err, test.expectedError)
|
||||
}
|
||||
if test.errorContains != "" {
|
||||
assert.ErrorContains(t, err, test.errorContains)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setupPublishGithubTests(t *testing.T) (*cli.App, string) {
|
||||
t.Helper()
|
||||
ex, err := os.Executable()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
testPath := filepath.Dir(ex)
|
||||
|
||||
newGithubClient = mockGithubRepositoryClient
|
||||
|
||||
testApp := cli.NewApp()
|
||||
testApp.Action = PublishGithub
|
||||
testApp.Flags = []cli.Flag{
|
||||
&util.DryRunFlag,
|
||||
&cli.StringFlag{
|
||||
Name: "path",
|
||||
Required: true,
|
||||
Usage: "Path to the asset to be published",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "repo",
|
||||
Required: true,
|
||||
Usage: "Github repository",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "tag",
|
||||
Usage: "Release tag (default from metadata)ß",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "create",
|
||||
Usage: "Create release if it doesn't exist",
|
||||
},
|
||||
}
|
||||
return testApp, testPath
|
||||
}
|
||||
|
||||
func captureStdout(t *testing.T, fn func() error) (string, error) {
|
||||
t.Helper()
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
err := fn()
|
||||
werr := w.Close()
|
||||
if werr != nil {
|
||||
return "", err
|
||||
}
|
||||
out, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
return string(out), err
|
||||
}
|
||||
|
||||
type mockGithubRepositoryServiceImpl struct {
|
||||
tagErr error
|
||||
createErr error
|
||||
uploadErr error
|
||||
}
|
||||
|
||||
func (m *mockGithubRepositoryServiceImpl) GetReleaseByTag(ctx context.Context, owner string, repo string, tag string) (*github.RepositoryRelease, *github.Response, error) {
|
||||
var release *github.RepositoryRelease
|
||||
res := &github.Response{Response: &http.Response{}}
|
||||
if m.tagErr == nil {
|
||||
releaseID := int64(1)
|
||||
release = &github.RepositoryRelease{ID: &releaseID}
|
||||
} else if errors.Is(m.tagErr, errReleaseNotFound) {
|
||||
res.StatusCode = 404
|
||||
}
|
||||
return release, res, m.tagErr
|
||||
}
|
||||
|
||||
func (m *mockGithubRepositoryServiceImpl) CreateRelease(ctx context.Context, owner string, repo string, release *github.RepositoryRelease) (*github.RepositoryRelease, *github.Response, error) {
|
||||
releaseID := int64(1)
|
||||
return &github.RepositoryRelease{ID: &releaseID}, &github.Response{}, m.createErr
|
||||
}
|
||||
|
||||
func (m *mockGithubRepositoryServiceImpl) UploadReleaseAsset(ctx context.Context, owner string, repo string, id int64, opt *github.UploadOptions, file *os.File) (*github.ReleaseAsset, *github.Response, error) {
|
||||
assetName := "test"
|
||||
assetUrl := "testurl.com.br"
|
||||
return &github.ReleaseAsset{Name: &assetName, BrowserDownloadURL: &assetUrl}, &github.Response{}, m.uploadErr
|
||||
}
|
||||
|
||||
func (m *mockGithubRepositoryServiceImpl) DeleteReleaseAsset(ctx context.Context, owner string, repo string, id int64) (*github.Response, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockGithubRepositoryServiceImpl) ListReleaseAssets(ctx context.Context, owner string, repo string, id int64, opts *github.ListOptions) ([]*github.ReleaseAsset, *github.Response, error) {
|
||||
resp := github.Response{}
|
||||
resp.LastPage = 1
|
||||
resp.FirstPage = 1
|
||||
resp.NextPage = 0
|
||||
return []*github.ReleaseAsset{}, &resp, nil
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/metrics"
|
||||
)
|
||||
|
||||
func PublishMetrics(c *cli.Context) error {
|
||||
apiKey := c.Args().Get(0)
|
||||
|
||||
input, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("Reading from stdin failed: %s", err), 1)
|
||||
}
|
||||
|
||||
reMetrics := regexp.MustCompile(`(?ms)^Metrics: (\{.+\})`)
|
||||
ms := reMetrics.FindSubmatch(input)
|
||||
if len(ms) == 0 {
|
||||
return cli.Exit(fmt.Sprintf("Input on wrong format: %q", string(input)), 1)
|
||||
}
|
||||
|
||||
m := map[string]string{}
|
||||
if err := json.Unmarshal(ms[1], &m); err != nil {
|
||||
return cli.Exit(fmt.Sprintf("decoding metrics failed: %s", err), 1)
|
||||
}
|
||||
|
||||
log.Printf("Received metrics %+v", m)
|
||||
|
||||
if err := metrics.Publish(m, apiKey); err != nil {
|
||||
return cli.Exit(fmt.Sprintf("publishing metrics failed: %s", err), 1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/env"
|
||||
"github.com/grafana/grafana/pkg/build/gcloud"
|
||||
"github.com/grafana/grafana/pkg/build/gcloud/storage"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// PublishStaticAssetsAction Action implements the sub-command "artifacts static-assets".
|
||||
func PublishStaticAssetsAction(c *cli.Context) error {
|
||||
if c.NArg() > 0 {
|
||||
if err := cli.ShowSubcommandHelp(c); err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
return cli.Exit("", 1)
|
||||
}
|
||||
|
||||
staticAssetEditions, err := env.RequireListWithEnvFallback(c, "static-asset-editions", "STATIC_ASSET_EDITIONS")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gcloud.ActivateServiceAccount(); err != nil {
|
||||
return fmt.Errorf("error connecting to gcp, %q", err)
|
||||
}
|
||||
|
||||
cfg := publishConfig{
|
||||
srcBucket: c.String("src-bucket"),
|
||||
staticAssetsBucket: c.String("static-assets-bucket"),
|
||||
staticAssetEditions: staticAssetEditions,
|
||||
tag: strings.TrimPrefix(c.String("tag"), "v"),
|
||||
}
|
||||
|
||||
gcs, err := storage.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bucket := gcs.Bucket(cfg.staticAssetsBucket)
|
||||
|
||||
for _, edition := range staticAssetEditions {
|
||||
if err := gcs.CopyRemoteDir(c.Context, gcs.Bucket(cfg.srcBucket), fmt.Sprintf("artifacts/static-assets/%s/%s", edition, cfg.tag), bucket, fmt.Sprintf("%s/%s", edition, cfg.tag)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/gcloud"
|
||||
"github.com/grafana/grafana/pkg/build/gcloud/storage"
|
||||
"github.com/grafana/grafana/pkg/build/versions"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// PublishStorybookAction Action implements the sub-command "publish-artifacts".
|
||||
func PublishStorybookAction(c *cli.Context) error {
|
||||
if c.NArg() > 0 {
|
||||
if err := cli.ShowSubcommandHelp(c); err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
return cli.Exit("", 1)
|
||||
}
|
||||
|
||||
if err := gcloud.ActivateServiceAccount(); err != nil {
|
||||
return fmt.Errorf("error connecting to gcp, %q", err)
|
||||
}
|
||||
|
||||
cfg := publishConfig{
|
||||
srcBucket: c.String("src-bucket"),
|
||||
storybookBucket: c.String("storybook-bucket"),
|
||||
tag: strings.TrimPrefix(c.String("tag"), "v"),
|
||||
}
|
||||
|
||||
gcs, err := storage.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bucket := gcs.Bucket(cfg.storybookBucket)
|
||||
if err := gcs.CopyRemoteDir(c.Context, gcs.Bucket(cfg.srcBucket), fmt.Sprintf("artifacts/storybook/v%s", cfg.tag), bucket, cfg.tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if latest, err := isLatest(cfg); err == nil && latest {
|
||||
log.Printf("Copying storybooks to latest...")
|
||||
if err := gcs.CopyRemoteDir(c.Context, gcs.Bucket(cfg.srcBucket), fmt.Sprintf("artifacts/storybook/v%s", cfg.tag), bucket, "latest"); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isLatest(cfg publishConfig) (bool, error) {
|
||||
stableVersion, err := versions.GetLatestVersion(versions.LatestStableVersionURL)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
isLatest, err := versions.IsGreaterThanOrEqual(cfg.tag, stableVersion)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return isLatest, nil
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/config"
|
||||
"github.com/grafana/grafana/pkg/build/gcloud/storage"
|
||||
)
|
||||
|
||||
// StoreStorybook implements the sub-command "store-storybook".
|
||||
func StoreStorybook(c *cli.Context) error {
|
||||
deployment := c.String("deployment")
|
||||
|
||||
metadata, err := config.GenerateMetadata(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buildConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
storybookBucket := buildConfig.Buckets.Storybook
|
||||
srcPath := buildConfig.Buckets.StorybookSrcDir
|
||||
srcPath = filepath.Join(srcPath, deployment)
|
||||
|
||||
gcs, err := storage.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bucket := gcs.Bucket(storybookBucket)
|
||||
|
||||
if err := gcs.DeleteDir(c.Context, bucket, srcPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Successfully cleaned source: %s/%s\n", storybookBucket, srcPath)
|
||||
|
||||
if err := gcs.CopyLocalDir(c.Context, "packages/grafana-ui/dist/storybook", bucket, srcPath, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Successfully stored storybook to: %s/%s!\n", storybookBucket, srcPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/config"
|
||||
"github.com/grafana/grafana/pkg/build/gcloud/storage"
|
||||
)
|
||||
|
||||
// UploadCDN implements the sub-command "upload-cdn".
|
||||
func UploadCDN(c *cli.Context) error {
|
||||
if c.NArg() > 0 {
|
||||
if err := cli.ShowSubcommandHelp(c); err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
return cli.Exit("", 1)
|
||||
}
|
||||
|
||||
metadata, err := config.GenerateMetadata(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
version := metadata.GrafanaVersion
|
||||
if err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
|
||||
buildConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
edition := os.Getenv("EDITION")
|
||||
log.Printf("Uploading Grafana CDN Assets, version %s, %s edition...", version, edition)
|
||||
|
||||
editionPath := ""
|
||||
|
||||
switch config.Edition(edition) {
|
||||
case config.EditionOSS:
|
||||
editionPath = "grafana-oss"
|
||||
case config.EditionEnterprise:
|
||||
editionPath = "grafana"
|
||||
case config.EditionEnterprise2:
|
||||
editionPath = os.Getenv("ENTERPRISE2_CDN_PATH")
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized edition %q", edition))
|
||||
}
|
||||
|
||||
gcs, err := storage.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bucket := gcs.Bucket(buildConfig.Buckets.CDNAssets)
|
||||
srcPath := buildConfig.Buckets.CDNAssetsDir
|
||||
srcPath = filepath.Join(srcPath, editionPath, version)
|
||||
|
||||
if err := gcs.DeleteDir(c.Context, bucket, srcPath); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Successfully cleaned source: %s/%s\n", buildConfig.Buckets.CDNAssets, srcPath)
|
||||
|
||||
if err := gcs.CopyLocalDir(c.Context, "./public", bucket, srcPath, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Successfully uploaded cdn static assets to: %s/%s!\n", buildConfig.Buckets.CDNAssets, srcPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/config"
|
||||
"github.com/grafana/grafana/pkg/build/droneutil"
|
||||
"github.com/grafana/grafana/pkg/build/gcloud"
|
||||
)
|
||||
|
||||
// PackageRegexp returns a regexp for matching packages corresponding to a certain Grafana edition.
|
||||
func PackageRegexp(edition config.Edition) *regexp.Regexp {
|
||||
var sfx string
|
||||
switch edition {
|
||||
case config.EditionOSS:
|
||||
case config.EditionEnterprise:
|
||||
sfx = "-enterprise"
|
||||
case config.EditionEnterprise2:
|
||||
sfx = "-enterprise2"
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized edition %q", edition))
|
||||
}
|
||||
rePkg, err := regexp.Compile(fmt.Sprintf(`^grafana%s(?:-rpi)?[-_][^-_]+.*$`, sfx))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to compile regexp: %s", err))
|
||||
}
|
||||
|
||||
return rePkg
|
||||
}
|
||||
|
||||
const releaseFolder = "release"
|
||||
const mainFolder = "main"
|
||||
const releaseBranchFolder = "prerelease"
|
||||
|
||||
type uploadConfig struct {
|
||||
config.Config
|
||||
|
||||
edition config.Edition
|
||||
versionMode config.VersionMode
|
||||
gcpKey string
|
||||
distDir string
|
||||
versionFolder string
|
||||
}
|
||||
|
||||
// UploadPackages implements the sub-command "upload-packages".
|
||||
func UploadPackages(c *cli.Context) error {
|
||||
if c.NArg() > 0 {
|
||||
if err := cli.ShowSubcommandHelp(c); err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
return cli.Exit("", 1)
|
||||
}
|
||||
|
||||
gcpKeyB64 := strings.TrimSpace(os.Getenv("GCP_KEY"))
|
||||
if gcpKeyB64 == "" {
|
||||
return cli.Exit("the environment variable GCP_KEY must be set", 1)
|
||||
}
|
||||
gcpKeyB, err := base64.StdEncoding.DecodeString(gcpKeyB64)
|
||||
if err != nil {
|
||||
return cli.Exit("failed to base64 decode $GCP_KEY", 1)
|
||||
}
|
||||
gcpKey := string(gcpKeyB)
|
||||
|
||||
distDir, err := filepath.Abs("dist")
|
||||
if err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
|
||||
metadata, err := config.GenerateMetadata(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
version := metadata.GrafanaVersion
|
||||
|
||||
releaseMode, err := metadata.GetReleaseMode()
|
||||
if err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
|
||||
releaseModeConfig, err := config.GetBuildConfig(releaseMode.Mode)
|
||||
if err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
|
||||
var edition config.Edition
|
||||
if e, ok := os.LookupEnv("EDITION"); ok {
|
||||
edition = config.Edition(e)
|
||||
}
|
||||
|
||||
if c.Bool("enterprise2") {
|
||||
edition = config.EditionEnterprise2
|
||||
}
|
||||
|
||||
if edition == "" {
|
||||
return fmt.Errorf("both EDITION envvar and '--enterprise2' flag are missing. At least one of those is required")
|
||||
}
|
||||
|
||||
// TODO: Verify config values
|
||||
cfg := uploadConfig{
|
||||
Config: config.Config{
|
||||
Version: version,
|
||||
Bucket: releaseModeConfig.Buckets.Artifacts,
|
||||
},
|
||||
edition: edition,
|
||||
versionMode: releaseMode.Mode,
|
||||
gcpKey: gcpKey,
|
||||
distDir: distDir,
|
||||
}
|
||||
|
||||
event, err := droneutil.GetDroneEventFromEnv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.edition == config.EditionEnterprise2 {
|
||||
cfg.Bucket, err = bucketForEnterprise2(releaseModeConfig, event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cfg.versionFolder, err = getVersionFolder(cfg, event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := uploadPackages(cfg); err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
|
||||
log.Println("Successfully uploaded packages!")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Corner case for custom enterprise2 mode
|
||||
func bucketForEnterprise2(releaseModeConfig *config.BuildConfig, event string) (string, error) {
|
||||
if event == config.Custom {
|
||||
buildConfig, err := config.GetBuildConfig(config.ReleaseBranchMode)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buildConfig.Buckets.ArtifactsEnterprise2, nil
|
||||
}
|
||||
|
||||
if releaseModeConfig.Buckets.ArtifactsEnterprise2 != "" {
|
||||
return releaseModeConfig.Buckets.ArtifactsEnterprise2, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("enterprise2 bucket var doesn't exist")
|
||||
}
|
||||
|
||||
func getVersionFolder(cfg uploadConfig, event string) (string, error) {
|
||||
switch cfg.versionMode {
|
||||
case config.TagMode, config.CloudMode:
|
||||
return releaseFolder, nil
|
||||
case config.MainMode, config.DownstreamMode:
|
||||
return mainFolder, nil
|
||||
case config.ReleaseBranchMode:
|
||||
return releaseBranchFolder, nil
|
||||
default:
|
||||
// Corner case for custom enterprise2 mode
|
||||
if event == config.Custom && cfg.versionMode == config.Enterprise2Mode {
|
||||
return releaseFolder, nil
|
||||
}
|
||||
return "", fmt.Errorf("unrecognized version mode: %s", cfg.versionMode)
|
||||
}
|
||||
}
|
||||
|
||||
func uploadPackages(cfg uploadConfig) error {
|
||||
log.Printf("Uploading Grafana packages, version %s, %s edition, %s mode...\n", cfg.Version, cfg.edition,
|
||||
cfg.versionMode)
|
||||
|
||||
if err := gcloud.ActivateServiceAccount(); err != nil {
|
||||
return fmt.Errorf("couldn't activate service account, err: %w", err)
|
||||
}
|
||||
|
||||
edition := strings.ToLower(string(cfg.edition))
|
||||
|
||||
var sfx string
|
||||
switch cfg.edition {
|
||||
case config.EditionOSS:
|
||||
case config.EditionEnterprise:
|
||||
sfx = "-enterprise"
|
||||
case config.EditionEnterprise2:
|
||||
sfx = "-enterprise2"
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized edition %q", cfg.edition))
|
||||
}
|
||||
matches, err := filepath.Glob(filepath.Join(cfg.distDir, fmt.Sprintf("grafana%s*", sfx)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list packages: %w", err)
|
||||
}
|
||||
fpaths := []string{}
|
||||
rePkg := PackageRegexp(cfg.edition)
|
||||
for _, fpath := range matches {
|
||||
fname := filepath.Base(fpath)
|
||||
if strings.Contains(fname, "latest") || !rePkg.MatchString(fname) {
|
||||
log.Printf("Ignoring file %q\n", fpath)
|
||||
continue
|
||||
}
|
||||
|
||||
fpaths = append(fpaths, fpath)
|
||||
}
|
||||
|
||||
var tag, gcsPath string
|
||||
droneTag := strings.TrimSpace(os.Getenv("DRONE_TAG"))
|
||||
if droneTag != "" {
|
||||
tag = droneTag
|
||||
gcsPath = fmt.Sprintf("gs://%s/%s/%s/%s", cfg.Bucket, tag, edition, cfg.versionFolder)
|
||||
} else {
|
||||
gcsPath = fmt.Sprintf("gs://%s/%s/%s/", cfg.Bucket, edition, cfg.versionFolder)
|
||||
}
|
||||
log.Printf("Uploading %d file(s) to GCS (%s)...\n", len(fpaths), gcsPath)
|
||||
|
||||
args := []string{"-m", "cp"}
|
||||
args = append(args, fpaths...)
|
||||
args = append(args, gcsPath)
|
||||
cmd := exec.Command("gsutil", args...)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("failed to upload files to GCS: %s", output)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/config"
|
||||
)
|
||||
|
||||
func Test_getVersionFolder(t *testing.T) {
|
||||
type args struct {
|
||||
cfg uploadConfig
|
||||
event string
|
||||
versionFolder string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
err error
|
||||
}{
|
||||
{"tag mode", args{uploadConfig{versionMode: config.TagMode}, "", releaseFolder}, nil},
|
||||
{"main mode", args{uploadConfig{versionMode: config.MainMode}, "", mainFolder}, nil},
|
||||
{"downstream mode", args{uploadConfig{versionMode: config.DownstreamMode}, "", mainFolder}, nil},
|
||||
{"release branch mode", args{uploadConfig{versionMode: config.ReleaseBranchMode}, "", releaseBranchFolder}, nil},
|
||||
{"enterprise pro mode", args{uploadConfig{versionMode: config.Enterprise2Mode}, config.Custom, releaseFolder}, nil},
|
||||
{"cloud mode", args{uploadConfig{versionMode: config.CloudMode}, "", releaseFolder}, nil},
|
||||
{"unrecognised version mode", args{uploadConfig{versionMode: "foo"}, config.Custom, ""}, errors.New("")},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
versionMode, err := getVersionFolder(tt.args.cfg, tt.args.event)
|
||||
if tt.err != nil {
|
||||
require.Error(t, err)
|
||||
}
|
||||
require.Equal(t, versionMode, tt.args.versionFolder)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_checkForEnterprise2Edition(t *testing.T) {
|
||||
type args struct {
|
||||
releaseModeConfig *config.BuildConfig
|
||||
event string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
err error
|
||||
}{
|
||||
{"event is not custom", args{releaseModeConfig: &config.BuildConfig{Buckets: config.Buckets{ArtifactsEnterprise2: "dummy"}}}, "dummy", nil},
|
||||
{"event is not custom and string is empty", args{releaseModeConfig: &config.BuildConfig{Buckets: config.Buckets{ArtifactsEnterprise2: ""}}}, "", fmt.Errorf("enterprise2 bucket var doesn't exist")},
|
||||
{"event is custom", args{releaseModeConfig: nil, event: "custom"}, "grafana-downloads-enterprise2", nil},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := bucketForEnterprise2(tt.args.releaseModeConfig, tt.args.event)
|
||||
if tt.err != nil {
|
||||
require.Error(t, err)
|
||||
}
|
||||
assert.Equalf(t, tt.want, got, "bucketForEnterprise2(%v, %v)", tt.args.releaseModeConfig, tt.args.event)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/drone/drone-cli/drone/lint"
|
||||
"github.com/drone/drone-cli/drone/starlark"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
cliv1 "github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/fsutil"
|
||||
)
|
||||
|
||||
func VerifyDrone(c *cli.Context) error {
|
||||
const yml = ".drone.yml"
|
||||
const backup = ".drone.yml.bak"
|
||||
|
||||
if err := fsutil.CopyFile(yml, backup); err != nil {
|
||||
return cli.Exit(fmt.Sprintf("failed to copy %s to %s: %s", yml, backup, err), 1)
|
||||
}
|
||||
defer func() {
|
||||
if err := os.Remove(yml); err != nil {
|
||||
log.Printf("Failed to rename %s to %s", backup, yml)
|
||||
}
|
||||
if err := os.Rename(backup, yml); err != nil {
|
||||
log.Printf("Failed to rename %s to %s", backup, yml)
|
||||
}
|
||||
}()
|
||||
|
||||
flags := &flag.FlagSet{}
|
||||
for _, flag := range starlark.Command.Flags {
|
||||
flag.Apply(flags)
|
||||
}
|
||||
if err := flags.Set("format", "true"); err != nil {
|
||||
return err
|
||||
}
|
||||
cStarlark := cliv1.NewContext(cliv1.NewApp(), flags, nil)
|
||||
action := starlark.Command.Action.(func(*cliv1.Context))
|
||||
action(cStarlark)
|
||||
|
||||
if err := verifyYAML(yml, backup); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flags = &flag.FlagSet{}
|
||||
for _, flag := range lint.Command.Flags {
|
||||
flag.Apply(flags)
|
||||
}
|
||||
err := flags.Set("trusted", "true")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cLint := cliv1.NewContext(cliv1.NewApp(), flags, nil)
|
||||
actionE := lint.Command.Action.(func(*cliv1.Context) error)
|
||||
if err := actionE(cLint); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("%s is valid", yml)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readConfig(fpath string) ([]map[string]any, error) {
|
||||
//nolint:gosec
|
||||
f, err := os.Open(fpath)
|
||||
if err != nil {
|
||||
return nil, cli.Exit(fmt.Sprintf("failed to read %s: %s", fpath, err), 1)
|
||||
}
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
log.Println("error closing file", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// The YAML stream may contain multiple pipeline configurations, read them all
|
||||
dec := yaml.NewDecoder(f)
|
||||
var c []map[string]any
|
||||
for {
|
||||
var m map[string]any
|
||||
if err := dec.Decode(&m); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
return nil, cli.Exit(fmt.Sprintf("Failed to decode %s: %s", fpath, err), 1)
|
||||
}
|
||||
|
||||
if m["kind"] == "signature" {
|
||||
log.Printf("Ignoring a signature")
|
||||
continue
|
||||
}
|
||||
|
||||
c = append(c, m)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func verifyYAML(yml, backup string) error {
|
||||
log.Printf("Comparing %s and %s", yml, backup)
|
||||
|
||||
c1, err := readConfig(yml)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c2, err := readConfig(backup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !cmp.Equal(c1, c2) {
|
||||
return cli.Exit(fmt.Sprintf("%s is out of sync with .drone.star - regenerate it with drone starlark convert",
|
||||
yml), 1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Package verifystorybook contains the sub-command "verify-storybook".
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/fsutil"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// VerifyStorybook Action implements the sub-command "verify-storybook".
|
||||
func VerifyStorybook(c *cli.Context) error {
|
||||
const grafanaDir = "."
|
||||
|
||||
paths := []string{
|
||||
"packages/grafana-ui/dist/storybook/index.html",
|
||||
"packages/grafana-ui/dist/storybook/iframe.html"}
|
||||
for _, p := range paths {
|
||||
exists, err := fsutil.Exists(filepath.Join(grafanaDir, p))
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("failed to verify Storybook build: %s", err), 1)
|
||||
}
|
||||
if !exists {
|
||||
return fmt.Errorf("failed to verify Storybook build, missing %q", p)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Successfully verified Storybook integrity")
|
||||
return nil
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package config
|
||||
|
||||
type Config struct {
|
||||
Version string
|
||||
Bucket string
|
||||
DebRepoBucket string
|
||||
DebDBBucket string
|
||||
RPMRepoBucket string
|
||||
GPGPassPath string
|
||||
GPGPrivateKey string
|
||||
GPGPublicKey string
|
||||
NumWorkers int
|
||||
GitHubUser string
|
||||
GitHubToken string
|
||||
PullEnterprise bool
|
||||
PackageVersion string
|
||||
SignPackages bool
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package config
|
||||
|
||||
type Edition string
|
||||
|
||||
const (
|
||||
EditionOSS Edition = "oss"
|
||||
EditionEnterprise Edition = "enterprise"
|
||||
EditionEnterprise2 Edition = "enterprise2"
|
||||
)
|
||||
@@ -1,103 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/droneutil"
|
||||
)
|
||||
|
||||
func GenerateMetadata(c *cli.Context) (Metadata, error) {
|
||||
var metadata Metadata
|
||||
version := ""
|
||||
|
||||
event, err := droneutil.GetDroneEventFromEnv()
|
||||
if err != nil {
|
||||
return Metadata{}, err
|
||||
}
|
||||
|
||||
tag, ok := os.LookupEnv("DRONE_TAG")
|
||||
if !ok {
|
||||
fmt.Println("DRONE_TAG envvar not present, %w", err)
|
||||
}
|
||||
|
||||
var releaseMode ReleaseMode
|
||||
switch event {
|
||||
case string(PullRequestMode):
|
||||
releaseMode = ReleaseMode{Mode: PullRequestMode}
|
||||
case Push:
|
||||
mode, err := CheckDroneTargetBranch()
|
||||
if err != nil {
|
||||
return Metadata{}, err
|
||||
}
|
||||
releaseMode = ReleaseMode{Mode: mode}
|
||||
case Custom:
|
||||
if edition, _ := os.LookupEnv("EDITION"); edition == string(EditionEnterprise2) {
|
||||
releaseMode = ReleaseMode{Mode: Enterprise2Mode}
|
||||
if tag != "" {
|
||||
version = strings.TrimPrefix(tag, "v")
|
||||
}
|
||||
break
|
||||
}
|
||||
mode, err := CheckDroneTargetBranch()
|
||||
if err != nil {
|
||||
return Metadata{}, err
|
||||
}
|
||||
// if there is a custom event targeting the main branch, that's an enterprise downstream build
|
||||
if mode == MainBranch {
|
||||
releaseMode = ReleaseMode{Mode: DownstreamMode}
|
||||
} else {
|
||||
releaseMode = ReleaseMode{Mode: mode}
|
||||
}
|
||||
case Tag, Promote:
|
||||
if tag == "" {
|
||||
return Metadata{}, fmt.Errorf("DRONE_TAG envvar not present for a tag/promotion event, %w", err)
|
||||
}
|
||||
version = strings.TrimPrefix(tag, "v")
|
||||
mode, err := CheckSemverSuffix()
|
||||
if err != nil {
|
||||
return Metadata{}, err
|
||||
}
|
||||
releaseMode = mode
|
||||
case Cronjob:
|
||||
releaseMode = ReleaseMode{Mode: CronjobMode}
|
||||
}
|
||||
|
||||
if version == "" {
|
||||
version, err = generateVersionFromBuildID()
|
||||
if err != nil {
|
||||
return Metadata{}, err
|
||||
}
|
||||
}
|
||||
|
||||
currentCommit, err := GetDroneCommit()
|
||||
if err != nil {
|
||||
return Metadata{}, err
|
||||
}
|
||||
metadata = Metadata{
|
||||
GrafanaVersion: version,
|
||||
ReleaseMode: releaseMode,
|
||||
GrabplVersion: c.App.Version,
|
||||
CurrentCommit: currentCommit,
|
||||
}
|
||||
|
||||
fmt.Printf("building Grafana version: %s, release mode: %+v\n", metadata.GrafanaVersion, metadata.ReleaseMode)
|
||||
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
func generateVersionFromBuildID() (string, error) {
|
||||
buildID, ok := os.LookupEnv("DRONE_BUILD_NUMBER")
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unable to get DRONE_BUILD_NUMBER environmental variable")
|
||||
}
|
||||
var err error
|
||||
version, err := GenerateGrafanaVersion(buildID, ".")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
DroneBuildEvent = "DRONE_BUILD_EVENT"
|
||||
DroneTargetBranch = "DRONE_TARGET_BRANCH"
|
||||
DroneTag = "DRONE_TAG"
|
||||
DroneSemverPrerelease = "DRONE_SEMVER_PRERELEASE"
|
||||
DroneBuildNumber = "DRONE_BUILD_NUMBER"
|
||||
)
|
||||
|
||||
const (
|
||||
hashedGrafanaVersion = "9.2.0-12345"
|
||||
versionedBranch = "v9.2.x"
|
||||
)
|
||||
|
||||
func TestGetMetadata(t *testing.T) {
|
||||
tcs := []struct {
|
||||
envMap map[string]string
|
||||
expVersion string
|
||||
mode ReleaseMode
|
||||
}{
|
||||
{map[string]string{DroneBuildEvent: PullRequest, DroneTargetBranch: "", DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, ReleaseMode{Mode: PullRequestMode}},
|
||||
{map[string]string{DroneBuildEvent: Push, DroneTargetBranch: versionedBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, ReleaseMode{Mode: ReleaseBranchMode}},
|
||||
{map[string]string{DroneBuildEvent: Push, DroneTargetBranch: MainBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, ReleaseMode{Mode: MainMode}},
|
||||
{map[string]string{DroneBuildEvent: Custom, DroneTargetBranch: versionedBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, ReleaseMode{Mode: ReleaseBranchMode}},
|
||||
{map[string]string{DroneBuildEvent: Custom, DroneTargetBranch: MainBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, ReleaseMode{Mode: DownstreamMode}},
|
||||
{map[string]string{DroneBuildEvent: Custom, DroneTargetBranch: MainBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345", "EDITION": string(EditionEnterprise2)}, hashedGrafanaVersion, ReleaseMode{Mode: Enterprise2Mode}},
|
||||
{map[string]string{DroneBuildEvent: Tag, DroneTargetBranch: "", DroneTag: "v9.2.0", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, "9.2.0", ReleaseMode{Mode: TagMode, IsPreview: false, IsTest: false}},
|
||||
{map[string]string{DroneBuildEvent: Tag, DroneTargetBranch: "", DroneTag: "v9.2.0-preview", DroneSemverPrerelease: "preview", DroneBuildNumber: "12345"}, "9.2.0-preview", ReleaseMode{Mode: TagMode, IsPreview: true, IsTest: false}},
|
||||
{map[string]string{DroneBuildEvent: Tag, DroneTargetBranch: "", DroneTag: "v9.2.0-test", DroneSemverPrerelease: "test", DroneBuildNumber: "12345"}, "9.2.0-test", ReleaseMode{Mode: TagMode, IsPreview: false, IsTest: true}},
|
||||
{map[string]string{DroneBuildEvent: Promote, DroneTargetBranch: "", DroneTag: "v9.2.0", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, "9.2.0", ReleaseMode{Mode: TagMode, IsPreview: false, IsTest: false}},
|
||||
{map[string]string{DroneBuildEvent: Promote, DroneTargetBranch: "", DroneTag: "v9.2.0-preview", DroneSemverPrerelease: "preview", DroneBuildNumber: "12345"}, "9.2.0-preview", ReleaseMode{Mode: TagMode, IsPreview: true, IsTest: false}},
|
||||
{map[string]string{DroneBuildEvent: Promote, DroneTargetBranch: "", DroneTag: "v9.2.0-test", DroneSemverPrerelease: "test", DroneBuildNumber: "12345"}, "9.2.0-test", ReleaseMode{Mode: TagMode, IsPreview: false, IsTest: true}},
|
||||
}
|
||||
|
||||
ctx := cli.NewContext(cli.NewApp(), &flag.FlagSet{}, nil)
|
||||
for _, tc := range tcs {
|
||||
t.Run("Should return valid metadata, ", func(t *testing.T) {
|
||||
setUpEnv(t, tc.envMap)
|
||||
testMetadata(t, ctx, tc.expVersion, tc.mode)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testMetadata(t *testing.T, ctx *cli.Context, version string, releaseMode ReleaseMode) {
|
||||
t.Helper()
|
||||
|
||||
metadata, err := GenerateMetadata(ctx)
|
||||
require.NoError(t, err)
|
||||
t.Run("with a valid version", func(t *testing.T) {
|
||||
expVersion := metadata.GrafanaVersion
|
||||
require.Equal(t, expVersion, version)
|
||||
})
|
||||
|
||||
t.Run("with a valid release mode from the built-in list", func(t *testing.T) {
|
||||
expMode := metadata.ReleaseMode
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expMode, releaseMode)
|
||||
})
|
||||
}
|
||||
|
||||
func setUpEnv(t *testing.T, envMap map[string]string) {
|
||||
t.Helper()
|
||||
|
||||
os.Clearenv()
|
||||
t.Setenv("DRONE_COMMIT", "abcd12345")
|
||||
for k, v := range envMap {
|
||||
t.Setenv(k, v)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"version": "9.2.0-pre"
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package config
|
||||
|
||||
// Variant is the OS / Architecture combination that Grafana can be compiled for.
|
||||
type Variant string
|
||||
|
||||
const (
|
||||
VariantLinuxAmd64 Variant = "linux-amd64"
|
||||
VariantLinuxAmd64Musl Variant = "linux-amd64-musl"
|
||||
VariantArmV6 Variant = "linux-armv6"
|
||||
VariantArmV7 Variant = "linux-armv7"
|
||||
VariantArmV7Musl Variant = "linux-armv7-musl"
|
||||
VariantArm64 Variant = "linux-arm64"
|
||||
VariantArm64Musl Variant = "linux-arm64-musl"
|
||||
VariantDarwinAmd64 Variant = "darwin-amd64"
|
||||
VariantWindowsAmd64 Variant = "windows-amd64"
|
||||
)
|
||||
|
||||
var AllVariants = []Variant{
|
||||
// https://github.com/golang/go/issues/58425 disabling arm builds until go issue is resolved
|
||||
// VariantArmV6,
|
||||
// VariantArmV7,
|
||||
// VariantArmV7Musl,
|
||||
VariantArm64,
|
||||
VariantArm64Musl,
|
||||
VariantDarwinAmd64,
|
||||
VariantWindowsAmd64,
|
||||
VariantLinuxAmd64,
|
||||
VariantLinuxAmd64Musl,
|
||||
}
|
||||
|
||||
// Architecture is an allowed value in the GOARCH environment variable.
|
||||
type Architecture string
|
||||
|
||||
const (
|
||||
ArchAMD64 Architecture = "amd64"
|
||||
ArchARMv6 Architecture = "armv6"
|
||||
ArchARMv7 Architecture = "armv7"
|
||||
ArchARM64 Architecture = "arm64"
|
||||
ArchARMHF Architecture = "armhf"
|
||||
ArchARMHFP Architecture = "armhfp"
|
||||
ArchARM Architecture = "arm"
|
||||
)
|
||||
|
||||
type OS string
|
||||
|
||||
const (
|
||||
OSWindows OS = "windows"
|
||||
OSDarwin OS = "darwin"
|
||||
OSLinux OS = "linux"
|
||||
)
|
||||
|
||||
type LibC string
|
||||
|
||||
const (
|
||||
LibCMusl = "musl"
|
||||
)
|
||||
|
||||
// Distribution is the base os image where the Grafana image is built on.
|
||||
type Distribution string
|
||||
|
||||
const (
|
||||
Ubuntu Distribution = "ubuntu"
|
||||
Alpine Distribution = "alpine"
|
||||
)
|
||||
@@ -5,72 +5,9 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/git"
|
||||
)
|
||||
|
||||
type Metadata struct {
|
||||
GrafanaVersion string `json:"version,omitempty"`
|
||||
ReleaseMode ReleaseMode `json:"releaseMode,omitempty"`
|
||||
GrabplVersion string `json:"grabplVersion,omitempty"`
|
||||
CurrentCommit string `json:"currentCommit,omitempty"`
|
||||
}
|
||||
|
||||
type ReleaseMode struct {
|
||||
Mode VersionMode `json:"mode,omitempty"`
|
||||
IsPreview bool `json:"IsPreview,omitempty"`
|
||||
IsTest bool `json:"isTest,omitempty"`
|
||||
}
|
||||
|
||||
type PluginSignature struct {
|
||||
Sign bool `json:"sign,omitempty"`
|
||||
AdminSign bool `json:"adminSign,omitempty"`
|
||||
}
|
||||
|
||||
type Docker struct {
|
||||
ShouldSave bool `json:"shouldSave,omitempty"`
|
||||
Distribution []Distribution `json:"distribution,omitempty"`
|
||||
Architectures []Architecture `json:"archs,omitempty"`
|
||||
PrereleaseBucket string `json:"prereleaseBucket,omitempty"`
|
||||
}
|
||||
|
||||
type Buckets struct {
|
||||
Artifacts string `json:"artifacts,omitempty"`
|
||||
ArtifactsEnterprise2 string `json:"artifactsEnterprise2,omitempty"`
|
||||
CDNAssets string `json:"CDNAssets,omitempty"`
|
||||
CDNAssetsDir string `json:"CDNAssetsDir,omitempty"`
|
||||
Storybook string `json:"storybook,omitempty"`
|
||||
StorybookSrcDir string `json:"storybookSrcDir,omitempty"`
|
||||
}
|
||||
|
||||
// BuildConfig represents the struct that defines all of the different variables used to build Grafana
|
||||
type BuildConfig struct {
|
||||
Variants []Variant `json:"variants,omitempty"`
|
||||
PluginSignature PluginSignature `json:"pluginSignature,omitempty"`
|
||||
Docker Docker `json:"docker,omitempty"`
|
||||
Buckets Buckets `json:"buckets,omitempty"`
|
||||
}
|
||||
|
||||
func (md *Metadata) GetReleaseMode() (ReleaseMode, error) {
|
||||
return md.ReleaseMode, nil
|
||||
}
|
||||
|
||||
// VersionMap is a map of versions. Each key of the Versions map is an event that uses the config as the value for that key.
|
||||
// For example, the 'pull_request' key will have data in it that might cause Grafana to be built differently in a pull request,
|
||||
// than the way it will be built in 'main'
|
||||
type VersionMap map[VersionMode]BuildConfig
|
||||
|
||||
// GetBuildConfig reads the embedded config.json and decodes it.
|
||||
func GetBuildConfig(mode VersionMode) (*BuildConfig, error) {
|
||||
if v, ok := Versions[mode]; ok {
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("mode '%s' not found in version list", mode)
|
||||
}
|
||||
|
||||
// GenerateGrafanaVersion gets the Grafana version from the package.json
|
||||
func GenerateGrafanaVersion(buildID, grafanaDir string) (string, error) {
|
||||
version, err := GetPackageJSONVersion(grafanaDir)
|
||||
@@ -106,55 +43,6 @@ func GetPackageJSONVersion(grafanaDir string) (string, error) {
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func CheckDroneTargetBranch() (VersionMode, error) {
|
||||
rePRCheckBranch := git.PRCheckRegexp()
|
||||
reRlsBranch := regexp.MustCompile(`^v\d+\.\d+\.x$`)
|
||||
target := os.Getenv("DRONE_TARGET_BRANCH")
|
||||
if target == "" {
|
||||
return "", fmt.Errorf("failed to get DRONE_TARGET_BRANCH environmental variable")
|
||||
} else if target == string(MainMode) {
|
||||
return MainMode, nil
|
||||
}
|
||||
if reRlsBranch.MatchString(target) {
|
||||
return ReleaseBranchMode, nil
|
||||
}
|
||||
if rePRCheckBranch.MatchString(target) {
|
||||
return PullRequestMode, nil
|
||||
}
|
||||
fmt.Printf("unrecognized target branch: %s, defaulting to %s\n", target, PullRequestMode)
|
||||
return PullRequestMode, nil
|
||||
}
|
||||
|
||||
func CheckSemverSuffix() (ReleaseMode, error) {
|
||||
rePreviewRls := regexp.MustCompile(`preview.*`)
|
||||
reTestRls := regexp.MustCompile(`test.*`)
|
||||
reCloudRls := regexp.MustCompile(`cloud.*`)
|
||||
tagSuffix, ok := os.LookupEnv("DRONE_SEMVER_PRERELEASE")
|
||||
if !ok || tagSuffix == "" {
|
||||
fmt.Println("DRONE_SEMVER_PRERELEASE doesn't exist for a tag, this is a release event...")
|
||||
return ReleaseMode{Mode: TagMode}, nil
|
||||
}
|
||||
switch {
|
||||
case rePreviewRls.MatchString(tagSuffix):
|
||||
return ReleaseMode{Mode: TagMode, IsPreview: true}, nil
|
||||
case reTestRls.MatchString(tagSuffix):
|
||||
return ReleaseMode{Mode: TagMode, IsTest: true}, nil
|
||||
case reCloudRls.MatchString(tagSuffix):
|
||||
return ReleaseMode{Mode: CloudMode}, nil
|
||||
default:
|
||||
fmt.Printf("DRONE_SEMVER_PRERELEASE is custom string, release event with %s suffix\n", tagSuffix)
|
||||
return ReleaseMode{Mode: TagMode}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetDroneCommit() (string, error) {
|
||||
commit := strings.TrimSpace(os.Getenv("DRONE_COMMIT"))
|
||||
if commit == "" {
|
||||
return "", fmt.Errorf("the environment variable DRONE_COMMIT is missing")
|
||||
}
|
||||
return commit, nil
|
||||
}
|
||||
|
||||
func shortenBuildID(buildID string) string {
|
||||
buildID = strings.ReplaceAll(buildID, "-", "")
|
||||
if len(buildID) < 9 {
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package config
|
||||
|
||||
// VersionMode defines the source event that created a release or published version
|
||||
type VersionMode string
|
||||
|
||||
const (
|
||||
MainMode VersionMode = "main"
|
||||
TagMode VersionMode = "release"
|
||||
ReleaseBranchMode VersionMode = "branch"
|
||||
PullRequestMode VersionMode = "pull_request"
|
||||
DownstreamMode VersionMode = "downstream"
|
||||
Enterprise2Mode VersionMode = "enterprise2"
|
||||
CronjobMode VersionMode = "cron"
|
||||
CloudMode VersionMode = "cloud"
|
||||
)
|
||||
|
||||
const (
|
||||
Tag = "tag"
|
||||
PullRequest = "pull_request"
|
||||
Push = "push"
|
||||
Custom = "custom"
|
||||
Promote = "promote"
|
||||
Cronjob = "cron"
|
||||
)
|
||||
|
||||
const (
|
||||
MainBranch = "main"
|
||||
)
|
||||
@@ -1,237 +0,0 @@
|
||||
package config
|
||||
|
||||
var Versions = VersionMap{
|
||||
PullRequestMode: {
|
||||
Variants: []Variant{
|
||||
VariantLinuxAmd64,
|
||||
VariantLinuxAmd64Musl,
|
||||
VariantDarwinAmd64,
|
||||
VariantWindowsAmd64,
|
||||
VariantArm64,
|
||||
VariantArm64Musl,
|
||||
},
|
||||
PluginSignature: PluginSignature{
|
||||
Sign: false,
|
||||
AdminSign: false,
|
||||
},
|
||||
Docker: Docker{
|
||||
ShouldSave: false,
|
||||
Architectures: []Architecture{
|
||||
ArchAMD64,
|
||||
ArchARM64,
|
||||
},
|
||||
Distribution: []Distribution{
|
||||
Alpine,
|
||||
},
|
||||
},
|
||||
},
|
||||
MainMode: {
|
||||
Variants: []Variant{
|
||||
VariantArmV6,
|
||||
VariantArmV7,
|
||||
VariantArmV7Musl,
|
||||
VariantArm64,
|
||||
VariantArm64Musl,
|
||||
VariantDarwinAmd64,
|
||||
VariantWindowsAmd64,
|
||||
VariantLinuxAmd64,
|
||||
VariantLinuxAmd64Musl,
|
||||
},
|
||||
PluginSignature: PluginSignature{
|
||||
Sign: true,
|
||||
AdminSign: true,
|
||||
},
|
||||
Docker: Docker{
|
||||
ShouldSave: false,
|
||||
Architectures: []Architecture{
|
||||
ArchAMD64,
|
||||
ArchARM64,
|
||||
ArchARMv7, // GOARCH=ARM is used for both armv6 and armv7. They are differentiated by the GOARM variable.
|
||||
},
|
||||
Distribution: []Distribution{
|
||||
Alpine,
|
||||
Ubuntu,
|
||||
},
|
||||
},
|
||||
Buckets: Buckets{
|
||||
Artifacts: "grafana-downloads",
|
||||
ArtifactsEnterprise2: "grafana-downloads-enterprise2",
|
||||
CDNAssets: "grafana-static-assets",
|
||||
Storybook: "grafana-storybook",
|
||||
},
|
||||
},
|
||||
DownstreamMode: {
|
||||
Variants: []Variant{
|
||||
VariantArmV6,
|
||||
VariantArmV7,
|
||||
VariantArmV7Musl,
|
||||
VariantArm64,
|
||||
VariantArm64Musl,
|
||||
VariantDarwinAmd64,
|
||||
VariantWindowsAmd64,
|
||||
VariantLinuxAmd64,
|
||||
VariantLinuxAmd64Musl,
|
||||
},
|
||||
PluginSignature: PluginSignature{
|
||||
Sign: true,
|
||||
AdminSign: true,
|
||||
},
|
||||
Docker: Docker{
|
||||
ShouldSave: true,
|
||||
Architectures: []Architecture{
|
||||
ArchAMD64,
|
||||
ArchARM64,
|
||||
ArchARMv7, // GOARCH=ARM is used for both armv6 and armv7. They are differentiated by the GOARM variable.
|
||||
},
|
||||
Distribution: []Distribution{
|
||||
Alpine,
|
||||
Ubuntu,
|
||||
},
|
||||
},
|
||||
Buckets: Buckets{
|
||||
Artifacts: "grafana-downloads",
|
||||
ArtifactsEnterprise2: "grafana-downloads-enterprise2",
|
||||
CDNAssets: "grafana-static-assets",
|
||||
},
|
||||
},
|
||||
ReleaseBranchMode: {
|
||||
Variants: []Variant{
|
||||
VariantArmV6,
|
||||
VariantArmV7,
|
||||
VariantArmV7Musl,
|
||||
VariantArm64,
|
||||
VariantArm64Musl,
|
||||
VariantDarwinAmd64,
|
||||
VariantWindowsAmd64,
|
||||
VariantLinuxAmd64,
|
||||
VariantLinuxAmd64Musl,
|
||||
},
|
||||
PluginSignature: PluginSignature{
|
||||
Sign: true,
|
||||
AdminSign: true,
|
||||
},
|
||||
Docker: Docker{
|
||||
ShouldSave: true,
|
||||
Architectures: []Architecture{
|
||||
ArchAMD64,
|
||||
ArchARM64,
|
||||
ArchARMv7,
|
||||
},
|
||||
Distribution: []Distribution{
|
||||
Alpine,
|
||||
Ubuntu,
|
||||
},
|
||||
PrereleaseBucket: "grafana-prerelease/artifacts/docker",
|
||||
},
|
||||
Buckets: Buckets{
|
||||
Artifacts: "grafana-downloads",
|
||||
ArtifactsEnterprise2: "grafana-downloads-enterprise2",
|
||||
CDNAssets: "grafana-static-assets",
|
||||
},
|
||||
},
|
||||
TagMode: {
|
||||
Variants: []Variant{
|
||||
VariantArmV6,
|
||||
VariantArmV7,
|
||||
VariantArmV7Musl,
|
||||
VariantArm64,
|
||||
VariantArm64Musl,
|
||||
VariantDarwinAmd64,
|
||||
VariantWindowsAmd64,
|
||||
VariantLinuxAmd64,
|
||||
VariantLinuxAmd64Musl,
|
||||
},
|
||||
PluginSignature: PluginSignature{
|
||||
Sign: true,
|
||||
AdminSign: true,
|
||||
},
|
||||
Docker: Docker{
|
||||
ShouldSave: true,
|
||||
Architectures: []Architecture{
|
||||
ArchAMD64,
|
||||
ArchARM64,
|
||||
ArchARMv7,
|
||||
},
|
||||
Distribution: []Distribution{
|
||||
Alpine,
|
||||
Ubuntu,
|
||||
},
|
||||
PrereleaseBucket: "grafana-prerelease/artifacts/docker",
|
||||
},
|
||||
Buckets: Buckets{
|
||||
Artifacts: "grafana-prerelease/artifacts/downloads",
|
||||
ArtifactsEnterprise2: "grafana-prerelease/artifacts/downloads-enterprise2",
|
||||
CDNAssets: "grafana-prerelease",
|
||||
CDNAssetsDir: "artifacts/static-assets",
|
||||
Storybook: "grafana-prerelease",
|
||||
StorybookSrcDir: "artifacts/storybook",
|
||||
},
|
||||
},
|
||||
Enterprise2Mode: {
|
||||
Variants: []Variant{
|
||||
VariantArmV6,
|
||||
VariantArmV7,
|
||||
VariantArmV7Musl,
|
||||
VariantArm64,
|
||||
VariantArm64Musl,
|
||||
VariantDarwinAmd64,
|
||||
VariantWindowsAmd64,
|
||||
VariantLinuxAmd64,
|
||||
VariantLinuxAmd64Musl,
|
||||
},
|
||||
PluginSignature: PluginSignature{
|
||||
Sign: true,
|
||||
AdminSign: true,
|
||||
},
|
||||
Docker: Docker{
|
||||
ShouldSave: true,
|
||||
Architectures: []Architecture{
|
||||
ArchAMD64,
|
||||
ArchARM64,
|
||||
ArchARMv7,
|
||||
},
|
||||
Distribution: []Distribution{
|
||||
Alpine,
|
||||
Ubuntu,
|
||||
},
|
||||
PrereleaseBucket: "grafana-prerelease/artifacts/docker",
|
||||
},
|
||||
Buckets: Buckets{
|
||||
Artifacts: "grafana-prerelease/artifacts/downloads",
|
||||
ArtifactsEnterprise2: "grafana-prerelease/artifacts/downloads-enterprise2",
|
||||
CDNAssets: "grafana-prerelease",
|
||||
CDNAssetsDir: "artifacts/static-assets",
|
||||
Storybook: "grafana-prerelease",
|
||||
StorybookSrcDir: "artifacts/storybook",
|
||||
},
|
||||
},
|
||||
CloudMode: {
|
||||
Variants: []Variant{
|
||||
VariantLinuxAmd64Musl,
|
||||
// We still need this variant to build the .deb file
|
||||
VariantLinuxAmd64,
|
||||
},
|
||||
PluginSignature: PluginSignature{
|
||||
Sign: true,
|
||||
AdminSign: true,
|
||||
},
|
||||
Docker: Docker{
|
||||
ShouldSave: true,
|
||||
Architectures: []Architecture{
|
||||
ArchAMD64,
|
||||
},
|
||||
Distribution: []Distribution{
|
||||
Alpine,
|
||||
},
|
||||
PrereleaseBucket: "grafana-prerelease/artifacts/docker",
|
||||
},
|
||||
Buckets: Buckets{
|
||||
Artifacts: "grafana-prerelease/artifacts/downloads",
|
||||
ArtifactsEnterprise2: "grafana-prerelease/artifacts/downloads-enterprise2",
|
||||
CDNAssets: "grafana-prerelease",
|
||||
CDNAssetsDir: "artifacts/static-assets",
|
||||
Storybook: "grafana-prerelease",
|
||||
StorybookSrcDir: "artifacts/storybook",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -106,7 +106,9 @@ func GolangContainer(
|
||||
WithExec([]string{"wget", "-q", "http://dl.grafana.com/ci/arm-linux-musleabihf-cross.tgz", "-P", "/toolchain"}).
|
||||
WithExec([]string{"tar", "-xf", "/toolchain/arm-linux-musleabihf-cross.tgz", "-C", "/toolchain"}).
|
||||
WithExec([]string{"wget", "-q", "https://dl.grafana.com/ci/s390x-linux-musl-cross.tgz", "-P", "/toolchain"}).
|
||||
WithExec([]string{"tar", "-xf", "/toolchain/s390x-linux-musl-cross.tgz", "-C", "/toolchain"})
|
||||
WithExec([]string{"tar", "-xf", "/toolchain/s390x-linux-musl-cross.tgz", "-C", "/toolchain"}).
|
||||
WithExec([]string{"wget", "-q", "https://dl.grafana.com/ci/riscv64-linux-musl-cross.tgz", "-P", "/toolchain"}).
|
||||
WithExec([]string{"tar", "-xf", "/toolchain/riscv64-linux-musl-cross.tgz", "-C", "/toolchain"})
|
||||
|
||||
return WithGoEnv(log, container, distro, opts)
|
||||
}
|
||||
|
||||
@@ -264,6 +264,22 @@ func BuildOptsStaticS390X(distro Distribution, experiments []string, tags []stri
|
||||
}
|
||||
}
|
||||
|
||||
// BuildOptsStaticS390X builds Grafana statically for the s390x arch
|
||||
func BuildOptsStaticRiscv64(distro Distribution, experiments []string, tags []string) *GoBuildOpts {
|
||||
var (
|
||||
os, _ = OSAndArch(distro)
|
||||
)
|
||||
|
||||
return &GoBuildOpts{
|
||||
CC: "/toolchain/riscv64-linux-musl-cross/bin/riscv64-linux-musl-gcc",
|
||||
CXX: "/toolchain/riscv64-linux-musl-cross/bin/riscv64-linux-musl-cpp",
|
||||
ExperimentalFlags: experiments,
|
||||
OS: os,
|
||||
Arch: "riscv64",
|
||||
CGOEnabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
func StdZigBuildOpts(distro Distribution, experiments []string, tags []string) *GoBuildOpts {
|
||||
var (
|
||||
os, arch = OSAndArch(distro)
|
||||
@@ -331,7 +347,7 @@ var DistributionGoOpts = map[Distribution]DistroBuildOptsFunc{
|
||||
DistLinuxAMD64: StdZigBuildOpts,
|
||||
DistLinuxAMD64Dynamic: StdZigBuildOpts,
|
||||
DistPlan9AMD64: StdZigBuildOpts,
|
||||
DistLinuxRISCV64: StdZigBuildOpts,
|
||||
DistLinuxRISCV64: BuildOptsStaticRiscv64,
|
||||
|
||||
// Non-Linux distros can have whatever they want in CC and CXX; it'll get overridden
|
||||
// but it's probably not best to rely on that.
|
||||
|
||||
@@ -26,27 +26,7 @@ func (c *CLI) App() *cli.App {
|
||||
Name: "grafana-build",
|
||||
Usage: "A build tool for Grafana",
|
||||
Commands: []*cli.Command{
|
||||
// Legacy commands, should eventually be completely replaced by what's in "artifacts"
|
||||
{
|
||||
Name: "package",
|
||||
Subcommands: []*cli.Command{
|
||||
PackagePublishCommand,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "docker",
|
||||
Subcommands: []*cli.Command{
|
||||
DockerPublishCommand,
|
||||
},
|
||||
},
|
||||
ProImageCommand,
|
||||
{
|
||||
Name: "npm",
|
||||
Subcommands: []*cli.Command{
|
||||
PublishNPMCommand,
|
||||
},
|
||||
},
|
||||
GCOMCommand,
|
||||
c.ArtifactsCommand(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipelines"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var DockerPublishCommand = &cli.Command{
|
||||
Name: "publish",
|
||||
Action: PipelineActionWithPackageInput(pipelines.PublishDocker),
|
||||
Usage: "Using a grafana.docker.tar.gz as input (ideally one built using the 'package' command), publish a docker image and manifest",
|
||||
Flags: JoinFlagsWithDefault(
|
||||
PackageInputFlags,
|
||||
DockerFlags,
|
||||
DockerPublishFlags,
|
||||
GCPFlags,
|
||||
ConcurrencyFlags,
|
||||
),
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var GCOMCommand = &cli.Command{
|
||||
Name: "gcom",
|
||||
Description: "Executes requests to grafana.com",
|
||||
Subcommands: []*cli.Command{GCOMPublishCommand},
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipelines"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var GCOMPublishCommand = &cli.Command{
|
||||
Name: "publish",
|
||||
Action: PipelineActionWithPackageInput(pipelines.PublishGCOM),
|
||||
Description: "Publishes a grafana.tar.gz (ideally one built using the 'package' command) to grafana.com (--destination will be the download path)",
|
||||
Flags: JoinFlagsWithDefault(
|
||||
GCOMFlags,
|
||||
PackageInputFlags,
|
||||
PublishFlags,
|
||||
ConcurrencyFlags,
|
||||
),
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipelines"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Deprecated: use the Artifact type instead
|
||||
func PipelineActionWithPackageInput(pf pipelines.PipelineFuncWithPackageInput) cli.ActionFunc {
|
||||
return func(c *cli.Context) error {
|
||||
var (
|
||||
ctx = c.Context
|
||||
opts = []dagger.ClientOpt{}
|
||||
)
|
||||
if c.Bool("verbose") {
|
||||
opts = append(opts, dagger.WithLogOutput(os.Stderr))
|
||||
}
|
||||
client, err := dagger.Connect(ctx, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(c *dagger.Client) {
|
||||
if err := c.Close(); err != nil {
|
||||
fmt.Println("error closing dagger client:", err)
|
||||
}
|
||||
}(client)
|
||||
|
||||
args, err := pipelines.PipelineArgsFromContext(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args.PackageInputOpts.Packages) == 0 {
|
||||
return errors.New("expected at least one package from a '--package' flag")
|
||||
}
|
||||
|
||||
if err := pf(ctx, client, args); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipelines"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var PublishNPMCommand = &cli.Command{
|
||||
Name: "publish",
|
||||
Action: PipelineActionWithPackageInput(pipelines.PublishNPM),
|
||||
Usage: "Using a grafana.tar.gz as input (ideally one built using the 'package' command), take the npm artifacts and publish them on NPM.",
|
||||
Flags: JoinFlagsWithDefault(
|
||||
PackageInputFlags,
|
||||
NPMFlags,
|
||||
GCPFlags,
|
||||
ConcurrencyFlags,
|
||||
),
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipelines"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var PackagePublishCommand = &cli.Command{
|
||||
Name: "publish",
|
||||
Action: PipelineActionWithPackageInput(pipelines.PublishPackage),
|
||||
Description: "Publishes a grafana.tar.gz (ideally one built using the 'package' command) in the destination directory (--destination)",
|
||||
Flags: JoinFlagsWithDefault(
|
||||
PackageInputFlags,
|
||||
PublishFlags,
|
||||
GCPFlags,
|
||||
ConcurrencyFlags,
|
||||
),
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipelines"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var ProImageCommand = &cli.Command{
|
||||
Name: "pro-image",
|
||||
Action: PipelineActionWithPackageInput(pipelines.ProImage),
|
||||
Description: "Creates a hosted grafana pro image",
|
||||
Flags: JoinFlagsWithDefault(ProImageFlags, GCPFlags, PackageInputFlags),
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
package containers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"dagger.io/dagger"
|
||||
)
|
||||
|
||||
const GoogleCloudImage = "google/cloud-sdk:alpine"
|
||||
|
||||
// GCPAuthenticator injects authentication information into the provided container.
|
||||
type GCPAuthenticator interface {
|
||||
Authenticate(*dagger.Client, *dagger.Container) (*dagger.Container, error)
|
||||
}
|
||||
|
||||
// GCPServiceAccount satisfies GCPAuthenticator and injects the provided ServiceAccount into the filesystem and adds a 'gcloud auth activate-service-account'
|
||||
type GCPServiceAccount struct {
|
||||
DaggerFile *dagger.File
|
||||
JSONFile string
|
||||
}
|
||||
|
||||
func (a *GCPServiceAccount) Authenticate(d *dagger.Client, c *dagger.Container) (*dagger.Container, error) {
|
||||
if a.DaggerFile == nil && a.JSONFile == "" {
|
||||
return nil, fmt.Errorf("GCPServiceAccount authentication missed JSONFile AND DaggerFile")
|
||||
}
|
||||
var container *dagger.Container
|
||||
|
||||
if a.JSONFile != "" {
|
||||
container = c.WithMountedFile(
|
||||
"/opt/service_account.json",
|
||||
d.Host().Directory(filepath.Dir(a.JSONFile)).File(filepath.Base(a.JSONFile)),
|
||||
)
|
||||
}
|
||||
|
||||
if a.DaggerFile != nil {
|
||||
container = c.WithMountedFile("/opt/service_account.json", a.DaggerFile)
|
||||
}
|
||||
|
||||
return container.WithExec([]string{"gcloud", "auth", "activate-service-account", "--key-file", "/opt/service_account.json"}), nil
|
||||
}
|
||||
|
||||
func NewGCPServiceAccount(filepath string) *GCPServiceAccount {
|
||||
return &GCPServiceAccount{
|
||||
JSONFile: filepath,
|
||||
}
|
||||
}
|
||||
|
||||
func NewGCPServiceAccountWithFile(file *dagger.File) *GCPServiceAccount {
|
||||
return &GCPServiceAccount{
|
||||
DaggerFile: file,
|
||||
}
|
||||
}
|
||||
|
||||
// InheritedServiceAccount uses `gcloud` command in the current shell to get the GCS credentials.
|
||||
// This type should really only be used when running locally.
|
||||
type GCPInheritedAuth struct{}
|
||||
|
||||
func (a *GCPInheritedAuth) Authenticate(d *dagger.Client, c *dagger.Container) (*dagger.Container, error) {
|
||||
if val, ok := os.LookupEnv("GOOGLE_APPLICATION_CREDENTIALS"); ok {
|
||||
return c.WithMountedDirectory("/auth/credentials.json", d.Host().Directory(val)).WithEnvVariable("GOOGLE_APPLICATION_CREDENTIALS", "/auth/credentials.json"), nil
|
||||
}
|
||||
|
||||
cfg, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.WithMountedDirectory("/root/.config/gcloud", d.Host().Directory(filepath.Join(cfg, ".config", "gcloud"))), nil
|
||||
}
|
||||
|
||||
func GCSUploadDirectory(d *dagger.Client, image string, auth GCPAuthenticator, dir *dagger.Directory, dst string) (*dagger.Container, error) {
|
||||
container := d.Container().From(image).
|
||||
WithMountedDirectory("/src", dir)
|
||||
|
||||
var err error
|
||||
container, err = auth.Authenticate(d, container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secret := d.SetSecret("gcs-destination", dst)
|
||||
container = container.WithSecretVariable("GCS_DESTINATION", secret)
|
||||
|
||||
return container.WithExec([]string{"/bin/sh", "-c", "gcloud storage cp -r /src/* ${GCS_DESTINATION}"}), nil
|
||||
}
|
||||
|
||||
func GCSDownloadFile(d *dagger.Client, image string, auth GCPAuthenticator, url string) (*dagger.File, error) {
|
||||
var (
|
||||
container = d.Container().From(image)
|
||||
err error
|
||||
r = rand.Int()
|
||||
)
|
||||
|
||||
container, err = auth.Authenticate(d, container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secret := d.SetSecret("gcs-download-url", url)
|
||||
file := container.
|
||||
WithEnvVariable("RAND", strconv.Itoa(r)).
|
||||
WithSecretVariable("GCS_DOWNLOAD_URL", secret).
|
||||
WithExec([]string{"/bin/sh", "-c", "gcloud storage cp ${GCS_DOWNLOAD_URL} /src/file"}).
|
||||
File("/src/file")
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func GCSAuth(d *dagger.Client, opts *GCPOpts) GCPAuthenticator {
|
||||
var auth GCPAuthenticator = &GCPInheritedAuth{}
|
||||
// The order of operations:
|
||||
// 1. Try to use base64 key.
|
||||
// 2. Try to use gcp-service-account-key (path to a file).
|
||||
// 3. Try mounting $XDG_CONFIG_HOME/gcloud
|
||||
if key := opts.ServiceAccountKeyBase64; key != "" {
|
||||
secret := d.SetSecret("gcp-sa-key-base64", key)
|
||||
// Write key to a file in an alpine container...
|
||||
file := d.Container().From("alpine").
|
||||
WithSecretVariable("GCP_SERVICE_ACCOUNT_KEY_BASE64", secret).
|
||||
WithExec([]string{"/bin/sh", "-c", "echo $GCP_SERVICE_ACCOUNT_KEY_BASE64 | base64 -d > /key.json"}).
|
||||
File("/key.json")
|
||||
|
||||
auth = NewGCPServiceAccountWithFile(file)
|
||||
} else if key := opts.ServiceAccountKey; key != "" {
|
||||
auth = NewGCPServiceAccount(key)
|
||||
}
|
||||
|
||||
return auth
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package containers
|
||||
|
||||
import "github.com/grafana/grafana/pkg/build/daggerbuild/cliutil"
|
||||
|
||||
type ProImageOpts struct {
|
||||
// Github token used to clone private repositories.
|
||||
GitHubToken string
|
||||
|
||||
// The path to a Grafana debian package.
|
||||
Deb string
|
||||
|
||||
// The Grafana version.
|
||||
GrafanaVersion string
|
||||
|
||||
// The docker image tag.
|
||||
ImageTag string
|
||||
|
||||
// The docker image repo.
|
||||
Repo string
|
||||
|
||||
// The release type.
|
||||
ReleaseType string
|
||||
|
||||
// True if the pro image should be pushed to the container registry.
|
||||
Push bool
|
||||
|
||||
// The container registry that the image should be pushed to. Required if Push is true.
|
||||
ContainerRegistry string
|
||||
}
|
||||
|
||||
func ProImageOptsFromFlags(c cliutil.CLIContext) *ProImageOpts {
|
||||
return &ProImageOpts{
|
||||
GitHubToken: c.String("github-token"),
|
||||
Deb: c.String("deb"),
|
||||
GrafanaVersion: c.String("grafana-version"),
|
||||
ImageTag: c.String("image-tag"),
|
||||
Repo: c.String("repo"),
|
||||
ReleaseType: c.String("release-type"),
|
||||
Push: c.Bool("push"),
|
||||
ContainerRegistry: c.String("registry"),
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package containers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/cliutil"
|
||||
)
|
||||
|
||||
type PackageInputOpts struct {
|
||||
// Name is used when overriding the artifact that is being produced. This is used in very specific scenarios where
|
||||
// the source package's name does not match the package's metadata name.
|
||||
Name string
|
||||
Packages []string
|
||||
}
|
||||
|
||||
func PackageInputOptsFromFlags(c cliutil.CLIContext) *PackageInputOpts {
|
||||
return &PackageInputOpts{
|
||||
Name: c.String("name"),
|
||||
Packages: c.StringSlice("package"),
|
||||
}
|
||||
}
|
||||
|
||||
// GetPackage uses the PackageInputOpts to get a Grafana package, either from the local filesystem (if the package is of type 'file://...')
|
||||
// or Google Cloud Storage if the package is a 'gs://' URL.
|
||||
func GetPackages(ctx context.Context, d *dagger.Client, packageOpts *PackageInputOpts, gcpOpts *GCPOpts) ([]*dagger.File, error) {
|
||||
files := make([]*dagger.File, len(packageOpts.Packages))
|
||||
for i, pkg := range packageOpts.Packages {
|
||||
u, err := url.Parse(pkg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var file *dagger.File
|
||||
switch u.Scheme {
|
||||
case "file", "fs":
|
||||
p := strings.TrimPrefix(u.String(), u.Scheme+"://")
|
||||
f, err := getLocalPackage(ctx, d, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file = f
|
||||
case "gs":
|
||||
f, err := getGCSPackage(ctx, d, gcpOpts, u.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file = f
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", ErrorUnrecognizedScheme, u.Scheme)
|
||||
}
|
||||
|
||||
files[i] = file
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func getLocalPackage(ctx context.Context, d *dagger.Client, file string) (*dagger.File, error) {
|
||||
// pending https://github.com/dagger/dagger/issues/4745
|
||||
return d.Host().Directory(filepath.Dir(file)).File(filepath.Base(file)), nil
|
||||
}
|
||||
|
||||
func getGCSPackage(ctx context.Context, d *dagger.Client, opts *GCPOpts, gcsURL string) (*dagger.File, error) {
|
||||
auth := GCSAuth(d, opts)
|
||||
return GCSDownloadFile(d, GoogleCloudImage, auth, gcsURL)
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package containers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"dagger.io/dagger"
|
||||
)
|
||||
|
||||
func publishLocalDir(ctx context.Context, dir *dagger.Directory, dst string) error {
|
||||
if _, err := dir.Export(ctx, dst); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func publishGCSDir(ctx context.Context, d *dagger.Client, dir *dagger.Directory, opts *GCPOpts, dst string) error {
|
||||
auth := GCSAuth(d, opts)
|
||||
uploader, err := GCSUploadDirectory(d, GoogleCloudImage, auth, dir, dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := ExitError(ctx, uploader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PublishDirectory publishes a directory to the given destination.
|
||||
func PublishDirectory(ctx context.Context, d *dagger.Client, dir *dagger.Directory, opts *GCPOpts, dst string) (string, error) {
|
||||
log.Println("Publishing directory", dst)
|
||||
u, err := url.Parse(dst)
|
||||
if err != nil {
|
||||
// If the destination URL is not a URL then we can assume that it's just a filepath.
|
||||
if err := publishLocalDir(ctx, dir, dst); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "file", "fs":
|
||||
dst := strings.TrimPrefix(u.String(), u.Scheme+"://")
|
||||
if err := publishLocalDir(ctx, dir, dst); err != nil {
|
||||
return "", err
|
||||
}
|
||||
case "gs":
|
||||
if err := publishGCSDir(ctx, d, dir, opts, dst); err != nil {
|
||||
return "", err
|
||||
}
|
||||
default:
|
||||
return "", fmt.Errorf("%w: '%s'", ErrorUnrecognizedScheme, u.Scheme)
|
||||
}
|
||||
|
||||
return dst, nil
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package gcom
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/cliutil"
|
||||
)
|
||||
|
||||
// GCOMOpts are options used when making requests to grafana.com.
|
||||
type GCOMOpts struct {
|
||||
URL *url.URL
|
||||
DownloadURL *url.URL
|
||||
ApiKey string
|
||||
Beta bool
|
||||
Nightly bool
|
||||
}
|
||||
|
||||
func GCOMOptsFromFlags(c cliutil.CLIContext) (*GCOMOpts, error) {
|
||||
apiUrl, err := url.Parse(c.String("api-url"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
downloadUrl, err := url.Parse(c.String("download-url"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &GCOMOpts{
|
||||
URL: apiUrl,
|
||||
DownloadURL: downloadUrl,
|
||||
ApiKey: c.String("api-key"),
|
||||
Beta: c.Bool("beta"),
|
||||
Nightly: c.Bool("nightly"),
|
||||
}, nil
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package gcom
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"dagger.io/dagger"
|
||||
)
|
||||
|
||||
type GCOMVersionPayload struct {
|
||||
Version string `json:"version"`
|
||||
ReleaseDate string `json:"releaseDate"`
|
||||
Stable bool `json:"stable"`
|
||||
Beta bool `json:"beta"`
|
||||
Nightly bool `json:"nightly"`
|
||||
WhatsNewURL string `json:"whatsNewUrl"`
|
||||
ReleaseNotesURL string `json:"releaseNotesUrl"`
|
||||
}
|
||||
|
||||
type GCOMPackagePayload struct {
|
||||
OS string `json:"os"`
|
||||
URL string `json:"url"`
|
||||
Sha256 string `json:"sha256"`
|
||||
Arch string `json:"arch"`
|
||||
}
|
||||
|
||||
// PublishGCOMVersion publishes a version to grafana.com.
|
||||
func PublishGCOMVersion(ctx context.Context, d *dagger.Client, versionPayload *GCOMVersionPayload, opts *GCOMOpts) (string, error) {
|
||||
versionApiUrl := opts.URL.JoinPath("/versions")
|
||||
|
||||
jsonVersionPayload, err := json.Marshal(versionPayload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
apiKeySecret := d.SetSecret("gcom-api-key", opts.ApiKey)
|
||||
|
||||
return d.Container().From("alpine/curl").
|
||||
WithSecretVariable("GCOM_API_KEY", apiKeySecret).
|
||||
WithExec([]string{"/bin/sh", "-c", fmt.Sprintf(`curl -H "Content-Type: application/json" -H "Authorization: Bearer $GCOM_API_KEY" -d '%s' %s`, string(jsonVersionPayload), versionApiUrl.String())}).
|
||||
Stdout(ctx)
|
||||
}
|
||||
|
||||
// PublishGCOMPackage publishes a package to grafana.com.
|
||||
func PublishGCOMPackage(ctx context.Context, d *dagger.Client, packagePayload *GCOMPackagePayload, opts *GCOMOpts, version string) (string, error) {
|
||||
packagesApiUrl := opts.URL.JoinPath("/versions/", version, "/packages")
|
||||
|
||||
jsonPackagePayload, err := json.Marshal(packagePayload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
apiKeySecret := d.SetSecret("gcom-api-key", opts.ApiKey)
|
||||
|
||||
return d.Container().From("alpine/curl").
|
||||
WithSecretVariable("GCOM_API_KEY", apiKeySecret).
|
||||
WithExec([]string{"/bin/sh", "-c", fmt.Sprintf(`curl -H "Content-Type: application/json" -H "Authorization: Bearer $GCOM_API_KEY" -d '%s' %s`, string(jsonPackagePayload), packagesApiUrl.String())}).
|
||||
Stdout(ctx)
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
package pipelines
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/containers"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/docker"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
func ImageManifest(tag string) string {
|
||||
log.Println(tag)
|
||||
log.Println(tag)
|
||||
log.Println(tag)
|
||||
log.Println(tag)
|
||||
manifest := strings.ReplaceAll(tag, "-image-tags", "")
|
||||
lastDash := strings.LastIndex(manifest, "-")
|
||||
return manifest[:lastDash]
|
||||
}
|
||||
|
||||
func LatestManifest(tag string) string {
|
||||
suffix := ""
|
||||
if strings.Contains(tag, "ubuntu") {
|
||||
suffix = "-ubuntu"
|
||||
}
|
||||
|
||||
manifest := strings.ReplaceAll(tag, "-image-tags", "")
|
||||
manifestImage := strings.Split(manifest, ":")[0]
|
||||
return strings.Join([]string{manifestImage, fmt.Sprintf("latest%s", suffix)}, ":")
|
||||
}
|
||||
|
||||
// PublishDocker is a pipeline that uses a grafana.docker.tar.gz as input and publishes a Docker image to a container registry or repository.
|
||||
// Grafana's Dockerfile should support supplying a tar.gz using a --build-arg.
|
||||
func PublishDocker(ctx context.Context, d *dagger.Client, args PipelineArgs) error {
|
||||
opts := args.DockerOpts
|
||||
packages, err := containers.GetPackages(ctx, d, args.PackageInputOpts, args.GCPOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
wg = &errgroup.Group{}
|
||||
sm = semaphore.NewWeighted(args.ConcurrencyOpts.Parallel)
|
||||
)
|
||||
|
||||
manifestTags := make(map[string][]string)
|
||||
for i, name := range args.PackageInputOpts.Packages {
|
||||
// For each package we retrieve the tags grafana-image-tags and grafana-oss-image-tags, or grafana-enterprise-image-tags
|
||||
format := opts.TagFormat
|
||||
if strings.Contains(name, "ubuntu") {
|
||||
format = opts.UbuntuTagFormat
|
||||
}
|
||||
|
||||
tarOpts := TarOptsFromFileName(name)
|
||||
|
||||
tags, err := docker.Tags(opts.Org, opts.Registry, []string{opts.Repository}, format, tarOpts.NameOpts())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Println(tags)
|
||||
for _, tag := range tags {
|
||||
// For each tag we publish an image and add the tag to the list of tags for a specific manifest
|
||||
// Since each package has a maximum of 2 tags, this for loop will only run twice on a worst case scenario
|
||||
manifest := ImageManifest(tag)
|
||||
manifestTags[manifest] = append(manifestTags[manifest], tag)
|
||||
|
||||
if opts.Latest {
|
||||
manifest := LatestManifest(tag)
|
||||
manifestTags[manifest] = append(manifestTags[manifest], tag)
|
||||
}
|
||||
|
||||
wg.Go(PublishPackageImageFunc(ctx, sm, d, packages[i], tag, opts))
|
||||
}
|
||||
}
|
||||
|
||||
if err := wg.Wait(); err != nil {
|
||||
// Wait for all images to be published
|
||||
return err
|
||||
}
|
||||
|
||||
for manifest, tags := range manifestTags {
|
||||
// Publish each manifest
|
||||
wg.Go(PublishDockerManifestFunc(ctx, sm, d, manifest, tags, opts))
|
||||
}
|
||||
|
||||
return wg.Wait()
|
||||
}
|
||||
|
||||
func PublishPackageImageFunc(ctx context.Context, sm *semaphore.Weighted, d *dagger.Client, pkg *dagger.File, tag string, opts *docker.DockerOpts) func() error {
|
||||
return func() error {
|
||||
log.Printf("[%s] Attempting to publish image", tag)
|
||||
log.Printf("[%s] Acquiring semaphore", tag)
|
||||
if err := sm.Acquire(ctx, 1); err != nil {
|
||||
return fmt.Errorf("failed to acquire semaphore: %w", err)
|
||||
}
|
||||
defer sm.Release(1)
|
||||
log.Printf("[%s] Acquired semaphore", tag)
|
||||
|
||||
log.Printf("[%s] Publishing image", tag)
|
||||
out, err := docker.PublishPackageImage(ctx, d, pkg, tag, opts.Username, opts.Password, opts.Registry)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] error: %w", tag, err)
|
||||
}
|
||||
log.Printf("[%s] Done publishing image", tag)
|
||||
|
||||
if _, err := fmt.Fprintln(Stdout, out); err != nil {
|
||||
return fmt.Errorf("error writing to stdout: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func PublishDockerManifestFunc(ctx context.Context, sm *semaphore.Weighted, d *dagger.Client, manifest string, tags []string, opts *docker.DockerOpts) func() error {
|
||||
return func() error {
|
||||
log.Printf("[%s] Attempting to publish manifest", manifest)
|
||||
log.Printf("[%s] Acquiring semaphore", manifest)
|
||||
if err := sm.Acquire(ctx, 1); err != nil {
|
||||
return fmt.Errorf("failed to acquire semaphore: %w", err)
|
||||
}
|
||||
defer sm.Release(1)
|
||||
log.Printf("[%s] Acquired semaphore", manifest)
|
||||
|
||||
log.Printf("[%s] Publishing manifest", manifest)
|
||||
out, err := docker.PublishManifest(ctx, d, manifest, tags, opts.Username, opts.Password, opts.Registry)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] error: %w", manifest, err)
|
||||
}
|
||||
log.Printf("[%s] Done publishing manifest", manifest)
|
||||
|
||||
if _, err := fmt.Fprintln(Stdout, out); err != nil {
|
||||
return fmt.Errorf("error writing to stdout: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package pipelines_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipelines"
|
||||
)
|
||||
|
||||
func TestImageManifest(t *testing.T) {
|
||||
manifests := map[string]string{
|
||||
"docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-amd64": "docker.io/grafana/grafana:1.2.3-test.1.2.3",
|
||||
"docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-amd64": "docker.io/grafana/grafana-oss:1.2.3-test.1.2.3",
|
||||
"docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-arm64": "docker.io/grafana/grafana:1.2.3-test.1.2.3",
|
||||
"docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-arm64": "docker.io/grafana/grafana-oss:1.2.3-test.1.2.3",
|
||||
"docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-ubuntu-amd64": "docker.io/grafana/grafana:1.2.3-test.1.2.3-ubuntu",
|
||||
"docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-ubuntu-amd64": "docker.io/grafana/grafana-oss:1.2.3-test.1.2.3-ubuntu",
|
||||
"docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-ubuntu-arm64": "docker.io/grafana/grafana:1.2.3-test.1.2.3-ubuntu",
|
||||
"docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-ubuntu-arm64": "docker.io/grafana/grafana-oss:1.2.3-test.1.2.3-ubuntu",
|
||||
"docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-amd64": "docker.io/grafana/grafana-enterprise:1.2.3-test.1.2.3",
|
||||
"docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-arm64": "docker.io/grafana/grafana-enterprise:1.2.3-test.1.2.3",
|
||||
"docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-ubuntu-amd64": "docker.io/grafana/grafana-enterprise:1.2.3-test.1.2.3-ubuntu",
|
||||
"docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-ubuntu-arm64": "docker.io/grafana/grafana-enterprise:1.2.3-test.1.2.3-ubuntu",
|
||||
}
|
||||
|
||||
for k, v := range manifests {
|
||||
if n := pipelines.ImageManifest(k); n != v {
|
||||
t.Errorf("Expected '%s' manifest to equal '%s' but got '%s'", k, v, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLatestManifest(t *testing.T) {
|
||||
manifests := map[string]string{
|
||||
"docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-amd64": "docker.io/grafana/grafana:latest",
|
||||
"docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-amd64": "docker.io/grafana/grafana-oss:latest",
|
||||
"docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-arm64": "docker.io/grafana/grafana:latest",
|
||||
"docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-arm64": "docker.io/grafana/grafana-oss:latest",
|
||||
"docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-ubuntu-amd64": "docker.io/grafana/grafana:latest-ubuntu",
|
||||
"docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-ubuntu-amd64": "docker.io/grafana/grafana-oss:latest-ubuntu",
|
||||
"docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-ubuntu-arm64": "docker.io/grafana/grafana:latest-ubuntu",
|
||||
"docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-ubuntu-arm64": "docker.io/grafana/grafana-oss:latest-ubuntu",
|
||||
"docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-amd64": "docker.io/grafana/grafana-enterprise:latest",
|
||||
"docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-arm64": "docker.io/grafana/grafana-enterprise:latest",
|
||||
"docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-ubuntu-amd64": "docker.io/grafana/grafana-enterprise:latest-ubuntu",
|
||||
"docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-ubuntu-arm64": "docker.io/grafana/grafana-enterprise:latest-ubuntu",
|
||||
}
|
||||
|
||||
for k, v := range manifests {
|
||||
if n := pipelines.LatestManifest(k); n != v {
|
||||
t.Errorf("Expected '%s' manifest to equal '%s' but got '%s'", k, v, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
package pipelines_test
|
||||
|
||||
//
|
||||
// func TestImageName(t *testing.T) {
|
||||
// // Normally I don't advocate for abstracting tests using test cases
|
||||
// // but I think in this case I would really like to get a clearer view into what docker image tags will be produced.
|
||||
// // Be sure that if you add additional test cases to this that you don't use formatting or concatenation; it should be obvious when looking at the test
|
||||
// // what the expected output should be. And that value should not change based on another value.
|
||||
// type tc struct {
|
||||
// Description string
|
||||
// Tags []string
|
||||
// BaseImage pipelines.BaseImage
|
||||
// DockerOpts *containers.DockerOpts
|
||||
// TarOpts pipelines.TarFileOpts
|
||||
// }
|
||||
//
|
||||
// var (
|
||||
// version = "v1.2.3-test.1.2.3"
|
||||
// )
|
||||
//
|
||||
// cases := []tc{
|
||||
// {
|
||||
// Description: "Grafana docker images are created for both the 'docker.io/grafana/grafana-image-tags' and 'docker.io/grafana/grafana-oss-image-tags' repositories. Alpine images have no suffix.",
|
||||
// TarOpts: pipelines.TarFileOpts{
|
||||
// Edition: "",
|
||||
// Distro: "linux/amd64",
|
||||
// Version: version,
|
||||
// },
|
||||
// DockerOpts: &containers.DockerOpts{
|
||||
// Org: "grafana",
|
||||
// Registry: "docker.io",
|
||||
// TagFormat: pipelines.DefaultTagFormat,
|
||||
// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat,
|
||||
// },
|
||||
// BaseImage: pipelines.BaseImageAlpine,
|
||||
// Tags: []string{
|
||||
// "docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-amd64",
|
||||
// "docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-amd64",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// Description: "Grafana docker images are created for both the 'docker.io/grafana/grafana-image-tags' and 'docker.io/grafana/grafana-oss-image-tags' repositories. ARM64 images have a -arm64 suffix. Alpine images have no suffix.",
|
||||
// TarOpts: pipelines.TarFileOpts{
|
||||
// Edition: "",
|
||||
// Distro: "linux/arm64",
|
||||
// Version: version,
|
||||
// },
|
||||
// DockerOpts: &containers.DockerOpts{
|
||||
// Org: "grafana",
|
||||
// Registry: "docker.io",
|
||||
// TagFormat: pipelines.DefaultTagFormat,
|
||||
// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat,
|
||||
// },
|
||||
// BaseImage: pipelines.BaseImageAlpine,
|
||||
// Tags: []string{
|
||||
// "docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-arm64",
|
||||
// "docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-arm64",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// Description: "Grafana docker images are created for both the 'docker.io/grafana/grafana-image-tags' and 'docker.io/grafana/grafana-oss-image-tags' repositories. Ubuntu images have a '-ubuntu' suffix.",
|
||||
// TarOpts: pipelines.TarFileOpts{
|
||||
// Edition: "",
|
||||
// Distro: "linux/amd64",
|
||||
// Version: version,
|
||||
// },
|
||||
// DockerOpts: &containers.DockerOpts{
|
||||
// Org: "grafana",
|
||||
// Registry: "docker.io",
|
||||
// TagFormat: pipelines.DefaultTagFormat,
|
||||
// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat,
|
||||
// },
|
||||
// BaseImage: pipelines.BaseImageUbuntu,
|
||||
// Tags: []string{
|
||||
// "docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-ubuntu-amd64",
|
||||
// "docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-ubuntu-amd64",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// Description: "Grafana docker images are created for both the 'docker.io/grafana/grafana-image-tags' and 'docker.io/grafana/grafana-oss-image-tags' repositories. ARM64 images have an -arm64 suffix. Ubuntu images have a '-ubuntu' suffix.",
|
||||
// TarOpts: pipelines.TarFileOpts{
|
||||
// Edition: "",
|
||||
// Distro: "linux/arm64",
|
||||
// Version: version,
|
||||
// },
|
||||
// DockerOpts: &containers.DockerOpts{
|
||||
// Org: "grafana",
|
||||
// Registry: "docker.io",
|
||||
// TagFormat: pipelines.DefaultTagFormat,
|
||||
// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat,
|
||||
// },
|
||||
// BaseImage: pipelines.BaseImageUbuntu,
|
||||
// Tags: []string{
|
||||
// "docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-ubuntu-arm64",
|
||||
// "docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-ubuntu-arm64",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// Description: "Enterprise docker images are created for only the docker.io/grafana/grafana-enterprise-image-tags repository. Alpine images have no suffix.",
|
||||
// TarOpts: pipelines.TarFileOpts{
|
||||
// Edition: "enterprise",
|
||||
// Distro: "linux/amd64",
|
||||
// Version: version,
|
||||
// },
|
||||
// DockerOpts: &containers.DockerOpts{
|
||||
// Org: "grafana",
|
||||
// Registry: "docker.io",
|
||||
// TagFormat: pipelines.DefaultTagFormat,
|
||||
// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat,
|
||||
// },
|
||||
// BaseImage: pipelines.BaseImageAlpine,
|
||||
// Tags: []string{
|
||||
// "docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-amd64",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// Description: "Enterprise docker images are created for only the docker.io/grafana/grafana-enterprise-image-tags repository. ARM64 images have an -arm64 suffix. Alpine images have no suffix.",
|
||||
// TarOpts: pipelines.TarFileOpts{
|
||||
// Edition: "enterprise",
|
||||
// Distro: "linux/arm64",
|
||||
// Version: version,
|
||||
// },
|
||||
// DockerOpts: &containers.DockerOpts{
|
||||
// Org: "grafana",
|
||||
// Registry: "docker.io",
|
||||
// TagFormat: pipelines.DefaultTagFormat,
|
||||
// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat,
|
||||
// },
|
||||
// BaseImage: pipelines.BaseImageAlpine,
|
||||
// Tags: []string{
|
||||
// "docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-arm64",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// Description: "Enterprise docker images are created for only the docker.io/grafana/grafana-enterprise-image-tags repository. Ubuntu images have a '-ubuntu' suffix.",
|
||||
// TarOpts: pipelines.TarFileOpts{
|
||||
// Edition: "enterprise",
|
||||
// Distro: "linux/amd64",
|
||||
// Version: version,
|
||||
// },
|
||||
// DockerOpts: &containers.DockerOpts{
|
||||
// Org: "grafana",
|
||||
// Registry: "docker.io",
|
||||
// TagFormat: pipelines.DefaultTagFormat,
|
||||
// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat,
|
||||
// },
|
||||
// BaseImage: pipelines.BaseImageUbuntu,
|
||||
// Tags: []string{
|
||||
// "docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-ubuntu-amd64",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// Description: "Enterprise docker images are created for only the docker.io/grafana/grafana-enterprise-image-tags repository. ARM64 images have an -arm64 suffix. Ubuntu images have a '-ubuntu' suffix.",
|
||||
// TarOpts: pipelines.TarFileOpts{
|
||||
// Edition: "enterprise",
|
||||
// Distro: "linux/arm64",
|
||||
// Version: version,
|
||||
// },
|
||||
// DockerOpts: &containers.DockerOpts{
|
||||
// Org: "grafana",
|
||||
// Registry: "docker.io",
|
||||
// TagFormat: pipelines.DefaultTagFormat,
|
||||
// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat,
|
||||
// },
|
||||
// BaseImage: pipelines.BaseImageUbuntu,
|
||||
// Tags: []string{
|
||||
// "docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-ubuntu-arm64",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// Description: "Grafana docker images are created for both the 'registry.io/org/grafana-image-tags' and 'registry.io/org/grafana-oss-image-tags' repositories. Alpine images have no suffix.",
|
||||
// TarOpts: pipelines.TarFileOpts{
|
||||
// Edition: "",
|
||||
// Distro: "linux/amd64",
|
||||
// Version: version,
|
||||
// },
|
||||
// DockerOpts: &containers.DockerOpts{
|
||||
// Org: "org",
|
||||
// Registry: "registry.io",
|
||||
// TagFormat: pipelines.DefaultTagFormat,
|
||||
// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat,
|
||||
// },
|
||||
// BaseImage: pipelines.BaseImageAlpine,
|
||||
// Tags: []string{
|
||||
// "registry.io/org/grafana-image-tags:1.2.3-test.1.2.3-amd64",
|
||||
// "registry.io/org/grafana-oss-image-tags:1.2.3-test.1.2.3-amd64",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// Description: "Grafana docker images are created for only the 'registry.io/org/grafana-dev' repository. Alpine images have no suffix.",
|
||||
// TarOpts: pipelines.TarFileOpts{
|
||||
// Edition: "",
|
||||
// Distro: "linux/amd64",
|
||||
// Version: version,
|
||||
// },
|
||||
// DockerOpts: &containers.DockerOpts{
|
||||
// Org: "org",
|
||||
// Registry: "registry.io",
|
||||
// Repository: "grafana-dev",
|
||||
// TagFormat: pipelines.DefaultTagFormat,
|
||||
// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat,
|
||||
// },
|
||||
// BaseImage: pipelines.BaseImageAlpine,
|
||||
// Tags: []string{
|
||||
// "registry.io/org/grafana-dev:1.2.3-test.1.2.3-amd64",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// Description: "Grafana docker images are created for only the 'registry.io/org/grafana-dev' repository.",
|
||||
// TarOpts: pipelines.TarFileOpts{
|
||||
// Edition: "",
|
||||
// Distro: "linux/amd64",
|
||||
// Version: version,
|
||||
// },
|
||||
// DockerOpts: &containers.DockerOpts{
|
||||
// Org: "org",
|
||||
// Registry: "registry.io",
|
||||
// Repository: "grafana-dev",
|
||||
// TagFormat: pipelines.DefaultTagFormat,
|
||||
// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat,
|
||||
// },
|
||||
// BaseImage: pipelines.BaseImageUbuntu,
|
||||
// Tags: []string{
|
||||
// "registry.io/org/grafana-dev:1.2.3-test.1.2.3-ubuntu-amd64",
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// for n, test := range cases {
|
||||
// t.Run(fmt.Sprintf("[%d / %d] %s", n+1, len(cases), test.Description), func(t *testing.T) {
|
||||
// expect := sort.StringSlice(test.Tags)
|
||||
// res, err := pipelines.GrafanaImageTags(test.BaseImage, test.DockerOpts, test.TarOpts)
|
||||
// if err != nil {
|
||||
// t.Fatal("Unexpected error:", err.Error())
|
||||
// }
|
||||
//
|
||||
// for i := range expect {
|
||||
// e := expect[i]
|
||||
// r := res[i]
|
||||
// if e != r {
|
||||
// t.Errorf("[%d / %d]\nExpected '%s'\nReceived '%s'", i+1, len(expect), e, r)
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
@@ -1,154 +0,0 @@
|
||||
package pipelines
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/containers"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/gcom"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
func VersionPayloadFromFileName(name string, opts *gcom.GCOMOpts) *gcom.GCOMVersionPayload {
|
||||
var (
|
||||
tarOpts = TarOptsFromFileName(name)
|
||||
splitVersion = strings.Split(tarOpts.Version, ".")
|
||||
stable = true
|
||||
nightly = false
|
||||
beta = false
|
||||
)
|
||||
|
||||
if opts.Beta {
|
||||
stable = false
|
||||
beta = true
|
||||
}
|
||||
if opts.Nightly {
|
||||
stable = false
|
||||
beta = false
|
||||
nightly = true
|
||||
}
|
||||
|
||||
return &gcom.GCOMVersionPayload{
|
||||
Version: tarOpts.Version,
|
||||
ReleaseDate: time.Now().Format(time.RFC3339Nano),
|
||||
Stable: stable,
|
||||
Beta: beta,
|
||||
Nightly: nightly,
|
||||
WhatsNewURL: fmt.Sprintf("https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-v%s-%s/", splitVersion[0], splitVersion[1]),
|
||||
ReleaseNotesURL: "https://grafana.com/docs/grafana/next/release-notes/",
|
||||
}
|
||||
}
|
||||
|
||||
func PackagePayloadFromFile(ctx context.Context, d *dagger.Client, name string, file *dagger.File, opts *gcom.GCOMOpts) (*gcom.GCOMPackagePayload, error) {
|
||||
tarOpts := TarOptsFromFileName(name)
|
||||
ext := filepath.Ext(name)
|
||||
os, _ := backend.OSAndArch(tarOpts.Distro)
|
||||
arch := strings.ReplaceAll(backend.FullArch(tarOpts.Distro), "/", "")
|
||||
|
||||
if os == "windows" {
|
||||
os = "win"
|
||||
}
|
||||
|
||||
if ext == ".deb" {
|
||||
os = "deb"
|
||||
}
|
||||
if ext == ".rpm" {
|
||||
os = "rhel"
|
||||
}
|
||||
if ext == ".exe" {
|
||||
os = "win-installer"
|
||||
}
|
||||
|
||||
sha256, err := containers.Sha256(d, file).Contents(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &gcom.GCOMPackagePayload{
|
||||
OS: os,
|
||||
URL: opts.DownloadURL.JoinPath(name).String(),
|
||||
Sha256: sha256,
|
||||
Arch: arch,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func PublishGCOM(ctx context.Context, d *dagger.Client, args PipelineArgs) error {
|
||||
var (
|
||||
opts = args.GCOMOpts
|
||||
wg = &errgroup.Group{}
|
||||
sm = semaphore.NewWeighted(args.ConcurrencyOpts.Parallel)
|
||||
)
|
||||
|
||||
packages, err := containers.GetPackages(ctx, d, args.PackageInputOpts, args.GCPOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Extract the package versions
|
||||
versionPayloads := make(map[string]*gcom.GCOMVersionPayload)
|
||||
for _, name := range args.PackageInputOpts.Packages {
|
||||
tarOpts := TarOptsFromFileName(name)
|
||||
if _, ok := versionPayloads[tarOpts.Version]; !ok {
|
||||
log.Printf("[%s] Building version payload", tarOpts.Version)
|
||||
versionPayloads[tarOpts.Version] = VersionPayloadFromFileName(name, opts)
|
||||
}
|
||||
}
|
||||
|
||||
// Publish each version only once
|
||||
for _, p := range versionPayloads {
|
||||
log.Printf("[%s] Attempting to publish version", p.Version)
|
||||
out, err := gcom.PublishGCOMVersion(ctx, d, p, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("[%s] Done publishing version", p.Version)
|
||||
if _, err := fmt.Fprintln(Stdout, strings.ReplaceAll(out, "\n", "")); err != nil {
|
||||
return fmt.Errorf("error writing to stdout: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Publish the package(s)
|
||||
for i, name := range args.PackageInputOpts.Packages {
|
||||
wg.Go(PublishGCOMPackageFunc(ctx, sm, d, opts, name, packages[i]))
|
||||
}
|
||||
return wg.Wait()
|
||||
}
|
||||
|
||||
func PublishGCOMPackageFunc(ctx context.Context, sm *semaphore.Weighted, d *dagger.Client, opts *gcom.GCOMOpts, path string, file *dagger.File) func() error {
|
||||
return func() error {
|
||||
name := filepath.Base(path)
|
||||
tarOpts := TarOptsFromFileName(name)
|
||||
log.Printf("[%s] Attempting to publish package", name)
|
||||
log.Printf("[%s] Acquiring semaphore", name)
|
||||
if err := sm.Acquire(ctx, 1); err != nil {
|
||||
return fmt.Errorf("failed to acquire semaphore: %w", err)
|
||||
}
|
||||
defer sm.Release(1)
|
||||
log.Printf("[%s] Acquired semaphore", name)
|
||||
|
||||
log.Printf("[%s] Building package payload", name)
|
||||
packagePayload, err := PackagePayloadFromFile(ctx, d, name, file, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] error: %w", name, err)
|
||||
}
|
||||
|
||||
log.Printf("[%s] Publishing package", name)
|
||||
out, err := gcom.PublishGCOMPackage(ctx, d, packagePayload, opts, tarOpts.Version)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] error: %w", name, err)
|
||||
}
|
||||
log.Printf("[%s] Done publishing package", name)
|
||||
|
||||
if _, err := fmt.Fprintln(Stdout, strings.ReplaceAll(out, "\n", "")); err != nil {
|
||||
return fmt.Errorf("error writing to stdout: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package pipelines
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/containers"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/frontend"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
func PublishNPM(ctx context.Context, d *dagger.Client, args PipelineArgs) error {
|
||||
var (
|
||||
wg = &errgroup.Group{}
|
||||
sm = semaphore.NewWeighted(args.ConcurrencyOpts.Parallel)
|
||||
)
|
||||
|
||||
packages, err := containers.GetPackages(ctx, d, args.PackageInputOpts, args.GCPOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Extract the package(s)
|
||||
for i := range args.PackageInputOpts.Packages {
|
||||
var (
|
||||
// name = ReplaceExt(v, "")
|
||||
targz = packages[i]
|
||||
)
|
||||
|
||||
artifacts := containers.ExtractedArchive(d, targz).Directory("npm-artifacts")
|
||||
|
||||
entries, err := artifacts.Entries(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, path := range entries {
|
||||
wg.Go(PublishNPMFunc(ctx, sm, d, artifacts.File(path), path, args.NpmToken, args.NpmRegistry, args.NpmTags))
|
||||
}
|
||||
}
|
||||
return wg.Wait()
|
||||
}
|
||||
|
||||
func PublishNPMFunc(ctx context.Context, sm *semaphore.Weighted, d *dagger.Client, pkg *dagger.File, path, token, registry string, tags []string) func() error {
|
||||
return func() error {
|
||||
log.Printf("[%s] Attempting to publish package", path)
|
||||
log.Printf("[%s] Acquiring semaphore", path)
|
||||
if err := sm.Acquire(ctx, 1); err != nil {
|
||||
return fmt.Errorf("failed to acquire semaphore: %w", err)
|
||||
}
|
||||
defer sm.Release(1)
|
||||
log.Printf("[%s] Acquired semaphore", path)
|
||||
|
||||
log.Printf("[%s] Publishing package", path)
|
||||
out, err := frontend.PublishNPM(ctx, d, pkg, token, registry, tags)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] error: %w", path, err)
|
||||
}
|
||||
log.Printf("[%s] Done publishing package", path)
|
||||
|
||||
if _, err := fmt.Fprintln(Stdout, out); err != nil {
|
||||
return fmt.Errorf("error writing to stdout: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package pipelines
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
|
||||
)
|
||||
|
||||
type TarFileOpts struct {
|
||||
// Name is the name of the product in the package. 99% of the time, this will be "grafana" or "grafana-enterprise".
|
||||
Name string
|
||||
Version string
|
||||
BuildID string
|
||||
// Edition is the flavor text after "grafana-", like "enterprise".
|
||||
Edition string
|
||||
Distro backend.Distribution
|
||||
Suffix string
|
||||
}
|
||||
|
||||
func (opts *TarFileOpts) NameOpts() packages.NameOpts {
|
||||
return packages.NameOpts{
|
||||
// Name is the name of the product in the package. 99% of the time, this will be "grafana" or "grafana-enterprise".
|
||||
Name: packages.Name(opts.Name),
|
||||
Version: opts.Version,
|
||||
BuildID: opts.BuildID,
|
||||
Distro: opts.Distro,
|
||||
}
|
||||
}
|
||||
|
||||
func WithoutExt(name string) string {
|
||||
ext := filepath.Ext(name)
|
||||
n := strings.TrimSuffix(name, ext)
|
||||
|
||||
// Explicitly handle `.gz` which might will also probably have a `.tar` extension as well.
|
||||
if ext == ".gz" {
|
||||
n = strings.TrimSuffix(n, ".ubuntu.docker.tar")
|
||||
n = strings.TrimSuffix(n, ".docker.tar")
|
||||
n = strings.TrimSuffix(n, ".tar")
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func TarOptsFromFileName(filename string) TarFileOpts {
|
||||
filename = filepath.Base(filename)
|
||||
n := WithoutExt(filename)
|
||||
components := strings.Split(n, "_")
|
||||
if len(components) != 5 {
|
||||
return TarFileOpts{}
|
||||
}
|
||||
|
||||
var (
|
||||
name = components[0]
|
||||
version = components[1]
|
||||
buildID = components[2]
|
||||
os = components[3]
|
||||
arch = components[4]
|
||||
)
|
||||
if archv := strings.Split(arch, "-"); len(archv) == 2 {
|
||||
// The reverse operation of removing the 'v' for 'arm' because the golang environment variable
|
||||
// GOARM doesn't want it, but the docker --platform flag either doesn't care or does want it.
|
||||
if archv[0] == "arm" {
|
||||
archv[1] = "v" + archv[1]
|
||||
}
|
||||
|
||||
// arm-7 should become arm/v7
|
||||
arch = strings.Join([]string{archv[0], archv[1]}, "/")
|
||||
}
|
||||
edition := ""
|
||||
suffix := ""
|
||||
if n := strings.Split(name, "-"); len(n) != 1 {
|
||||
edition = strings.Join(n[1:], "-")
|
||||
suffix = fmt.Sprintf("-%s", n[1])
|
||||
}
|
||||
|
||||
return TarFileOpts{
|
||||
Name: name,
|
||||
Edition: edition,
|
||||
Version: version,
|
||||
BuildID: buildID,
|
||||
Distro: backend.Distribution(strings.Join([]string{os, arch}, "/")),
|
||||
Suffix: suffix,
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package pipelines_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipelines"
|
||||
)
|
||||
|
||||
func TestWithoutExt(t *testing.T) {
|
||||
names := map[string]string{
|
||||
"grafana_v1.0.1-test_333_plan9_amd64.tar.gz": "grafana_v1.0.1-test_333_plan9_amd64",
|
||||
"grafana-enterprise_v1.0.1-test_333_plan9_amd64.tar.gz": "grafana-enterprise_v1.0.1-test_333_plan9_amd64",
|
||||
"grafana-enterprise_v1.0.1-test_333_plan9_arm-6.tar.gz": "grafana-enterprise_v1.0.1-test_333_plan9_arm-6",
|
||||
"grafana-enterprise_v1.0.1-test_333_plan9_amd64.deb": "grafana-enterprise_v1.0.1-test_333_plan9_amd64",
|
||||
"grafana-enterprise_v1.0.1-test_333_plan9_arm-6.deb": "grafana-enterprise_v1.0.1-test_333_plan9_arm-6",
|
||||
"grafana-enterprise_v1.0.1-test_333_plan9_arm-6.docker.tar.gz": "grafana-enterprise_v1.0.1-test_333_plan9_arm-6",
|
||||
"grafana-enterprise_v1.0.1-test_333_plan9_arm-6.ubuntu.docker.tar.gz": "grafana-enterprise_v1.0.1-test_333_plan9_arm-6",
|
||||
}
|
||||
|
||||
for k, v := range names {
|
||||
if n := pipelines.WithoutExt(k); n != v {
|
||||
t.Errorf("Expected '%s' without file name to equal '%s' but got '%s'", k, v, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptsFromFile(t *testing.T) {
|
||||
t.Run("It should get the correct tar file opts from a valid name", func(t *testing.T) {
|
||||
name := "grafana-enterprise_v1.0.1-test_333_plan9_arm-6.tar.gz"
|
||||
distro := backend.Distribution("plan9/arm/v6")
|
||||
expect := pipelines.TarFileOpts{
|
||||
Edition: "enterprise",
|
||||
Version: "v1.0.1-test",
|
||||
BuildID: "333",
|
||||
Distro: distro,
|
||||
}
|
||||
got := pipelines.TarOptsFromFileName(name)
|
||||
if got.Edition != expect.Edition {
|
||||
t.Errorf("got.Edition != expect.Edition, expected '%s'", expect.Edition)
|
||||
}
|
||||
if got.Version != expect.Version {
|
||||
t.Errorf("got.Version != expect.Version, expected '%s', got '%s'", expect.Version, got.Version)
|
||||
}
|
||||
if got.BuildID != expect.BuildID {
|
||||
t.Errorf("got.BuildID != expect.BuildID, expected '%s', got '%s'", expect.BuildID, got.BuildID)
|
||||
}
|
||||
if got.Distro != expect.Distro {
|
||||
t.Errorf("got.Distro != expect.Distro, expected '%s', got '%s'", expect.Distro, got.Distro)
|
||||
}
|
||||
})
|
||||
t.Run("It should consider only the basename", func(t *testing.T) {
|
||||
name := "somewhere/grafana-enterprise_v1.0.1-test_333_plan9_arm-6.tar.gz"
|
||||
distro := backend.Distribution("plan9/arm/v6")
|
||||
expect := pipelines.TarFileOpts{
|
||||
Edition: "enterprise",
|
||||
Version: "v1.0.1-test",
|
||||
BuildID: "333",
|
||||
Distro: distro,
|
||||
}
|
||||
got := pipelines.TarOptsFromFileName(name)
|
||||
if got.Edition != expect.Edition {
|
||||
t.Errorf("got.Edition != expect.Edition, expected '%s'", expect.Edition)
|
||||
}
|
||||
if got.Version != expect.Version {
|
||||
t.Errorf("got.Version != expect.Version, expected '%s', got '%s'", expect.Version, got.Version)
|
||||
}
|
||||
if got.BuildID != expect.BuildID {
|
||||
t.Errorf("got.BuildID != expect.BuildID, expected '%s', got '%s'", expect.BuildID, got.BuildID)
|
||||
}
|
||||
if got.Distro != expect.Distro {
|
||||
t.Errorf("got.Distro != expect.Distro, expected '%s', got '%s'", expect.Distro, got.Distro)
|
||||
}
|
||||
})
|
||||
t.Run("It should support editions with multiple hyphens", func(t *testing.T) {
|
||||
name := "somewhere/grafana-enterprise-rpi_v1.0.1-test_333_plan9_arm-6.tar.gz"
|
||||
distro := backend.Distribution("plan9/arm/v6")
|
||||
expect := pipelines.TarFileOpts{
|
||||
Edition: "enterprise-rpi",
|
||||
Version: "v1.0.1-test",
|
||||
BuildID: "333",
|
||||
Distro: distro,
|
||||
}
|
||||
got := pipelines.TarOptsFromFileName(name)
|
||||
if got.Edition != expect.Edition {
|
||||
t.Errorf("got.Edition != expect.Edition, expected '%s', got '%s'", expect.Edition, got.Edition)
|
||||
}
|
||||
if got.Version != expect.Version {
|
||||
t.Errorf("got.Version != expect.Version, expected '%s', got '%s'", expect.Version, got.Version)
|
||||
}
|
||||
if got.BuildID != expect.BuildID {
|
||||
t.Errorf("got.BuildID != expect.BuildID, expected '%s', got '%s'", expect.BuildID, got.BuildID)
|
||||
}
|
||||
if got.Distro != expect.Distro {
|
||||
t.Errorf("got.Distro != expect.Distro, expected '%s', got '%s'", expect.Distro, got.Distro)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package pipelines
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/containers"
|
||||
)
|
||||
|
||||
// PublishPackage takes one or multiple grafana.tar.gz as input and publishes it to a set destination.
|
||||
func PublishPackage(ctx context.Context, d *dagger.Client, args PipelineArgs) error {
|
||||
packages, err := containers.GetPackages(ctx, d, args.PackageInputOpts, args.GCPOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c := d.Container().From("alpine")
|
||||
for i, name := range args.PackageInputOpts.Packages {
|
||||
c = c.WithFile("/dist/"+filepath.Base(name), packages[i])
|
||||
}
|
||||
|
||||
dst, err := containers.PublishDirectory(ctx, d, c.Directory("dist"), args.GCPOpts, args.PublishOpts.Destination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := fmt.Fprintln(os.Stdout, dst); err != nil {
|
||||
return fmt.Errorf("error writing to stdout: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package pipelines_test
|
||||
@@ -1,147 +0,0 @@
|
||||
// package pipelines has functions and types that orchestrate containers.
|
||||
package pipelines
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/cliutil"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/containers"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/docker"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/gcom"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/gpg"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
type PipelineFunc func(context.Context, *dagger.Client, *dagger.Directory, PipelineArgs) error
|
||||
type PipelineFuncWithPackageInput func(context.Context, *dagger.Client, PipelineArgs) error
|
||||
|
||||
func DockerOptsFromFlags(c cliutil.CLIContext) *docker.DockerOpts {
|
||||
return &docker.DockerOpts{
|
||||
Registry: c.String("registry"),
|
||||
AlpineBase: c.String("alpine-base"),
|
||||
UbuntuBase: c.String("ubuntu-base"),
|
||||
Username: c.String("username"),
|
||||
Password: c.String("password"),
|
||||
Org: c.String("org"),
|
||||
Repository: c.String("repo"),
|
||||
Latest: c.Bool("latest"),
|
||||
TagFormat: c.String("tag-format"),
|
||||
UbuntuTagFormat: c.String("ubuntu-tag-format"),
|
||||
}
|
||||
}
|
||||
|
||||
type ConcurrencyOpts struct {
|
||||
Parallel int64
|
||||
}
|
||||
|
||||
func ConcurrencyOptsFromFlags(c cliutil.CLIContext) *ConcurrencyOpts {
|
||||
return &ConcurrencyOpts{
|
||||
Parallel: c.Int64("parallel"),
|
||||
}
|
||||
}
|
||||
|
||||
type PipelineArgs struct {
|
||||
// These arguments are ones that are available at the global level.
|
||||
Verbose bool
|
||||
|
||||
// Platform, where applicable, specifies what platform (linux/arm64, for example) to run the dagger containers on.
|
||||
// This should really only be used if you know what you're doing. misusing this flag can result in really slow builds.
|
||||
// Some example scenarios where you might want to use this:
|
||||
// * You're on linux/amd64 and you're building a docker image for linux/armv7 or linux/arm64
|
||||
// * You're on linux/arm64 and you're building a package for linux/arm64
|
||||
Platform dagger.Platform
|
||||
|
||||
// Context is available for all sub-commands that define their own flags.
|
||||
Context cliutil.CLIContext
|
||||
|
||||
// GrafanaOpts will be populated if the GrafanaFlags are enabled on the current sub-command.
|
||||
// GrafanaOpts *containers.GrafanaOpts
|
||||
|
||||
// PackageOpts will be populated if the PackageFlags are enabled on the current sub-command.
|
||||
// PackageOpts *containers.PackageOpts
|
||||
|
||||
// PublishOpts will be populated if the PublishFlags flags are enabled on the current sub-command
|
||||
// This is set for pipelines that publish artifacts.
|
||||
PublishOpts *containers.PublishOpts
|
||||
|
||||
// PackageInputOpts will be populated if the PackageInputFlags are enabled on current sub-command.
|
||||
// This is set for pipelines that accept a package as input.
|
||||
PackageInputOpts *containers.PackageInputOpts
|
||||
GPGOpts *gpg.GPGOpts
|
||||
DockerOpts *docker.DockerOpts
|
||||
GCPOpts *containers.GCPOpts
|
||||
ConcurrencyOpts *ConcurrencyOpts
|
||||
|
||||
// ProImageOpts will be populated if ProImageFlags are enabled on the current sub-command.
|
||||
ProImageOpts *containers.ProImageOpts
|
||||
|
||||
// NPMOpts will be populated if NPMFlags are enabled on the current sub-command.
|
||||
NpmToken string
|
||||
NpmRegistry string
|
||||
NpmTags []string
|
||||
|
||||
// GCOMOpts will be populated if GCOMFlags are enabled on the current sub-command.
|
||||
GCOMOpts *gcom.GCOMOpts
|
||||
}
|
||||
|
||||
// PipelineArgsFromContext populates a pipelines.PipelineArgs from a CLI context.
|
||||
func PipelineArgsFromContext(ctx context.Context, c cliutil.CLIContext) (PipelineArgs, error) {
|
||||
// Global flags
|
||||
var (
|
||||
verbose = c.Bool("v")
|
||||
platform = c.String("platform")
|
||||
)
|
||||
// grafanaOpts, err := containers.GrafanaOptsFromFlags(ctx, c)
|
||||
// if err != nil {
|
||||
// return PipelineArgs{}, err
|
||||
// }
|
||||
gcomOpts, err := gcom.GCOMOptsFromFlags(c)
|
||||
if err != nil {
|
||||
return PipelineArgs{}, err
|
||||
}
|
||||
|
||||
return PipelineArgs{
|
||||
Context: c,
|
||||
Verbose: verbose,
|
||||
Platform: dagger.Platform(platform),
|
||||
// GrafanaOpts: grafanaOpts,
|
||||
GPGOpts: &gpg.GPGOpts{},
|
||||
// PackageOpts: containers.PackageOptsFromFlags(c),
|
||||
PublishOpts: containers.PublishOptsFromFlags(c),
|
||||
PackageInputOpts: containers.PackageInputOptsFromFlags(c),
|
||||
DockerOpts: DockerOptsFromFlags(c),
|
||||
GCPOpts: containers.GCPOptsFromFlags(c),
|
||||
ConcurrencyOpts: ConcurrencyOptsFromFlags(c),
|
||||
ProImageOpts: containers.ProImageOptsFromFlags(c),
|
||||
GCOMOpts: gcomOpts,
|
||||
NpmToken: c.String("token"),
|
||||
NpmRegistry: c.String("registry"),
|
||||
NpmTags: c.StringSlice("tag"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// InjectPipelineArgsIntoSpan is used to copy some of the arguments passed to
|
||||
// the pipeline into a top-level OpenTelemtry span. Fields that might contain
|
||||
// secrets are left out.
|
||||
func InjectPipelineArgsIntoSpan(span trace.Span, args PipelineArgs) {
|
||||
attributes := make([]attribute.KeyValue, 0, 10)
|
||||
attributes = append(attributes, attribute.String("platform", string(args.Platform)))
|
||||
// if args.GrafanaOpts != nil {
|
||||
// attributes = append(attributes, attribute.String("go-version", args.GrafanaOpts.GoVersion))
|
||||
// attributes = append(attributes, attribute.String("version", args.GrafanaOpts.Version))
|
||||
// attributes = append(attributes, attribute.String("grafana-dir", args.GrafanaOpts.GrafanaDir))
|
||||
// attributes = append(attributes, attribute.String("grafana-ref", args.GrafanaOpts.GrafanaRef))
|
||||
// attributes = append(attributes, attribute.String("enterprise-dir", args.GrafanaOpts.EnterpriseDir))
|
||||
// attributes = append(attributes, attribute.String("enterprise-ref", args.GrafanaOpts.EnterpriseRef))
|
||||
// }
|
||||
// if args.PackageOpts != nil {
|
||||
// distros := []string{}
|
||||
// for _, distro := range args.PackageOpts.Distros {
|
||||
// distros = append(distros, string(distro))
|
||||
// }
|
||||
// attributes = append(attributes, attribute.StringSlice("package-distros", distros))
|
||||
// }
|
||||
span.SetAttributes(attributes...)
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
package pipelines_test
|
||||
|
||||
// type TestCLIContext struct {
|
||||
// Data map[string]interface{}
|
||||
// }
|
||||
//
|
||||
// func (t *TestCLIContext) Bool(key string) bool {
|
||||
// if _, ok := t.Data[key]; !ok {
|
||||
// return false
|
||||
// }
|
||||
//
|
||||
// return t.Data[key].(bool)
|
||||
// }
|
||||
//
|
||||
// func (t *TestCLIContext) String(key string) string {
|
||||
// if _, ok := t.Data[key]; !ok {
|
||||
// return ""
|
||||
// }
|
||||
//
|
||||
// return t.Data[key].(string)
|
||||
// }
|
||||
//
|
||||
// func (t *TestCLIContext) Set(key string, val string) error {
|
||||
// t.Data[key] = val
|
||||
//
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// func (t *TestCLIContext) StringSlice(key string) []string {
|
||||
// if _, ok := t.Data[key]; !ok {
|
||||
// return nil
|
||||
// }
|
||||
// return t.Data[key].([]string)
|
||||
// }
|
||||
//
|
||||
// func (t *TestCLIContext) Path(key string) string {
|
||||
// return t.Data[key].(string)
|
||||
// }
|
||||
//
|
||||
// func (t *TestCLIContext) Int64(key string) int64 {
|
||||
// if _, ok := t.Data[key]; !ok {
|
||||
// return 0
|
||||
// }
|
||||
//
|
||||
// return t.Data[key].(int64)
|
||||
// }
|
||||
//
|
||||
// func TestPipelineArgsFromContext(t *testing.T) {
|
||||
// enterpriseDir, err := os.MkdirTemp("", "grafana-enterprise-*")
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// validData := map[string]interface{}{
|
||||
// "v": true,
|
||||
// "version": "v1.0.0",
|
||||
// "grafana": true,
|
||||
// "grafana-dir": "/grafana",
|
||||
// "grafana-ref": "asdf",
|
||||
// "enterprise": true,
|
||||
// "enterprise-dir": enterpriseDir,
|
||||
// "enterprise-ref": "1234",
|
||||
// "build-id": "build-1234",
|
||||
// "github-token": "",
|
||||
// "sign": false,
|
||||
// }
|
||||
//
|
||||
// // t.Run("It should return a PipelineArgs object if there are no errors", func(t *testing.T) {
|
||||
// // args, err := pipelines.PipelineArgsFromContext(context.Background(), &TestCLIContext{
|
||||
// // Data: validData,
|
||||
// // })
|
||||
// // if err != nil {
|
||||
// // t.Fatal(err)
|
||||
// // }
|
||||
//
|
||||
// // if args.Verbose != true {
|
||||
// // t.Error("args.Verbose should be true")
|
||||
// // }
|
||||
// // // opts := args.GrafanaOpts
|
||||
// // // if opts.Version != "v1.0.0" {
|
||||
// // // t.Error("args.Version should be v1.0.0")
|
||||
// // // }
|
||||
//
|
||||
// // if opts.BuildGrafana != true {
|
||||
// // t.Error("args.BuildGrafana should be true")
|
||||
// // }
|
||||
//
|
||||
// // if opts.GrafanaDir != "/grafana" {
|
||||
// // t.Error("args.GrafanaDir should be /grafana")
|
||||
// // }
|
||||
//
|
||||
// // if opts.GrafanaRef != "asdf" {
|
||||
// // t.Error("args.GrafanaRef should be asdf")
|
||||
// // }
|
||||
//
|
||||
// // if opts.BuildEnterprise != true {
|
||||
// // t.Error("args.Enterprise should be true")
|
||||
// // }
|
||||
//
|
||||
// // if opts.EnterpriseDir != enterpriseDir {
|
||||
// // t.Errorf("args.EnterpriseDir should be %s", enterpriseDir)
|
||||
// // }
|
||||
//
|
||||
// // if opts.EnterpriseRef != "1234" {
|
||||
// // t.Error("args.EnterpriseRef should be 1234")
|
||||
// // }
|
||||
// // })
|
||||
//
|
||||
// // t.Run("If no build ID is provided, a random 12-character string should be given", func(t *testing.T) {
|
||||
// // data := validData
|
||||
// // data["build-id"] = ""
|
||||
// // args, err := pipelines.PipelineArgsFromContext(context.Background(), &TestCLIContext{
|
||||
// // Data: data,
|
||||
// // })
|
||||
// // if err != nil {
|
||||
// // t.Fatal(err)
|
||||
// // }
|
||||
// // opts := args.GrafanaOpts
|
||||
// // if opts.BuildID == "" {
|
||||
// // t.Fatal("BuildID should not be empty")
|
||||
// // }
|
||||
// // if len(opts.BuildID) != 12 {
|
||||
// // t.Fatal("BuildID should be a 12-character string")
|
||||
// // }
|
||||
// // })
|
||||
//
|
||||
// // t.Run("If the --enterprise-ref is set to a non-default value, it should set the enterprise flag to true", func(t *testing.T) {
|
||||
// // data := validData
|
||||
// // data["enterprise"] = false
|
||||
// // data["enterprise-ref"] = "ref-1234"
|
||||
//
|
||||
// // args, err := pipelines.PipelineArgsFromContext(context.Background(), &TestCLIContext{
|
||||
// // Data: data,
|
||||
// // })
|
||||
// // if err != nil {
|
||||
// // t.Fatal(err)
|
||||
// // }
|
||||
// // opts := args.GrafanaOpts
|
||||
// // if opts.BuildEnterprise != true {
|
||||
// // t.Fatal("args.BuildEnterprise should be true")
|
||||
// // }
|
||||
// // })
|
||||
//
|
||||
// t.Run("If the --enterprise-ref is set to a non-default value, it should set the enterprise flag to true", func(t *testing.T) {
|
||||
// data := validData
|
||||
// data["enterprise"] = false
|
||||
// data["enterprise-ref"] = ""
|
||||
// data["enterprise-dir"] = filepath.Join(enterpriseDir, "does-not-exist")
|
||||
//
|
||||
// _, err := pipelines.PipelineArgsFromContext(context.Background(), &TestCLIContext{
|
||||
// Data: data,
|
||||
// })
|
||||
// if err == nil {
|
||||
// t.Fatal("error should not be empty")
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
@@ -1,85 +0,0 @@
|
||||
package pipelines
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/containers"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/git"
|
||||
)
|
||||
|
||||
func ProImage(ctx context.Context, dc *dagger.Client, args PipelineArgs) error {
|
||||
if len(args.PackageInputOpts.Packages) > 1 {
|
||||
return fmt.Errorf("only one package is allowed: packages=%+v", args.PackageInputOpts.Packages)
|
||||
}
|
||||
packages, err := containers.GetPackages(ctx, dc, args.PackageInputOpts, args.GCPOpts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting packages: packages=%+v %w", args.PackageInputOpts.Packages, err)
|
||||
}
|
||||
|
||||
debianPackageFile := packages[0]
|
||||
|
||||
log.Printf("Cloning hosted Grafana...")
|
||||
hostedGrafanaRepo, err := git.CloneWithGitHubToken(dc, args.ProImageOpts.GitHubToken, "https://github.com/grafana/hosted-grafana.git", "main")
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloning hosted-grafana repo: %w", err)
|
||||
}
|
||||
|
||||
socketPath := "/var/run/docker.sock"
|
||||
socket := dc.Host().UnixSocket(socketPath)
|
||||
|
||||
hostedGrafanaImage := fmt.Sprintf("%s/%s:%s", args.ProImageOpts.ContainerRegistry, args.ProImageOpts.Repo, args.ProImageOpts.ImageTag)
|
||||
|
||||
log.Printf("Building hosted Grafana image: %s", hostedGrafanaImage)
|
||||
container := dc.Container().From("google/cloud-sdk:433.0.0-alpine").
|
||||
WithExec([]string{
|
||||
"/bin/sh", "-c",
|
||||
"gcloud auth configure-docker --quiet",
|
||||
}).
|
||||
WithUnixSocket(socketPath, socket).
|
||||
WithDirectory("/src", hostedGrafanaRepo).
|
||||
WithFile("/src/grafana.deb", debianPackageFile).
|
||||
WithWorkdir("/src").
|
||||
WithExec([]string{
|
||||
"/bin/sh", "-c",
|
||||
"docker build --platform=linux/amd64 . -f ./cmd/hgrun/Dockerfile -t hgrun:latest",
|
||||
}).
|
||||
WithExec([]string{
|
||||
"/bin/sh", "-c",
|
||||
fmt.Sprintf("docker build --platform=linux/amd64 --build-arg=RELEASE_TYPE=%s --build-arg=GRAFANA_VERSION=%s --build-arg=HGRUN_IMAGE=hgrun:latest . -f ./docker/hosted-grafana-all/Dockerfile -t %s",
|
||||
args.ProImageOpts.ReleaseType,
|
||||
args.ProImageOpts.GrafanaVersion,
|
||||
hostedGrafanaImage,
|
||||
),
|
||||
})
|
||||
|
||||
if args.ProImageOpts.Push {
|
||||
if args.ProImageOpts.ContainerRegistry == "" {
|
||||
return fmt.Errorf("--registry=<string> is required")
|
||||
}
|
||||
|
||||
authenticator := containers.GCSAuth(dc, &containers.GCPOpts{
|
||||
ServiceAccountKey: args.GCPOpts.ServiceAccountKey,
|
||||
ServiceAccountKeyBase64: args.GCPOpts.ServiceAccountKeyBase64,
|
||||
})
|
||||
|
||||
authenticatedContainer, err := authenticator.Authenticate(dc, container)
|
||||
if err != nil {
|
||||
return fmt.Errorf("authenticating container with gcs auth: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("Pushing hosted Grafana image to registry...")
|
||||
container = authenticatedContainer.WithExec([]string{
|
||||
"/bin/sh", "-c",
|
||||
fmt.Sprintf("docker push %s", hostedGrafanaImage),
|
||||
})
|
||||
}
|
||||
|
||||
if _, err := containers.ExitError(ctx, container); err != nil {
|
||||
return fmt.Errorf("container did not exit successfully: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package pipelines
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type SyncWriter struct {
|
||||
Writer io.Writer
|
||||
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
func NewSyncWriter(w io.Writer) *SyncWriter {
|
||||
return &SyncWriter{
|
||||
Writer: w,
|
||||
mutex: &sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SyncWriter) Write(b []byte) (int, error) {
|
||||
w.mutex.Lock()
|
||||
defer w.mutex.Unlock()
|
||||
|
||||
return w.Writer.Write(b)
|
||||
}
|
||||
|
||||
var Stdout = NewSyncWriter(os.Stdout)
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
local_dst="dist/${DRONE_BUILD_EVENT}"
|
||||
set -e
|
||||
|
||||
docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*'
|
||||
# This command enables qemu emulators for building Docker images for arm64/armv6/armv7/etc on the host.
|
||||
docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all
|
||||
|
||||
dagger run --silent go run ./pkg/build/cmd \
|
||||
artifacts \
|
||||
-a targz:grafana:linux/amd64 \
|
||||
-a targz:grafana:linux/arm64 \
|
||||
-a targz:grafana:linux/arm/v6 \
|
||||
-a targz:grafana:linux/arm/v7 \
|
||||
-a targz:grafana:windows/amd64 \
|
||||
-a targz:grafana:darwin/amd64 \
|
||||
-a deb:grafana:linux/amd64 \
|
||||
-a deb:grafana:linux/arm64 \
|
||||
-a deb:grafana:linux/arm/v6 \
|
||||
-a deb:grafana:linux/arm/v7 \
|
||||
-a docker:grafana:linux/amd64 \
|
||||
-a docker:grafana:linux/arm64 \
|
||||
-a docker:grafana:linux/arm/v7 \
|
||||
--yarn-cache=${YARN_CACHE_FOLDER} \
|
||||
--checksum \
|
||||
--build-id=${DRONE_BUILD_NUMBER} \
|
||||
--grafana-dir=${GRAFANA_DIR} \
|
||||
--github-token=${GITHUB_TOKEN} \
|
||||
--ubuntu-base=${UBUNTU_BASE} \
|
||||
--alpine-base=${ALPINE_BASE} \
|
||||
--destination=${local_dst} > assets.txt
|
||||
|
||||
echo "Final list of artifacts:"
|
||||
cat assets.txt
|
||||
|
||||
# Move the tar.gz packages to their expected locations
|
||||
cat assets.txt | IS_MAIN=true go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/main
|
||||
@@ -1,37 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
local_dst="dist/${DRONE_BUILD_EVENT}"
|
||||
set -e
|
||||
|
||||
docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*'
|
||||
# This command enables qemu emulators for building Docker images for arm64/armv6/armv7/etc on the host.
|
||||
docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all
|
||||
dagger run --silent go run ./pkg/build/cmd \
|
||||
artifacts \
|
||||
-a targz:enterprise:linux/amd64 \
|
||||
-a targz:enterprise:linux/arm64 \
|
||||
-a targz:enterprise:linux/arm/v6 \
|
||||
-a targz:enterprise:linux/arm/v7 \
|
||||
-a deb:enterprise:linux/amd64 \
|
||||
-a deb:enterprise:linux/arm64 \
|
||||
-a deb:enterprise:linux/arm/v6 \
|
||||
-a deb:enterprise:linux/arm/v7 \
|
||||
-a docker:enterprise:linux/amd64 \
|
||||
-a docker:enterprise:linux/arm64 \
|
||||
--yarn-cache=${YARN_CACHE_FOLDER} \
|
||||
--checksum \
|
||||
--verify \
|
||||
--build-id=${DRONE_BUILD_NUMBER} \
|
||||
--grafana-ref=${SOURCE_COMMIT} \
|
||||
--grafana-repo="https://github.com/grafana/grafana.git" \
|
||||
--enterprise-ref=${DRONE_COMMIT} \
|
||||
--github-token=${GITHUB_TOKEN} \
|
||||
--ubuntu-base=${UBUNTU_BASE} \
|
||||
--alpine-base=${ALPINE_BASE} \
|
||||
--patches-repo=${PATCHES_REPO} \
|
||||
--patches-path=${PATCHES_PATH} \
|
||||
--destination=${local_dst} > assets.txt
|
||||
|
||||
cat assets.txt
|
||||
|
||||
# Move the tar.gz packages to their expected locations
|
||||
cat assets.txt | DESTINATION=gs://grafana-downloads IS_MAIN=true go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/main
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
local_dst="./dist/${DRONE_BUILD_EVENT}"
|
||||
set -e
|
||||
|
||||
docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*'
|
||||
# This command enables qemu emulators for building Docker images for arm64/armv6/armv7/etc on the host.
|
||||
docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all
|
||||
# Build all of the grafana.tar.gz packages.
|
||||
dagger run --silent go run ./pkg/build/cmd \
|
||||
artifacts \
|
||||
-a targz:pro:linux/amd64 \
|
||||
-a targz:pro:linux/arm64 \
|
||||
-a deb:pro:linux/amd64 \
|
||||
-a deb:pro:linux/arm64 \
|
||||
-a frontend:enterprise \
|
||||
--yarn-cache=${YARN_CACHE_FOLDER} \
|
||||
--checksum \
|
||||
--build-id=${DRONE_BUILD_NUMBER} \
|
||||
--grafana-ref=${SOURCE_COMMIT} \
|
||||
--grafana-repo="https://github.com/grafana/grafana.git" \
|
||||
--enterprise-ref=${DRONE_COMMIT} \
|
||||
--github-token=${GITHUB_TOKEN} \
|
||||
--ubuntu-base=${UBUNTU_BASE} \
|
||||
--alpine-base=${ALPINE_BASE} \
|
||||
--patches-repo=${PATCHES_REPO} \
|
||||
--patches-path=${PATCHES_PATH} \
|
||||
--destination=${local_dst} > assets.txt
|
||||
|
||||
echo "Final list of artifacts:"
|
||||
# Move the tar.gz packages to their expected locations
|
||||
cat assets.txt | grep -v "public" | DESTINATION=gs://grafana-downloads-enterprise2 IS_MAIN=true go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/main
|
||||
cat assets.txt | grep "public" | DESTINATION=gs://grafana-static-assets IS_MAIN=true go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/cdn
|
||||
@@ -1,52 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
local_dst="${DRONE_WORKSPACE}/dist"
|
||||
|
||||
docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*'
|
||||
# This command enables qemu emulators for building Docker images for arm64/armv6/armv7/etc on the host.
|
||||
docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all
|
||||
|
||||
# -a targz:enterprise:linux/arm/v6 \
|
||||
# -a targz:enterprise:linux/arm/v7 \
|
||||
# -a deb:enterprise:linux/arm/v6:nightly \
|
||||
# -a deb:enterprise:linux/arm/v7:nightly \
|
||||
# -a docker:enterprise:linux/arm/v7 \
|
||||
# -a docker:enterprise:linux/arm/v7:ubuntu \
|
||||
|
||||
dagger run --silent go run ./pkg/build/cmd \
|
||||
artifacts \
|
||||
-a targz:enterprise:linux/amd64 \
|
||||
-a targz:enterprise:linux/arm64 \
|
||||
-a targz:enterprise:linux/arm/v7 \
|
||||
-a targz:enterprise:linux/arm/v6 \
|
||||
-a deb:enterprise:linux/amd64:nightly \
|
||||
-a deb:enterprise:linux/arm64:nightly \
|
||||
-a deb:enterprise:linux/arm/v6:nightly \
|
||||
-a deb:enterprise:linux/arm/v7:nightly \
|
||||
-a rpm:enterprise:linux/amd64:sign:nightly \
|
||||
-a rpm:enterprise:linux/arm64:sign:nightly \
|
||||
-a targz:enterprise:windows/amd64 \
|
||||
-a targz:enterprise:windows/arm64 \
|
||||
-a targz:enterprise:darwin/amd64 \
|
||||
-a targz:enterprise:darwin/arm64 \
|
||||
-a zip:enterprise:windows/amd64 \
|
||||
-a msi:enterprise:windows/amd64 \
|
||||
-a docker:enterprise:linux/amd64 \
|
||||
-a docker:enterprise:linux/arm64 \
|
||||
-a docker:enterprise:linux/arm/v7 \
|
||||
-a docker:enterprise:linux/amd64:ubuntu \
|
||||
-a docker:enterprise:linux/arm64:ubuntu \
|
||||
-a docker:enterprise:linux/arm/v7:ubuntu \
|
||||
--checksum \
|
||||
--verify=false \
|
||||
--build-id=${DRONE_BUILD_NUMBER} \
|
||||
--grafana-ref=main \
|
||||
--enterprise-ref=main \
|
||||
--grafana-repo=https://github.com/grafana/grafana.git \
|
||||
--github-token=${GITHUB_TOKEN} \
|
||||
--destination=${local_dst} \
|
||||
--yarn-cache=${YARN_CACHE_FOLDER} \
|
||||
--ubuntu-base="${UBUNTU_BASE}" \
|
||||
--alpine-base="${ALPINE_BASE}" > assets.txt
|
||||
|
||||
cat assets.txt
|
||||
@@ -1,43 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
local_dst="${DRONE_WORKSPACE}/dist"
|
||||
|
||||
docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*'
|
||||
# This command enables qemu emulators for building Docker images for arm64/armv6/armv7/etc on the host.
|
||||
docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all
|
||||
|
||||
dagger run --silent go run ./pkg/build/cmd \
|
||||
artifacts \
|
||||
-a targz:grafana:linux/amd64 \
|
||||
-a targz:grafana:linux/arm64 \
|
||||
-a targz:grafana:linux/arm/v7 \
|
||||
-a targz:grafana:linux/arm/v6 \
|
||||
-a deb:grafana:linux/amd64:nightly \
|
||||
-a deb:grafana:linux/arm64:nightly \
|
||||
-a deb:grafana:linux/arm/v6:nightly \
|
||||
-a deb:grafana:linux/arm/v7:nightly \
|
||||
-a rpm:grafana:linux/amd64:sign:nightly \
|
||||
-a rpm:grafana:linux/arm64:sign:nightly \
|
||||
-a targz:grafana:windows/amd64 \
|
||||
-a targz:grafana:windows/arm64 \
|
||||
-a targz:grafana:darwin/amd64 \
|
||||
-a targz:grafana:darwin/arm64 \
|
||||
-a zip:grafana:windows/amd64 \
|
||||
-a msi:grafana:windows/amd64 \
|
||||
-a docker:grafana:linux/amd64 \
|
||||
-a docker:grafana:linux/arm64 \
|
||||
-a docker:grafana:linux/arm/v7 \
|
||||
-a docker:grafana:linux/amd64:ubuntu \
|
||||
-a docker:grafana:linux/arm64:ubuntu \
|
||||
-a docker:grafana:linux/arm/v7:ubuntu \
|
||||
--checksum \
|
||||
--verify \
|
||||
--build-id=${DRONE_BUILD_NUMBER} \
|
||||
--grafana-dir=${GRAFANA_DIR} \
|
||||
--github-token=${GITHUB_TOKEN} \
|
||||
--destination=${local_dst} \
|
||||
--yarn-cache=${YARN_CACHE_FOLDER} \
|
||||
--ubuntu-base="${UBUNTU_BASE}" \
|
||||
--alpine-base="${ALPINE_BASE}" > assets.txt
|
||||
|
||||
cat assets.txt
|
||||
@@ -1,51 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
local_dst="dist/${DRONE_BUILD_EVENT}"
|
||||
set -e
|
||||
|
||||
docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*'
|
||||
# This command enables qemu emulators for building Docker images for arm64/armv6/armv7/etc on the host.
|
||||
docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all
|
||||
|
||||
# Build all of the grafana.tar.gz packages.
|
||||
dagger run --silent go run ./pkg/build/cmd \
|
||||
artifacts \
|
||||
-a targz:enterprise:linux/amd64 \
|
||||
-a targz:enterprise:linux/arm64 \
|
||||
-a targz:enterprise:linux/arm/v6 \
|
||||
-a targz:enterprise:linux/arm/v7 \
|
||||
-a deb:enterprise:linux/amd64 \
|
||||
-a deb:enterprise:linux/arm64 \
|
||||
-a deb:enterprise:linux/arm/v6 \
|
||||
-a deb:enterprise:linux/arm/v7 \
|
||||
-a rpm:enterprise:linux/amd64:sign \
|
||||
-a rpm:enterprise:linux/arm64:sign \
|
||||
-a targz:enterprise:windows/amd64 \
|
||||
-a targz:enterprise:windows/arm64 \
|
||||
-a targz:enterprise:darwin/amd64 \
|
||||
-a targz:enterprise:darwin/arm64 \
|
||||
-a targz:boring:linux/amd64/dynamic \
|
||||
-a zip:enterprise:windows/amd64 \
|
||||
-a msi:enterprise:windows/amd64 \
|
||||
-a docker:enterprise:linux/amd64 \
|
||||
-a docker:enterprise:linux/arm64 \
|
||||
-a docker:enterprise:linux/arm/v7 \
|
||||
-a docker:enterprise:linux/amd64:ubuntu \
|
||||
-a docker:enterprise:linux/arm64:ubuntu \
|
||||
-a docker:enterprise:linux/arm/v7:ubuntu \
|
||||
-a docker:boring:linux/amd64/dynamic \
|
||||
--yarn-cache=${YARN_CACHE_FOLDER} \
|
||||
--verify \
|
||||
--checksum \
|
||||
--parallel=5 \
|
||||
--build-id=${DRONE_BUILD_NUMBER} \
|
||||
--enterprise-ref=${DRONE_TAG} \
|
||||
--grafana-ref=${DRONE_TAG} \
|
||||
--grafana-repo=https://github.com/grafana/grafana-security-mirror.git \
|
||||
--github-token=${GITHUB_TOKEN} \
|
||||
--ubuntu-base="${UBUNTU_BASE}" \
|
||||
--alpine-base="${ALPINE_BASE}" \
|
||||
--version=${DRONE_TAG} \
|
||||
--destination=${local_dst} > assets.txt
|
||||
|
||||
# Move the tar.gz packages to their expected locations
|
||||
cat assets.txt | go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/prerelease
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
dst="${DESTINATION}/${DRONE_BUILD_EVENT}"
|
||||
local_dst="file://dist/${DRONE_BUILD_EVENT}"
|
||||
set -e
|
||||
|
||||
docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*'
|
||||
# This command enables qemu emulators for building Docker images for arm64/armv6/armv7/etc on the host.
|
||||
docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all
|
||||
|
||||
dagger run --silent go run ./pkg/build/cmd \
|
||||
artifacts \
|
||||
-a npm:grafana \
|
||||
-a storybook \
|
||||
-a targz:grafana:linux/amd64 \
|
||||
-a targz:grafana:linux/arm64 \
|
||||
-a targz:grafana:linux/arm/v6 \
|
||||
-a targz:grafana:linux/arm/v7 \
|
||||
-a deb:grafana:linux/amd64 \
|
||||
-a deb:grafana:linux/arm64 \
|
||||
-a deb:grafana:linux/arm/v6 \
|
||||
-a deb:grafana:linux/arm/v7 \
|
||||
-a rpm:grafana:linux/amd64:sign \
|
||||
-a rpm:grafana:linux/arm64:sign \
|
||||
-a docker:grafana:linux/amd64 \
|
||||
-a docker:grafana:linux/arm64 \
|
||||
-a docker:grafana:linux/arm/v7 \
|
||||
-a docker:grafana:linux/amd64:ubuntu \
|
||||
-a docker:grafana:linux/arm64:ubuntu \
|
||||
-a docker:grafana:linux/arm/v7:ubuntu \
|
||||
-a targz:grafana:windows/amd64 \
|
||||
-a targz:grafana:windows/arm64 \
|
||||
-a targz:grafana:darwin/amd64 \
|
||||
-a targz:grafana:darwin/arm64 \
|
||||
-a zip:grafana:windows/amd64 \
|
||||
-a msi:grafana:windows/amd64 \
|
||||
--yarn-cache=${YARN_CACHE_FOLDER} \
|
||||
--checksum \
|
||||
--verify \
|
||||
--build-id=${DRONE_BUILD_NUMBER} \
|
||||
--grafana-dir=${GRAFANA_DIR} \
|
||||
--github-token=${GITHUB_TOKEN} \
|
||||
--ubuntu-base="${UBUNTU_BASE}" \
|
||||
--alpine-base="${ALPINE_BASE}" \
|
||||
--version=${DRONE_TAG} \
|
||||
--destination=${local_dst} > assets.txt
|
||||
|
||||
cat assets.txt | go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/prerelease
|
||||
@@ -1,41 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
local_dst="dist/${DRONE_BUILD_EVENT}"
|
||||
set -e
|
||||
|
||||
docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*'
|
||||
# This command enables qemu emulators for building Docker images for arm64/armv6/armv7/etc on the host.
|
||||
docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all
|
||||
|
||||
# Build all of the grafana.tar.gz packages.
|
||||
dagger run --silent go run ./pkg/build/cmd \
|
||||
artifacts \
|
||||
-a frontend:enterprise \
|
||||
-a targz:pro:linux/amd64 \
|
||||
-a targz:pro:linux/arm64 \
|
||||
-a targz:pro:linux/arm/v6 \
|
||||
-a targz:pro:linux/arm/v7 \
|
||||
-a deb:pro:linux/amd64 \
|
||||
-a deb:pro:linux/arm64 \
|
||||
-a targz:pro:darwin/amd64 \
|
||||
-a targz:pro:windows/amd64 \
|
||||
-a docker:pro:linux/amd64 \
|
||||
-a docker:pro:linux/arm64 \
|
||||
-a docker:pro:linux/arm/v7 \
|
||||
-a docker:pro:linux/amd64:ubuntu \
|
||||
-a docker:pro:linux/arm64:ubuntu \
|
||||
-a docker:pro:linux/arm/v7:ubuntu \
|
||||
--checksum \
|
||||
--parallel=2 \
|
||||
--yarn-cache=${YARN_CACHE_FOLDER} \
|
||||
--build-id=${DRONE_BUILD_NUMBER} \
|
||||
--enterprise-ref=${DRONE_TAG} \
|
||||
--grafana-ref=${DRONE_TAG} \
|
||||
--grafana-repo=https://github.com/grafana/grafana-security-mirror.git \
|
||||
--github-token=${GITHUB_TOKEN} \
|
||||
--version=${DRONE_TAG} \
|
||||
--ubuntu-base="${UBUNTU_BASE}" \
|
||||
--alpine-base="${ALPINE_BASE}" \
|
||||
--destination=${local_dst} > assets.txt
|
||||
|
||||
# Move the tar.gz packages to their expected locations
|
||||
cat assets.txt | go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/prerelease
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
local_dir="${DRONE_WORKSPACE}/dist"
|
||||
|
||||
# Publish the docker images present in the bucket
|
||||
dagger run --silent go run ./pkg/build/cmd docker publish \
|
||||
$(find $local_dir | grep docker.tar.gz | grep -v sha256 | awk '{print "--package=file://"$0}') \
|
||||
--username=${DOCKER_USERNAME} \
|
||||
--password=${DOCKER_PASSWORD} \
|
||||
--latest \
|
||||
--repo="grafana-enterprise-dev"
|
||||
|
||||
# Publish packages to the downloads bucket
|
||||
dagger run --silent go run ./pkg/build/cmd package publish \
|
||||
$(find $local_dir | grep -e .rpm -e .tar.gz -e .exe -e .zip -e .deb | awk '{print "--package=file://"$0}') \
|
||||
--gcp-service-account-key-base64=${GCP_KEY_BASE64} \
|
||||
--destination="${DOWNLOADS_DESTINATION}/enterprise/release"
|
||||
|
||||
# Publish packages to grafana.com
|
||||
dagger run --silent go run ./pkg/build/cmd gcom publish \
|
||||
$(find $local_dir | grep -e .rpm -e .tar.gz -e .exe -e .zip -e .deb | grep -v sha256 | grep -v docker | awk '{print "--package=file://"$0}') \
|
||||
--api-key=${GCOM_API_KEY} \
|
||||
--api-url="https://grafana.com/api/grafana-enterprise" \
|
||||
--download-url="https://dl.grafana.com/enterprise/release" \
|
||||
--nightly
|
||||
@@ -1,43 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
# ver=$(cat ${GRAFANA_DIR}/package.json | jq -r .version | sed -E "s/$/-/" | sed -E "s/-.*/-${DRONE_BUILD_NUMBER}/")
|
||||
local_dir="${DRONE_WORKSPACE}/dist"
|
||||
|
||||
# Publish the docker images present in the bucket
|
||||
dagger run --silent go run ./pkg/build/cmd docker publish \
|
||||
$(find $local_dir | grep docker.tar.gz | grep -v sha256 | awk '{print "--package=file://"$0}') \
|
||||
--username=${DOCKER_USERNAME} \
|
||||
--password=${DOCKER_PASSWORD} \
|
||||
--repo="grafana-dev"
|
||||
|
||||
# Publish packages to the downloads bucket
|
||||
dagger run --silent go run ./pkg/build/cmd package publish \
|
||||
$(find $local_dir | grep -e .rpm -e .tar.gz -e .exe -e .zip -e .deb | awk '{print "--package=file://"$0}') \
|
||||
--gcp-service-account-key-base64=${GCP_KEY_BASE64} \
|
||||
--destination="${DOWNLOADS_DESTINATION}/oss/release"
|
||||
|
||||
# Publish only the linux/amd64 edition storybook into the storybook bucket
|
||||
# dagger run --silent go run ./pkg/build/cmd storybook \
|
||||
# $(find $local_dir | grep tar.gz | grep linux | grep amd64 | grep -v sha256 | grep -v docker | awk '{print "--package=file://"$0}') \
|
||||
# --gcp-service-account-key-base64=${GCP_KEY_BASE64} \
|
||||
# --destination="${STORYBOOK_DESTINATION}/${ver}"
|
||||
|
||||
# # Publish only the linux/amd64 edition static assets into the static assets bucket
|
||||
# dagger run --silent go run ./pkg/build/cmd cdn \
|
||||
# $(find $local_dir | grep tar.gz | grep linux | grep amd64 | grep -v sha256 | grep -v docker | awk '{print "--package=file://"$0}') \
|
||||
# --gcp-service-account-key-base64=${GCP_KEY_BASE64} \
|
||||
# --destination="${CDN_DESTINATION}/${ver}/public"
|
||||
|
||||
# Publish only the linux/amd64 edition npm packages to npm
|
||||
dagger run --silent go run ./pkg/build/cmd npm publish \
|
||||
$(find $local_dir | grep tar.gz | grep linux | grep amd64 | grep -v sha256 | grep -v docker | awk '{print "--package=file://"$0}') \
|
||||
--token=${NPM_TOKEN} \
|
||||
--tag="nightly"
|
||||
|
||||
# Publish packages to grafana.com
|
||||
dagger run --silent go run ./pkg/build/cmd gcom publish \
|
||||
$(find $local_dir | grep -e .rpm -e .tar.gz -e .exe -e .zip -e .deb | grep -v sha256 | grep -v docker | awk '{print "--package=file://"$0}') \
|
||||
--api-key=${GCOM_API_KEY} \
|
||||
--api-url="https://grafana.com/api/grafana" \
|
||||
--download-url="https://dl.grafana.com/oss/release" \
|
||||
--nightly
|
||||
@@ -1,495 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/containers"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipelines"
|
||||
)
|
||||
|
||||
const (
|
||||
proName = "enterprise2"
|
||||
// 1: The version (with a v prefix)
|
||||
// 2: The "edition". Options: 'oss', 'pro', 'enterprise'.
|
||||
// 3: The full name. 'grafana', 'grafana-enterprise', 'grafana-pro
|
||||
// 4: The 'ersion', or 'version' without the 'v'.
|
||||
// 5: The OS: 'windows', 'linux', 'darwin'
|
||||
// 6: The architecture: 'amd64', 'armv6', 'armv7', 'arm64'.
|
||||
// 7: -musl, sometimes.
|
||||
// 8: '.sha256', sometimes.
|
||||
tarGzFormat = "artifacts/downloads%[9]s/%[1]s/%[2]s/release/%[3]s-%[4]s.%[5]s-%[6]s%[7]s.tar.gz%[8]s"
|
||||
debFormat = "artifacts/downloads%[9]s/%[1]s/%[2]s/release/%[3]s_%[4]s_%[6]s.deb%[8]s"
|
||||
rpmFormat = "artifacts/downloads%[9]s/%[1]s/%[2]s/release/%[3]s-%[4]s-1.%[6]s.rpm%[8]s"
|
||||
exeFormat = "artifacts/downloads%[9]s/%[1]s/%[2]s/release/%[3]s_%[4]s_%[6]s.exe%[8]s"
|
||||
msiFormat = "artifacts/downloads%[9]s/%[1]s/%[2]s/release/%[3]s_%[4]s_%[6]s.msi%[8]s"
|
||||
|
||||
tarGzMainFormat = "%[2]s/main/%[3]s-%[4]s.%[5]s-%[6]s%[7]s.tar.gz%[8]s"
|
||||
debMainFormat = "%[2]s/main/%[3]s_%[4]s_%[6]s.deb%[8]s"
|
||||
|
||||
// 1: ersion
|
||||
// 2. name (grafana-oss | grafana-enterprise)
|
||||
// 3: '-ubuntu', if set
|
||||
// 4: arch
|
||||
// 5: '.sha256', if set
|
||||
dockerFormat = "artifacts/docker/%[1]s/%[2]s-%[1]s%[3]s-%[4]s.img%[5]s"
|
||||
|
||||
// 1: ersion
|
||||
// 2. name (grafana-oss | grafana-enterprise)
|
||||
cdnFormat = "artifacts/static-assets/%[2]s/%[1]s/public"
|
||||
cdnMainFormat = "grafana/%s/public"
|
||||
|
||||
// 1: ersion
|
||||
storybookFormat = "artifacts/storybook/v%[1]s"
|
||||
|
||||
// 1: version
|
||||
// 2: package name (@grafana-ui-10.0.0.tgz)
|
||||
npmFormat = "artifacts/npm/v%[1]s/npm-artifacts"
|
||||
|
||||
sha256Ext = ".sha256"
|
||||
grafana = "grafana"
|
||||
)
|
||||
|
||||
// One artifact and be copied to multiple different locations (like armv7 tar.gz packages should be copied to tar.gz and -musl.tar.gz)
|
||||
type HandlerFunc func(name string) []string
|
||||
|
||||
var Handlers = map[string]HandlerFunc{
|
||||
".tar.gz": TarGZHandler,
|
||||
".deb": DebHandler,
|
||||
".rpm": RPMHandler,
|
||||
".docker.tar.gz": DockerHandler,
|
||||
".exe": EXEHandler,
|
||||
".msi": MSIHandler,
|
||||
".zip": ZipHandler,
|
||||
}
|
||||
|
||||
func IsMain() bool {
|
||||
return os.Getenv("IS_MAIN") != ""
|
||||
}
|
||||
|
||||
func NPMHandler(name string) []string {
|
||||
var (
|
||||
version = strings.TrimPrefix(os.Getenv("DRONE_TAG"), "v")
|
||||
)
|
||||
|
||||
return []string{fmt.Sprintf(npmFormat, version)}
|
||||
}
|
||||
|
||||
func ZipHandler(name string) []string {
|
||||
files := EXEHandler(strings.ReplaceAll(name, "zip", "exe"))
|
||||
|
||||
for i, v := range files {
|
||||
files[i] = strings.ReplaceAll(v, "exe", "zip")
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
func MSIHandler(name string) []string {
|
||||
files := EXEHandler(strings.ReplaceAll(name, "msi", "exe"))
|
||||
|
||||
for i, v := range files {
|
||||
files[i] = strings.ReplaceAll(v, "exe", "msi")
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
func RPMHandler(name string) []string {
|
||||
ext := filepath.Ext(name)
|
||||
|
||||
// If we're copying a sha256 file and not a tar.gz then we want to add .sha256 to the template
|
||||
// or just give it emptystring if it's not the sha256 file
|
||||
sha256 := ""
|
||||
if ext == sha256Ext {
|
||||
sha256 = sha256Ext
|
||||
}
|
||||
|
||||
n := filepath.Base(name) // Surprisingly still works even with 'gs://' urls
|
||||
opts := pipelines.TarOptsFromFileName(strings.ReplaceAll(strings.ReplaceAll(n, sha256Ext, ""), "rpm", "tar.gz"))
|
||||
|
||||
// In grafana-build we just use "" to refer to "oss"
|
||||
edition := "oss"
|
||||
fullName := grafana
|
||||
if opts.Edition != "" {
|
||||
edition = opts.Edition
|
||||
fullName += "-" + opts.Edition
|
||||
}
|
||||
|
||||
goos, arch := backend.OSAndArch(opts.Distro)
|
||||
arm := backend.ArchVersion(opts.Distro)
|
||||
if arch == "arm" {
|
||||
if arm == "7" {
|
||||
arch = "armhfp"
|
||||
}
|
||||
}
|
||||
|
||||
if arch == "arm64" {
|
||||
arch = "aarch64"
|
||||
}
|
||||
|
||||
if arch == "amd64" {
|
||||
arch = "x86_64"
|
||||
}
|
||||
|
||||
enterprise2 := ""
|
||||
version := opts.Version
|
||||
ersion := strings.Replace(strings.TrimPrefix(version, "v"), "-", "~", 1)
|
||||
|
||||
if edition == "pro" {
|
||||
// "pro" in this case is called "enterprise2"
|
||||
fullName = "grafana-enterprise2"
|
||||
edition = proName
|
||||
// and is in the 'downloads-enterprise2' folder instead of 'downloads'
|
||||
enterprise2 = "-enterprise2"
|
||||
// and has an period separator {version}.{arch} instead of {version}_{arch}
|
||||
}
|
||||
dst := fmt.Sprintf(rpmFormat, version, edition, fullName, ersion, goos, arch, edition, sha256, enterprise2)
|
||||
|
||||
return []string{
|
||||
dst,
|
||||
}
|
||||
}
|
||||
|
||||
func EXEHandler(name string) []string {
|
||||
packages := DebHandler(strings.ReplaceAll(name, "exe", "deb"))
|
||||
for i, v := range packages {
|
||||
v = strings.ReplaceAll(v, "deb", "exe")
|
||||
v = strings.ReplaceAll(v, "amd64", "windows-amd64")
|
||||
v = strings.ReplaceAll(v, "_", "-")
|
||||
v = strings.ReplaceAll(v, "~", "-")
|
||||
v = strings.ReplaceAll(v, "-windows", ".windows")
|
||||
packages[i] = v
|
||||
}
|
||||
|
||||
return packages
|
||||
}
|
||||
|
||||
func DebHandler(name string) []string {
|
||||
ext := filepath.Ext(name)
|
||||
format := debFormat
|
||||
if IsMain() {
|
||||
format = debMainFormat
|
||||
}
|
||||
|
||||
// If we're copying a sha256 file and not a tar.gz then we want to add .sha256 to the template
|
||||
// or just give it emptystring if it's not the sha256 file
|
||||
sha256 := ""
|
||||
if ext == sha256Ext {
|
||||
sha256 = sha256Ext
|
||||
}
|
||||
|
||||
n := filepath.Base(name) // Surprisingly still works even with 'gs://' urls
|
||||
opts := pipelines.TarOptsFromFileName(strings.ReplaceAll(strings.ReplaceAll(n, sha256Ext, ""), "deb", "tar.gz"))
|
||||
|
||||
// In grafana-build we just use "" to refer to "oss"
|
||||
edition := "oss"
|
||||
fullName := grafana
|
||||
version := opts.Version
|
||||
ersion := strings.TrimPrefix(version, "v")
|
||||
ersion = strings.Replace(ersion, "-", "~", 1)
|
||||
enterprise2 := ""
|
||||
if opts.Edition != "" {
|
||||
edition = opts.Edition
|
||||
fullName += "-" + opts.Edition
|
||||
if edition == "pro" {
|
||||
// "pro" in this case is called "enterprise2"
|
||||
fullName = "grafana-enterprise2"
|
||||
edition = proName
|
||||
// and is in the 'downloads-enterprise2' folder instead of 'downloads'
|
||||
enterprise2 = "-enterprise2"
|
||||
}
|
||||
|
||||
if edition == "pro-rpi" {
|
||||
// "pro" in this case is called "enterprise2"
|
||||
fullName = "grafana-enterprise2-rpi"
|
||||
edition = proName
|
||||
// and is in the 'downloads-enterprise2' folder instead of 'downloads'
|
||||
enterprise2 = "-enterprise2"
|
||||
}
|
||||
|
||||
if edition == "rpi" {
|
||||
edition = "oss"
|
||||
}
|
||||
|
||||
if edition == "enterprise-rpi" {
|
||||
edition = "enterprise"
|
||||
}
|
||||
}
|
||||
|
||||
names := []string{fullName}
|
||||
goos, arch := backend.OSAndArch(opts.Distro)
|
||||
if arch == "arm" {
|
||||
arch = "armhf"
|
||||
// If we're building for arm then we also copy the same thing, but with the name '-rpi'. for osme reason?
|
||||
names = []string{fullName}
|
||||
}
|
||||
|
||||
dst := []string{}
|
||||
for _, n := range names {
|
||||
dst = append(dst, fmt.Sprintf(format, opts.Version, edition, n, ersion, goos, arch, edition, sha256, enterprise2))
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
func TarGZHandler(name string) []string {
|
||||
ext := filepath.Ext(name)
|
||||
|
||||
// If we're copying a sha256 file and not a tar.gz then we want to add .sha256 to the template
|
||||
// or just give it emptystring if it's not the sha256 file
|
||||
sha256 := ""
|
||||
if ext == sha256Ext {
|
||||
sha256 = sha256Ext
|
||||
}
|
||||
|
||||
n := filepath.Base(name) // Surprisingly still works even with 'gs://' urls
|
||||
opts := pipelines.TarOptsFromFileName(strings.ReplaceAll(n, sha256Ext, ""))
|
||||
|
||||
// In grafana-build we just use "" to refer to "oss"
|
||||
edition := "oss"
|
||||
fullName := grafana
|
||||
version := opts.Version
|
||||
ersion := strings.TrimPrefix(version, "v")
|
||||
enterprise2 := ""
|
||||
if opts.Edition != "" {
|
||||
edition = opts.Edition
|
||||
fullName += "-" + opts.Edition
|
||||
if edition == "pro" {
|
||||
enterprise2 = "-enterprise2"
|
||||
fullName = "grafana-enterprise2"
|
||||
edition = proName
|
||||
}
|
||||
}
|
||||
|
||||
libc := []string{""}
|
||||
goos, arch := backend.OSAndArch(opts.Distro)
|
||||
|
||||
if arch == "arm64" || arch == "arm" || arch == "amd64" && goos == "linux" {
|
||||
libc = []string{"", "-musl"}
|
||||
}
|
||||
|
||||
arm := backend.ArchVersion(opts.Distro)
|
||||
if arch == "arm" {
|
||||
arch += "v" + arm
|
||||
// I guess we don't create an arm-6-musl?
|
||||
if arm == "6" {
|
||||
libc = []string{""}
|
||||
}
|
||||
}
|
||||
format := tarGzFormat
|
||||
if IsMain() {
|
||||
format = tarGzMainFormat
|
||||
}
|
||||
dst := []string{}
|
||||
for _, m := range libc {
|
||||
dst = append(dst, fmt.Sprintf(format, opts.Version, edition, fullName, ersion, goos, arch, m, sha256, enterprise2))
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
func DockerHandler(name string) []string {
|
||||
ext := filepath.Ext(name)
|
||||
|
||||
// If we're copying a sha256 file and not a tar.gz then we want to add .sha256 to the template
|
||||
// or just give it emptystring if it's not the sha256 file
|
||||
sha256 := ""
|
||||
if ext == sha256Ext {
|
||||
sha256 = sha256Ext
|
||||
}
|
||||
|
||||
n := filepath.Base(name) // Surprisingly still works even with 'gs://' urls
|
||||
|
||||
// try to get .ubuntu.docker.tar.gz.sha256 / .ubuntu.docker.tar.gz / docker.tar.gz to all just end in 'tar.gz'
|
||||
normalized := strings.ReplaceAll(n, sha256Ext, "")
|
||||
normalized = strings.ReplaceAll(normalized, ".ubuntu", "")
|
||||
normalized = strings.ReplaceAll(normalized, ".docker", "")
|
||||
|
||||
opts := pipelines.TarOptsFromFileName(normalized)
|
||||
|
||||
// In grafana-build we just use "" to refer to "oss"
|
||||
edition := "oss"
|
||||
fullName := grafana
|
||||
if opts.Edition != "" {
|
||||
edition = opts.Edition
|
||||
if edition == "pro" {
|
||||
edition = proName
|
||||
}
|
||||
}
|
||||
|
||||
fullName += "-" + edition
|
||||
ubuntu := ""
|
||||
if strings.Contains(name, "ubuntu") {
|
||||
ubuntu = "-ubuntu"
|
||||
}
|
||||
|
||||
_, arch := backend.OSAndArch(opts.Distro)
|
||||
if arch == "arm" {
|
||||
arch += "v" + backend.ArchVersion(opts.Distro)
|
||||
}
|
||||
return []string{
|
||||
fmt.Sprintf(dockerFormat, strings.TrimPrefix(opts.Version, "v"), fullName, ubuntu, arch, sha256),
|
||||
}
|
||||
}
|
||||
|
||||
func CDNHandler(name string) []string {
|
||||
if IsMain() {
|
||||
// This folder is is always ${dist}/${version}/${name}/${public}
|
||||
dist, err := filepath.Rel(".", filepath.Join(name, "../../../"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
path, err := filepath.Rel(dist, name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s := strings.Split(path, string(os.PathSeparator))
|
||||
return []string{fmt.Sprintf(cdnMainFormat, s[0])}
|
||||
}
|
||||
version := strings.TrimPrefix(os.Getenv("DRONE_TAG"), "v")
|
||||
return []string{fmt.Sprintf(cdnFormat, version, grafana)}
|
||||
}
|
||||
|
||||
func StorybookHandler(name string) []string {
|
||||
version := strings.TrimPrefix(os.Getenv("DRONE_TAG"), "v")
|
||||
return []string{fmt.Sprintf(storybookFormat, version)}
|
||||
}
|
||||
|
||||
// A hopefully temporary script that prints the gsutil commands that will move these artifacts into the location where they were expected previously.
|
||||
// Just pipe this into bash or exec or whatever to do the actual copying.
|
||||
// Run without redirecting stdout to verify the operations.
|
||||
func main() {
|
||||
prefix := os.Args[1]
|
||||
|
||||
ctx := context.Background()
|
||||
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var (
|
||||
scanner = bufio.NewScanner(os.Stdin)
|
||||
authenticator = containers.GCSAuth(client, &containers.GCPOpts{
|
||||
ServiceAccountKeyBase64: os.Getenv("GCP_KEY_BASE64"),
|
||||
})
|
||||
|
||||
container = client.Container().From("google/cloud-sdk:alpine")
|
||||
)
|
||||
//
|
||||
if c, err := authenticator.Authenticate(client, container); err == nil {
|
||||
container = c
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for scanner.Scan() {
|
||||
var (
|
||||
name = scanner.Text()
|
||||
)
|
||||
handler, ext := getHandler(name, Handlers)
|
||||
destinations := handler(name)
|
||||
if ext == "" {
|
||||
for _, v := range destinations {
|
||||
dir := filepath.Join(prefix, filepath.Dir(v))
|
||||
v := filepath.Join(prefix, v)
|
||||
|
||||
log.Println("Creating dir", dir)
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.Println("Copying", name, "to", v)
|
||||
//nolint:gosec
|
||||
cmd := exec.Command("cp", "-r", strings.TrimPrefix(name, "file://"), v)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
log.Println("File:", name, "to be copied as", destinations)
|
||||
for _, v := range destinations {
|
||||
dir := filepath.Join(prefix, filepath.Dir(v))
|
||||
v := filepath.Join(prefix, v)
|
||||
log.Println("Creating directory", dir)
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log.Println("Copying", name, "to", dir, "as", v)
|
||||
|
||||
//nolint:gosec
|
||||
cmd := exec.Command("cp", strings.TrimPrefix(name, "file://"), v)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Copying", prefix, "to gcs")
|
||||
dst := os.Getenv("DESTINATION")
|
||||
container = container.WithMountedDirectory("dist", client.Host().Directory(prefix)).
|
||||
WithExec([]string{"gcloud", "storage", "cp", "-r", "/dist/*", dst})
|
||||
|
||||
stdout, err := container.Stdout(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stderr, err := container.Stdout(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Fprint(os.Stdout, stdout)
|
||||
fmt.Fprint(os.Stderr, stderr)
|
||||
}
|
||||
|
||||
func getHandler(name string, handlers map[string]HandlerFunc) (HandlerFunc, string) {
|
||||
ext := filepath.Ext(name)
|
||||
// sha256 extensions should be handled the same way what precedes the extension
|
||||
if ext == sha256Ext {
|
||||
ext = filepath.Ext(strings.ReplaceAll(name, sha256Ext, ""))
|
||||
}
|
||||
|
||||
// tar.gz extensions can also have docker.tar.gz so we need to make sure we don't skip that
|
||||
if ext == ".gz" {
|
||||
ext = ".tar.gz"
|
||||
if filepath.Ext(strings.ReplaceAll(name, ".tar.gz", "")) == ".docker" ||
|
||||
filepath.Ext(strings.ReplaceAll(name, ".tar.gz.sha256", "")) == ".docker" {
|
||||
ext = ".docker.tar.gz"
|
||||
}
|
||||
}
|
||||
|
||||
handler := handlers[ext]
|
||||
// If there is no extension, then we are either dealing with public assets
|
||||
// or the storybook, which both require some extra handling:
|
||||
if ext != "" {
|
||||
return handler, ext
|
||||
}
|
||||
|
||||
if filepath.Base(name) == "public" {
|
||||
return CDNHandler, ""
|
||||
}
|
||||
if filepath.Base(name) == "storybook" {
|
||||
return StorybookHandler, ""
|
||||
}
|
||||
if filepath.Base(name) == "npm-packages" {
|
||||
return NPMHandler, ""
|
||||
}
|
||||
panic("no handler found")
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package main
|
||||
|
||||
var cdnMapping = map[string]m{
|
||||
"OSS: Linux AMD64": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64/public",
|
||||
output: []string{
|
||||
"artifacts/static-assets/grafana/1.2.3/public",
|
||||
},
|
||||
env: map[string]string{"DRONE_TAG": "1.2.3"},
|
||||
},
|
||||
"ENT: Linux AMD64": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64/public",
|
||||
output: []string{
|
||||
"artifacts/static-assets/grafana/1.2.3/public",
|
||||
},
|
||||
env: map[string]string{"DRONE_TAG": "1.2.3"},
|
||||
},
|
||||
"PRO: Linux AMD64": {
|
||||
input: "gs://bucket/tag/grafana-pro_v1.2.3_102_linux_amd64/public",
|
||||
output: []string{
|
||||
"artifacts/static-assets/grafana/1.2.3/public",
|
||||
},
|
||||
env: map[string]string{"DRONE_TAG": "1.2.3"},
|
||||
},
|
||||
"main": {
|
||||
input: "dist/10.3.0-62960/grafana-enterprise/public",
|
||||
output: []string{
|
||||
"grafana/10.3.0-62960/public",
|
||||
},
|
||||
env: map[string]string{"IS_MAIN": "true"},
|
||||
},
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
package main
|
||||
|
||||
var debMapping = map[string]m{
|
||||
"OSS: Linux AMD64 on main": {
|
||||
env: map[string]string{
|
||||
"IS_MAIN": "true",
|
||||
},
|
||||
input: "file://dist/grafana_v1.2.3_102_linux_amd64.deb",
|
||||
output: []string{
|
||||
"oss/main/grafana_1.2.3_amd64.deb",
|
||||
},
|
||||
},
|
||||
"OSS: Linux AMD64 on main with - in version": {
|
||||
env: map[string]string{
|
||||
"IS_MAIN": "true",
|
||||
},
|
||||
input: "file://dist/grafana_v1.2.3-102_102_linux_amd64.deb",
|
||||
output: []string{
|
||||
"oss/main/grafana_1.2.3~102_amd64.deb",
|
||||
},
|
||||
},
|
||||
"OSS: Linux AMD64": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.deb",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana_1.2.3_amd64.deb",
|
||||
},
|
||||
},
|
||||
"OSS: Linux AMD64 SHA256": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.deb.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana_1.2.3_amd64.deb.sha256",
|
||||
},
|
||||
},
|
||||
"OSS: Linux ARM7": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.deb",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana_1.2.3_armhf.deb",
|
||||
},
|
||||
},
|
||||
"OSS: RPI ARM7": {
|
||||
input: "gs://bucket/tag/grafana-rpi_v1.2.3_102_linux_arm-7.deb",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-rpi_1.2.3_armhf.deb",
|
||||
},
|
||||
},
|
||||
"OSS: RPI ARM6": {
|
||||
input: "gs://bucket/tag/grafana-rpi_v1.2.3_102_linux_arm-6.deb",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-rpi_1.2.3_armhf.deb",
|
||||
},
|
||||
},
|
||||
"OSS: Linux ARM7 SHA256": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.deb.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana_1.2.3_armhf.deb.sha256",
|
||||
},
|
||||
},
|
||||
"OSS: Linux ARM64": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm64.deb",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana_1.2.3_arm64.deb",
|
||||
},
|
||||
},
|
||||
"OSS: Linux ARM64 SHA256": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm64.deb.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana_1.2.3_arm64.deb.sha256",
|
||||
},
|
||||
},
|
||||
"ENT: Linux AMD64": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.deb",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise_1.2.3_amd64.deb",
|
||||
},
|
||||
},
|
||||
"ENT: Linux AMD64 SHA256": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.deb.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise_1.2.3_amd64.deb.sha256",
|
||||
},
|
||||
},
|
||||
"ENT: Linux ARM64": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.deb",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise_1.2.3_arm64.deb",
|
||||
},
|
||||
},
|
||||
"ENT: Linux ARM64 SHA256": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.deb.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise_1.2.3_arm64.deb.sha256",
|
||||
},
|
||||
},
|
||||
"ENT: Linux ARM7": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.deb",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise_1.2.3_armhf.deb",
|
||||
},
|
||||
},
|
||||
"ENT: RPI ARM7": {
|
||||
input: "gs://bucket/tag/grafana-enterprise-rpi_v1.2.3_102_linux_arm-7.deb",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-rpi_1.2.3_armhf.deb",
|
||||
},
|
||||
},
|
||||
"ENT: ARM7 SHA256": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.deb.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise_1.2.3_armhf.deb.sha256",
|
||||
},
|
||||
},
|
||||
"ENT2: RPI ARM7": {
|
||||
input: "gs://bucket/tag/grafana-pro-rpi_v1.2.3_102_linux_arm-7.deb",
|
||||
output: []string{
|
||||
"artifacts/downloads-enterprise2/v1.2.3/enterprise2/release/grafana-enterprise2-rpi_1.2.3_armhf.deb",
|
||||
},
|
||||
},
|
||||
"ENT2: Pre-release AMD64": {
|
||||
input: "gs://bucket/tag/grafana-pro_v1.2.3-pre.4_102_linux_amd64.deb",
|
||||
output: []string{
|
||||
"artifacts/downloads-enterprise2/v1.2.3-pre.4/enterprise2/release/grafana-enterprise2_1.2.3~pre.4_amd64.deb",
|
||||
},
|
||||
},
|
||||
"ENT2: Pre-release AMD64 SHA256": {
|
||||
input: "gs://bucket/tag/grafana-pro_v1.2.3-pre.4_102_linux_amd64.deb.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads-enterprise2/v1.2.3-pre.4/enterprise2/release/grafana-enterprise2_1.2.3~pre.4_amd64.deb.sha256",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
package main
|
||||
|
||||
var dockerMapping = map[string]m{
|
||||
"ENT: Linux AMD64 Ubuntu": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.ubuntu.docker.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-enterprise-1.2.3-ubuntu-amd64.img",
|
||||
},
|
||||
},
|
||||
"ENT: Linux AMD64 Ubuntu SHA256": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.ubuntu.docker.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-enterprise-1.2.3-ubuntu-amd64.img.sha256",
|
||||
},
|
||||
},
|
||||
"ENT: Linux ARM64": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.docker.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-enterprise-1.2.3-arm64.img",
|
||||
},
|
||||
},
|
||||
"ENT: Linux ARM64 SHA256": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.docker.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-enterprise-1.2.3-arm64.img.sha256",
|
||||
},
|
||||
},
|
||||
"ENT: Linux ARM7": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.docker.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-enterprise-1.2.3-armv7.img",
|
||||
},
|
||||
},
|
||||
"ENT: Linux ARM7 SHA256": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.docker.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-enterprise-1.2.3-armv7.img.sha256",
|
||||
},
|
||||
},
|
||||
"ENT: Linux ARM7 Ubuntu": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.ubuntu.docker.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-enterprise-1.2.3-ubuntu-armv7.img",
|
||||
},
|
||||
},
|
||||
"ENT: Linux AR7 Ubuntu SHA256": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.ubuntu.docker.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-enterprise-1.2.3-ubuntu-armv7.img.sha256",
|
||||
},
|
||||
},
|
||||
"ENT: Linux AMD64": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.docker.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-enterprise-1.2.3-amd64.img",
|
||||
},
|
||||
},
|
||||
"ENT: Linux AMD64 SHA256": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.docker.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-enterprise-1.2.3-amd64.img.sha256",
|
||||
},
|
||||
},
|
||||
"ENT: Linux ARM64 Ubuntu": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.ubuntu.docker.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-enterprise-1.2.3-ubuntu-arm64.img",
|
||||
},
|
||||
},
|
||||
"ENT: Linux ARM64 Ubuntu SHA256": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.ubuntu.docker.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-enterprise-1.2.3-ubuntu-arm64.img.sha256",
|
||||
},
|
||||
},
|
||||
"OSS: Linux ARM7": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.docker.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-oss-1.2.3-armv7.img",
|
||||
},
|
||||
},
|
||||
"OSS: Linux ARM7 SHA256": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.docker.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-oss-1.2.3-armv7.img.sha256",
|
||||
},
|
||||
},
|
||||
"OSS: Linux ARM7 Ubuntu": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.ubuntu.docker.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-oss-1.2.3-ubuntu-armv7.img",
|
||||
},
|
||||
},
|
||||
"OSS: Linux AR7 Ubuntu SHA256": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.ubuntu.docker.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-oss-1.2.3-ubuntu-armv7.img.sha256",
|
||||
},
|
||||
},
|
||||
"OSS: Linux AMD64": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.docker.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-oss-1.2.3-amd64.img",
|
||||
},
|
||||
},
|
||||
"OSS: Linux AMD64 SHA256": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.docker.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-oss-1.2.3-amd64.img.sha256",
|
||||
},
|
||||
},
|
||||
"OSS: Linux AMD64 Ubuntu": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.ubuntu.docker.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-oss-1.2.3-ubuntu-amd64.img",
|
||||
},
|
||||
},
|
||||
"OSS: Linux AMD64 Ubuntu SHA256": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.ubuntu.docker.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-oss-1.2.3-ubuntu-amd64.img.sha256",
|
||||
},
|
||||
},
|
||||
"OSS: Linux ARM64": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm64.docker.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-oss-1.2.3-arm64.img",
|
||||
},
|
||||
},
|
||||
"OSS: Linux ARM64 SHA256": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm64.docker.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-oss-1.2.3-arm64.img.sha256",
|
||||
},
|
||||
},
|
||||
"OSS: Linux ARM64 Ubuntu": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm64.ubuntu.docker.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-oss-1.2.3-ubuntu-arm64.img",
|
||||
},
|
||||
},
|
||||
"OSS: Linux ARM64 Ubuntu SHA256": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm64.ubuntu.docker.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-oss-1.2.3-ubuntu-arm64.img.sha256",
|
||||
},
|
||||
},
|
||||
"PRO: Linux AMD64": {
|
||||
input: "gs://bucket/tag/grafana-pro_v1.2.3_102_linux_amd64.docker.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-enterprise2-1.2.3-amd64.img",
|
||||
},
|
||||
},
|
||||
"PRO: Linux ARM64": {
|
||||
input: "gs://bucket/tag/grafana-pro_v1.2.3_102_linux_arm64.docker.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-enterprise2-1.2.3-arm64.img",
|
||||
},
|
||||
},
|
||||
"PRO: Linux ARM7": {
|
||||
input: "gs://bucket/tag/grafana-pro_v1.2.3_102_linux_arm-7.docker.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-enterprise2-1.2.3-armv7.img",
|
||||
},
|
||||
},
|
||||
"PRO: Linux AMD64 Ubuntu": {
|
||||
input: "gs://bucket/tag/grafana-pro_v1.2.3_102_linux_amd64.ubuntu.docker.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-enterprise2-1.2.3-ubuntu-amd64.img",
|
||||
},
|
||||
},
|
||||
"PRO: Linux ARM64 Ubuntu": {
|
||||
input: "gs://bucket/tag/grafana-pro_v1.2.3_102_linux_arm64.ubuntu.docker.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-enterprise2-1.2.3-ubuntu-arm64.img",
|
||||
},
|
||||
},
|
||||
"PRO: Linux ARM7 Ubuntu": {
|
||||
input: "gs://bucket/tag/grafana-pro_v1.2.3_102_linux_arm-7.ubuntu.docker.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/docker/1.2.3/grafana-enterprise2-1.2.3-ubuntu-armv7.img",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package main
|
||||
|
||||
var exeMapping = map[string]m{
|
||||
"ENT": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_windows_amd64.exe",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.windows-amd64.exe",
|
||||
},
|
||||
},
|
||||
"ENT SHA256": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_windows_amd64.exe.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.windows-amd64.exe.sha256",
|
||||
},
|
||||
},
|
||||
"OSS": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_windows_amd64.exe",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.windows-amd64.exe",
|
||||
},
|
||||
},
|
||||
"OSS SHA256": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_windows_amd64.exe.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.windows-amd64.exe.sha256",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package main
|
||||
|
||||
var msiMapping = map[string]m{
|
||||
"ENT": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_windows_amd64.msi",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.windows-amd64.msi",
|
||||
},
|
||||
},
|
||||
"ENT SHA256": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_windows_amd64.msi.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.windows-amd64.msi.sha256",
|
||||
},
|
||||
},
|
||||
"OSS": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_windows_amd64.msi",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.windows-amd64.msi",
|
||||
},
|
||||
},
|
||||
"OSS SHA256": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_windows_amd64.msi.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.windows-amd64.msi.sha256",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package main
|
||||
|
||||
var npmMapping = map[string]m{
|
||||
"Grafana data": {
|
||||
input: "file://dist/tag/grafana-10.2.0-pre/npm-packages",
|
||||
output: []string{
|
||||
"artifacts/npm/v10.2.0-pre/npm-artifacts",
|
||||
},
|
||||
env: map[string]string{"DRONE_TAG": "10.2.0-pre"},
|
||||
},
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package main
|
||||
|
||||
var rpmMapping = map[string]m{
|
||||
"OSS: Linux AMD64": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.rpm",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3-1.x86_64.rpm",
|
||||
},
|
||||
},
|
||||
"OSS: Linux AMD64 SHA256": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.rpm.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3-1.x86_64.rpm.sha256",
|
||||
},
|
||||
},
|
||||
"OSS: Linux ARM7": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.rpm",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3-1.armhfp.rpm",
|
||||
},
|
||||
},
|
||||
"OSS: Linux ARM7 SHA256": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.rpm.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3-1.armhfp.rpm.sha256",
|
||||
},
|
||||
},
|
||||
"OSS: Linux aarch64": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_aarch64.rpm",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3-1.aarch64.rpm",
|
||||
},
|
||||
},
|
||||
"OSS: Linux aarch64 SHA256": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm64.rpm.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3-1.aarch64.rpm.sha256",
|
||||
},
|
||||
},
|
||||
"ENT: Linux AMD64": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.rpm",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3-1.x86_64.rpm",
|
||||
},
|
||||
},
|
||||
"ENT: Linux AMD64 SHA256": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.rpm.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3-1.x86_64.rpm.sha256",
|
||||
},
|
||||
},
|
||||
"ENT: Linux ARM64": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.rpm",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3-1.aarch64.rpm",
|
||||
},
|
||||
},
|
||||
"ENT: Linux ARM64 SHA256": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.rpm.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3-1.aarch64.rpm.sha256",
|
||||
},
|
||||
},
|
||||
"ENT: Linux ARM7": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.rpm",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3-1.armhfp.rpm",
|
||||
},
|
||||
},
|
||||
"ENT: Linux ARM7 SHA256": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.rpm.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3-1.armhfp.rpm.sha256",
|
||||
},
|
||||
},
|
||||
"PRO: Linux AMD64": {
|
||||
input: "gs://bucket/tag/grafana-pro_v1.2.3-pre.4_102_linux_amd64.rpm",
|
||||
output: []string{
|
||||
"artifacts/downloads-enterprise2/v1.2.3-pre.4/enterprise2/release/grafana-enterprise2-1.2.3~pre.4-1.x86_64.rpm",
|
||||
},
|
||||
},
|
||||
"PRO: Linux AMD64 SHA256": {
|
||||
input: "gs://bucket/tag/grafana-pro_v1.2.3-pre.4_102_linux_amd64.rpm.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads-enterprise2/v1.2.3-pre.4/enterprise2/release/grafana-enterprise2-1.2.3~pre.4-1.x86_64.rpm.sha256",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package main
|
||||
|
||||
var storybookMapping = map[string]m{
|
||||
"OSS": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64/storybook",
|
||||
output: []string{
|
||||
"artifacts/storybook/v1.2.3",
|
||||
},
|
||||
env: map[string]string{"DRONE_TAG": "1.2.3"},
|
||||
},
|
||||
"ENT": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64/storybook",
|
||||
output: []string{
|
||||
"artifacts/storybook/v1.2.3",
|
||||
},
|
||||
env: map[string]string{"DRONE_TAG": "1.2.3"},
|
||||
},
|
||||
"PRO": {
|
||||
input: "gs://bucket/tag/grafana-pro_v1.2.3_102_linux_amd64/storybook",
|
||||
output: []string{
|
||||
"artifacts/storybook/v1.2.3",
|
||||
},
|
||||
env: map[string]string{"DRONE_TAG": "1.2.3"},
|
||||
},
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type m struct {
|
||||
input string
|
||||
output []string
|
||||
env map[string]string
|
||||
}
|
||||
|
||||
var targzMapping = map[string]m{
|
||||
"ENT: Darwin AMD64": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_darwin_amd64.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.darwin-amd64.tar.gz",
|
||||
},
|
||||
},
|
||||
"ENT: Darwin AMD64 SHA256": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_darwin_amd64.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.darwin-amd64.tar.gz.sha256",
|
||||
},
|
||||
},
|
||||
"ENT: AMD64 with MUSL copy": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-amd64-musl.tar.gz",
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-amd64.tar.gz",
|
||||
},
|
||||
},
|
||||
"ENT: AMD64 SHA256 with MUSL copy": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-amd64-musl.tar.gz.sha256",
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-amd64.tar.gz.sha256",
|
||||
},
|
||||
},
|
||||
"ENT: ARM64 with MUSL copy": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-arm64-musl.tar.gz",
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-arm64.tar.gz",
|
||||
},
|
||||
},
|
||||
"ENT: ARM64 SHA256 with MUSL copy": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-arm64-musl.tar.gz.sha256",
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-arm64.tar.gz.sha256",
|
||||
},
|
||||
},
|
||||
"ENT: ARM6": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-6.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-armv6.tar.gz",
|
||||
},
|
||||
},
|
||||
"ENT: ARM6 SHA256": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-6.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-armv6.tar.gz.sha256",
|
||||
},
|
||||
},
|
||||
"ENT: ARM7 with MUSL copy": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-armv7-musl.tar.gz",
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-armv7.tar.gz",
|
||||
},
|
||||
},
|
||||
"ENT: ARM7 SHA256 with MUSL copy": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-armv7-musl.tar.gz.sha256",
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-armv7.tar.gz.sha256",
|
||||
},
|
||||
},
|
||||
"ENT: Windows AMD64": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_windows_amd64.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.windows-amd64.tar.gz",
|
||||
},
|
||||
},
|
||||
"ENT: Windows AMD64 SHA256": {
|
||||
input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_windows_amd64.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.windows-amd64.tar.gz.sha256",
|
||||
},
|
||||
},
|
||||
"OSS: ARM6": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-6.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-armv6.tar.gz",
|
||||
},
|
||||
},
|
||||
"OSS: ARM6 SHA256": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-6.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-armv6.tar.gz.sha256",
|
||||
},
|
||||
},
|
||||
"OSS: ARM7 with MUSL copy": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-armv7-musl.tar.gz",
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-armv7.tar.gz",
|
||||
},
|
||||
},
|
||||
"OSS: ARM7 SHA256 with MUSL copy": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-armv7-musl.tar.gz.sha256",
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-armv7.tar.gz.sha256",
|
||||
},
|
||||
},
|
||||
"OSS: Windows AMD64": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_windows_amd64.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.windows-amd64.tar.gz",
|
||||
},
|
||||
},
|
||||
"OSS: Windows AMD64 SHA256": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_windows_amd64.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.windows-amd64.tar.gz.sha256",
|
||||
},
|
||||
},
|
||||
"OSS: Darwin AMD64": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_darwin_amd64.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.darwin-amd64.tar.gz",
|
||||
},
|
||||
},
|
||||
"OSS: Darwin AMD64 SHA256": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_darwin_amd64.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.darwin-amd64.tar.gz.sha256",
|
||||
},
|
||||
},
|
||||
"OSS: Linux AMD64 with MUSL copy": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-amd64-musl.tar.gz",
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-amd64.tar.gz",
|
||||
},
|
||||
},
|
||||
"OSS: Linux AMD64 SHA256 with MUSL copy": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-amd64-musl.tar.gz.sha256",
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-amd64.tar.gz.sha256",
|
||||
},
|
||||
},
|
||||
"OSS: Linux ARM64 with MUSL copy": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm64.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-arm64-musl.tar.gz",
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-arm64.tar.gz",
|
||||
},
|
||||
},
|
||||
"OSS: Linux ARM64 SHA256 with MUSL copy": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm64.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-arm64-musl.tar.gz.sha256",
|
||||
"artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-arm64.tar.gz.sha256",
|
||||
},
|
||||
},
|
||||
"ENT2: Linux AMD64 with MUSL copy": {
|
||||
input: "gs://bucket/tag/grafana-pro_v1.2.3-pre.4_102_linux_amd64.tar.gz",
|
||||
output: []string{
|
||||
"artifacts/downloads-enterprise2/v1.2.3-pre.4/enterprise2/release/grafana-enterprise2-1.2.3-pre.4.linux-amd64-musl.tar.gz",
|
||||
"artifacts/downloads-enterprise2/v1.2.3-pre.4/enterprise2/release/grafana-enterprise2-1.2.3-pre.4.linux-amd64.tar.gz",
|
||||
},
|
||||
},
|
||||
"ENT2: Linux AMD64 SHA256 with MUSL copy": {
|
||||
input: "gs://bucket/tag/grafana-pro_v1.2.3-pre.4_102_linux_amd64.tar.gz.sha256",
|
||||
output: []string{
|
||||
"artifacts/downloads-enterprise2/v1.2.3-pre.4/enterprise2/release/grafana-enterprise2-1.2.3-pre.4.linux-amd64-musl.tar.gz.sha256",
|
||||
"artifacts/downloads-enterprise2/v1.2.3-pre.4/enterprise2/release/grafana-enterprise2-1.2.3-pre.4.linux-amd64.tar.gz.sha256",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestGetHandler(t *testing.T) {
|
||||
runTests(t, "TARGZ: ", targzMapping)
|
||||
runTests(t, "DOCKER: ", dockerMapping)
|
||||
runTests(t, "CDN: ", cdnMapping)
|
||||
runTests(t, "ZIP: ", zipMapping)
|
||||
runTests(t, "MSI: ", msiMapping)
|
||||
runTests(t, "NPM: ", npmMapping)
|
||||
runTests(t, "DEB: ", debMapping)
|
||||
runTests(t, "RPM: ", rpmMapping)
|
||||
runTests(t, "EXE: ", exeMapping)
|
||||
runTests(t, "STORYBOOK: ", storybookMapping)
|
||||
}
|
||||
|
||||
func runTests(t *testing.T, namePrefix string, tests map[string]m) {
|
||||
t.Helper()
|
||||
for testname, testcase := range tests {
|
||||
t.Run(namePrefix+testname, func(t *testing.T) {
|
||||
for envName, envValue := range testcase.env {
|
||||
t.Setenv(envName, envValue)
|
||||
}
|
||||
handler, _ := getHandler(testcase.input, Handlers)
|
||||
output := handler(testcase.input)
|
||||
sort.Strings(output)
|
||||
require.Equal(t, testcase.output, output)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package main
|
||||
|
||||
var zipMapping = map[string]m{
|
||||
"OSS: Windows AMD64": {
|
||||
input: "gs://bucket/tag/grafana_v1.2.3-test.1_102_windows_amd64.zip",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3-test.1/oss/release/grafana-1.2.3-test.1.windows-amd64.zip",
|
||||
},
|
||||
},
|
||||
"OSS: Windows AMD64 from file://": {
|
||||
input: "file://bucket/tag/grafana_v1.2.3-test.1_102_windows_amd64.zip",
|
||||
output: []string{
|
||||
"artifacts/downloads/v1.2.3-test.1/oss/release/grafana-1.2.3-test.1.windows-amd64.zip",
|
||||
},
|
||||
},
|
||||
"OSS: Windows AMD64 main from file://": {
|
||||
input: "file://bucket/tag/grafana_main_102_windows_amd64.zip",
|
||||
output: []string{
|
||||
"artifacts/downloads/main/oss/release/grafana-main.windows-amd64.zip",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/build/config"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Bucket string
|
||||
Edition string
|
||||
Tag string
|
||||
Distribution []config.Distribution
|
||||
Archs []config.Architecture
|
||||
DockerHubRepo string
|
||||
Security bool
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
// Package droneutil provides utility functions for working with Drone.
|
||||
package droneutil
|
||||
@@ -1,34 +0,0 @@
|
||||
package droneutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Lookup is the equivalent of os.LookupEnv, but also accepts a list of strings rather than only checking os.Environ()
|
||||
func Lookup(values []string, val string) (string, bool) {
|
||||
for _, v := range values {
|
||||
prefix := val + "="
|
||||
if strings.HasPrefix(v, prefix) {
|
||||
return strings.TrimPrefix(v, prefix), true
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// GetDroneEvent looks for the "DRONE_BUILD_EVENT" in the provided env list and returns the value.
|
||||
// if it was not found, then an error is returned.
|
||||
func GetDroneEvent(env []string) (string, error) {
|
||||
event, ok := Lookup(env, "DRONE_BUILD_EVENT")
|
||||
if !ok {
|
||||
return "", fmt.Errorf("failed to get DRONE_BUILD_EVENT environmental variable")
|
||||
}
|
||||
return event, nil
|
||||
}
|
||||
|
||||
// GetDroneEventFromEnv returns the value of DRONE_BUILD_EVENT from os.Environ()
|
||||
func GetDroneEventFromEnv() (string, error) {
|
||||
return GetDroneEvent(os.Environ())
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package droneutil_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/droneutil"
|
||||
)
|
||||
|
||||
func TestGetDroneEvent(t *testing.T) {
|
||||
t.Run("Should return the Drone Event", func(t *testing.T) {
|
||||
env := []string{"DRONE_BUILD_EVENT=pull_request"}
|
||||
droneEvent, err := droneutil.GetDroneEvent(env)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, droneEvent, "pull_request")
|
||||
})
|
||||
t.Run("Should return error, Drone Event env var is missing", func(t *testing.T) {
|
||||
droneEvent, err := droneutil.GetDroneEvent([]string{})
|
||||
require.Error(t, err)
|
||||
require.Empty(t, droneEvent)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLookup(t *testing.T) {
|
||||
env := []string{"", "EXAMPLE_KEY=value", "EXAMPLE_KEY"}
|
||||
t.Run("A valid lookup should return a string and no error", func(t *testing.T) {
|
||||
val, ok := droneutil.Lookup(env, "EXAMPLE_KEY")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, val, "value")
|
||||
})
|
||||
|
||||
t.Run("An invalid lookup should return an error", func(t *testing.T) {
|
||||
_, ok := droneutil.Lookup(env, "EXAMPLE_KEY_DOES_NOT_EXIST")
|
||||
require.False(t, ok)
|
||||
})
|
||||
}
|
||||
Vendored
-40
@@ -1,40 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// RequireListWithEnvFallback first checks the CLI for a flag with the required
|
||||
// name. If this is empty, it falls back to taking the environment variable.
|
||||
// Sadly, we cannot use cli.Flag.EnvVars for this due to it potentially leaking
|
||||
// environment variables as default values in usage-errors.
|
||||
func RequireListWithEnvFallback(cctx *cli.Context, name string, envName string) ([]string, error) {
|
||||
result := cctx.StringSlice(name)
|
||||
if len(result) == 0 {
|
||||
for _, v := range strings.Split(os.Getenv(envName), ",") {
|
||||
value := strings.TrimSpace(v)
|
||||
if value != "" {
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil, cli.Exit(fmt.Sprintf("Required flag (%s) or environment variable (%s) not set", name, envName), 1)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func RequireStringWithEnvFallback(cctx *cli.Context, name string, envName string) (string, error) {
|
||||
result := cctx.String(name)
|
||||
if result == "" {
|
||||
result = os.Getenv(envName)
|
||||
}
|
||||
if result == "" {
|
||||
return "", cli.Exit(fmt.Sprintf("Required flag (%s) or environment variable (%s) not set", name, envName), 1)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
Vendored
-144
@@ -1,144 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
flag1 = "flag1"
|
||||
flag2 = "flag2"
|
||||
)
|
||||
|
||||
type flagObj struct {
|
||||
name string
|
||||
value string
|
||||
}
|
||||
|
||||
func TestRequireListWithEnvFallback(t *testing.T) {
|
||||
var app = cli.NewApp()
|
||||
tests := []struct {
|
||||
testName string
|
||||
ctx *cli.Context
|
||||
name string
|
||||
envName string
|
||||
expected []string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
testName: "string slice present in context",
|
||||
ctx: cli.NewContext(app, applyFlagSet(t, flag1, "a"), nil),
|
||||
name: flag1,
|
||||
envName: "",
|
||||
expected: []string{"a"},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
testName: "string slice present in env",
|
||||
ctx: cli.NewContext(app, flag.NewFlagSet("test", 0), nil),
|
||||
name: flag1,
|
||||
envName: setEnv(t, flag1, "a"),
|
||||
expected: []string{"a"},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
testName: "string slice absent in both context and env",
|
||||
ctx: cli.NewContext(app, flag.NewFlagSet("test", 0), nil),
|
||||
name: flag1,
|
||||
envName: "",
|
||||
expected: []string(nil),
|
||||
expectedErr: cli.Exit("Required flag (flag1) or environment variable () not set", 1),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
flagList, err := RequireListWithEnvFallback(tt.ctx, tt.name, tt.envName)
|
||||
if tt.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
}
|
||||
require.Equal(t, tt.expected, flagList)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequireStringWithEnvFallback(t *testing.T) {
|
||||
var app = cli.NewApp()
|
||||
tests := []struct {
|
||||
testName string
|
||||
ctx *cli.Context
|
||||
name string
|
||||
envName string
|
||||
expected string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
testName: "string present in the context",
|
||||
ctx: cli.NewContext(app, setFlags(t, flag.NewFlagSet("test", flag.ContinueOnError), flagObj{name: flag1, value: "a"}), nil),
|
||||
name: flag1,
|
||||
envName: "",
|
||||
expected: "a",
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
testName: "string present in env",
|
||||
ctx: cli.NewContext(app, setFlags(t, flag.NewFlagSet("test", flag.ContinueOnError)), nil),
|
||||
name: flag1,
|
||||
envName: setEnv(t, flag1, "a"),
|
||||
expected: "a",
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
testName: "string absent from both context and env",
|
||||
ctx: cli.NewContext(app, setFlags(t, flag.NewFlagSet("test", flag.ContinueOnError), flagObj{name: flag2, value: "b"}), nil),
|
||||
name: flag1,
|
||||
envName: "",
|
||||
expected: "",
|
||||
expectedErr: cli.Exit("Required flag (flag1) or environment variable () not set", 1),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.testName, func(t *testing.T) {
|
||||
flagString, err := RequireStringWithEnvFallback(tt.ctx, tt.name, tt.envName)
|
||||
if tt.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
}
|
||||
require.Equal(t, tt.expected, flagString)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func applyFlagSet(t *testing.T, aFlag, aValue string) *flag.FlagSet {
|
||||
t.Helper()
|
||||
var val cli.StringSlice
|
||||
fl := cli.StringSliceFlag{Name: aFlag, EnvVars: []string{"FLAG"}, Value: &val}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
parseInput := []string{fmt.Sprintf("-%s", aFlag), aValue}
|
||||
err := fl.Apply(set)
|
||||
require.NoError(t, err)
|
||||
err = set.Parse(parseInput)
|
||||
require.NoError(t, err)
|
||||
return set
|
||||
}
|
||||
|
||||
func setFlags(t *testing.T, flagSet *flag.FlagSet, flags ...flagObj) *flag.FlagSet {
|
||||
t.Helper()
|
||||
for _, f := range flags {
|
||||
if f.name != "" {
|
||||
flagSet.StringVar(&f.name, f.name, f.value, "")
|
||||
}
|
||||
}
|
||||
return flagSet
|
||||
}
|
||||
|
||||
func setEnv(t *testing.T, key, value string) string {
|
||||
t.Helper()
|
||||
os.Clearenv()
|
||||
|
||||
t.Setenv(key, value)
|
||||
return key
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package fsutil_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/fsutil"
|
||||
)
|
||||
|
||||
func TestCopyFile(t *testing.T) {
|
||||
src, err := os.CreateTemp("", "")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
if err := os.RemoveAll(src.Name()); err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = os.WriteFile(src.Name(), []byte("Contents"), 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
dst, err := os.CreateTemp("", "")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
if err := os.RemoveAll(dst.Name()); err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = fsutil.CopyFile(src.Name(), dst.Name())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCopyFile_Permissions(t *testing.T) {
|
||||
perms := os.FileMode(0700)
|
||||
if runtime.GOOS == "windows" {
|
||||
// Windows doesn't have file Unix style file permissions
|
||||
// It seems you have either 0444 for read-only or 0666 for read-write
|
||||
perms = os.FileMode(0666)
|
||||
}
|
||||
|
||||
src, err := os.CreateTemp("", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
if err := os.RemoveAll(src.Name()); err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = os.WriteFile(src.Name(), []byte("Contents"), perms)
|
||||
require.NoError(t, err)
|
||||
err = os.Chmod(src.Name(), perms)
|
||||
require.NoError(t, err)
|
||||
|
||||
dst, err := os.CreateTemp("", "")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
if err := os.RemoveAll(dst.Name()); err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = fsutil.CopyFile(src.Name(), dst.Name())
|
||||
require.NoError(t, err)
|
||||
|
||||
fi, err := os.Stat(dst.Name())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, perms, fi.Mode()&os.ModePerm)
|
||||
}
|
||||
|
||||
// Test case where destination directory doesn't exist.
|
||||
func TestCopyFile_NonExistentDestDir(t *testing.T) {
|
||||
src, err := os.CreateTemp("", "")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
if err := os.RemoveAll(src.Name()); err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = fsutil.CopyFile(src.Name(), "non-existent/dest")
|
||||
require.EqualError(t, err, "destination directory doesn't exist: \"non-existent\"")
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// CopyFile copies a file from src to dst.
|
||||
//
|
||||
// If src and dst files exist, and are the same, then return success. Otherwise, attempt to create a hard link
|
||||
// between the two files. If that fails, copy the file contents from src to dst.
|
||||
func CopyFile(src, dst string) (err error) {
|
||||
absSrc, err := filepath.Abs(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get absolute path of source file %q: %w", src, err)
|
||||
}
|
||||
sfi, err := os.Stat(src)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("couldn't stat source file %q: %w", absSrc, err)
|
||||
return
|
||||
}
|
||||
if !sfi.Mode().IsRegular() {
|
||||
// Cannot copy non-regular files (e.g., directories, symlinks, devices, etc.)
|
||||
return fmt.Errorf("non-regular source file %s (%q)", absSrc, sfi.Mode().String())
|
||||
}
|
||||
dpath := filepath.Dir(dst)
|
||||
exists, err := Exists(dpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
err = fmt.Errorf("destination directory doesn't exist: %q", dpath)
|
||||
return
|
||||
}
|
||||
|
||||
var dfi os.FileInfo
|
||||
dfi, err = os.Stat(dst)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if !(dfi.Mode().IsRegular()) {
|
||||
return fmt.Errorf("non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String())
|
||||
}
|
||||
if os.SameFile(sfi, dfi) {
|
||||
return copyPermissions(sfi.Name(), dfi.Name())
|
||||
}
|
||||
}
|
||||
|
||||
err = copyFileContents(src, dst)
|
||||
return err
|
||||
}
|
||||
|
||||
// copyFileContents copies the contents of the file named src to the file named
|
||||
// by dst. The file will be created if it does not already exist. If the
|
||||
// destination file exists, all it's contents will be replaced by the contents
|
||||
// of the source file.
|
||||
func copyFileContents(src, dst string) (err error) {
|
||||
//nolint:gosec
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := in.Close(); err != nil {
|
||||
log.Println("error closing file", err)
|
||||
}
|
||||
}()
|
||||
|
||||
//nolint:gosec
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if cerr := out.Close(); cerr != nil && err == nil {
|
||||
err = cerr
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err = io.Copy(out, in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := out.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return copyPermissions(src, dst)
|
||||
}
|
||||
|
||||
func copyPermissions(src, dst string) error {
|
||||
sfi, err := os.Lstat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Chmod(dst, sfi.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package fsutil_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/fsutil"
|
||||
)
|
||||
|
||||
func TestExists_NonExistent(t *testing.T) {
|
||||
exists, err := fsutil.Exists("non-existent")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.False(t, exists)
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package fsutil
|
||||
|
||||
import "os"
|
||||
|
||||
// Exists determines whether a file/directory exists or not.
|
||||
func Exists(fpath string) (bool, error) {
|
||||
_, err := os.Stat(fpath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package gcloud
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetDecodedKey() ([]byte, error) {
|
||||
gcpKey := strings.TrimSpace(os.Getenv("GCP_KEY"))
|
||||
if gcpKey == "" {
|
||||
return nil, fmt.Errorf("the environment variable GCP_KEY must be set")
|
||||
}
|
||||
|
||||
gcpKeyB, err := base64.StdEncoding.DecodeString(gcpKey)
|
||||
if err != nil {
|
||||
// key is not always base64 encoded
|
||||
validKey := []byte(gcpKey)
|
||||
if json.Valid(validKey) {
|
||||
return validKey, nil
|
||||
}
|
||||
return nil, fmt.Errorf("error decoding the gcp_key, err: %q", err)
|
||||
}
|
||||
|
||||
return gcpKeyB, nil
|
||||
}
|
||||
|
||||
func ActivateServiceAccount() error {
|
||||
byteKey, err := GetDecodedKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.CreateTemp("", "*.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := os.Remove(f.Name()); err != nil {
|
||||
log.Printf("error removing %s: %s", f.Name(), err)
|
||||
}
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
log.Println("error closing file:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := f.Write(byteKey); err != nil {
|
||||
return fmt.Errorf("failed to write GCP key file: %w", err)
|
||||
}
|
||||
keyArg := fmt.Sprintf("--key-file=%s", f.Name())
|
||||
//nolint:gosec
|
||||
cmd := exec.Command("gcloud", "auth", "activate-service-account", keyArg)
|
||||
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("failed to sign into GCP: %w\n%s", err, output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,490 +0,0 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"mime"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/storage"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/fsutil"
|
||||
"github.com/grafana/grafana/pkg/build/gcloud"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrorNilBucket is returned when a function is called where a bucket argument is expected and the bucket is nil.
|
||||
ErrorNilBucket = errors.New("a bucket must be provided")
|
||||
)
|
||||
|
||||
const (
|
||||
// maxThreads specify the number of max threads that can run at the same time.
|
||||
// Set to 1000, since the maximum number of simultaneous open files for the runners is 1024.
|
||||
maxThreads = 1000
|
||||
)
|
||||
|
||||
// Client wraps the gcloud storage Client with convenient helper functions.
|
||||
// By using an embedded type we can still use the functions provided by storage.Client if we need to.
|
||||
type Client struct {
|
||||
storage.Client
|
||||
}
|
||||
|
||||
// File represents a file in Google Cloud Storage.
|
||||
type File struct {
|
||||
FullPath string
|
||||
PathTrimmed string
|
||||
}
|
||||
|
||||
// New creates a new Client by checking for the Google Cloud SDK auth key and/or environment variable.
|
||||
func New() (*Client, error) {
|
||||
storageClient, err := newClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
Client: *storageClient,
|
||||
}
|
||||
client.SetRetryer()
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// SetRetryer adds a retry strategy for the googleapi client calls that fail.
|
||||
func (client *Client) SetRetryer() {
|
||||
client.SetRetry([]storage.RetryOption{
|
||||
storage.WithPolicy(storage.RetryAlways),
|
||||
storage.WithErrorFunc(storage.ShouldRetry),
|
||||
}...)
|
||||
}
|
||||
|
||||
// newClient initializes the google-cloud-storage (GCS) client.
|
||||
// It first checks for the application-default_credentials.json file then the GCP_KEY environment variable.
|
||||
func newClient() (*storage.Client, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
byteKey, err := gcloud.GetDecodedKey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get gcp key, err: %w", err)
|
||||
}
|
||||
client, err := storage.NewClient(ctx, option.WithCredentialsJSON(byteKey))
|
||||
if err != nil {
|
||||
log.Println("failed to login with GCP_KEY, trying with default application credentials...")
|
||||
client, err = storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open Google Cloud Storage client: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// CopyLocalDir copies a local directory 'dir' to the bucket 'bucket' at the path 'bucketPath'.
|
||||
func (client *Client) CopyLocalDir(ctx context.Context, dir string, bucket *storage.BucketHandle, bucketPath string, trim bool) error {
|
||||
if bucket == nil {
|
||||
return ErrorNilBucket
|
||||
}
|
||||
|
||||
files, err := ListLocalFiles(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Number or files to be copied over: %d\n", len(files))
|
||||
|
||||
errs := make([]error, 0, 10)
|
||||
errsLock := sync.Mutex{}
|
||||
|
||||
for _, chunk := range asChunks(files, maxThreads) {
|
||||
var wg sync.WaitGroup
|
||||
for _, f := range chunk {
|
||||
wg.Add(1)
|
||||
go func(file File) {
|
||||
defer wg.Done()
|
||||
err := client.Copy(ctx, file, bucket, bucketPath, trim)
|
||||
if err != nil {
|
||||
log.Printf("failed to copy objects, err: %s\n", err.Error())
|
||||
errsLock.Lock()
|
||||
errs = append(errs, err)
|
||||
errsLock.Unlock()
|
||||
}
|
||||
}(f)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("copy operation failed: %s", errs)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy copies a single local file into the bucket at the provided path.
|
||||
// trim variable should be set to true if the full object path is needed - false otherwise.
|
||||
func (client *Client) Copy(ctx context.Context, file File, bucket *storage.BucketHandle, remote string, trim bool) (resultErr error) {
|
||||
if bucket == nil {
|
||||
return ErrorNilBucket
|
||||
}
|
||||
|
||||
localFile, err := os.Open(file.FullPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open file %s, err: %q", file.FullPath, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := localFile.Close(); err != nil {
|
||||
log.Println("failed to close localfile", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
extension := strings.ToLower(path.Ext(file.FullPath))
|
||||
contentType := mime.TypeByExtension(extension)
|
||||
|
||||
filePath := file.FullPath
|
||||
if trim {
|
||||
filePath = file.PathTrimmed
|
||||
}
|
||||
|
||||
objectPath := path.Join(remote, filePath)
|
||||
|
||||
wc := bucket.Object(objectPath).NewWriter(ctx)
|
||||
wc.ContentType = contentType
|
||||
defer func() {
|
||||
if err := wc.Close(); err != nil {
|
||||
log.Println("failed to close writer", "err", err)
|
||||
// Keep the original error intact if there was one:
|
||||
if resultErr == nil {
|
||||
resultErr = err
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err = io.Copy(wc, localFile); err != nil {
|
||||
resultErr = fmt.Errorf("failed to copy to Cloud Storage: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyRemoteDir copies an entire directory 'from' from the bucket 'fromBucket' into the 'toBucket' at the path 'to'.
|
||||
func (client *Client) CopyRemoteDir(ctx context.Context, fromBucket *storage.BucketHandle, from string, toBucket *storage.BucketHandle, to string) error {
|
||||
if toBucket == nil || fromBucket == nil {
|
||||
return ErrorNilBucket
|
||||
}
|
||||
|
||||
files, err := ListRemoteFiles(ctx, fromBucket, FilesFilter{Prefix: from})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ch = make(chan File, len(files))
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(maxThreads)
|
||||
|
||||
for i := 0; i < maxThreads; i++ {
|
||||
go func() {
|
||||
for {
|
||||
file, ok := <-ch
|
||||
if !ok {
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
if err := client.RemoteCopy(ctx, file, fromBucket, toBucket, to); err != nil {
|
||||
log.Printf("failed to copy files between buckets: err: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
ch <- file
|
||||
}
|
||||
|
||||
close(ch)
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoteCopy will copy the file 'file' from the 'fromBucket' to the 'toBucket' at the path 'path'.
|
||||
func (client *Client) RemoteCopy(ctx context.Context, file File, fromBucket, toBucket *storage.BucketHandle, path string) error {
|
||||
// Should this be path.Join instead of filepath.Join? filepath.Join on Windows will produce `\\` separators instead of `/`.
|
||||
var (
|
||||
src = fromBucket.Object(file.FullPath)
|
||||
dstObject = filepath.Join(path, file.PathTrimmed)
|
||||
dst = toBucket.Object(dstObject)
|
||||
)
|
||||
|
||||
if _, err := dst.CopierFrom(src).Run(ctx); err != nil {
|
||||
return fmt.Errorf("failed to copy object %s, to %s, err: %w", file.FullPath, dstObject, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteDir deletes a directory at 'path' from the bucket.
|
||||
func (client *Client) DeleteDir(ctx context.Context, bucket *storage.BucketHandle, path string) error {
|
||||
if bucket == nil {
|
||||
return ErrorNilBucket
|
||||
}
|
||||
|
||||
files, err := ListRemoteFiles(ctx, bucket, FilesFilter{Prefix: path})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ch = make(chan string, len(files))
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(maxThreads)
|
||||
|
||||
for i := 0; i < maxThreads; i++ {
|
||||
go func() {
|
||||
for {
|
||||
fullPath, ok := <-ch
|
||||
if !ok {
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
err := client.Delete(ctx, bucket, fullPath)
|
||||
if err != nil && !errors.Is(err, storage.ErrObjectNotExist) {
|
||||
log.Printf("failed to delete objects, err %s\n", err.Error())
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
ch <- file.FullPath
|
||||
}
|
||||
|
||||
close(ch)
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes single item from the bucket at 'path'.
|
||||
func (client *Client) Delete(ctx context.Context, bucket *storage.BucketHandle, path string) error {
|
||||
object := bucket.Object(path)
|
||||
if err := object.Delete(ctx); err != nil {
|
||||
return fmt.Errorf("cannot delete %s, err: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListLocalFiles lists files in a local filesystem.
|
||||
func ListLocalFiles(dir string) ([]File, error) {
|
||||
var files []File
|
||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if !info.IsDir() {
|
||||
files = append(files, File{
|
||||
FullPath: path,
|
||||
// Strip the dir name from the filepath
|
||||
PathTrimmed: strings.ReplaceAll(path, dir, ""),
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error walking path: %v", err)
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
type FilesFilter struct {
|
||||
Prefix string
|
||||
FileExts []string
|
||||
}
|
||||
|
||||
// ListRemoteFiles lists all the files in the directory (filtering by FilesFilter) and returns a File struct for each one.
|
||||
func ListRemoteFiles(ctx context.Context, bucket *storage.BucketHandle, filter FilesFilter) ([]File, error) {
|
||||
if bucket == nil {
|
||||
return []File{}, ErrorNilBucket
|
||||
}
|
||||
|
||||
it := bucket.Objects(ctx, &storage.Query{
|
||||
Prefix: filter.Prefix,
|
||||
})
|
||||
|
||||
var files []File
|
||||
for {
|
||||
attrs, err := it.Next()
|
||||
if err != nil {
|
||||
if errors.Is(err, iterator.Done) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to iterate through bucket, err: %w", err)
|
||||
}
|
||||
|
||||
extMatch := len(filter.FileExts) == 0
|
||||
for _, ext := range filter.FileExts {
|
||||
if ext == filepath.Ext(attrs.Name) {
|
||||
extMatch = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if extMatch {
|
||||
files = append(files, File{FullPath: attrs.Name, PathTrimmed: strings.TrimPrefix(attrs.Name, filter.Prefix)})
|
||||
}
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// DownloadDirectory downloads files from bucket (filtering by FilesFilter) to destPath on disk.
|
||||
func (client *Client) DownloadDirectory(ctx context.Context, bucket *storage.BucketHandle, destPath string, filter FilesFilter) error {
|
||||
if bucket == nil {
|
||||
return ErrorNilBucket
|
||||
}
|
||||
|
||||
files, err := ListRemoteFiles(ctx, bucket, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// return err if dir already exists
|
||||
exists, err := fsutil.Exists(destPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return fmt.Errorf("destination path %q already exists", destPath)
|
||||
}
|
||||
|
||||
err = os.MkdirAll(destPath, 0750)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
err = client.downloadFile(ctx, bucket, file.FullPath, file.PathTrimmed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLatestMainBuild gets the latest main build which is successfully uploaded to the gcs bucket.
|
||||
func GetLatestMainBuild(ctx context.Context, bucket *storage.BucketHandle, path string) (string, error) {
|
||||
if bucket == nil {
|
||||
return "", ErrorNilBucket
|
||||
}
|
||||
|
||||
query := &storage.Query{
|
||||
Prefix: path,
|
||||
}
|
||||
err := query.SetAttrSelection([]string{"Name", "Generation"})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to set attribute selector, err: %q", err)
|
||||
}
|
||||
it := bucket.Objects(ctx, query)
|
||||
|
||||
var files []string
|
||||
var oldGeneration int64
|
||||
for {
|
||||
attrs, err := it.Next()
|
||||
if errors.Is(err, iterator.Done) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to iterate through bucket, err: %w", err)
|
||||
}
|
||||
if attrs.Generation >= oldGeneration {
|
||||
files = append([]string{attrs.Name}, files...)
|
||||
oldGeneration = attrs.Generation
|
||||
} else {
|
||||
files = append(files, attrs.Name)
|
||||
}
|
||||
}
|
||||
|
||||
var latestVersion string
|
||||
for i := 0; i < len(files); i++ {
|
||||
captureVersion := regexp.MustCompile(`(\d+\.\d+\.\d+-\d+)`)
|
||||
if captureVersion.MatchString(files[i]) {
|
||||
latestVersion = captureVersion.FindString(files[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return latestVersion, nil
|
||||
}
|
||||
|
||||
// downloadFile downloads an object to a file.
|
||||
func (client *Client) downloadFile(ctx context.Context, bucket *storage.BucketHandle, objectName, destFileName string) error {
|
||||
if bucket == nil {
|
||||
return ErrorNilBucket
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Second*10)
|
||||
defer cancel()
|
||||
|
||||
// nolint:gosec
|
||||
f, err := os.Create(destFileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("os.Create: %v", err)
|
||||
}
|
||||
|
||||
rc, err := bucket.Object(objectName).NewReader(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Object(%q).NewReader: %v", objectName, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := rc.Close(); err != nil {
|
||||
log.Println("failed to close reader", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := io.Copy(f, rc); err != nil {
|
||||
return fmt.Errorf("io.Copy: %v", err)
|
||||
}
|
||||
|
||||
if err = f.Close(); err != nil {
|
||||
return fmt.Errorf("f.Close: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// asChunks will split the supplied []File into slices with a max size of `chunkSize`
|
||||
// []string{"a", "b", "c"}, 1 => [][]string{[]string{"a"}, []string{"b"}, []string{"c"}}
|
||||
// []string{"a", "b", "c"}, 2 => [][]string{[]string{"a", "b"}, []string{"c"}}.
|
||||
func asChunks(files []File, chunkSize int) [][]File {
|
||||
var fileChunks [][]File
|
||||
|
||||
if len(files) == 0 {
|
||||
return [][]File{}
|
||||
}
|
||||
|
||||
if len(files) > chunkSize && chunkSize > 0 {
|
||||
for i := 0; i < len(files); i += chunkSize {
|
||||
end := i + chunkSize
|
||||
|
||||
if end > len(files) {
|
||||
end = len(files)
|
||||
}
|
||||
fileChunks = append(fileChunks, files[i:end])
|
||||
}
|
||||
} else {
|
||||
fileChunks = [][]File{files}
|
||||
}
|
||||
return fileChunks
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user