Skip to content

Commit

Permalink
Add mgmt configmap tests (#6957)
Browse files Browse the repository at this point in the history
* test full mgmt config params

* fix mgmt client auth runtime secret location

* add test for full suit of mgmt configmap params

* update snaps, fix signature in tests

* update snaps
  • Loading branch information
j1m-ryan authored and web-flow committed Dec 12, 2024
1 parent 025ddd7 commit 60d540b
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 43 deletions.
30 changes: 17 additions & 13 deletions internal/configs/configurator.go
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,12 @@ func (cnf *Configurator) AddOrUpdateSpecialTLSSecrets(secret *api_v1.Secret, sec
}
}

// AddOrUpdateMGMTClientAuthSecret adds or updates the MGMT Client Auth Secret file with a TLS cert and key.
func (cnf *Configurator) AddOrUpdateMGMTClientAuthSecret(secret *api_v1.Secret) {
data := GenerateCertAndKeyFileContent(secret)
cnf.nginxManager.CreateSecret("mgmt/client", data, nginx.ReadWriteOnlyFileMode)

Check warning on line 959 in internal/configs/configurator.go

View check run for this annotation

Codecov / codecov/patch

internal/configs/configurator.go#L957-L959

Added lines #L957 - L959 were not covered by tests
}

// GenerateCertAndKeyFileContent generates a pem file content from the TLS secret.
func GenerateCertAndKeyFileContent(secret *api_v1.Secret) []byte {
var res bytes.Buffer
Expand Down Expand Up @@ -1323,9 +1329,7 @@ func (cnf *Configurator) updateStreamServersInPlus(upstream string, servers []st
// UpdateConfig updates NGINX configuration parameters.
//
//gocyclo:ignore
func (cnf *Configurator) UpdateConfig(cfgParams *ConfigParams, mgmtCfgParams *MGMTConfigParams, resources ExtendedResources) (Warnings, error) {
cnf.CfgParams = cfgParams
cnf.MgmtCfgParams = mgmtCfgParams
func (cnf *Configurator) UpdateConfig(resources ExtendedResources) (Warnings, error) {
allWarnings := newWarnings()
allWeightUpdates := []WeightUpdate{}

Expand All @@ -1334,12 +1338,12 @@ func (cnf *Configurator) UpdateConfig(cfgParams *ConfigParams, mgmtCfgParams *MG
if err != nil {
return allWarnings, fmt.Errorf("error when updating dhparams: %w", err)
}
cfgParams.MainServerSSLDHParam = fileName
cnf.CfgParams.MainServerSSLDHParam = fileName

Check warning on line 1341 in internal/configs/configurator.go

View check run for this annotation

Codecov / codecov/patch

internal/configs/configurator.go#L1341

Added line #L1341 was not covered by tests
}

// Apply custom main-template defined in ConfigMap obj
if cfgParams.MainTemplate != nil {
err := cnf.templateExecutor.UpdateMainTemplate(cfgParams.MainTemplate)
if cnf.CfgParams.MainTemplate != nil {
err := cnf.templateExecutor.UpdateMainTemplate(cnf.CfgParams.MainTemplate)
if err != nil {
return allWarnings, fmt.Errorf("error when parsing the main template: %w", err)
}
Expand All @@ -1348,8 +1352,8 @@ func (cnf *Configurator) UpdateConfig(cfgParams *ConfigParams, mgmtCfgParams *MG
cnf.templateExecutor.UseOriginalMainTemplate()
}

if cfgParams.IngressTemplate != nil {
err := cnf.templateExecutor.UpdateIngressTemplate(cfgParams.IngressTemplate)
if cnf.CfgParams.IngressTemplate != nil {
err := cnf.templateExecutor.UpdateIngressTemplate(cnf.CfgParams.IngressTemplate)
if err != nil {
return allWarnings, fmt.Errorf("error when parsing the ingress template: %w", err)
}
Expand All @@ -1358,8 +1362,8 @@ func (cnf *Configurator) UpdateConfig(cfgParams *ConfigParams, mgmtCfgParams *MG
cnf.templateExecutor.UseOriginalIngressTemplate()
}

if cfgParams.VirtualServerTemplate != nil {
err := cnf.templateExecutorV2.UpdateVirtualServerTemplate(cfgParams.VirtualServerTemplate)
if cnf.CfgParams.VirtualServerTemplate != nil {
err := cnf.templateExecutorV2.UpdateVirtualServerTemplate(cnf.CfgParams.VirtualServerTemplate)
if err != nil {
return allWarnings, fmt.Errorf("error when parsing the VirtualServer template: %w", err)
}
Expand All @@ -1368,8 +1372,8 @@ func (cnf *Configurator) UpdateConfig(cfgParams *ConfigParams, mgmtCfgParams *MG
cnf.templateExecutorV2.UseOriginalVStemplate()
}

if cfgParams.TransportServerTemplate != nil {
err := cnf.templateExecutorV2.UpdateTransportServerTemplate(cfgParams.TransportServerTemplate)
if cnf.CfgParams.TransportServerTemplate != nil {
err := cnf.templateExecutorV2.UpdateTransportServerTemplate(cnf.CfgParams.TransportServerTemplate)
if err != nil {
return allWarnings, fmt.Errorf("error when parsing the TransportServer template: %w", err)
}
Expand All @@ -1378,7 +1382,7 @@ func (cnf *Configurator) UpdateConfig(cfgParams *ConfigParams, mgmtCfgParams *MG
cnf.templateExecutorV2.UseOriginalTStemplate()
}

mainCfg := GenerateNginxMainConfig(cnf.staticCfgParams, cfgParams, mgmtCfgParams)
mainCfg := GenerateNginxMainConfig(cnf.staticCfgParams, cnf.CfgParams, cnf.MgmtCfgParams)
mainCfgContent, err := cnf.templateExecutor.ExecuteMainConfigTemplate(mainCfg)
if err != nil {
return allWarnings, fmt.Errorf("error when writing main Config")
Expand Down
42 changes: 25 additions & 17 deletions internal/configs/configurator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ func TestConfiguratorUpdatesConfigWithNilCustomMainTemplate(t *testing.T) {
t.Parallel()

cnf := createTestConfigurator(t)
warnings, err := cnf.UpdateConfig(&ConfigParams{MainTemplate: nil}, &MGMTConfigParams{}, ExtendedResources{})
cnf.CfgParams = &ConfigParams{MainTemplate: nil}
cnf.MgmtCfgParams = &MGMTConfigParams{}
warnings, err := cnf.UpdateConfig(ExtendedResources{})
if err != nil {
t.Fatal(err)
}
Expand All @@ -117,9 +119,9 @@ func TestConfiguratorUpdatesConfigWithCustomMainTemplate(t *testing.T) {
t.Parallel()

cnf := createTestConfigurator(t)
warnings, err := cnf.UpdateConfig(&ConfigParams{
MainTemplate: &customTestMainTemplate,
}, &MGMTConfigParams{}, ExtendedResources{})
cnf.CfgParams = &ConfigParams{MainTemplate: &customTestMainTemplate}
cnf.MgmtCfgParams = &MGMTConfigParams{}
warnings, err := cnf.UpdateConfig(ExtendedResources{})
if err != nil {
t.Fatal(err)
}
Expand All @@ -139,7 +141,9 @@ func TestConfiguratorUpdatesConfigWithNilCustomIngressTemplate(t *testing.T) {
t.Parallel()

cnf := createTestConfigurator(t)
warnings, err := cnf.UpdateConfig(&ConfigParams{IngressTemplate: nil}, &MGMTConfigParams{}, ExtendedResources{})
cnf.CfgParams = &ConfigParams{IngressTemplate: nil}
cnf.MgmtCfgParams = &MGMTConfigParams{}
warnings, err := cnf.UpdateConfig(ExtendedResources{})
if err != nil {
t.Fatal(err)
}
Expand All @@ -155,7 +159,9 @@ func TestConfiguratorUpdatesConfigWithCustomIngressTemplate(t *testing.T) {
t.Parallel()

cnf := createTestConfigurator(t)
warnings, err := cnf.UpdateConfig(&ConfigParams{IngressTemplate: &customTestIngressTemplate}, &MGMTConfigParams{}, ExtendedResources{})
cnf.CfgParams = &ConfigParams{IngressTemplate: &customTestIngressTemplate}
cnf.MgmtCfgParams = &MGMTConfigParams{}
warnings, err := cnf.UpdateConfig(ExtendedResources{})
if err != nil {
t.Fatal(err)
}
Expand All @@ -175,9 +181,9 @@ func TestConfigratorUpdatesConfigWithCustomVStemplate(t *testing.T) {
t.Parallel()

cnf := createTestConfigurator(t)
warnings, err := cnf.UpdateConfig(&ConfigParams{
VirtualServerTemplate: &customTestVStemplate,
}, &MGMTConfigParams{}, ExtendedResources{})
cnf.CfgParams = &ConfigParams{VirtualServerTemplate: &customTestVStemplate}
cnf.MgmtCfgParams = &MGMTConfigParams{}
warnings, err := cnf.UpdateConfig(ExtendedResources{})
if err != nil {
t.Fatal(err)
}
Expand All @@ -197,9 +203,9 @@ func TestConfiguratorUpdatesConfigWithNilCustomVSemplate(t *testing.T) {
t.Parallel()

cnf := createTestConfigurator(t)
warnings, err := cnf.UpdateConfig(&ConfigParams{
VirtualServerTemplate: nil,
}, &MGMTConfigParams{}, ExtendedResources{})
cnf.CfgParams = &ConfigParams{VirtualServerTemplate: nil}
cnf.MgmtCfgParams = &MGMTConfigParams{}
warnings, err := cnf.UpdateConfig(ExtendedResources{})
if err != nil {
t.Fatal(err)
}
Expand All @@ -215,9 +221,11 @@ func TestConfigratorUpdatesConfigWithCustomTStemplate(t *testing.T) {
t.Parallel()

cnf := createTestConfigurator(t)
warnings, err := cnf.UpdateConfig(&ConfigParams{
cnf.MgmtCfgParams = &MGMTConfigParams{}
cnf.CfgParams = &ConfigParams{
TransportServerTemplate: &customTestTStemplate,
}, &MGMTConfigParams{}, ExtendedResources{})
}
warnings, err := cnf.UpdateConfig(ExtendedResources{})
if err != nil {
t.Fatal(err)
}
Expand All @@ -237,9 +245,9 @@ func TestConfiguratorUpdatesConfigWithNilCustomTStemplate(t *testing.T) {
t.Parallel()

cnf := createTestConfigurator(t)
warnings, err := cnf.UpdateConfig(&ConfigParams{
TransportServerTemplate: nil,
}, &MGMTConfigParams{}, ExtendedResources{})
cnf.CfgParams = &ConfigParams{TransportServerTemplate: nil}
cnf.MgmtCfgParams = &MGMTConfigParams{}
warnings, err := cnf.UpdateConfig(ExtendedResources{})
if err != nil {
t.Fatal(err)
}
Expand Down
33 changes: 23 additions & 10 deletions internal/k8s/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,13 @@ func (lbc *LoadBalancerController) updateAllConfigs() {
if mgmtErr != nil {
nl.Errorf(lbc.Logger, "configmap %s/%s: %v", lbc.mgmtConfigMap.GetNamespace(), lbc.mgmtConfigMap.GetName(), mgmtErr)
}
// update special license secret in mgmtConfigParams
}

lbc.configurator.CfgParams = cfgParams
lbc.configurator.MgmtCfgParams = mgmtCfgParams

// update special license secret in mgmtConfigParams
if lbc.mgmtConfigMap != nil && lbc.isNginxPlus {

Check warning on line 902 in internal/k8s/controller.go

View check run for this annotation

Codecov / codecov/patch

internal/k8s/controller.go#L898-L902

Added lines #L898 - L902 were not covered by tests
if mgmtCfgParams.Secrets.License != "" {
secret, err := lbc.client.CoreV1().Secrets(lbc.mgmtConfigMap.GetNamespace()).Get(context.TODO(), mgmtCfgParams.Secrets.License, meta_v1.GetOptions{})
if err != nil {
Expand All @@ -909,7 +915,7 @@ func (lbc *LoadBalancerController) updateAllConfigs() {
nl.Errorf(lbc.Logger, "secret %s/%s: %v", lbc.mgmtConfigMap.GetNamespace(), mgmtCfgParams.Secrets.TrustedCert, err)
}
if _, hasCRL := secret.Data[configs.CACrlKey]; hasCRL {
mgmtCfgParams.Secrets.TrustedCRL = secret.Name
lbc.configurator.MgmtCfgParams.Secrets.TrustedCRL = secret.Name

Check warning on line 918 in internal/k8s/controller.go

View check run for this annotation

Codecov / codecov/patch

internal/k8s/controller.go#L918

Added line #L918 was not covered by tests
}
lbc.specialSecrets.trustedCertSecret = fmt.Sprintf("%s/%s", secret.Namespace, secret.Name)
lbc.handleSpecialSecretUpdate(secret, reloadNginx)
Expand All @@ -924,14 +930,11 @@ func (lbc *LoadBalancerController) updateAllConfigs() {
lbc.handleSpecialSecretUpdate(secret, reloadNginx)
}
}

resources := lbc.configuration.GetResources()

nl.Debugf(lbc.Logger, "Updating %v resources", len(resources))

resourceExes := lbc.createExtendedResources(resources)
warnings, updateErr := lbc.configurator.UpdateConfig(resourceExes)

Check warning on line 936 in internal/k8s/controller.go

View check run for this annotation

Codecov / codecov/patch

internal/k8s/controller.go#L936

Added line #L936 was not covered by tests

warnings, updateErr := lbc.configurator.UpdateConfig(cfgParams, mgmtCfgParams, resourceExes)
eventTitle := "Updated"
eventType := api_v1.EventTypeNormal
eventWarningMessage := ""
Expand Down Expand Up @@ -1874,7 +1877,7 @@ func (lbc *LoadBalancerController) handleSpecialSecretUpdate(secret *api_v1.Secr
return
}

if ok := lbc.writeSpecialSecrets(secret, secretNsName, specialTLSSecretsToUpdate); !ok {
if ok := lbc.writeSpecialSecrets(secret, specialTLSSecretsToUpdate); !ok {

Check warning on line 1880 in internal/k8s/controller.go

View check run for this annotation

Codecov / codecov/patch

internal/k8s/controller.go#L1880

Added line #L1880 was not covered by tests
// if not ok bail early
return
}
Expand Down Expand Up @@ -1910,7 +1913,12 @@ func (lbc *LoadBalancerController) handleSpecialSecretUpdate(secret *api_v1.Secr
}

// writeSpecialSecrets generates content and writes the secret to disk
func (lbc *LoadBalancerController) writeSpecialSecrets(secret *api_v1.Secret, secretNsName string, specialTLSSecretsToUpdate []string) bool {
func (lbc *LoadBalancerController) writeSpecialSecrets(secret *api_v1.Secret, specialTLSSecretsToUpdate []string) bool {
secretNsName := generateSecretNSName(secret)
var mgmtClientAuthNamespaceName string
if lbc.configurator.MgmtCfgParams != nil {
mgmtClientAuthNamespaceName = fmt.Sprintf("%s/%s", lbc.metadata.pod.Namespace, lbc.configurator.MgmtCfgParams.Secrets.ClientAuth)
}

Check warning on line 1921 in internal/k8s/controller.go

View check run for this annotation

Codecov / codecov/patch

internal/k8s/controller.go#L1916-L1921

Added lines #L1916 - L1921 were not covered by tests
switch secret.Type {
case secrets.SecretTypeLicense:
err := lbc.configurator.AddOrUpdateLicenseSecret(secret)
Expand All @@ -1922,7 +1930,12 @@ func (lbc *LoadBalancerController) writeSpecialSecrets(secret *api_v1.Secret, se
case secrets.SecretTypeCA:
lbc.configurator.AddOrUpdateCASecret(secret, fmt.Sprintf("mgmt/%s", configs.CACrtKey), fmt.Sprintf("mgmt/%s", configs.CACrlKey))
case api_v1.SecretTypeTLS:
lbc.configurator.AddOrUpdateSpecialTLSSecrets(secret, specialTLSSecretsToUpdate)
// if the secret name matches the specified
if secretNsName == mgmtClientAuthNamespaceName {
lbc.configurator.AddOrUpdateMGMTClientAuthSecret(secret)
} else {
lbc.configurator.AddOrUpdateSpecialTLSSecrets(secret, specialTLSSecretsToUpdate)
}

Check warning on line 1938 in internal/k8s/controller.go

View check run for this annotation

Codecov / codecov/patch

internal/k8s/controller.go#L1933-L1938

Added lines #L1933 - L1938 were not covered by tests
}
return true
}
Expand Down Expand Up @@ -1961,7 +1974,7 @@ func (lbc *LoadBalancerController) specialSecretValidation(secretNsName string,
}
}
if secretNsName == lbc.specialSecrets.clientAuthSecret {
err := lbc.validationTLSSpecialSecret(secret, configs.ClientAuthCertSecretFileName, specialTLSSecretsToUpdate)
err := secrets.ValidateTLSSecret(secret)

Check warning on line 1977 in internal/k8s/controller.go

View check run for this annotation

Codecov / codecov/patch

internal/k8s/controller.go#L1977

Added line #L1977 was not covered by tests
if err != nil {
nl.Errorf(lbc.Logger, "Couldn't validate the special Secret %v: %v", secretNsName, err)
lbc.recorder.Eventf(lbc.metadata.pod, api_v1.EventTypeWarning, "Rejected", "the special Secret %v was rejected, using the previous version: %v", secretNsName, err)
Expand Down
16 changes: 16 additions & 0 deletions tests/data/mgmt-configmap-keys/all-options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config-mgmt
namespace: nginx-ingress
data:
license-token-secret-name: "license-token"
ssl-trusted-certificate-secret-name: "ssl-trusted-cert"
ssl-certificate-secret-name: "ssl-cert"
ssl-verify: "false"
enforce-initial-report: "false"
usage-report-endpoint: "product.connect.nginx.com"
usage-report-interval: "2h"
resolver-addresses: "1.1.1.1,8.8.8.8"
resolver-ipv6: "false"
resolver-valid: "1h"
8 changes: 8 additions & 0 deletions tests/data/mgmt-configmap-keys/ssl-cert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: ssl-cert
type: kubernetes.io/tls
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURMakNDQWhZQ0NRREFPRjl0THNhWFdqQU5CZ2txaGtpRzl3MEJBUXNGQURCYU1Rc3dDUVlEVlFRR0V3SlYKVXpFTE1Ba0dBMVVFQ0F3Q1EwRXhJVEFmQmdOVkJBb01HRWx1ZEdWeWJtVjBJRmRwWkdkcGRITWdVSFI1SUV4MApaREViTUJrR0ExVUVBd3dTWTJGbVpTNWxlR0Z0Y0d4bExtTnZiU0FnTUI0WERURTRNRGt4TWpFMk1UVXpOVm9YCkRUSXpNRGt4TVRFMk1UVXpOVm93V0RFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVNFd0h3WUQKVlFRS0RCaEpiblJsY201bGRDQlhhV1JuYVhSeklGQjBlU0JNZEdReEdUQVhCZ05WQkFNTUVHTmhabVV1WlhoaApiWEJzWlM1amIyMHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDcDZLbjdzeTgxCnAwanVKL2N5ayt2Q0FtbHNmanRGTTJtdVpOSzBLdGVjcUcyZmpXUWI1NXhRMVlGQTJYT1N3SEFZdlNkd0kyaloKcnVXOHFYWENMMnJiNENaQ0Z4d3BWRUNyY3hkam0zdGVWaVJYVnNZSW1tSkhQUFN5UWdwaW9iczl4N0RsTGM2SQpCQTBaalVPeWwwUHFHOVNKZXhNVjczV0lJYTVyRFZTRjJyNGtTa2JBajREY2o3TFhlRmxWWEgySTVYd1hDcHRDCm42N0pDZzQyZitrOHdnemNSVnA4WFprWldaVmp3cTlSVUtEWG1GQjJZeU4xWEVXZFowZXdSdUtZVUpsc202OTIKc2tPcktRajB2a29QbjQxRUUvK1RhVkVwcUxUUm9VWTNyemc3RGtkemZkQml6Rk8yZHNQTkZ4MkNXMGpYa05MdgpLbzI1Q1pyT2hYQUhBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLSEZDY3lPalp2b0hzd1VCTWRMClJkSEliMzgzcFdGeW5acS9MdVVvdnNWQTU4QjBDZzdCRWZ5NXZXVlZycTVSSWt2NGxaODFOMjl4MjFkMUpINnIKalNuUXgrRFhDTy9USkVWNWxTQ1VwSUd6RVVZYVVQZ1J5anNNL05VZENKOHVIVmhaSitTNkZBK0NuT0Q5cm4yaQpaQmVQQ0k1ckh3RVh3bm5sOHl3aWozdnZRNXpISXV5QmdsV3IvUXl1aTlmalBwd1dVdlVtNG52NVNNRzl6Q1Y3ClBwdXd2dWF0cWpPMTIwOEJqZkUvY1pISWc4SHc5bXZXOXg5QytJUU1JTURFN2IvZzZPY0s3TEdUTHdsRnh2QTgKN1dqRWVxdW5heUlwaE1oS1JYVmYxTjM0OWVOOThFejM4Zk9USFRQYmRKakZBL1BjQytHeW1lK2lHdDVPUWRGaAp5UkU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBcWVpcCs3TXZOYWRJN2lmM01wUHJ3Z0pwYkg0N1JUTnBybVRTdENyWG5LaHRuNDFrCkcrZWNVTldCUU5semtzQndHTDBuY0NObzJhN2x2S2wxd2k5cTIrQW1RaGNjS1ZSQXEzTVhZNXQ3WGxZa1YxYkcKQ0pwaVJ6ejBza0lLWXFHN1BjZXc1UzNPaUFRTkdZMURzcGRENmh2VWlYc1RGZTkxaUNHdWF3MVVoZHErSkVwRwp3SStBM0kreTEzaFpWVng5aU9WOEZ3cWJRcCt1eVFvT05uL3BQTUlNM0VWYWZGMlpHVm1WWThLdlVWQ2cxNWhRCmRtTWpkVnhGbldkSHNFYmltRkNaYkp1dmRySkRxeWtJOUw1S0Q1K05SQlAvazJsUkthaTAwYUZHTjY4NE93NUgKYzMzUVlzeFR0bmJEelJjZGdsdEkxNURTN3lxTnVRbWF6b1Z3QndJREFRQUJBb0lCQVFDUFNkU1luUXRTUHlxbApGZlZGcFRPc29PWVJoZjhzSStpYkZ4SU91UmF1V2VoaEp4ZG01Uk9ScEF6bUNMeUw1VmhqdEptZTIyM2dMcncyCk45OUVqVUtiL1ZPbVp1RHNCYzZvQ0Y2UU5SNThkejhjbk9SVGV3Y290c0pSMXBuMWhobG5SNUhxSkpCSmFzazEKWkVuVVFmY1hackw5NGxvOUpIM0UrVXFqbzFGRnM4eHhFOHdvUEJxalpzVjdwUlVaZ0MzTGh4bndMU0V4eUZvNApjeGI5U09HNU9tQUpvelN0Rm9RMkdKT2VzOHJKNXFmZHZ5dGdnOXhiTGFRTC94MGtwUTYyQm9GTUJEZHFPZVBXCktmUDV6WjYvMDcvdnBqNDh5QTFRMzJQem9idWJzQkxkM0tjbjMyamZtMUU3cHJ0V2wrSmVPRmlPem5CUUZKYk4KNHFQVlJ6NWhBb0dCQU50V3l4aE5DU0x1NFArWGdLeWNrbGpKNkY1NjY4Zk5qNUN6Z0ZScUowOXpuMFRsc05ybwpGVExaY3hEcW5SM0hQWU00MkpFUmgySi9xREZaeW5SUW8zY2czb2VpdlVkQlZHWTgrRkkxVzBxZHViL0w5K3l1CmVkT1pUUTVYbUdHcDZyNmpleHltY0ppbS9Pc0IzWm5ZT3BPcmxEN1NQbUJ2ek5MazRNRjZneGJYQW9HQkFNWk8KMHA2SGJCbWNQMHRqRlhmY0tFNzdJbUxtMHNBRzR1SG9VeDBlUGovMnFyblRuT0JCTkU0TXZnRHVUSnp5K2NhVQprOFJxbWRIQ2JIelRlNmZ6WXEvOWl0OHNaNzdLVk4xcWtiSWN1YytSVHhBOW5OaDFUanNSbmU3NFowajFGQ0xrCmhIY3FIMHJpN1BZU0tIVEU4RnZGQ3haWWRidUI4NENtWmlodnhicFJBb0dBSWJqcWFNWVBUWXVrbENkYTVTNzkKWVNGSjFKelplMUtqYS8vdER3MXpGY2dWQ0thMzFqQXdjaXowZi9sU1JxM0hTMUdHR21lemhQVlRpcUxmZVpxYwpSMGlLYmhnYk9jVlZrSkozSzB5QXlLd1BUdW14S0haNnpJbVpTMGMwYW0rUlk5WUdxNVQ3WXJ6cHpjZnZwaU9VCmZmZTNSeUZUN2NmQ21mb09oREN0enVrQ2dZQjMwb0xDMVJMRk9ycW40M3ZDUzUxemM1em9ZNDR1QnpzcHd3WU4KVHd2UC9FeFdNZjNWSnJEakJDSCtULzZzeXNlUGJKRUltbHpNK0l3eXRGcEFOZmlJWEV0LzQ4WGY2ME54OGdXTQp1SHl4Wlp4L05LdER3MFY4dlgxUE9ucTJBNWVpS2ErOGpSQVJZS0pMWU5kZkR1d29seHZHNmJaaGtQaS80RXRUCjNZMThzUUtCZ0h0S2JrKzdsTkpWZXN3WEU1Y1VHNkVEVXNEZS8yVWE3ZlhwN0ZjanFCRW9hcDFMU3crNlRYcDAKWmdybUtFOEFSek00NytFSkhVdmlpcS9udXBFMTVnMGtKVzNzeWhwVTl6WkxPN2x0QjBLSWtPOVpSY21Vam84UQpjcExsSE1BcWJMSjhXWUdKQ2toaVd4eWFsNmhZVHlXWTRjVmtDMHh0VGwvaFVFOUllTktvCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
Loading

0 comments on commit 60d540b

Please sign in to comment.