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 }