Skip to content

Commit aecdf73

Browse files
Add support for AAD auth
1 parent fd20e9a commit aecdf73

File tree

4 files changed

+261
-12
lines changed

4 files changed

+261
-12
lines changed

.drone.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ platform:
1212

1313
steps:
1414
- name: vet
15-
image: golang:1.17
15+
image: golang:1.20
1616
commands:
1717
- go vet ./...
1818
environment:
@@ -43,7 +43,7 @@ pool:
4343
steps:
4444
- name: build
4545
pull: always
46-
image: golang:1.19
46+
image: golang:1.20
4747
commands:
4848
- GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -a -tags netgo -o release/drone-buildx-acr-linux-amd64 ./cmd/drone-buildx-acr
4949
- GOOS=linux GOARCH=arm64 go build -ldflags "-s -w" -a -tags netgo -o release/drone-buildx-acr-linux-arm64 ./cmd/drone-buildx-acr

cmd/drone-buildx-acr/main.go

+208
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,39 @@
11
package main
22

33
import (
4+
"context"
5+
"encoding/base64"
6+
"encoding/json"
47
"fmt"
8+
"io/ioutil"
9+
"net/http"
10+
"net/url"
511
"os"
612
"strings"
713

14+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
15+
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
816
"github.com/joho/godotenv"
17+
"github.com/pkg/errors"
18+
"github.com/sirupsen/logrus"
919

1020
docker "github.com/drone-plugins/drone-buildx"
1121
)
1222

23+
const (
24+
acrCertPath = "/tmp/acr-cert.pem"
25+
azSubscriptionApiVersion = "2021-04-01"
26+
azSubscriptionBaseUrl = "https://management.azure.com/subscriptions/"
27+
basePublicUrl = "https://portal.azure.com/#view/Microsoft_Azure_ContainerRegistries/TagMetadataBlade/registryId/"
28+
defaultUsername = "00000000-0000-0000-0000-000000000000"
29+
30+
// Environment variable names for Azure Environment Credential
31+
clientIdEnv = "AZURE_CLIENT_ID"
32+
clientSecretKeyEnv = "AZURE_CLIENT_SECRET"
33+
tenantKeyEnv = "AZURE_TENANT_ID"
34+
certPathEnv = "AZURE_CLIENT_CERTIFICATE_PATH"
35+
)
36+
1337
func main() {
1438
// Load env-file if it exists first
1539
if env := os.Getenv("PLUGIN_ENV_FILE"); env != "" {
@@ -19,15 +43,37 @@ func main() {
1943
var (
2044
repo = getenv("PLUGIN_REPO")
2145
registry = getenv("PLUGIN_REGISTRY")
46+
47+
// If these credentials are provided, they will be directly used
48+
// for docker login
2249
username = getenv("SERVICE_PRINCIPAL_CLIENT_ID")
2350
password = getenv("SERVICE_PRINCIPAL_CLIENT_SECRET")
51+
52+
// Service principal credentials
53+
clientId = getenv("CLIENT_ID")
54+
clientSecret = getenv("CLIENT_SECRET")
55+
clientCert = getenv("CLIENT_CERTIFICATE")
56+
tenantId = getenv("TENANT_ID")
57+
subscriptionId = getenv("SUBSCRIPTION_ID")
58+
publicUrl = getenv("DAEMON_REGISTRY")
2459
)
2560

2661
// default registry value
2762
if registry == "" {
2863
registry = "azurecr.io"
2964
}
3065

66+
// Get auth if username and password is not specified
67+
if username == "" && password == "" {
68+
// docker login credentials are not provided
69+
var err error
70+
username = defaultUsername
71+
password, publicUrl, err = getAuth(clientId, clientSecret, clientCert, tenantId, subscriptionId, registry)
72+
if err != nil {
73+
logrus.Fatal(err)
74+
}
75+
}
76+
3177
// must use the fully qualified repo name. If the
3278
// repo name does not have the registry prefix we
3379
// should prepend.
@@ -40,11 +86,167 @@ func main() {
4086
os.Setenv("DOCKER_USERNAME", username)
4187
os.Setenv("DOCKER_PASSWORD", password)
4288
os.Setenv("PLUGIN_REGISTRY_TYPE", "ACR")
89+
if publicUrl != "" {
90+
// Set this env variable if public URL for artifact is available
91+
// If not, we will fall back to registry url
92+
os.Setenv("ARTIFACT_REGISTRY", publicUrl)
93+
}
4394

4495
// invoke the base docker plugin binary
4596
docker.Run()
4697
}
4798

99+
func getAuth(clientId, clientSecret, clientCert, tenantId, subscriptionId, registry string) (string, string, error) {
100+
// Verify inputs
101+
if tenantId == "" {
102+
return "", "", fmt.Errorf("tenantId cannot be empty for AAD authentication")
103+
}
104+
if clientId == "" {
105+
return "", "", fmt.Errorf("clientId cannot be empty for AAD authentication")
106+
}
107+
if clientSecret == "" && clientCert == "" {
108+
return "", "", fmt.Errorf("one of client secret or client cert should be defined")
109+
}
110+
111+
// Setup cert
112+
if clientCert != "" {
113+
err := setupACRCert(clientCert, acrCertPath)
114+
if err != nil {
115+
errors.Wrap(err, "failed to push setup cert file")
116+
}
117+
}
118+
119+
// Get AZ env
120+
if err := os.Setenv(clientIdEnv, clientId); err != nil {
121+
return "", "", errors.Wrap(err, "failed to set env variable client Id")
122+
}
123+
if err := os.Setenv(clientSecretKeyEnv, clientSecret); err != nil {
124+
return "", "", errors.Wrap(err, "failed to set env variable client secret")
125+
}
126+
if err := os.Setenv(tenantKeyEnv, tenantId); err != nil {
127+
return "", "", errors.Wrap(err, "failed to set env variable tenant Id")
128+
}
129+
if err := os.Setenv(certPathEnv, acrCertPath); err != nil {
130+
return "", "", errors.Wrap(err, "failed to set env variable cert path")
131+
}
132+
env, err := azidentity.NewEnvironmentCredential(nil)
133+
if err != nil {
134+
return "", "", errors.Wrap(err, "failed to get env credentials from azure")
135+
}
136+
os.Unsetenv(clientIdEnv)
137+
os.Unsetenv(clientSecretKeyEnv)
138+
os.Unsetenv(tenantKeyEnv)
139+
os.Unsetenv(certPathEnv)
140+
141+
// Fetch AAD token
142+
policy := policy.TokenRequestOptions{
143+
Scopes: []string{"https://management.azure.com/.default"},
144+
}
145+
aadToken, err := env.GetToken(context.Background(), policy)
146+
if err != nil {
147+
return "", "", errors.Wrap(err, "failed to fetch access token")
148+
}
149+
150+
// Get public URL for artifacts
151+
publicUrl, err := getPublicUrl(aadToken.Token, registry, subscriptionId)
152+
if err != nil {
153+
// execution should not fail because of this error
154+
fmt.Fprintf(os.Stderr, "failed to get public url with error: %s\n", err)
155+
}
156+
157+
// Fetch token
158+
ACRToken, err := fetchACRToken(tenantId, aadToken.Token, registry)
159+
if err != nil {
160+
return "", "", errors.Wrap(err, "failed to fetch ACR token")
161+
}
162+
return ACRToken, publicUrl, nil
163+
}
164+
165+
func fetchACRToken(tenantId, token, registry string) (string, error) {
166+
// oauth exchange
167+
formData := url.Values{
168+
"grant_type": {"access_token"},
169+
"service": {registry},
170+
"tenant": {tenantId},
171+
"access_token": {token},
172+
}
173+
jsonResponse, err := http.PostForm(fmt.Sprintf("https://%s/oauth2/exchange", registry), formData)
174+
if err != nil || jsonResponse == nil {
175+
return "", errors.Wrap(err, "failed to fetch ACR token")
176+
}
177+
178+
// fetch token from response
179+
var response map[string]interface{}
180+
err = json.NewDecoder(jsonResponse.Body).Decode(&response)
181+
if err != nil {
182+
return "", errors.Wrap(err, "failed to decode oauth exchange response")
183+
}
184+
185+
// Parse the refresh_token from the response
186+
if t, found := response["refresh_token"]; found {
187+
if refreshToken, ok := t.(string); ok {
188+
return refreshToken, nil
189+
}
190+
return "", errors.New("failed to cast refresh token from acr")
191+
}
192+
return "", errors.Wrap(err, "refresh token not found in response of oauth exchange call")
193+
}
194+
195+
func setupACRCert(cert, certPath string) error {
196+
decoded, err := base64.StdEncoding.DecodeString(cert)
197+
if err != nil {
198+
return errors.Wrap(err, "failed to base64 decode ACR certificate")
199+
}
200+
err = ioutil.WriteFile(certPath, decoded, 0644)
201+
if err != nil {
202+
return errors.Wrap(err, "failed to write ACR certificate")
203+
}
204+
return nil
205+
}
206+
207+
func getPublicUrl(token, registryUrl, subscriptionId string) (string, error) {
208+
if len(subscriptionId) == 0 || registryUrl == "" {
209+
return "", nil
210+
}
211+
212+
registry := strings.Split(registryUrl, ".")[0]
213+
filter := fmt.Sprintf("resourceType eq 'Microsoft.ContainerRegistry/registries' and name eq '%s'", registry)
214+
params := url.Values{}
215+
params.Add("$filter", filter)
216+
params.Add("api-version", azSubscriptionApiVersion)
217+
params.Add("$select", "id")
218+
url := azSubscriptionBaseUrl + subscriptionId + "/resources?" + params.Encode()
219+
220+
client := &http.Client{}
221+
req, err := http.NewRequest("GET", url, nil)
222+
if err != nil {
223+
fmt.Println(err)
224+
return "", errors.Wrap(err, "failed to create request for getting container registry setting")
225+
}
226+
227+
req.Header.Add("Authorization", "Bearer "+token)
228+
res, err := client.Do(req)
229+
if err != nil {
230+
fmt.Println(err)
231+
return "", errors.Wrap(err, "failed to send request for getting container registry setting")
232+
}
233+
defer res.Body.Close()
234+
235+
var response subscriptionUrlResponse
236+
err = json.NewDecoder(res.Body).Decode(&response)
237+
if err != nil {
238+
return "", errors.Wrap(err, "failed to send request for getting container registry setting")
239+
}
240+
if len(response.Value) == 0 {
241+
return "", errors.New("no id present for base url")
242+
}
243+
return basePublicUrl + encodeParam(response.Value[0].ID), nil
244+
}
245+
246+
func encodeParam(s string) string {
247+
return url.QueryEscape(s)
248+
}
249+
48250
func getenv(key ...string) (s string) {
49251
for _, k := range key {
50252
s = os.Getenv(k)
@@ -54,3 +256,9 @@ func getenv(key ...string) (s string) {
54256
}
55257
return
56258
}
259+
260+
type subscriptionUrlResponse struct {
261+
Value []struct {
262+
ID string `json:"id"`
263+
} `json:"value"`
264+
}

go.mod

+17-4
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,32 @@
11
module github.com/drone-plugins/drone-buildx-acr
22

3-
go 1.19
3+
go 1.20
44

55
require (
6-
github.com/drone-plugins/drone-buildx v1.0.2-0.20230512061850-99d0da95877e
6+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1
7+
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1
8+
github.com/drone-plugins/drone-buildx v1.0.3
79
github.com/joho/godotenv v1.5.1
10+
github.com/pkg/errors v0.9.1
11+
github.com/sirupsen/logrus v1.9.3
812
)
913

1014
require (
15+
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
16+
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect
1117
github.com/coreos/go-semver v0.3.0 // indirect
1218
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
19+
github.com/drone-plugins/drone-plugin-lib v0.4.1 // indirect
1320
github.com/drone/drone-go v1.7.1 // indirect
21+
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
22+
github.com/google/uuid v1.3.0 // indirect
1423
github.com/inhies/go-bytesize v0.0.0-20210819104631-275770b98743 // indirect
24+
github.com/kylelemons/godebug v1.1.0 // indirect
25+
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
1526
github.com/russross/blackfriday/v2 v2.1.0 // indirect
16-
github.com/sirupsen/logrus v1.9.3 // indirect
1727
github.com/urfave/cli v1.22.2 // indirect
18-
golang.org/x/sys v0.0.0-20220731174439-a90be440212d // indirect
28+
golang.org/x/crypto v0.12.0 // indirect
29+
golang.org/x/net v0.14.0 // indirect
30+
golang.org/x/sys v0.11.0 // indirect
31+
golang.org/x/text v0.12.0 // indirect
1932
)

go.sum

+34-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e/go.mod h1:Xa6lInWHNQnuWoF0YPSsx+INFA9qk7/7pTjwb3PInkY=
2+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 h1:/iHxaJhsFr0+xVFfbMr5vxz848jyiWuIEDhYq3y5odY=
3+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
4+
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1 h1:LNHhpdK7hzUcx/k1LIcuh5k7k1LGIWLQfCjaneSj7Fc=
5+
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M=
6+
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
7+
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
8+
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw72xHJc34BNNykqSOeEJDAWkhf0u12/Jk=
9+
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
210
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
311
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
412
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@@ -8,16 +16,29 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
816
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
917
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
1018
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
11-
github.com/drone-plugins/drone-buildx v1.0.2-0.20230512061850-99d0da95877e h1:UVfB6v/wptaDtyfhEwxnUXViMfEiN97LudyfSeLw2hA=
12-
github.com/drone-plugins/drone-buildx v1.0.2-0.20230512061850-99d0da95877e/go.mod h1:2CEwXGujWoB9bBQqknU214yd/FJzUNDDHGlg9qb3M4c=
19+
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
20+
github.com/drone-plugins/drone-buildx v1.0.3 h1:xaKX5yA0oUKM35EFnlAmHcrOKg9cmSJPKkdj3AMKyNo=
21+
github.com/drone-plugins/drone-buildx v1.0.3/go.mod h1:z4BaR8J5oF/yly5VPW0PU61XOmrqsZvb5kdarmmSmho=
22+
github.com/drone-plugins/drone-plugin-lib v0.4.1 h1:47rZlmcMpr1hSp+6Gl+1Z4t+efi/gMQU3lxukC1Yg64=
23+
github.com/drone-plugins/drone-plugin-lib v0.4.1/go.mod h1:KwCu92jFjHV3xv2hu5Qg/8zBNvGwbhoJDQw/EwnTvoM=
1324
github.com/drone/drone-go v1.7.1 h1:ZX+3Rs8YHUSUQ5mkuMLmm1zr1ttiiE2YGNxF3AnyDKw=
1425
github.com/drone/drone-go v1.7.1/go.mod h1:fxCf9jAnXDZV1yDr0ckTuWd1intvcQwfJmTRpTZ1mXg=
26+
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
27+
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
1528
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
1629
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
30+
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
31+
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
1732
github.com/inhies/go-bytesize v0.0.0-20210819104631-275770b98743 h1:X3Xxno5Ji8idrNiUoFc7QyXpqhSYlDRYQmc7mlpMBzU=
1833
github.com/inhies/go-bytesize v0.0.0-20210819104631-275770b98743/go.mod h1:KrtyD5PFj++GKkFS/7/RRrfnRhAMGQwy75GLCHWrCNs=
1934
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
2035
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
36+
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
37+
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
38+
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
39+
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
40+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
41+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
2142
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
2243
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
2344
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -31,11 +52,18 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
3152
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
3253
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
3354
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
55+
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
56+
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
57+
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
58+
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
59+
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
3460
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
35-
golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80=
36-
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
61+
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
62+
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
63+
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
64+
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
3765
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
3866
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
39-
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
40-
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
67+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
4168
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
69+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

0 commit comments

Comments
 (0)