github.com/fastly/cli@v1.7.2-0.20240304164155-9d0f1d77c3bf/pkg/commands/logging/azureblob/azureblob_integration_test.go (about)

     1  package azureblob_test
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"io"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/fastly/go-fastly/v9/fastly"
    11  
    12  	"github.com/fastly/cli/pkg/app"
    13  	"github.com/fastly/cli/pkg/global"
    14  	"github.com/fastly/cli/pkg/mock"
    15  	"github.com/fastly/cli/pkg/testutil"
    16  )
    17  
    18  func TestBlobStorageCreate(t *testing.T) {
    19  	args := testutil.Args
    20  	scenarios := []struct {
    21  		args       []string
    22  		api        mock.API
    23  		wantError  string
    24  		wantOutput string
    25  	}{
    26  		{
    27  			args: args("logging azureblob create --service-id 123 --version 1 --name log --account-name account --container log --sas-token abc --autoclone"),
    28  			api: mock.API{
    29  				ListVersionsFn:      testutil.ListVersions,
    30  				CloneVersionFn:      testutil.CloneVersionResult(4),
    31  				CreateBlobStorageFn: createBlobStorageOK,
    32  			},
    33  			wantOutput: "Created Azure Blob Storage logging endpoint log (service 123 version 4)",
    34  		},
    35  		{
    36  			args: args("logging azureblob create --service-id 123 --version 1 --name log --account-name account --container log --sas-token abc --autoclone"),
    37  			api: mock.API{
    38  				ListVersionsFn:      testutil.ListVersions,
    39  				CloneVersionFn:      testutil.CloneVersionResult(4),
    40  				CreateBlobStorageFn: createBlobStorageError,
    41  			},
    42  			wantError: errTest.Error(),
    43  		},
    44  		{
    45  			args: args("logging azureblob create --service-id 123 --version 1 --name log --account-name account --container log --sas-token abc --compression-codec zstd --gzip-level 9 --autoclone"),
    46  			api: mock.API{
    47  				ListVersionsFn:      testutil.ListVersions,
    48  				CloneVersionFn:      testutil.CloneVersionResult(4),
    49  				CreateBlobStorageFn: createBlobStorageError,
    50  			},
    51  			wantError: "error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag",
    52  		},
    53  	}
    54  	for testcaseIdx := range scenarios {
    55  		testcase := &scenarios[testcaseIdx]
    56  		t.Run(strings.Join(testcase.args, " "), func(t *testing.T) {
    57  			var stdout bytes.Buffer
    58  			app.Init = func(_ []string, _ io.Reader) (*global.Data, error) {
    59  				opts := testutil.MockGlobalData(testcase.args, &stdout)
    60  				opts.APIClientFactory = mock.APIClient(testcase.api)
    61  				return opts, nil
    62  			}
    63  			err := app.Run(testcase.args, nil)
    64  			testutil.AssertErrorContains(t, err, testcase.wantError)
    65  			testutil.AssertStringContains(t, stdout.String(), testcase.wantOutput)
    66  		})
    67  	}
    68  }
    69  
    70  func TestBlobStorageList(t *testing.T) {
    71  	args := testutil.Args
    72  	scenarios := []struct {
    73  		args       []string
    74  		api        mock.API
    75  		wantError  string
    76  		wantOutput string
    77  	}{
    78  		{
    79  			args: args("logging azureblob list --service-id 123 --version 1"),
    80  			api: mock.API{
    81  				ListVersionsFn:     testutil.ListVersions,
    82  				ListBlobStoragesFn: listBlobStoragesOK,
    83  			},
    84  			wantOutput: listBlobStoragesShortOutput,
    85  		},
    86  		{
    87  			args: args("logging azureblob list --service-id 123 --version 1 --verbose"),
    88  			api: mock.API{
    89  				ListVersionsFn:     testutil.ListVersions,
    90  				ListBlobStoragesFn: listBlobStoragesOK,
    91  			},
    92  			wantOutput: listBlobStoragesVerboseOutput,
    93  		},
    94  		{
    95  			args: args("logging azureblob list --service-id 123 --version 1 -v"),
    96  			api: mock.API{
    97  				ListVersionsFn:     testutil.ListVersions,
    98  				ListBlobStoragesFn: listBlobStoragesOK,
    99  			},
   100  			wantOutput: listBlobStoragesVerboseOutput,
   101  		},
   102  		{
   103  			args: args("logging azureblob --verbose list --service-id 123 --version 1"),
   104  			api: mock.API{
   105  				ListVersionsFn:     testutil.ListVersions,
   106  				ListBlobStoragesFn: listBlobStoragesOK,
   107  			},
   108  			wantOutput: listBlobStoragesVerboseOutput,
   109  		},
   110  		{
   111  			args: args("logging -v azureblob list --service-id 123 --version 1"),
   112  			api: mock.API{
   113  				ListVersionsFn:     testutil.ListVersions,
   114  				ListBlobStoragesFn: listBlobStoragesOK,
   115  			},
   116  			wantOutput: listBlobStoragesVerboseOutput,
   117  		},
   118  		{
   119  			args: args("logging azureblob list --service-id 123 --version 1"),
   120  			api: mock.API{
   121  				ListVersionsFn:     testutil.ListVersions,
   122  				ListBlobStoragesFn: listBlobStoragesError,
   123  			},
   124  			wantError: errTest.Error(),
   125  		},
   126  	}
   127  	for testcaseIdx := range scenarios {
   128  		testcase := &scenarios[testcaseIdx]
   129  		t.Run(strings.Join(testcase.args, " "), func(t *testing.T) {
   130  			var stdout bytes.Buffer
   131  			app.Init = func(_ []string, _ io.Reader) (*global.Data, error) {
   132  				opts := testutil.MockGlobalData(testcase.args, &stdout)
   133  				opts.APIClientFactory = mock.APIClient(testcase.api)
   134  				return opts, nil
   135  			}
   136  			err := app.Run(testcase.args, nil)
   137  			testutil.AssertErrorContains(t, err, testcase.wantError)
   138  			testutil.AssertString(t, testcase.wantOutput, stdout.String())
   139  		})
   140  	}
   141  }
   142  
   143  func TestBlobStorageDescribe(t *testing.T) {
   144  	args := testutil.Args
   145  	scenarios := []struct {
   146  		args       []string
   147  		api        mock.API
   148  		wantError  string
   149  		wantOutput string
   150  	}{
   151  		{
   152  			args:      args("logging azureblob describe --service-id 123 --version 1"),
   153  			wantError: "error parsing arguments: required flag --name not provided",
   154  		},
   155  		{
   156  			args: args("logging azureblob describe --service-id 123 --version 1 --name logs"),
   157  			api: mock.API{
   158  				ListVersionsFn:   testutil.ListVersions,
   159  				GetBlobStorageFn: getBlobStorageError,
   160  			},
   161  			wantError: errTest.Error(),
   162  		},
   163  		{
   164  			args: args("logging azureblob describe --service-id 123 --version 1 --name logs"),
   165  			api: mock.API{
   166  				ListVersionsFn:   testutil.ListVersions,
   167  				GetBlobStorageFn: getBlobStorageOK,
   168  			},
   169  			wantOutput: describeBlobStorageOutput,
   170  		},
   171  	}
   172  	for testcaseIdx := range scenarios {
   173  		testcase := &scenarios[testcaseIdx]
   174  		t.Run(strings.Join(testcase.args, " "), func(t *testing.T) {
   175  			var stdout bytes.Buffer
   176  			app.Init = func(_ []string, _ io.Reader) (*global.Data, error) {
   177  				opts := testutil.MockGlobalData(testcase.args, &stdout)
   178  				opts.APIClientFactory = mock.APIClient(testcase.api)
   179  				return opts, nil
   180  			}
   181  			err := app.Run(testcase.args, nil)
   182  			testutil.AssertErrorContains(t, err, testcase.wantError)
   183  			testutil.AssertString(t, testcase.wantOutput, stdout.String())
   184  		})
   185  	}
   186  }
   187  
   188  func TestBlobStorageUpdate(t *testing.T) {
   189  	args := testutil.Args
   190  	scenarios := []struct {
   191  		args       []string
   192  		api        mock.API
   193  		wantError  string
   194  		wantOutput string
   195  	}{
   196  		{
   197  			args:      args("logging azureblob update --service-id 123 --version 1 --new-name log"),
   198  			wantError: "error parsing arguments: required flag --name not provided",
   199  		},
   200  		{
   201  			args: args("logging azureblob update --service-id 123 --version 1 --name logs --new-name log --autoclone"),
   202  			api: mock.API{
   203  				ListVersionsFn:      testutil.ListVersions,
   204  				CloneVersionFn:      testutil.CloneVersionResult(4),
   205  				UpdateBlobStorageFn: updateBlobStorageError,
   206  			},
   207  			wantError: errTest.Error(),
   208  		},
   209  		{
   210  			args: args("logging azureblob update --service-id 123 --version 1 --name logs --new-name log --autoclone"),
   211  			api: mock.API{
   212  				ListVersionsFn:      testutil.ListVersions,
   213  				CloneVersionFn:      testutil.CloneVersionResult(4),
   214  				UpdateBlobStorageFn: updateBlobStorageOK,
   215  			},
   216  			wantOutput: "Updated Azure Blob Storage logging endpoint log (service 123 version 4)",
   217  		},
   218  	}
   219  	for testcaseIdx := range scenarios {
   220  		testcase := &scenarios[testcaseIdx]
   221  		t.Run(strings.Join(testcase.args, " "), func(t *testing.T) {
   222  			var stdout bytes.Buffer
   223  			app.Init = func(_ []string, _ io.Reader) (*global.Data, error) {
   224  				opts := testutil.MockGlobalData(testcase.args, &stdout)
   225  				opts.APIClientFactory = mock.APIClient(testcase.api)
   226  				return opts, nil
   227  			}
   228  			err := app.Run(testcase.args, nil)
   229  			testutil.AssertErrorContains(t, err, testcase.wantError)
   230  			testutil.AssertStringContains(t, stdout.String(), testcase.wantOutput)
   231  		})
   232  	}
   233  }
   234  
   235  func TestBlobStorageDelete(t *testing.T) {
   236  	args := testutil.Args
   237  	scenarios := []struct {
   238  		args       []string
   239  		api        mock.API
   240  		wantError  string
   241  		wantOutput string
   242  	}{
   243  		{
   244  			args:      args("logging azureblob delete --service-id 123 --version 1"),
   245  			wantError: "error parsing arguments: required flag --name not provided",
   246  		},
   247  		{
   248  			args: args("logging azureblob delete --service-id 123 --version 1 --name logs --autoclone"),
   249  			api: mock.API{
   250  				ListVersionsFn:      testutil.ListVersions,
   251  				CloneVersionFn:      testutil.CloneVersionResult(4),
   252  				DeleteBlobStorageFn: deleteBlobStorageError,
   253  			},
   254  			wantError: errTest.Error(),
   255  		},
   256  		{
   257  			args: args("logging azureblob delete --service-id 123 --version 1 --name logs --autoclone"),
   258  			api: mock.API{
   259  				ListVersionsFn:      testutil.ListVersions,
   260  				CloneVersionFn:      testutil.CloneVersionResult(4),
   261  				DeleteBlobStorageFn: deleteBlobStorageOK,
   262  			},
   263  			wantOutput: "Deleted Azure Blob Storage logging endpoint logs (service 123 version 4)",
   264  		},
   265  	}
   266  	for testcaseIdx := range scenarios {
   267  		testcase := &scenarios[testcaseIdx]
   268  		t.Run(strings.Join(testcase.args, " "), func(t *testing.T) {
   269  			var stdout bytes.Buffer
   270  			app.Init = func(_ []string, _ io.Reader) (*global.Data, error) {
   271  				opts := testutil.MockGlobalData(testcase.args, &stdout)
   272  				opts.APIClientFactory = mock.APIClient(testcase.api)
   273  				return opts, nil
   274  			}
   275  			err := app.Run(testcase.args, nil)
   276  			testutil.AssertErrorContains(t, err, testcase.wantError)
   277  			testutil.AssertStringContains(t, stdout.String(), testcase.wantOutput)
   278  		})
   279  	}
   280  }
   281  
   282  var errTest = errors.New("fixture error")
   283  
   284  func createBlobStorageOK(i *fastly.CreateBlobStorageInput) (*fastly.BlobStorage, error) {
   285  	s := fastly.BlobStorage{
   286  		ServiceID:         fastly.ToPointer(i.ServiceID),
   287  		ServiceVersion:    fastly.ToPointer(i.ServiceVersion),
   288  		Name:              fastly.ToPointer("log"),
   289  		Path:              fastly.ToPointer("/logs"),
   290  		AccountName:       fastly.ToPointer("account"),
   291  		Container:         fastly.ToPointer("container"),
   292  		SASToken:          fastly.ToPointer("token"),
   293  		Period:            fastly.ToPointer(3600),
   294  		TimestampFormat:   fastly.ToPointer("%Y-%m-%dT%H:%M:%S.000"),
   295  		PublicKey:         fastly.ToPointer(pgpPublicKey()),
   296  		Format:            fastly.ToPointer(`%h %l %u %t "%r" %>s %b`),
   297  		FormatVersion:     fastly.ToPointer(2),
   298  		MessageType:       fastly.ToPointer("classic"),
   299  		Placement:         fastly.ToPointer("none"),
   300  		ResponseCondition: fastly.ToPointer("Prevent default logging"),
   301  		CompressionCodec:  fastly.ToPointer("zstd"),
   302  	}
   303  
   304  	return &s, nil
   305  }
   306  
   307  func createBlobStorageError(_ *fastly.CreateBlobStorageInput) (*fastly.BlobStorage, error) {
   308  	return nil, errTest
   309  }
   310  
   311  func listBlobStoragesOK(i *fastly.ListBlobStoragesInput) ([]*fastly.BlobStorage, error) {
   312  	return []*fastly.BlobStorage{
   313  		{
   314  			ServiceID:         fastly.ToPointer(i.ServiceID),
   315  			ServiceVersion:    fastly.ToPointer(i.ServiceVersion),
   316  			Name:              fastly.ToPointer("logs"),
   317  			Path:              fastly.ToPointer("/logs"),
   318  			AccountName:       fastly.ToPointer("account"),
   319  			Container:         fastly.ToPointer("container"),
   320  			SASToken:          fastly.ToPointer("token"),
   321  			Period:            fastly.ToPointer(3600),
   322  			TimestampFormat:   fastly.ToPointer("%Y-%m-%dT%H:%M:%S.000"),
   323  			PublicKey:         fastly.ToPointer(pgpPublicKey()),
   324  			Format:            fastly.ToPointer(`%h %l %u %t "%r" %>s %b`),
   325  			FormatVersion:     fastly.ToPointer(2),
   326  			MessageType:       fastly.ToPointer("classic"),
   327  			Placement:         fastly.ToPointer("none"),
   328  			ResponseCondition: fastly.ToPointer("Prevent default logging"),
   329  			CompressionCodec:  fastly.ToPointer("zstd"),
   330  		},
   331  		{
   332  			ServiceID:         fastly.ToPointer(i.ServiceID),
   333  			ServiceVersion:    fastly.ToPointer(i.ServiceVersion),
   334  			Name:              fastly.ToPointer("analytics"),
   335  			AccountName:       fastly.ToPointer("account"),
   336  			Container:         fastly.ToPointer("analytics"),
   337  			SASToken:          fastly.ToPointer("token"),
   338  			Path:              fastly.ToPointer("/logs"),
   339  			Period:            fastly.ToPointer(86400),
   340  			Format:            fastly.ToPointer(`%h %l %u %t "%r" %>s %b`),
   341  			FormatVersion:     fastly.ToPointer(2),
   342  			MessageType:       fastly.ToPointer("classic"),
   343  			ResponseCondition: fastly.ToPointer("Prevent default logging"),
   344  			TimestampFormat:   fastly.ToPointer("%Y-%m-%dT%H:%M:%S.000"),
   345  			Placement:         fastly.ToPointer("none"),
   346  			PublicKey:         fastly.ToPointer(pgpPublicKey()),
   347  			CompressionCodec:  fastly.ToPointer("zstd"),
   348  		},
   349  	}, nil
   350  }
   351  
   352  func listBlobStoragesError(_ *fastly.ListBlobStoragesInput) ([]*fastly.BlobStorage, error) {
   353  	return nil, errTest
   354  }
   355  
   356  var listBlobStoragesShortOutput = strings.TrimSpace(`
   357  SERVICE  VERSION  NAME
   358  123      1        logs
   359  123      1        analytics
   360  `) + "\n"
   361  
   362  var listBlobStoragesVerboseOutput = strings.TrimSpace(`
   363  Fastly API endpoint: https://api.fastly.com
   364  Fastly API token provided via config file (profile: user)
   365  
   366  Service ID (via --service-id): 123
   367  
   368  Version: 1
   369  	BlobStorage 1/2
   370  		Service ID: 123
   371  		Version: 1
   372  		Name: logs
   373  		Container: container
   374  		Account name: account
   375  		SAS token: token
   376  		Path: /logs
   377  		Period: 3600
   378  		GZip level: 0
   379  		Format: %h %l %u %t "%r" %>s %b
   380  		Format version: 2
   381  		Response condition: Prevent default logging
   382  		Message type: classic
   383  		Timestamp format: %Y-%m-%dT%H:%M:%S.000
   384  		Placement: none
   385  		Public key: `+pgpPublicKey()+`
   386  		File max bytes: 0
   387  		Compression codec: zstd
   388  	BlobStorage 2/2
   389  		Service ID: 123
   390  		Version: 1
   391  		Name: analytics
   392  		Container: analytics
   393  		Account name: account
   394  		SAS token: token
   395  		Path: /logs
   396  		Period: 86400
   397  		GZip level: 0
   398  		Format: %h %l %u %t "%r" %>s %b
   399  		Format version: 2
   400  		Response condition: Prevent default logging
   401  		Message type: classic
   402  		Timestamp format: %Y-%m-%dT%H:%M:%S.000
   403  		Placement: none
   404  		Public key: `+pgpPublicKey()+`
   405  		File max bytes: 0
   406  		Compression codec: zstd
   407  `) + "\n\n"
   408  
   409  func getBlobStorageOK(i *fastly.GetBlobStorageInput) (*fastly.BlobStorage, error) {
   410  	return &fastly.BlobStorage{
   411  		ServiceID:         fastly.ToPointer(i.ServiceID),
   412  		ServiceVersion:    fastly.ToPointer(i.ServiceVersion),
   413  		Name:              fastly.ToPointer("logs"),
   414  		Container:         fastly.ToPointer("container"),
   415  		AccountName:       fastly.ToPointer("account"),
   416  		SASToken:          fastly.ToPointer("token"),
   417  		Path:              fastly.ToPointer("/logs"),
   418  		Period:            fastly.ToPointer(3600),
   419  		GzipLevel:         fastly.ToPointer(0),
   420  		Format:            fastly.ToPointer(`%h %l %u %t "%r" %>s %b`),
   421  		FormatVersion:     fastly.ToPointer(2),
   422  		ResponseCondition: fastly.ToPointer("Prevent default logging"),
   423  		MessageType:       fastly.ToPointer("classic"),
   424  		TimestampFormat:   fastly.ToPointer("%Y-%m-%dT%H:%M:%S.000"),
   425  		Placement:         fastly.ToPointer("none"),
   426  		PublicKey:         fastly.ToPointer(pgpPublicKey()),
   427  		CompressionCodec:  fastly.ToPointer("zstd"),
   428  	}, nil
   429  }
   430  
   431  func getBlobStorageError(_ *fastly.GetBlobStorageInput) (*fastly.BlobStorage, error) {
   432  	return nil, errTest
   433  }
   434  
   435  var describeBlobStorageOutput = "\n" + strings.TrimSpace(`
   436  Account name: account
   437  Compression codec: zstd
   438  Container: container
   439  File max bytes: 0
   440  Format: %h %l %u %t "%r" %>s %b
   441  Format version: 2
   442  GZip level: 0
   443  Message type: classic
   444  Name: logs
   445  Path: /logs
   446  Period: 3600
   447  Placement: none
   448  Public key: `+pgpPublicKey()+`
   449  Response condition: Prevent default logging
   450  SAS token: token
   451  Service ID: 123
   452  Timestamp format: %Y-%m-%dT%H:%M:%S.000
   453  Version: 1
   454  `) + "\n"
   455  
   456  func updateBlobStorageOK(i *fastly.UpdateBlobStorageInput) (*fastly.BlobStorage, error) {
   457  	return &fastly.BlobStorage{
   458  		ServiceID:         fastly.ToPointer(i.ServiceID),
   459  		ServiceVersion:    fastly.ToPointer(i.ServiceVersion),
   460  		Name:              fastly.ToPointer("log"),
   461  		Container:         fastly.ToPointer("container"),
   462  		AccountName:       fastly.ToPointer("account"),
   463  		SASToken:          fastly.ToPointer("token"),
   464  		Path:              fastly.ToPointer("/logs"),
   465  		Period:            fastly.ToPointer(3600),
   466  		Format:            fastly.ToPointer(`%h %l %u %t "%r" %>s %b`),
   467  		FormatVersion:     fastly.ToPointer(2),
   468  		ResponseCondition: fastly.ToPointer("Prevent default logging"),
   469  		MessageType:       fastly.ToPointer("classic"),
   470  		TimestampFormat:   fastly.ToPointer("%Y-%m-%dT%H:%M:%S.000"),
   471  		Placement:         fastly.ToPointer("none"),
   472  		PublicKey:         fastly.ToPointer(pgpPublicKey()),
   473  		CompressionCodec:  fastly.ToPointer("zstd"),
   474  	}, nil
   475  }
   476  
   477  func updateBlobStorageError(_ *fastly.UpdateBlobStorageInput) (*fastly.BlobStorage, error) {
   478  	return nil, errTest
   479  }
   480  
   481  func deleteBlobStorageOK(_ *fastly.DeleteBlobStorageInput) error {
   482  	return nil
   483  }
   484  
   485  func deleteBlobStorageError(_ *fastly.DeleteBlobStorageInput) error {
   486  	return errTest
   487  }
   488  
   489  // pgpPublicKey returns a PEM encoded PGP public key suitable for testing.
   490  func pgpPublicKey() string {
   491  	return strings.TrimSpace(`-----BEGIN PGP PUBLIC KEY BLOCK-----
   492  mQENBFyUD8sBCACyFnB39AuuTygseek+eA4fo0cgwva6/FSjnWq7riouQee8GgQ/
   493  ibXTRyv4iVlwI12GswvMTIy7zNvs1R54i0qvsLr+IZ4GVGJqs6ZJnvQcqe3xPoR4
   494  8AnBfw90o32r/LuHf6QCJXi+AEu35koNlNAvLJ2B+KACaNB7N0EeWmqpV/1V2k9p
   495  lDYk+th7LcCuaFNGqKS/PrMnnMqR6VDLCjHhNx4KR79b0Twm/2qp6an3hyNRu8Gn
   496  dwxpf1/BUu3JWf+LqkN4Y3mbOmSUL3MaJNvyQguUzTfS0P0uGuBDHrJCVkMZCzDB
   497  89ag55jCPHyGeHBTd02gHMWzsg3WMBWvCsrzABEBAAG0JXRlcnJhZm9ybSAodGVz
   498  dCkgPHRlc3RAdGVycmFmb3JtLmNvbT6JAU4EEwEIADgWIQSHYyc6Kj9l6HzQsau6
   499  vFFc9jxV/wUCXJQPywIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRC6vFFc
   500  9jxV/815CAClb32OxV7wG01yF97TzlyTl8TnvjMtoG29Mw4nSyg+mjM3b8N7iXm9
   501  OLX59fbDAWtBSldSZE22RXd3CvlFOG/EnKBXSjBtEqfyxYSnyOPkMPBYWGL/ApkX
   502  SvPYJ4LKdvipYToKFh3y9kk2gk1DcDBDyaaHvR+3rv1u3aoy7/s2EltAfDS3ZQIq
   503  7/cWTLJml/lleeB/Y6rPj8xqeCYhE5ahw9gsV/Mdqatl24V9Tks30iijx0Hhw+Gx
   504  kATUikMGr2GDVqoIRga5kXI7CzYff4rkc0Twn47fMHHHe/KY9M2yVnMHUXmAZwbG
   505  M1cMI/NH1DjevCKdGBLcRJlhuLPKF/anuQENBFyUD8sBCADIpd7r7GuPd6n/Ikxe
   506  u6h7umV6IIPoAm88xCYpTbSZiaK30Svh6Ywra9jfE2KlU9o6Y/art8ip0VJ3m07L
   507  4RSfSpnzqgSwdjSq5hNour2Fo/BzYhK7yaz2AzVSbe33R0+RYhb4b/6N+bKbjwGF
   508  ftCsqVFMH+PyvYkLbvxyQrHlA9woAZaNThI1ztO5rGSnGUR8xt84eup28WIFKg0K
   509  UEGUcTzz+8QGAwAra+0ewPXo/AkO+8BvZjDidP417u6gpBHOJ9qYIcO9FxHeqFyu
   510  YrjlrxowEgXn5wO8xuNz6Vu1vhHGDHGDsRbZF8pv1d5O+0F1G7ttZ2GRRgVBZPwi
   511  kiyRABEBAAGJATYEGAEIACAWIQSHYyc6Kj9l6HzQsau6vFFc9jxV/wUCXJQPywIb
   512  DAAKCRC6vFFc9jxV/9YOCACe8qmOSnKQpQfW+PqYOqo3dt7JyweTs3FkD6NT8Zml
   513  dYy/vkstbTjPpX6aTvUZjkb46BVi7AOneVHpD5GBqvRsZ9iVgDYHaehmLCdKiG5L
   514  3Tp90NN+QY5WDbsGmsyk6+6ZMYejb4qYfweQeduOj27aavCJdLkCYMoRKfcFYI8c
   515  FaNmEfKKy/r1PO20NXEG6t9t05K/frHy6ZG8bCNYdpagfFVot47r9JaQqWlTNtIR
   516  5+zkkSq/eG9BEtRij3a6cTdQbktdBzx2KBeI0PYc1vlZR0LpuFKZqY9vlE6vTGLR
   517  wMfrTEOvx0NxUM3rpaCgEmuWbB1G1Hu371oyr4srrr+N
   518  =28dr
   519  -----END PGP PUBLIC KEY BLOCK-----
   520  `)
   521  }