github.com/verrazzano/verrazzano@v1.7.1/tools/vz/cmd/sanitize/sanitize.go (about)

     1  // Copyright (c) 2024, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package sanitize
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"io/fs"
    10  	"os"
    11  	"strings"
    12  
    13  	"github.com/spf13/cobra"
    14  	"github.com/verrazzano/verrazzano/pkg/files"
    15  	cmdhelpers "github.com/verrazzano/verrazzano/tools/vz/cmd/helpers"
    16  	"github.com/verrazzano/verrazzano/tools/vz/pkg/constants"
    17  	"github.com/verrazzano/verrazzano/tools/vz/pkg/helpers"
    18  )
    19  
    20  const (
    21  	CommandName = "sanitize"
    22  	helpShort   = "Sanitize information from a directory or tar file"
    23  	helpLong    = "This command sanitizes information from an existing directory or tar file and outputs it into a directory or tar.gz file of your choosing. The results of the sanitization should still be checked by the customer before sending them to support."
    24  )
    25  
    26  type flagValidation struct {
    27  	inputDirectory  string
    28  	inputTarFile    string
    29  	outputDirectory string
    30  	outputTarGZFile string
    31  }
    32  
    33  // NewCmdSanitize creates a sanitize command with the appropriate arguments and makes the command hidden
    34  func NewCmdSanitize(vzHelper helpers.VZHelper) *cobra.Command {
    35  	cmd := cmdhelpers.NewCommand(vzHelper, CommandName, helpShort, helpLong)
    36  	cmd.Hidden = true
    37  
    38  	cmd.RunE = func(cmd *cobra.Command, args []string) error {
    39  		return runCmdSanitize(cmd, args, vzHelper)
    40  	}
    41  
    42  	cmd.PersistentFlags().String(constants.InputDirectoryFlagName, constants.InputDirectoryFlagValue, constants.InputDirectoryFlagUsage)
    43  	cmd.PersistentFlags().String(constants.OutputDirectoryFlagName, constants.OutputDirectoryFlagValue, constants.OutputDirectoryFlagUsage)
    44  	cmd.PersistentFlags().String(constants.InputTarFileFlagName, constants.InputTarFileFlagValue, constants.InputTarFileFlagUsage)
    45  	cmd.PersistentFlags().String(constants.OutputTarGZFileFlagName, constants.OutputTarGZFileFlagValue, constants.OutputTarGZFileFlagUsage)
    46  	cmd.PersistentFlags().String(constants.RedactedValuesFlagName, constants.RedactedValuesFlagValue, constants.RedactedValuesFlagUsage)
    47  
    48  	// Verifies that the CLI args are not set at the creation of a command
    49  	vzHelper.VerifyCLIArgsNil(cmd)
    50  
    51  	return cmd
    52  }
    53  
    54  // runCmdSanitize runs a sanitize command which takes an input directory or tar file to sanitize and an output directory or tar.gz file to place the sanitized files
    55  func runCmdSanitize(cmd *cobra.Command, args []string, vzHelper helpers.VZHelper) error {
    56  	validatedStruct, err := parseInputAndOutputFlags(cmd, vzHelper)
    57  	if err != nil {
    58  		return err
    59  	}
    60  	if validatedStruct.inputDirectory == "" {
    61  		//This is the case where only the tar string is specified, so a temporary directory is made to untar it into
    62  		validatedStruct.inputDirectory, err = os.MkdirTemp("", constants.SanitizeDirInput)
    63  		if err != nil {
    64  			return err
    65  		}
    66  		defer os.RemoveAll(validatedStruct.inputDirectory)
    67  		file, err := os.Open(validatedStruct.inputTarFile)
    68  		defer file.Close()
    69  		if err != nil {
    70  			return fmt.Errorf("an error occurred when trying to open %s: %s", validatedStruct.inputTarFile, err.Error())
    71  		}
    72  		err = helpers.UntarArchive(validatedStruct.inputDirectory, file)
    73  		if err != nil {
    74  			return fmt.Errorf("an error occurred while trying to untar %s: %s", validatedStruct.inputTarFile, err.Error())
    75  		}
    76  
    77  	}
    78  	// If an output directory is not specified, create a temporary output directory that can be used to tar the sanitize files
    79  	if validatedStruct.outputDirectory == "" {
    80  		validatedStruct.outputDirectory, err = os.MkdirTemp("", constants.SanitizeDirOutput)
    81  		if err != nil {
    82  			return err
    83  		}
    84  		defer os.RemoveAll(validatedStruct.outputDirectory)
    85  	}
    86  	if err = sanitizeDirectory(*validatedStruct); err != nil {
    87  		return err
    88  	}
    89  
    90  	// Process the redacted values file flag.
    91  	redactionFilePath, err := cmd.PersistentFlags().GetString(constants.RedactedValuesFlagName)
    92  	if err != nil {
    93  		return fmt.Errorf(constants.FlagErrorMessage, constants.RedactedValuesFlagName, err.Error())
    94  	}
    95  	if redactionFilePath != "" {
    96  		// Create the redaction map file if the user provides a non-empty file path.
    97  		if err := helpers.WriteRedactionMapFile(redactionFilePath, nil); err != nil {
    98  			return fmt.Errorf(constants.RedactionMapCreationError, redactionFilePath, err.Error())
    99  		}
   100  	}
   101  	return nil
   102  }
   103  
   104  // parseInputAndOutputFlags validates the directory and tar file flags along with checking that the directory flag and the tar file are not both specified
   105  func parseInputAndOutputFlags(cmd *cobra.Command, vzHelper helpers.VZHelper) (*flagValidation, error) {
   106  	inputDirectory, err := cmd.PersistentFlags().GetString(constants.InputDirectoryFlagName)
   107  	if err != nil {
   108  		return nil, fmt.Errorf(constants.FlagErrorMessage, constants.InputDirectoryFlagName, err.Error())
   109  	}
   110  	inputTarFileString, err := cmd.PersistentFlags().GetString(constants.InputTarFileFlagName)
   111  	if err != nil {
   112  		return nil, fmt.Errorf(constants.FlagErrorMessage, constants.InputTarFileFlagName, err.Error())
   113  	}
   114  	if inputDirectory != "" && inputTarFileString != "" {
   115  		return nil, fmt.Errorf("an input directory and an input tar file cannot be both specified")
   116  	}
   117  	if inputDirectory == "" && inputTarFileString == "" {
   118  		return nil, fmt.Errorf("an input directory or an input tar file must be specified")
   119  	}
   120  	outputDirectory, err := cmd.PersistentFlags().GetString(constants.OutputDirectoryFlagName)
   121  	if err != nil {
   122  		return nil, fmt.Errorf(constants.FlagErrorMessage, constants.OutputDirectoryFlagName, err.Error())
   123  	}
   124  	outputTarGZFileString, err := cmd.PersistentFlags().GetString(constants.OutputTarGZFileFlagName)
   125  	if err != nil {
   126  		return nil, fmt.Errorf(constants.FlagErrorMessage, constants.OutputTarGZFileFlagName, err.Error())
   127  	}
   128  	if outputDirectory != "" && outputTarGZFileString != "" {
   129  		return nil, fmt.Errorf("an output directory and an output tar.gz file cannot be specified")
   130  	}
   131  	if outputDirectory == "" && outputTarGZFileString == "" {
   132  		return nil, fmt.Errorf("an output directory or an output tar.gz file must be specified")
   133  	}
   134  	return &flagValidation{inputDirectory: inputDirectory, inputTarFile: inputTarFileString, outputDirectory: outputDirectory, outputTarGZFile: outputTarGZFileString}, nil
   135  }
   136  
   137  // sanitizeDirectory sanitizes all the files in a directory, outputs the sanitized files to a separate directory, and tars the sanitized directory if necessary
   138  func sanitizeDirectory(validation flagValidation) error {
   139  	listOfFilesToSanitize, err := files.GetAllDirectoriesAndFiles(validation.inputDirectory)
   140  	if !(strings.HasSuffix(validation.inputDirectory, string(os.PathSeparator))) {
   141  		validation.inputDirectory = validation.inputDirectory + string(os.PathSeparator)
   142  	}
   143  	if !(strings.HasSuffix(validation.outputDirectory, string(os.PathSeparator))) {
   144  		validation.outputDirectory = validation.outputDirectory + string(os.PathSeparator)
   145  	}
   146  	if _, err := os.Stat(validation.outputDirectory); errors.Is(err, os.ErrNotExist) {
   147  		os.Mkdir(validation.outputDirectory, 0700)
   148  	}
   149  	if err != nil {
   150  		return err
   151  	}
   152  	for i := range listOfFilesToSanitize {
   153  		fileInfo, err := os.Stat(listOfFilesToSanitize[i])
   154  		if err != nil {
   155  			return err
   156  		}
   157  		fileMode := fileInfo.Mode()
   158  		isDir := fileInfo.IsDir()
   159  		if isMetadataFile(fileInfo.Name(), isDir) {
   160  			continue
   161  		}
   162  		err = sanitizeFileAndWriteItToOutput(validation, isDir, listOfFilesToSanitize[i], fileMode)
   163  		if err != nil {
   164  			return err
   165  		}
   166  
   167  	}
   168  	if validation.outputTarGZFile != "" {
   169  		tarGZFileForOutput, err := os.Create(validation.outputTarGZFile)
   170  		defer tarGZFileForOutput.Close()
   171  		if err != nil {
   172  			return err
   173  		}
   174  		if err = helpers.CreateReportArchive(validation.outputDirectory, tarGZFileForOutput, false); err != nil {
   175  			return err
   176  		}
   177  	}
   178  	return nil
   179  }
   180  
   181  // sanitizeFileAndWriteItToOutput sanitizes a file and writes the sanitized file to its corresponding path in a separate directory
   182  func sanitizeFileAndWriteItToOutput(validation flagValidation, isDirectory bool, fileToSanitizePath string, fileMode fs.FileMode) error {
   183  	pathOfSanitizedFileOrDirectoryForOutput := strings.ReplaceAll(fileToSanitizePath, validation.inputDirectory, validation.outputDirectory)
   184  	if isDirectory {
   185  		err := os.MkdirAll(pathOfSanitizedFileOrDirectoryForOutput, fileMode)
   186  		return err
   187  	}
   188  	unsanitizedFileBytes, err := os.ReadFile(fileToSanitizePath)
   189  	if err != nil {
   190  		return err
   191  	}
   192  	notSanitizedFileString := string(unsanitizedFileBytes)
   193  	sanitizedFileString := helpers.SanitizeString(notSanitizedFileString, nil)
   194  	sanitizedFileStringAsBytes := []byte(sanitizedFileString)
   195  
   196  	err = os.WriteFile(pathOfSanitizedFileOrDirectoryForOutput, sanitizedFileStringAsBytes, fileMode)
   197  	return err
   198  }
   199  
   200  // isMetadataFile determines if a file in a tar archive fits the format of Mac Metadata
   201  func isMetadataFile(fileBaseName string, isDir bool) bool {
   202  	return strings.HasPrefix(fileBaseName, "._") && !isDir
   203  }