github.com/aclaygray/packer@v1.3.2/post-processor/vagrant/util.go (about) 1 package vagrant 2 3 import ( 4 "archive/tar" 5 "compress/flate" 6 "encoding/json" 7 "fmt" 8 "io" 9 "log" 10 "os" 11 "path/filepath" 12 "runtime" 13 14 "github.com/hashicorp/packer/packer" 15 "github.com/klauspost/pgzip" 16 ) 17 18 var ( 19 // ErrInvalidCompressionLevel is returned when the compression level passed 20 // to gzip is not in the expected range. See compress/flate for details. 21 ErrInvalidCompressionLevel = fmt.Errorf( 22 "Invalid compression level. Expected an integer from -1 to 9.") 23 ) 24 25 // Copies a file by copying the contents of the file to another place. 26 func CopyContents(dst, src string) error { 27 srcF, err := os.Open(src) 28 if err != nil { 29 return err 30 } 31 defer srcF.Close() 32 33 dstDir, _ := filepath.Split(dst) 34 if dstDir != "" { 35 err := os.MkdirAll(dstDir, 0755) 36 if err != nil { 37 return err 38 } 39 } 40 41 dstF, err := os.Create(dst) 42 if err != nil { 43 return err 44 } 45 defer dstF.Close() 46 47 if _, err := io.Copy(dstF, srcF); err != nil { 48 return err 49 } 50 51 return nil 52 } 53 54 // Creates a (hard) link to a file, ensuring that all parent directories also exist. 55 func LinkFile(dst, src string) error { 56 dstDir, _ := filepath.Split(dst) 57 if dstDir != "" { 58 err := os.MkdirAll(dstDir, 0755) 59 if err != nil { 60 return err 61 } 62 } 63 64 if err := os.Link(src, dst); err != nil { 65 return err 66 } 67 68 return nil 69 } 70 71 // DirToBox takes the directory and compresses it into a Vagrant-compatible 72 // box. This function does not perform checks to verify that dir is 73 // actually a proper box. This is an expected precondition. 74 func DirToBox(dst, dir string, ui packer.Ui, level int) error { 75 log.Printf("Turning dir into box: %s => %s", dir, dst) 76 77 // Make the containing directory, if it does not already exist 78 err := os.MkdirAll(filepath.Dir(dst), 0755) 79 if err != nil { 80 return err 81 } 82 83 dstF, err := os.Create(dst) 84 if err != nil { 85 return err 86 } 87 defer dstF.Close() 88 89 var dstWriter io.WriteCloser = dstF 90 if level != flate.NoCompression { 91 log.Printf("Compressing with gzip compression level: %d", level) 92 gzipWriter, err := makePgzipWriter(dstWriter, level) 93 if err != nil { 94 return err 95 } 96 defer gzipWriter.Close() 97 98 dstWriter = gzipWriter 99 } 100 101 tarWriter := tar.NewWriter(dstWriter) 102 defer tarWriter.Close() 103 104 // This is the walk func that tars each of the files in the dir 105 tarWalk := func(path string, info os.FileInfo, prevErr error) error { 106 // If there was a prior error, return it 107 if prevErr != nil { 108 return prevErr 109 } 110 111 // Skip directories 112 if info.IsDir() { 113 log.Printf("Skipping directory '%s' for box '%s'", path, dst) 114 return nil 115 } 116 117 log.Printf("Box add: '%s' to '%s'", path, dst) 118 f, err := os.Open(path) 119 if err != nil { 120 return err 121 } 122 defer f.Close() 123 124 header, err := tar.FileInfoHeader(info, "") 125 if err != nil { 126 return err 127 } 128 129 // go >=1.10 wants to use GNU tar format to workaround issues in 130 // libarchive < 3.3.2 131 setHeaderFormat(header) 132 133 // We have to set the Name explicitly because it is supposed to 134 // be a relative path to the root. Otherwise, the tar ends up 135 // being a bunch of files in the root, even if they're actually 136 // nested in a dir in the original "dir" param. 137 header.Name, err = filepath.Rel(dir, path) 138 if err != nil { 139 return err 140 } 141 142 if ui != nil { 143 ui.Message(fmt.Sprintf("Compressing: %s", header.Name)) 144 } 145 146 if err := tarWriter.WriteHeader(header); err != nil { 147 return err 148 } 149 150 if _, err := io.Copy(tarWriter, f); err != nil { 151 return err 152 } 153 154 return nil 155 } 156 157 // Tar.gz everything up 158 return filepath.Walk(dir, tarWalk) 159 } 160 161 // WriteMetadata writes the "metadata.json" file for a Vagrant box. 162 func WriteMetadata(dir string, contents interface{}) error { 163 if _, err := os.Stat(filepath.Join(dir, "metadata.json")); os.IsNotExist(err) { 164 f, err := os.Create(filepath.Join(dir, "metadata.json")) 165 if err != nil { 166 return err 167 } 168 defer f.Close() 169 170 enc := json.NewEncoder(f) 171 return enc.Encode(contents) 172 } 173 174 return nil 175 } 176 177 func makePgzipWriter(output io.WriteCloser, compressionLevel int) (io.WriteCloser, error) { 178 gzipWriter, err := pgzip.NewWriterLevel(output, compressionLevel) 179 if err != nil { 180 return nil, ErrInvalidCompressionLevel 181 } 182 gzipWriter.SetConcurrency(500000, runtime.GOMAXPROCS(-1)) 183 return gzipWriter, nil 184 }