# Refactor `notation-go`
This markdown refactors notation-go to clean up and align interface designs among packages in the library.
This refactoring is intended to improve the design of the library including improved readability, maintainability and extensibility. No functionality changes. Instead, refactoring will make sure the implementation of sign and verify workflow follow the specs. The library maturity is quite important from the whole notary project point of view, since it is the dependency for both notation CLI and other user implementations. The following rc.1 features and issues will benefit from refactoring:
1. Support of “–debug” option
2. Using Verifier without a repository
3. Notation supporting OCI artifact type and referrer API
4. Signing workflow is not fully compliant with spec, which was found during notation-go code review.
Implementation is planned to be completed within several PRs:
1. PR for package notation
2. PR for package signer
3. PR for package verifier, verifier/trustpolicy, verifier/truststore
4. PR for package registry, log
5. PR for package plugin, plugin/proto
6. PR for package dir, config
## package `notation`
```go
type Descriptor
func (d Descriptor) Equal(t Descriptor) bool
type Payload // to be removed; may be move to internal
type Service // to be removed
type SignOptions struct {
Expiry time.Time
TSA timestamp.Timestamper
TSAVerifyOptions x509.VerifyOptions
PluginConfig map[string]string
}
type Signer interface {
Sign(ctx context.Context, desc Descriptor, opts SignOptions) ([]byte, error)
}
type VerifyOptions struct {
SignatureMediaType string
}
func (opts VerifyOptions) Validate() error // to be removed
type Verifier interface {
Verify(ctx context.Context, signature []byte, opts VerifyOptions) (Descriptor, error)
}
```
After refactoring:
```go
// Errors
type ErrorNoApplicableTrustPolicy
func (e ErrorNoApplicableTrustPolicy) Error() string
type ErrorSignatureRetrievalFailed
func (e ErrorSignatureRetrievalFailed) Error() string
type ErrorVerificationFailed
func (e ErrorVerificationFailed) Error() string
type ErrorVerificationInconclusive
func (e ErrorVerificationInconclusive) Error() string
type Descriptor
func (d Descriptor) Equal(t Descriptor) bool
type SignOptions struct {
Expiry time.Time
TSA timestamp.Timestamper
TSAVerifyOptions x509.VerifyOptions
PluginConfig map[string]string
}
type Signer interface {
// Sign returns the signature []byte and SignerInfo
Sign(ctx context.Context, desc Descriptor, envelopeMediaType string,opts SignOptions) ([]byte, *signature.SignerInfo, error)
}
// Sign signs the artifact in the remote registry and push the signature to the remote.
// The descriptor of the sign content is returned upon sucessful signing.
func Sign(ctx context.Context, signer Signer, repo registry.Repository, reference string, envelopeMediaType string,opts SignOptions) (Descriptor, error)
type VerifyOptions struct {
ArtifactReference string
SignatureMediaType string
PluginConfig map[string]string
// VerificationDetails any // Export verification details
}
type Verifier interface {
Verify(ctx context.Context, signature []byte, opts VerifyOptions) (Descriptor, *VerificiationOutcome, error)
}
func Verify(ctx context.Context, verifier Verifier, repo registry.Repository, opts VerifyOptions) (Descriptor, []*VerificiationOutcome, error)
// Verification examples:
// 1. Local verification with pre-fetched signature:
// Verifier -> Verifier.Verify(ctx context.Context, signature []byte, opts VerifyOptions)
// 2. Remote verification with signatures in a repository:
// Verify(ctx context.Context, verifier Verifier, repo registry.Repository, ref string, opts VerifyOptions) -> verifier.Verify(ctx context.Context, signature []byte, opts VerifyOptions)
```
## package `signature`
```go
func NewSigner(key crypto.PrivateKey, certChain []*x509.Certificate, envelopeMediaType string) (notation.Signer, error)
func NewSignerFromFiles(keyPath, certPath, envelopeMediaType string) (notation.Signer, error)
func NewSignerPlugin(runner plugin.Runner, keyID string, pluginConfig map[string]string, ...) (notation.Signer, error)
func ValidateEnvelopeMediaType(mediaType string) error // unexport
func ValidatePayloadContentType(payload *signature.Payload) error // move to internal
```
Refactor to package `signer`:
```go
func New(key crypto.PrivateKey, certChain []*x509.Certificate) (notation.Signer, error)
func NewFromFiles(keyPath, certChainPath string) (notation.Signer, error)
func NewFromPlugin(plugin plugin.Plugin, keyID string, pluginConfig map[string]string) (notation.Signer, error)
```
## package `verification`
```go
const (
Integrity VerificationType = "integrity"
Authenticity VerificationType = "authenticity"
AuthenticTimestamp VerificationType = "authenticTimestamp"
Expiry VerificationType = "expiry"
Revocation VerificationType = "revocation"
Enforced VerificationAction = "enforce"
Logged VerificationAction = "log"
Skipped VerificationAction = "skip"
TrustStorePrefixCA TrustStorePrefix = "ca"
TrustStorePrefixSigningAuthority TrustStorePrefix = "signingAuthority"
VerificationPlugin = "io.cncf.notary.verificationPlugin"
VerificationPluginMinVersion = "io.cncf.notary.verificationPluginMinVersion"
)
var (
Strict = &VerificationLevel{
"strict",
map[VerificationType]VerificationAction{
Integrity: Enforced,
Authenticity: Enforced,
AuthenticTimestamp: Enforced,
Expiry: Enforced,
Revocation: Enforced,
},
}
Permissive = &VerificationLevel{
"permissive",
map[VerificationType]VerificationAction{
Integrity: Enforced,
Authenticity: Enforced,
AuthenticTimestamp: Logged,
Expiry: Logged,
Revocation: Logged,
},
}
Audit = &VerificationLevel{
"audit",
map[VerificationType]VerificationAction{
Integrity: Enforced,
Authenticity: Logged,
AuthenticTimestamp: Logged,
Expiry: Logged,
Revocation: Logged,
},
}
Skip = &VerificationLevel{
"skip",
map[VerificationType]VerificationAction{
Integrity: Skipped,
Authenticity: Skipped,
AuthenticTimestamp: Skipped,
Expiry: Skipped,
Revocation: Skipped,
},
}
VerificationTypes = []VerificationType{
Integrity,
Authenticity,
AuthenticTimestamp,
Expiry,
Revocation,
}
VerificationActions = []VerificationAction{
Enforced,
Logged,
Skipped,
}
VerificationLevels = []*VerificationLevel{
Strict,
Permissive,
Audit,
Skip,
}
TrustStorePrefixes = []TrustStorePrefix{
TrustStorePrefixCA,
TrustStorePrefixSigningAuthority,
}
)
func IsValidTrustStorePrefix(s string) bool
func WithPluginConfig(ctx context.Context, config map[string]string) context.Context
type ErrorNoApplicableTrustPolicy
func (e ErrorNoApplicableTrustPolicy) Error() string
type ErrorSignatureRetrievalFailed
func (e ErrorSignatureRetrievalFailed) Error() string
type ErrorVerificationFailed
func (e ErrorVerificationFailed) Error() string
type ErrorVerificationInconclusive
func (e ErrorVerificationInconclusive) Error() string
type PolicyDocument
func (policyDoc *PolicyDocument) ValidatePolicyDocument() error
type SignatureVerification
type SignatureVerificationOutcome
type TrustPolicy
type TrustStorePrefix
type VerificationAction
type VerificationLevel
func GetVerificationLevel(signatureVerification SignatureVerification) (*VerificationLevel, error)
type VerificationResult
type VerificationType
type Verifier
func NewVerifier(repository registry.Repository) (*Verifier, error)
func (v *Verifier) Verify(ctx context.Context, artifactUri string) ([]*SignatureVerificationOutcome, error)
type X509TrustStore
func LoadX509TrustStore(path string) (*X509TrustStore, error)
```
Refactor to package `verifier`:
```go
// Constants
const (
HeaderVerificationPlugin = "io.cncf.notary.verificationPlugin"
HearderVerificationPluginMinVersion = "io.cncf.notary.verificationPluginMinVersion"
)
// internal structs
// type SignatureVerificationOutcome struct {
// EnvelopeContent *signature.EnvelopeContent
// VerificationLevel *VerificationLevel
// VerificationResults []*VerificationResult
// SignedAnnotations map[string]string
// Error error
// }
// type VerificationResult struct {
// Success bool
// Type VerificationType
// Action VerificationAction
// Error error
// }
// New creates a verifier using local file system
func New() (*notation.Verifier, error)
func NewVerifier(trustPolicy *trustpolicy.Document, trustStore truststore.X509TrustStore, pluginManager plugin.Manager)(*notation.Verifier, error)
```
Refactor to package `verifier/trustpolicy`:
```go
const (
TypeIntegrity ValidationType = "integrity"
TypeAuthenticity ValidationType = "authenticity"
TypeAuthenticTimestamp ValidationType = "authenticTimestamp"
TypeExpiry ValidationType = "expiry"
TypeRevocation ValidationType = "revocation"
)
const (
ActionEnforce ValidationAction = "enforce"
ActionLog ValidationAction = "log"
ActionSkip ValidationAction = "skip"
)
var (
LevelStrict = &VerificationLevel{
Name: "strict",
Enforcement: map[ValidationType]ValidationAction{
TypeIntegrity: ActionEnforce,
TypeAuthenticity: ActionEnforce,
TypeAuthenticTimestamp: ActionEnforce,
TypeExpiry: ActionEnforce,
TypeRevocation: ActionEnforce,
},
}
LevelPermissive = &VerificationLevel{
Name: "permissive",
Enforcement: map[ValidationType]ValidationAction{
TypeIntegrity: ActionEnforce,
TypeAuthenticity: ActionEnforce,
TypeAuthenticTimestamp: ActionLog,
TypeExpiry: ActionLog,
TypeRevocation: ActionLog,
},
}
LevelAudit = &VerificationLevel{
Name: "audit",
Enforcement: map[ValidationType]ValidationAction{
TypeIntegrity: ActionEnforce,
TypeAuthenticity: ActionLog,
TypeAuthenticTimestamp: ActionLog,
TypeExpiry: ActionLog,
TypeRevocation: ActionLog,
},
}
LevelSkip = &VerificationLevel{
Name: "skip",
Enforcement: map[ValidationType]ValidationAction{
TypeIntegrity: ActionSkip,
TypeAuthenticity: ActionSkip,
TypeAuthenticTimestamp: ActionSkip,
TypeExpiry: ActionSkip,
TypeRevocation: ActionSkip,
},
}
ValidationTypes = []ValidationType{
TypeIntegrity,
TypeAuthenticity,
TypeAuthenticTimestamp,
TypeExpiry,
TypeRevocation,
}
ValidationActions = []ValidationAction{
ActionEnforce,
ActionLog,
ActionSkip,
}
VerificationLevels = []*VerificationLevel{
LevelStrict,
LevelPermissive,
LevelAudit,
LevelSkip,
}
)
type Document struct {
Version string `json:"version"`
TrustPolicies []TrustPolicy `json:"trustPolicies"`
}
func (doc *Document) Validate() error
type TrustPolicy struct {
Name string `json:"name"`
RegistryScopes []string `json:"registryScopes"`
SignatureVerification SignatureVerification `json:"signatureVerification"`
TrustStores []string `json:"trustStores"`
TrustedIdentities []string `json:"trustedIdentities"`
}
type SignatureVerification struct {
VerificationLevel string `json:"level"`
Override map[ValidationType]ValidationAction `json:"override,omitempty"` // change type
}
func GetVerificationLevel(signatureVerification SignatureVerification) (*VerificationLevel, error)
func isValidTrustStoreType(s string) bool // renamed from IsValidTrustStorePrefix; unexport
type ValidationType string
type ValidationAction string
type VerificationLevel struct {
Name string
Enforcement map[ValidationType]ValidationAction
}
```
Refactor to package `verifier/truststore`:
```go
type X509TrustStore interface {
GetCertificates(ctx context.Context, storeType Type, namedStore string) ([]*x509.Certificate, error)
}
func NewX509TrustStore(trustStorefs SysFS) X509TrustStore
type Type // renamed from TrustStorePrefix;
// unexport
// x509TrustStore implements X509TrustStore
// type x509TrustStore
const ( // rename from Prefix
TypeCA Type = "ca"
TypeSigningAuthority Type = "signingAuthority"
)
var (
Types = []Type{ // rename from TrustStorePrefixes
TypeCA,
TypeSigningAuthority,
}
)
```
## package `registry`
```go
type Repository
type RepositoryClient
func NewRepositoryClient(client remote.Client, ref registry.Reference, plainHTTP bool) *RepositoryClient
func (c *RepositoryClient) GetBlob(ctx context.Context, digest digest.Digest) ([]byte, error)
func (c *RepositoryClient) ListSignatureManifests(ctx context.Context, manifestDigest digest.Digest) ([]SignatureManifest, error)
func (c *RepositoryClient) PutSignatureManifest(ctx context.Context, signature []byte, signatureMediaType string, ...) (notation.Descriptor, SignatureManifest, error)
func (c *RepositoryClient) Resolve(ctx context.Context, reference string) (notation.Descriptor, error)
type SignatureManifest
type SignatureRepository
```
Refactor as
```go
type Repository interface {
// returns artifactDesc
Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error)
// input: artifactDesc
// returns []signatureManifestDesc
ListSignatures(ctx context.Context, desc ocispec.Descriptor, fn func(signatureManifests []ocispec.Descriptor) error) error
// input: signatureManifestDesc
// returns signatureblob, signatureblobDesc
FetchSignatureBlob(ctx context.Context, desc ocispec.Descriptor) ([]byte, ocispec.Descriptor, error)
// push both signatureManifest and signature envelope
// annotatoins is signatureManifestAnnotations
PushSignature(ctx context.Context, blob []byte, mediaType string, subject ocispec.Descriptor, annotations map[string]string) (blobDesc, manifestDesc ocispec.Descriptor, err error)
}
func NewRepository(repo remote.Repository) Repository
```
## package `plugin`
```go
func KeySpecHashString(k signature.KeySpec) string
func KeySpecString(k signature.KeySpec) string
func ParseKeySpec(raw string) (keySpec signature.KeySpec, err error)
func ParseSigningAlgorithm(raw string) (signature.Algorithm, error)
func SigningAlgorithmString(alg signature.Algorithm) string
type Capability
func (c Capability) In(capabilities []Capability) bool
type Command
type CriticalAttributes
type DescribeKeyRequest
func (DescribeKeyRequest) Command() Command
type DescribeKeyResponse
type ErrorCode
type GenerateEnvelopeRequest
func (GenerateEnvelopeRequest) Command() Command
type GenerateEnvelopeResponse
type GenerateSignatureRequest
func (GenerateSignatureRequest) Command() Command
type GenerateSignatureResponse
type GetMetadataRequest
func (GetMetadataRequest) Command() Command
type Metadata
func (Metadata) Command() Command
func (m *Metadata) HasCapability(capability Capability) bool
func (m *Metadata) SupportsContract(ver string) bool
func (m *Metadata) Validate() error
type Request
type RequestError
func (e RequestError) Error() string
func (e RequestError) Is(target error) bool
func (e RequestError) MarshalJSON() ([]byte, error)
func (e *RequestError) UnmarshalJSON(data []byte) error
func (e RequestError) Unwrap() error
type Runner
type Signature
type SigningScheme
type TrustPolicy
type VerificationCapability
type VerificationResult
type VerifySignatureRequest
func (VerifySignatureRequest) Command() Command
type VerifySignatureResponse
```
Refactor as `plugin`
```go
type GenericPlugin interface {
GetMetadata(ctx context.Context, req *proto.GetMetadataRequest) (*proto.GetMetadataResponse, error)
}
type SignPlugin interface {
GenericPlugin
DescribeKey(ctx context.Context, req *proto.DescribeKeyRequest) (*proto.DescribeKeyResponse, error)
GenerateSignature(ctx context.Context, req *proto.GenerateSignatureRequest) (*proto.GenerateSignatureResponse, error)
GenerateEnvelope(ctx context.Context, req *proto.GenerateEnvelopeRequest) (*proto.GenerateEnvelopeResponse, error)
}
type VerifyPlugin interface {
GenericPlugin
VerifySignature(ctx context.Context, req *proto.VerifySignatureRequest) (*proto.VerifySignatureResponse, error)
}
type Plugin interface {
SignPlugin
VerifyPlugin
}
type Manager interface {
Get(ctx context.Context, name string) (Plugin, error)
List(ctx context.Context) ([]string, error)
}
// CLIManager implements Manager
type CLIManager
func NewCLIManager() *CLIManager
```
Refactor as `plugin/proto`
```go
type Command string
const (
CommandGetMetadata Command = "get-plugin-metadata"
CommandDescribeKey Command = "describe-key"
CommandGenerateSignature Command = "generate-signature"
CommandGenerateEnvelope Command = "generate-envelope"
CommandVerifySignature Command = "verify-signature"
)
type Request interface {
Command() Command
}
type RequestError
func (e *RequestError) Error() string
func (e *RequestError) Is(target error) bool
func (e *RequestError) MarshalJSON() ([]byte, error)
func (e *RequestError) UnmarshalJSON(data []byte) error
func (e *RequestError) Unwrap() error
type GetMetadataRequest
func (GetMetadataRequest) Command() Command
type GetMetadataResponse
func (resp *GetMetadataResponse) HasCapability(capability Capability) bool
func (resp *GetMetadataResponse) SupportsContract(ver string) bool
func (resp *GetMetadataResponse) Validate() error
type DescribeKeyRequest
func (DescribeKeyRequest) Command() Command
type DescribeKeyResponse
type GenerateSignatureRequest
func (GenerateSignatureRequest) Command() Command
type GenerateSignatureResponse
type GenerateEnvelopeRequest
func (GenerateEnvelopeRequest) Command() Command
type GenerateEnvelopeResponse
type VerifySignatureRequest
func (VerifySignatureRequest) Command() Command
type VerifySignatureResponse
type CriticalAttributes
type KeySpec string
const (
KeySpecRSA2048 KeySpec = "RSA-2048"
KeySpecRSA3072 KeySpec = "RSA-3072"
KeySpecRSA4096 KeySpec = "RSA-4096"
KeySpecEC256 KeySpec = "EC-256"
KeySpecEC384 KeySpec = "EC-384"
KeySpecEC521 KeySpec = "EC-521"
)
func EncodeKeySpec(k signature.KeySpec) (KeySpec, error)
func DecodeKeySpec(k KeySpec) (signature.KeySpec, error)
type HashAlgorithm string
const (
HashAlgorithmSHA256 = "SHA-256"
HashAlgorithmSHA384 = "SHA-384"
HashAlgorithmSHA512 = "SHA-512"
)
func HashAlgorithmFromKeySpec(k signature.KeySpec) (HashAlgorithm, error)
type SignatureAlgorithm string
const (
SignatureAlgorithmECDSA_SHA256 = "ECDSA-SHA-256"
SignatureAlgorithmECDSA_SHA384 = "ECDSA-SHA-384"
SignatureAlgorithmECDSA_SHA512 = "ECDSA-SHA-512"
SignatureAlgorithmRSASSA_PSS_SHA256 = "RSASSA-PSS-SHA-256"
SignatureAlgorithmRSASSA_PSS_SHA384 = "RSASSA-PSS-SHA-384"
SignatureAlgorithmRSASSA_PSS_SHA512 = "RSASSA-PSS-SHA-512"
)
func EncodeSigningAlgorithm(alg signature.Algorithm) (SignatureAlgorithm, error)
func DecodeSigningAlgorithm(raw SignatureAlgorithm) (signature.Algorithm, error)
const (
CapabilitySignatureGenerator Capability = "SIGNATURE_GENERATOR.RAW"
CapabilityEnvelopeGenerator Capability = "SIGNATURE_GENERATOR.ENVELOPE"
CapabilityTrustedIdentityVerifier = Capability(VerificationCapabilityTrustedIdentity)
CapabilityRevocationCheckVerifier = Capability(VerificationCapabilityRevocationCheck)
)
type Capability
func (c Capability) In(capabilities []Capability) bool
type ErrorCode string
const (
ErrorCodeValidation ErrorCode = "VALIDATION_ERROR"
ErrorCodeUnsupportedContractVersion ErrorCode = "UNSUPPORTED_CONTRACT_VERSION"
ErrorCodeAccessDenied ErrorCode = "ACCESS_DENIED"
ErrorCodeTimeout ErrorCode = "TIMEOUT"
ErrorCodeThrottled ErrorCode = "THROTTLED"
ErrorCodeGeneric ErrorCode = "ERROR"
)
type Signature
type SigningScheme
type TrustPolicy
type VerificationCapability
type VerificationResult
```
## package `plugin/manager`
```go
type Manager
func New(roots ...string) *Manager
func (mgr *Manager) Get(ctx context.Context, name string) (*Plugin, error)
func (mgr *Manager) List(ctx context.Context) ([]*Plugin, error)
func (mgr *Manager) Runner(name string) (plugin.Runner, error)
type Plugin
```
Merge to `plugin`
## package `dir`
``` go
ConfigFile = "config.json"
LocalCertificateExtension = ".crt"
LocalKeyExtension = ".key"
LocalKeysDir = "localkeys"
SignatureExtension = ".sig"
SignatureStoreDirName = "signatures"
SigningKeysFile = "signingkeys.json"
TrustPolicyFile = "trustpolicy.json"
TrustStoreDir = "truststore"
type PathManager struct {
ConfigFS UnionDirFS
LibexecFS UnionDirFS
UserConfigFS UnionDirFS
}
func (p *PathManager) Config() string
func (p *PathManager) Localkey(name string) (keyPath, certPath string)
func (p *PathManager) SigningKeyConfig() string
func (p *PathManager) TrustPolicy() string
func (p *PathManager) X509TrustStore(prefix, namedStore string) string
type RootedFS
func NewRootedFS(root string, fsys fs.FS) RootedFS
type UnionDirFS
func NewUnionDirFS(rootedFsys ...RootedFS) UnionDirFS
func (u unionDirFS) Open(name string) (fs.File, error)
func (u unionDirFS) GetPath(elem ...string) (string, error)
func (u unionDirFS) ReadDir(name string) ([]fs.DirEntry, error)
func PluginFS(dirs ...string) UnionDirFS
```
Directory Structure consists of a file system (FS) and several paths.
```go
// The relative path to {NOTATION_CONFIG}
const (
PathConfigFile = "config.json"
PathSigningKeys = "signingkeys.json"
PathTrustPolicy = "trustpolicy.json"
)
func LocalKeyPath(name string) (keyPath, certPath string)
func X509TrustStoreDir(items... string) string
var (
UserConfigDir string // Aboslute path of user level {NOTATION_CONFIG}
UserLibexecDir string // Aboslute path of user level {NOTATION_LIBEXEC}
)
// ConfigFS and PluginFS can be an union fs
func ConfigFS() SysFS
func PluginFS() SysFS
type SysFS interface {
fs.FS
SysPath(items ...string) (string, error)
}
func NewSysFS(roots ...string) SysFS
```
## package `config`
Functions and types:
```go
type CertificateReference
func (c CertificateReference) Is(name string) bool
type Config
func LoadConfig() (*Config, error)
func NewConfig() *Config
func (c *Config) Save() error
type ExternalKey
type KeySuite
func (k KeySuite) Is(name string) bool
type SigningKeys
func LoadSigningKeys() (*SigningKeys, error)
func NewSigningKeys() *SigningKeys
func (s *SigningKeys) Save() error
type VerificationCertificates
type X509KeyPair
```
Refactor:
```go
type Config struct
func LoadConfig() (*Config, error)
func NewConfig() *Config
func (c *Config) Save() error
func (c *Config) SaveToFile(path string) error
type SigningKeys struct
func LoadSigningKeys() (*SigningKeys, error)
func NewSigningKeys() *SigningKeys
func (s *SigningKeys) Save() error
func (s *SigningKeys) SaveToFile(path string) error
type KeySuite
func (k KeySuite) Is(name string) bool
type X509KeyPair
type ExternalKey
```
## package `log`
```go
package log
type contextKey int
// loggerKey is the associated key type for logger entry in context.
const loggerKey contextKey = iota
// Logger is implemented by users and/or 3rd party loggers.
// For example, uber/zap/SugaredLogger and sirupsen/logrus/Logger.
type Logger interface {
Debug(args ...interface{})
Debugf(format string, args ...interface{})
Debugln(args ...interface{})
Info(args ...interface{})
Infof(format string, args ...interface{})
Infoln(args ...interface{})
Warn(args ...interface{})
Warnf(format string, args ...interface{})
Warnln(args ...interface{})
Error(args ...interface{})
Errorf(format string, args ...interface{})
Errorln(args ...interface{})
}
// WithLogger is used by callers to set the Logger in the context.
func WithLogger(ctx context.Context, logger Logger) context.Context {
return context.WithValue(ctx, loggerKey, logger)
}
// GetLogger is used to retrieve the Logger from the context.
func GetLogger(ctx context.Context) Logger {
if logger, ok := ctx.Value(loggerKey).(Logger); ok {
return logger
}
return &discardLogger{}
}
type discardLogger struct{}
```