cdk-ecr-deployment 3.3.1 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,149 +0,0 @@
1
- // Taken from https://github.com/containers/image
2
- // Modifications Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
-
4
- package s3
5
-
6
- import (
7
- "cdk-ecr-deployment-handler/internal/tarfile"
8
- "context"
9
- "fmt"
10
- "strconv"
11
- "strings"
12
-
13
- "github.com/pkg/errors"
14
-
15
- "github.com/containers/image/v5/docker/reference"
16
- "github.com/containers/image/v5/image"
17
- "github.com/containers/image/v5/transports"
18
- "github.com/containers/image/v5/types"
19
- )
20
-
21
- func init() {
22
- transports.Register(Transport)
23
- }
24
-
25
- var Transport = &s3Transport{}
26
-
27
- type s3Transport struct{}
28
-
29
- func (t *s3Transport) Name() string {
30
- return "s3"
31
- }
32
-
33
- func (t *s3Transport) ParseReference(reference string) (types.ImageReference, error) {
34
- return ParseReference(reference)
35
- }
36
-
37
- func (t *s3Transport) ValidatePolicyConfigurationScope(scope string) error {
38
- // See the explanation in archiveReference.PolicyConfigurationIdentity.
39
- return errors.New(`s3: does not support any scopes except the default "" one`)
40
- }
41
-
42
- type s3ArchiveReference struct {
43
- s3uri *tarfile.S3Uri
44
- // May be nil to read the only image in an archive, or to create an untagged image.
45
- ref reference.NamedTagged
46
- // If not -1, a zero-based index of the image in the manifest. Valid only for sources.
47
- // Must not be set if ref is set.
48
- sourceIndex int
49
- }
50
-
51
- func ParseReference(refString string) (types.ImageReference, error) {
52
- if refString == "" {
53
- return nil, errors.New("s3 reference cannot be empty")
54
- }
55
- parts := strings.SplitN(refString, ":", 2)
56
- s3uri, err := tarfile.ParseS3Uri("s3:" + parts[0])
57
- if err != nil {
58
- return nil, err
59
- }
60
- var nt reference.NamedTagged
61
- sourceIndex := -1
62
-
63
- if len(parts) == 2 {
64
- // A :tag or :@index was specified.
65
- if len(parts[1]) > 0 && parts[1][0] == '@' {
66
- i, err := strconv.Atoi(parts[1][1:])
67
- if err != nil {
68
- return nil, errors.Wrapf(err, "Invalid source index %s", parts[1])
69
- }
70
- if i < 0 {
71
- return nil, errors.Errorf("Invalid source index @%d: must not be negative", i)
72
- }
73
- sourceIndex = i
74
- } else {
75
- ref, err := reference.ParseNormalizedNamed(parts[1])
76
- if err != nil {
77
- return nil, errors.Wrapf(err, "s3 parsing reference")
78
- }
79
- ref = reference.TagNameOnly(ref)
80
- refTagged, isTagged := ref.(reference.NamedTagged)
81
- if !isTagged { // If ref contains a digest, TagNameOnly does not change it
82
- return nil, errors.Errorf("reference does not include a tag: %s", ref.String())
83
- }
84
- nt = refTagged
85
- }
86
- }
87
-
88
- return newReference(s3uri, nt, sourceIndex)
89
- }
90
-
91
- func newReference(s3uri *tarfile.S3Uri, ref reference.NamedTagged, sourceIndex int) (types.ImageReference, error) {
92
- if ref != nil && sourceIndex != -1 {
93
- return nil, errors.Errorf("Invalid s3: reference: cannot use both a tag and a source index")
94
- }
95
- if _, isDigest := ref.(reference.Canonical); isDigest {
96
- return nil, errors.Errorf("s3 doesn't support digest references: %s", ref.String())
97
- }
98
- if sourceIndex != -1 && sourceIndex < 0 {
99
- return nil, errors.Errorf("Invalid s3: reference: index @%d must not be negative", sourceIndex)
100
- }
101
- return &s3ArchiveReference{
102
- s3uri: s3uri,
103
- ref: ref,
104
- sourceIndex: sourceIndex,
105
- }, nil
106
- }
107
-
108
- func (r *s3ArchiveReference) Transport() types.ImageTransport {
109
- return Transport
110
- }
111
-
112
- func (r *s3ArchiveReference) StringWithinTransport() string {
113
- if r.s3uri.Key == "" {
114
- return fmt.Sprintf("//%s", r.s3uri.Bucket)
115
- }
116
- return fmt.Sprintf("//%s/%s", r.s3uri.Bucket, r.s3uri.Key)
117
- }
118
-
119
- func (r *s3ArchiveReference) DockerReference() reference.Named {
120
- return r.ref
121
- }
122
-
123
- func (r *s3ArchiveReference) PolicyConfigurationIdentity() string {
124
- return ""
125
- }
126
-
127
- func (r *s3ArchiveReference) PolicyConfigurationNamespaces() []string {
128
- return []string{}
129
- }
130
-
131
- func (r *s3ArchiveReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
132
- src, err := newImageSource(ctx, sys, r)
133
- if err != nil {
134
- return nil, err
135
- }
136
- return image.FromSource(ctx, sys, src)
137
- }
138
-
139
- func (r *s3ArchiveReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
140
- return errors.New("deleting images not implemented for s3")
141
- }
142
-
143
- func (r *s3ArchiveReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (types.ImageSource, error) {
144
- return newImageSource(ctx, sys, r)
145
- }
146
-
147
- func (r *s3ArchiveReference) NewImageDestination(ctx context.Context, sys *types.SystemContext) (types.ImageDestination, error) {
148
- return nil, fmt.Errorf(`s3 locations can only be read from, not written to`)
149
- }
@@ -1,96 +0,0 @@
1
- // Taken from https://github.com/containers/image
2
- // Modifications Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
-
4
- package s3
5
-
6
- import (
7
- "testing"
8
-
9
- "github.com/containers/image/v5/types"
10
- "github.com/stretchr/testify/assert"
11
- "github.com/stretchr/testify/require"
12
- )
13
-
14
- const (
15
- sha256digestHex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
16
- sha256digest = "@sha256:" + sha256digestHex
17
- tarFixture = "fixtures/almostempty.tar"
18
- )
19
-
20
- func TestTransportName(t *testing.T) {
21
- assert.Equal(t, "s3", Transport.Name())
22
- }
23
-
24
- func TestTransportParseReference(t *testing.T) {
25
- testParseReference(t, Transport.ParseReference)
26
- }
27
-
28
- func TestTransportValidatePolicyConfigurationScope(t *testing.T) {
29
- for _, scope := range []string{ // A semi-representative assortment of values; everything is rejected.
30
- "docker.io/library/busybox:notlatest",
31
- "docker.io/library/busybox",
32
- "docker.io/library",
33
- "docker.io",
34
- "",
35
- } {
36
- err := Transport.ValidatePolicyConfigurationScope(scope)
37
- assert.Error(t, err, scope)
38
- }
39
- }
40
-
41
- func TestParseReference(t *testing.T) {
42
- testParseReference(t, ParseReference)
43
- }
44
-
45
- // testParseReference is a test shared for Transport.ParseReference and ParseReference.
46
- func testParseReference(t *testing.T, fn func(string) (types.ImageReference, error)) {
47
- for _, c := range []struct {
48
- input, expectedBucket, expectedKey, expectedRef string
49
- expectedSourceIndex int
50
- }{
51
- {"", "", "", "", -1}, // Empty input is explicitly rejected
52
- {"//bucket", "bucket", "", "", -1},
53
- {"//bucket/a/b", "bucket", "a/b", "", -1},
54
- {"//bucket/", "bucket", "", "", -1},
55
- {"//hello.com/", "hello.com", "", "", -1},
56
- {"//bucket", "bucket", "", "", -1},
57
- {"//bucket:busybox:notlatest", "bucket", "", "docker.io/library/busybox:notlatest", -1}, // Explicit tag
58
- {"//bucket:busybox" + sha256digest, "", "", "", -1}, // Digest references are forbidden
59
- {"//bucket:busybox", "bucket", "", "docker.io/library/busybox:latest", -1}, // Default tag
60
- // A github.com/distribution/reference value can have a tag and a digest at the same time!
61
- {"//bucket:busybox:latest" + sha256digest, "", "", "", -1}, // Both tag and digest is rejected
62
- {"//bucket:docker.io/library/busybox:latest", "bucket", "", "docker.io/library/busybox:latest", -1}, // All implied reference parts explicitly specified
63
- {"//bucket:UPPERCASEISINVALID", "", "", "", -1}, // Invalid reference format
64
- {"//bucket:@", "", "", "", -1}, // Missing source index
65
- {"//bucket:@0", "bucket", "", "", 0}, // Valid source index
66
- {"//bucket:@999999", "bucket", "", "", 999999}, // Valid source index
67
- {"//bucket:@-2", "", "", "", -1}, // Negative source index
68
- {"//bucket:@-1", "", "", "", -1}, // Negative source index, using the placeholder value
69
- {"//bucket:busybox@0", "", "", "", -1}, // References and source indices can’t be combined.
70
- {"//bucket:@0:busybox", "", "", "", -1}, // References and source indices can’t be combined.
71
- } {
72
- ref, err := fn(c.input)
73
- if c.expectedBucket == "" {
74
- assert.Error(t, err, c.input)
75
- } else {
76
- require.NoError(t, err, c.input)
77
- archiveRef, ok := ref.(*s3ArchiveReference)
78
- require.True(t, ok, c.input)
79
- assert.Equal(t, c.expectedBucket, archiveRef.s3uri.Bucket, c.input)
80
- assert.Equal(t, c.expectedKey, archiveRef.s3uri.Key, c.input)
81
- if c.expectedRef == "" {
82
- assert.Nil(t, archiveRef.ref, c.input)
83
- } else {
84
- require.NotNil(t, archiveRef.ref, c.input)
85
- assert.Equal(t, c.expectedRef, archiveRef.ref.String(), c.input)
86
- }
87
- assert.Equal(t, c.expectedSourceIndex, archiveRef.sourceIndex, c.input)
88
- }
89
- }
90
- }
91
-
92
- func TestReferenceTransport(t *testing.T) {
93
- ref, err := ParseReference("//bucket/archive.tar:nginx:latest")
94
- require.NoError(t, err)
95
- assert.Equal(t, Transport, ref.Transport())
96
- }
@@ -1,206 +0,0 @@
1
- // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
- // SPDX-License-Identifier: Apache-2.0
3
-
4
- package main
5
-
6
- import (
7
- "context"
8
- "encoding/base64"
9
- "encoding/json"
10
- "fmt"
11
- "log"
12
- "regexp"
13
- "strings"
14
- "time"
15
-
16
- "github.com/aws/aws-sdk-go-v2/aws"
17
- "github.com/aws/aws-sdk-go-v2/config"
18
- "github.com/aws/aws-sdk-go-v2/service/ecr"
19
- "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
20
- "github.com/containers/image/v5/types"
21
- )
22
-
23
- const (
24
- SRC_IMAGE string = "SrcImage"
25
- DEST_IMAGE string = "DestImage"
26
- IMAGE_ARCH string = "ImageArch"
27
- SRC_CREDS string = "SrcCreds"
28
- DEST_CREDS string = "DestCreds"
29
- )
30
-
31
- type ECRAuth struct {
32
- Token string
33
- User string
34
- Pass string
35
- ProxyEndpoint string
36
- ExpiresAt time.Time
37
- }
38
-
39
- func GetECRRegion(uri string) string {
40
- re := regexp.MustCompile(`dkr\.ecr\.(.+?)\.`)
41
- m := re.FindStringSubmatch(uri)
42
- if m != nil {
43
- return m[1]
44
- }
45
- return "us-east-1"
46
- }
47
-
48
- func GetECRLogin(region string) ([]ECRAuth, error) {
49
- cfg, err := config.LoadDefaultConfig(
50
- context.TODO(),
51
- config.WithRegion(region),
52
- )
53
- if err != nil {
54
- return nil, fmt.Errorf("api client configuration error: %v", err.Error())
55
- }
56
- client := ecr.NewFromConfig(cfg)
57
- input := &ecr.GetAuthorizationTokenInput{}
58
-
59
- resp, err := client.GetAuthorizationToken(context.TODO(), input)
60
- if err != nil {
61
- return nil, fmt.Errorf("error login into ECR: %v", err.Error())
62
- }
63
-
64
- auths := make([]ECRAuth, len(resp.AuthorizationData))
65
- for i, auth := range resp.AuthorizationData {
66
- // extract base64 token
67
- data, err := base64.StdEncoding.DecodeString(*auth.AuthorizationToken)
68
- if err != nil {
69
- return nil, err
70
- }
71
- // extract username and password
72
- token := strings.SplitN(string(data), ":", 2)
73
- // object to pass to template
74
- auths[i] = ECRAuth{
75
- Token: *auth.AuthorizationToken,
76
- User: token[0],
77
- Pass: token[1],
78
- ProxyEndpoint: *(auth.ProxyEndpoint),
79
- ExpiresAt: *(auth.ExpiresAt),
80
- }
81
- }
82
- return auths, nil
83
- }
84
-
85
- type ImageOpts struct {
86
- uri string
87
- requireECRLogin bool
88
- region string
89
- creds string
90
- arch string
91
- }
92
-
93
- func NewImageOpts(uri string, arch string) *ImageOpts {
94
- requireECRLogin := strings.Contains(uri, "dkr.ecr")
95
- if requireECRLogin {
96
- return &ImageOpts{uri, requireECRLogin, GetECRRegion(uri), "", arch}
97
- } else {
98
- return &ImageOpts{uri, requireECRLogin, "", "", arch}
99
- }
100
- }
101
-
102
- func (s *ImageOpts) SetRegion(region string) {
103
- s.region = region
104
- }
105
-
106
- func (s *ImageOpts) SetCreds(creds string) {
107
- s.creds = creds
108
- }
109
-
110
- func (s *ImageOpts) NewSystemContext() (*types.SystemContext, error) {
111
- ctx := &types.SystemContext{
112
- DockerRegistryUserAgent: "ecr-deployment",
113
- DockerAuthConfig: &types.DockerAuthConfig{},
114
- ArchitectureChoice: s.arch,
115
- }
116
-
117
- if s.creds != "" {
118
- log.Printf("Credentials login mode for %v", s.uri)
119
-
120
- token := strings.SplitN(s.creds, ":", 2)
121
- ctx.DockerAuthConfig = &types.DockerAuthConfig{
122
- Username: token[0],
123
- }
124
- if len(token) == 2 {
125
- ctx.DockerAuthConfig.Password = token[1]
126
- }
127
- } else {
128
- if s.requireECRLogin {
129
- log.Printf("ECR auto login mode for %v", s.uri)
130
-
131
- auths, err := GetECRLogin(s.region)
132
- if err != nil {
133
- return nil, err
134
- }
135
- if len(auths) == 0 {
136
- return nil, fmt.Errorf("empty ECR login auth token list")
137
- }
138
- auth0 := auths[0]
139
- ctx.DockerAuthConfig = &types.DockerAuthConfig{
140
- Username: auth0.User,
141
- Password: auth0.Pass,
142
- }
143
- }
144
- }
145
- return ctx, nil
146
- }
147
-
148
- func Dumps(v interface{}) string {
149
- bytes, err := json.MarshalIndent(v, "", " ")
150
- if err != nil {
151
- return fmt.Sprintf("dumps error: %s", err.Error())
152
- }
153
- return string(bytes)
154
- }
155
-
156
- const (
157
- SECRET_ARN = "SECRET_ARN"
158
- SECRET_NAME = "SECRET_NAME"
159
- SECRET_TEXT = "SECRET_TEXT"
160
- )
161
-
162
- func GetCredsType(s string) string {
163
- if strings.HasPrefix(s, "arn:aws") {
164
- return SECRET_ARN
165
- } else if strings.Contains(s, ":") {
166
- return SECRET_TEXT
167
- } else {
168
- return SECRET_NAME
169
- }
170
- }
171
-
172
- func ParseJsonSecret(s string) (secret string, err error) {
173
- var jsonData map[string]interface{}
174
- jsonErr := json.Unmarshal([]byte(s), &jsonData)
175
- if jsonErr != nil {
176
- return "", fmt.Errorf("error parsing json secret: %v", jsonErr.Error())
177
- }
178
- username, ok := jsonData["username"].(string)
179
- if !ok {
180
- return "", fmt.Errorf("error parsing username from json secret")
181
- }
182
- password, ok := jsonData["password"].(string)
183
- if !ok {
184
- return "", fmt.Errorf("error parsing password from json secret")
185
- }
186
- return fmt.Sprintf("%s:%s", username, password), nil
187
- }
188
-
189
- func GetSecret(secretId string) (secret string, err error) {
190
- cfg, err := config.LoadDefaultConfig(
191
- context.TODO(),
192
- )
193
- log.Printf("get secret id: %s of region: %s", secretId, cfg.Region)
194
- if err != nil {
195
- return "", fmt.Errorf("api client configuration error: %v", err.Error())
196
- }
197
-
198
- client := secretsmanager.NewFromConfig(cfg)
199
- resp, err := client.GetSecretValue(context.TODO(), &secretsmanager.GetSecretValueInput{
200
- SecretId: aws.String(secretId),
201
- })
202
- if err != nil {
203
- return "", fmt.Errorf("fetch secret value error: %v", err.Error())
204
- }
205
- return *resp.SecretString, nil
206
- }
@@ -1,63 +0,0 @@
1
- // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
- // SPDX-License-Identifier: Apache-2.0
3
-
4
- package main
5
-
6
- import (
7
- "encoding/json"
8
- "testing"
9
-
10
- "github.com/stretchr/testify/assert"
11
- )
12
-
13
- func TestGetECRRegion(t *testing.T) {
14
- assert.Equal(t,
15
- "us-west-2",
16
- GetECRRegion("docker://1234567890.dkr.ecr.us-west-2.amazonaws.com/test:ubuntu"),
17
- )
18
- assert.Equal(t,
19
- "us-east-1",
20
- GetECRRegion("docker://1234567890.dkr.ecr.us-east-1.amazonaws.com/test:ubuntu"),
21
- )
22
- assert.Equal(t,
23
- "cn-north-1",
24
- GetECRRegion("docker://1234567890.dkr.ecr.cn-north-1.amazonaws.com/test:ubuntu"),
25
- )
26
- }
27
-
28
- func TestGetCredsType(t *testing.T) {
29
- assert.Equal(t, SECRET_ARN, GetCredsType("arn:aws:secretsmanager:us-west-2:00000:secret:fake-secret"))
30
- assert.Equal(t, SECRET_ARN, GetCredsType("arn:aws-cn:secretsmanager:cn-north-1:00000:secret:fake-secret"))
31
- assert.Equal(t, SECRET_NAME, GetCredsType("fake-secret"))
32
- assert.Equal(t, SECRET_TEXT, GetCredsType("username:password"))
33
- assert.Equal(t, SECRET_NAME, GetCredsType(""))
34
- }
35
-
36
- func TestParseJsonSecret(t *testing.T) {
37
- secretPlainText := "user_val:pass_val"
38
- isValid := json.Valid([]byte(secretPlainText))
39
- assert.False(t, isValid)
40
-
41
- secretJson := "{\"username\":\"user_val\",\"password\":\"pass_val\"}"
42
- isValid = json.Valid([]byte(secretJson))
43
- assert.True(t, isValid)
44
-
45
- successCase, noError := ParseJsonSecret(secretJson)
46
- assert.NoError(t, noError)
47
- assert.Equal(t, secretPlainText, successCase)
48
-
49
- failParseCase, jsonParseError := ParseJsonSecret("{\"user}")
50
- assert.Equal(t, "", failParseCase)
51
- assert.Error(t, jsonParseError)
52
- assert.Contains(t, "error parsing json secret: unexpected end of JSON input", jsonParseError.Error())
53
-
54
- noUsernameCase, usernameError := ParseJsonSecret("{\"password\":\"pass_val\"}")
55
- assert.Equal(t, "", noUsernameCase)
56
- assert.Error(t, usernameError)
57
- assert.Contains(t, "error parsing username from json secret", usernameError.Error())
58
-
59
- noPasswordCase, passwordError := ParseJsonSecret("{\"username\":\"user_val\"}")
60
- assert.Equal(t, "", noPasswordCase)
61
- assert.Error(t, passwordError)
62
- assert.Contains(t, "error parsing password from json secret", passwordError.Error())
63
- }
package/lib/config.d.ts DELETED
@@ -1 +0,0 @@
1
- export declare function shouldUsePrebuiltLambda(): boolean;
package/lib/config.js DELETED
@@ -1,12 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.shouldUsePrebuiltLambda = shouldUsePrebuiltLambda;
4
- const TRUTHY = ['true', true, 1, '1'];
5
- function shouldUsePrebuiltLambda() {
6
- const { CI, NO_PREBUILT_LAMBDA, FORCE_PREBUILT_LAMBDA } = process.env;
7
- const isCI = CI && TRUTHY.includes(CI);
8
- const isNoPrebuilt = NO_PREBUILT_LAMBDA && TRUTHY.includes(NO_PREBUILT_LAMBDA);
9
- const isForcePrebuilt = FORCE_PREBUILT_LAMBDA && TRUTHY.includes(FORCE_PREBUILT_LAMBDA);
10
- return isForcePrebuilt || (!(isCI || isNoPrebuilt));
11
- }
12
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2NvbmZpZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUVBLDBEQU9DO0FBVEQsTUFBTSxNQUFNLEdBQUcsQ0FBQyxNQUFNLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBRSxHQUFHLENBQUMsQ0FBQztBQUV0QyxTQUFnQix1QkFBdUI7SUFDckMsTUFBTSxFQUFFLEVBQUUsRUFBRSxrQkFBa0IsRUFBRSxxQkFBcUIsRUFBRSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUM7SUFDdEUsTUFBTSxJQUFJLEdBQUcsRUFBRSxJQUFJLE1BQU0sQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUM7SUFDdkMsTUFBTSxZQUFZLEdBQUcsa0JBQWtCLElBQUksTUFBTSxDQUFDLFFBQVEsQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDO0lBQy9FLE1BQU0sZUFBZSxHQUFHLHFCQUFxQixJQUFJLE1BQU0sQ0FBQyxRQUFRLENBQUMscUJBQXFCLENBQUMsQ0FBQztJQUV4RixPQUFPLGVBQWUsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLElBQUksWUFBWSxDQUFDLENBQUMsQ0FBQztBQUN0RCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiY29uc3QgVFJVVEhZID0gWyd0cnVlJywgdHJ1ZSwgMSwgJzEnXTtcblxuZXhwb3J0IGZ1bmN0aW9uIHNob3VsZFVzZVByZWJ1aWx0TGFtYmRhKCk6IGJvb2xlYW4ge1xuICBjb25zdCB7IENJLCBOT19QUkVCVUlMVF9MQU1CREEsIEZPUkNFX1BSRUJVSUxUX0xBTUJEQSB9ID0gcHJvY2Vzcy5lbnY7XG4gIGNvbnN0IGlzQ0kgPSBDSSAmJiBUUlVUSFkuaW5jbHVkZXMoQ0kpO1xuICBjb25zdCBpc05vUHJlYnVpbHQgPSBOT19QUkVCVUlMVF9MQU1CREEgJiYgVFJVVEhZLmluY2x1ZGVzKE5PX1BSRUJVSUxUX0xBTUJEQSk7XG4gIGNvbnN0IGlzRm9yY2VQcmVidWlsdCA9IEZPUkNFX1BSRUJVSUxUX0xBTUJEQSAmJiBUUlVUSFkuaW5jbHVkZXMoRk9SQ0VfUFJFQlVJTFRfTEFNQkRBKTtcblxuICByZXR1cm4gaXNGb3JjZVByZWJ1aWx0IHx8ICghKGlzQ0kgfHwgaXNOb1ByZWJ1aWx0KSk7XG59Il19