github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/cmd/termination/termination.go (about)

     1  package termination
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"os"
     9  	"path"
    10  
    11  	log "github.com/authzed/spicedb/internal/logging"
    12  	"github.com/authzed/spicedb/pkg/spiceerrors"
    13  
    14  	"github.com/jzelinskie/cobrautil/v2"
    15  	"github.com/spf13/cobra"
    16  	flag "github.com/spf13/pflag"
    17  )
    18  
    19  const (
    20  	terminationLogFlagName  = "termination-log-path"
    21  	kubeTerminationLogLimit = 4096
    22  )
    23  
    24  // PublishError returns a new wrapping cobra run function that executes the provided argument runFunc, and
    25  // writes to disk an error returned by the latter if it is of type termination.TerminationError.
    26  func PublishError(runFunc cobrautil.CobraRunFunc) cobrautil.CobraRunFunc {
    27  	return func(cmd *cobra.Command, args []string) error {
    28  		runFuncErr := runFunc(cmd, args)
    29  		var termErr spiceerrors.TerminationError
    30  		if runFuncErr != nil && errors.As(runFuncErr, &termErr) {
    31  			ctx := context.Background()
    32  			if cmd.Context() != nil {
    33  				ctx = cmd.Context()
    34  			}
    35  			terminationLogPath := cobrautil.MustGetString(cmd, terminationLogFlagName)
    36  			if terminationLogPath == "" {
    37  				return runFuncErr
    38  			}
    39  			bytes, err := json.Marshal(termErr)
    40  			if err != nil {
    41  				log.Ctx(ctx).Error().Err(fmt.Errorf("unable to marshall termination log: %w", err)).Msg("failed to report termination log")
    42  				return runFuncErr
    43  			}
    44  
    45  			if len(bytes) > kubeTerminationLogLimit {
    46  				log.Ctx(ctx).Warn().Msg("termination log exceeds 4096 bytes limit, metadata will be truncated")
    47  				termErr.Metadata = nil
    48  				bytes, err = json.Marshal(termErr)
    49  				if err != nil {
    50  					return runFuncErr
    51  				}
    52  			}
    53  
    54  			if _, err := os.Stat(path.Dir(terminationLogPath)); os.IsNotExist(err) {
    55  				mkdirErr := os.MkdirAll(path.Dir(terminationLogPath), 0o700) // Create your file
    56  				if mkdirErr != nil {
    57  					log.Ctx(ctx).Error().Err(fmt.Errorf("unable to create directory for termination log: %w", err)).Msg("failed to report termination log")
    58  					return runFuncErr
    59  				}
    60  			}
    61  			if err := os.WriteFile(terminationLogPath, bytes, 0o600); err != nil {
    62  				log.Ctx(ctx).Error().Err(fmt.Errorf("unable to write terminationlog file: %w", err)).Msg("failed to report termination log")
    63  			}
    64  		}
    65  		return runFuncErr
    66  	}
    67  }
    68  
    69  // RegisterFlags registers the termination log flag
    70  func RegisterFlags(flagset *flag.FlagSet) {
    71  	flagset.String(terminationLogFlagName,
    72  		"",
    73  		"define the path to the termination log file, which contains a JSON payload to surface as reason for termination - disabled by default",
    74  	)
    75  }