github.com/paketo-buildpacks/spring-boot/v5@v5.29.2-0.20240515125826-896d9a009549/cmd/main/unpack_test.go (about) 1 package main 2 3 import ( 4 "archive/zip" 5 "fmt" 6 "github.com/magiconair/properties" 7 "github.com/paketo-buildpacks/libjvm" 8 "github.com/paketo-buildpacks/libpak/sherpa" 9 "gopkg.in/yaml.v3" 10 "io" 11 "log" 12 "os" 13 "path/filepath" 14 "strings" 15 "text/template" 16 "time" 17 ) 18 19 // This file was a place to experiment with zipping / unzipping in a proper Jar fashion 20 // Not an actual test file 21 22 const createdBy = "17.9.9 (Spring Boot Paketo Buildpack)" 23 24 func HelloName() { 25 26 const originalJarBasename = "demo-0.0.1-SNAPSHOT.jar" 27 const originalJarFullPath = "~/workspaces/paketo-buildpacks/samples/java/gradle/build/libs/" + originalJarBasename 28 const targetUnpackedDirectory = "~/workspaces/paketo-buildpacks/spring-boot/unpacked" 29 originalJarExplodedDirectory, _ := os.CreateTemp("", "unpack") 30 31 os.RemoveAll(originalJarExplodedDirectory.Name() + "/") 32 //Unzip(originalJarFullPath, originalJarExplodedDirectory.Name()) 33 os.MkdirAll(targetUnpackedDirectory+"/application", 0755) 34 os.MkdirAll(targetUnpackedDirectory+"/dependencies", 0755) 35 Zip(targetUnpackedDirectory+"/application/"+originalJarBasename, originalJarExplodedDirectory.Name()+"/BOOT-INF/classes/", true) 36 37 tempDirectory := fmt.Sprint(time.Now().UnixMilli()) + "/" 38 os.MkdirAll(os.TempDir()+tempDirectory+"/META-INF/", 0755) 39 runAppJarManifest, _ := os.Create(os.TempDir() + tempDirectory + "/META-INF/MANIFEST.MF") 40 writeRunAppJarManifest(originalJarExplodedDirectory.Name(), runAppJarManifest, "application/"+originalJarBasename) 41 Zip(targetUnpackedDirectory+"/run-app.jar", os.TempDir()+tempDirectory, false) 42 sherpa.CopyDir(originalJarExplodedDirectory.Name()+"/BOOT-INF/lib/", targetUnpackedDirectory+"/dependencies/") 43 44 } 45 46 //func archiveWithFastZip(source, target string) { 47 // // Create archiveWithFastZip file 48 // w, err := os.Create(target) 49 // if err != nil { 50 // panic(err) 51 // } 52 // defer w.Close() 53 // 54 // // Create new Archiver 55 // //var options ArchiverOption = nil 56 // a, err := fastzip.NewArchiver(w, source, fastzip.WithArchiverMethod(zip.Deflate)) 57 // if err != nil { 58 // panic(err) 59 // } 60 // defer a.Close() 61 // 62 // // Register a non-default level compressor if required 63 // //a.RegisterCompressor(zip.Deflate, fastzip.FlateCompressor(1)) 64 // 65 // // Walk directory, adding the files we want to add 66 // files := make(map[string]os.FileInfo) 67 // err = filepath.Walk(source, func(pathname string, info os.FileInfo, err error) error { 68 // if info.IsDir() { 69 // return nil 70 // } 71 // files[pathname] = info 72 // return nil 73 // }) 74 // 75 // // Archive 76 // if err = a.Archive(context.Background(), files); err != nil { 77 // panic(err) 78 // } 79 //} 80 // 81 //func archiveWithArchiver(source, target string) { 82 // 83 // walkedFiles := make(map[string]string) 84 // _ = filepath.Walk(source, func(pathname string, info os.FileInfo, err error) error { 85 // if info.IsDir() { 86 // return nil 87 // } 88 // walkedFiles[pathname], _ = filepath.Rel(filepath.Dir(source), pathname) 89 // return nil 90 // }) 91 // 92 // files, _ := archiver.FilesFromDisk(nil, walkedFiles) 93 // out, _ := os.Create(target) 94 // defer out.Close() 95 // 96 // format := archiver.Zip{ 97 // SelectiveCompression: false, 98 // Compression: zip.Store, 99 // ContinueOnError: false, 100 // TextEncoding: "UTF8", 101 // } 102 // format.Archive(context.Background(), out, files) 103 // 104 //} 105 106 //func TestZip(t *testing.T) { 107 // boot.CreateJar("~/workspaces/spring-cds-demo/build/libs/unzip/", "~/workspaces/spring-cds-demo/build/libs/spring-cds-demo-1.0.0-SNAPSHOT-rezip.jar") 108 //} 109 110 func zipSource2(source, target string) error { 111 // 1. Create a ZIP file and zip.Writer 112 f, err := os.Create(target) 113 if err != nil { 114 return err 115 } 116 defer f.Close() 117 118 writer := zip.NewWriter(f) 119 defer writer.Close() 120 121 // 2. Go through all the files of the source 122 return filepath.Walk(source, func(path string, info os.FileInfo, err error) error { 123 if err != nil { 124 return err 125 } 126 127 // 3. Create a local file header 128 header, err := zip.FileInfoHeader(info) 129 if err != nil { 130 return err 131 } 132 133 // set compression 134 if strings.HasSuffix(header.Name, ".jar") { 135 header.Method = zip.Store 136 } else { 137 header.Method = zip.Deflate 138 } 139 140 // 4. Set relative path of a file as the header name 141 header.Name, err = filepath.Rel(filepath.Dir(source), path) 142 if err != nil { 143 return err 144 } 145 if info.IsDir() { 146 header.Name += "/" 147 } 148 149 // 5. Create writer for the file header and save content of the file 150 headerWriter, err := writer.CreateHeader(header) 151 if err != nil { 152 return err 153 } 154 155 if info.IsDir() { 156 return nil 157 } 158 159 f, err := os.Open(path) 160 if err != nil { 161 return err 162 } 163 defer f.Close() 164 165 _, err = io.Copy(headerWriter, f) 166 return err 167 }) 168 } 169 170 func zipSource(source, target string) error { 171 // 1. Create a ZIP file and zip.Writer 172 f, err := os.Create(target) 173 if err != nil { 174 return err 175 } 176 defer f.Close() 177 178 writer := zip.NewWriter(f) 179 defer writer.Close() 180 181 // 2. Go through all the files of the source 182 return filepath.Walk(source, func(path string, info os.FileInfo, err error) error { 183 if err != nil { 184 return err 185 } 186 187 // 3. Create a local file header 188 header, err := zip.FileInfoHeader(info) 189 if err != nil { 190 return err 191 } 192 193 // set compression 194 header.Method = zip.Store 195 196 // 4. Set relative path of a file as the header name 197 header.Name, err = filepath.Rel(filepath.Dir(source), path) 198 if err != nil { 199 return err 200 } 201 if info.IsDir() { 202 header.Name += "/" 203 } 204 205 // 5. Create writer for the file header and save content of the file 206 headerWriter, err := writer.CreateHeader(header) 207 if err != nil { 208 return err 209 } 210 211 if info.IsDir() { 212 return nil 213 } 214 215 f, err := os.Open(path) 216 if err != nil { 217 return err 218 } 219 defer f.Close() 220 221 _, err = io.Copy(headerWriter, f) 222 return err 223 }) 224 } 225 226 func writeRunAppJarManifest(originalJarExplodedDirectory string, runAppJarManifest *os.File, relocatedOriginalJar string) { 227 originalManifest, _ := libjvm.NewManifest(originalJarExplodedDirectory) 228 startClassValue, _ := retrieveStartClassValue(originalManifest) 229 classPathValue, _ := retrieveClasspathFromIdx(originalManifest, originalJarExplodedDirectory, "dependencies/", relocatedOriginalJar) 230 231 type Manifest struct { 232 MainClass string 233 ClassPath string 234 CreatedBy string 235 } 236 237 manifestValues := Manifest{startClassValue, rewriteWithMaxLineLength("Class-Path: "+classPathValue, 72), createdBy} 238 tmpl, err := template.New("manifest").Parse("Manifest-Version: 1.0\n" + 239 "Main-Class: {{.MainClass}}\n" + 240 "{{.ClassPath}}\n" + 241 "Created-By: {{.CreatedBy}}\n" + 242 " ") 243 if err != nil { 244 panic(err) 245 } 246 //buf := &bytes.Buffer{} 247 err = tmpl.Execute(runAppJarManifest, manifestValues) 248 if err != nil { 249 panic(err) 250 } 251 252 //reformattedClassPath := 253 //runAppJarManifest.Write([]byte(reformattedClassPath)) 254 } 255 256 func rewriteWithMaxLineLength(s string, length int) string { 257 258 //a := []rune(s) 259 result := "" 260 currentLine := "" 261 indent := 0 262 remainder := "" 263 264 for i, r := range s { 265 currentLine = currentLine + string(r) 266 remainder = remainder + string(r) 267 //fmt.Printf("i%d r %c\n", i, r) 268 j := i + 1 269 if indent > 0 { 270 j = i + 1 + indent 271 } 272 if i > 0 && j%length == 0 { 273 //fmt.Printf("%v\n", currentLine) 274 result = result + currentLine + "\n" 275 currentLine = " " 276 indent = indent + 1 277 remainder = " " 278 } 279 } 280 result = result + remainder 281 //fmt.Printf("%v\n", remainder) 282 return result 283 } 284 func retrieveClasspathFromIdx(manifest *properties.Properties, dir string, relocatedDir string, relocatedOriginalJar string) (string, error) { 285 classpathIdx, ok := manifest.Get("Spring-Boot-Classpath-Index") 286 if !ok { 287 return "", fmt.Errorf("manifest does not contain Spring-Boot-Classpath-Index") 288 } 289 290 file := filepath.Join(dir, classpathIdx) 291 in, err := os.Open(filepath.Join(dir, classpathIdx)) 292 if err != nil { 293 return "", fmt.Errorf("unable to open %s\n%w", file, err) 294 } 295 defer in.Close() 296 297 var libs []string 298 if err := yaml.NewDecoder(in).Decode(&libs); err != nil { 299 return "", fmt.Errorf("unable to decode %s\n%w", file, err) 300 } 301 302 var relocatedLibs []string 303 relocatedLibs = append(relocatedLibs, relocatedOriginalJar) 304 for _, lib := range libs { 305 relocatedLibs = append(relocatedLibs, strings.ReplaceAll(lib, "BOOT-INF/lib/", relocatedDir)) 306 } 307 308 return strings.Join(relocatedLibs, " "), nil 309 } 310 311 func retrieveStartClassValue(manifest *properties.Properties) (string, error) { 312 startClass, ok := manifest.Get("Start-Class") 313 if !ok { 314 return "", fmt.Errorf("no Start-Class foudn int he manifest, are you sure the jar it was built with Spring Boot") 315 } else { 316 return startClass, nil 317 } 318 319 } 320 321 func Zip(archivePath string, folderPath string, create bool) { 322 os.Chdir(folderPath) 323 if create { 324 CreateEmptyManifest() 325 } 326 327 file, err := os.Create(archivePath) 328 if err != nil { 329 panic(err) 330 } 331 defer file.Close() 332 333 w := zip.NewWriter(file) 334 defer w.Close() 335 336 walker := func(path string, info os.FileInfo, err error) error { 337 path = strings.TrimPrefix(path, folderPath) 338 339 fmt.Printf("Crawling: %#v\n", path) 340 if err != nil { 341 return err 342 } 343 if info.IsDir() { 344 var err error 345 if path != "" { 346 _, err = w.Create(path) 347 } 348 if err != nil { 349 return err 350 } 351 return nil 352 } 353 file, err := os.Open(path) 354 if err != nil { 355 return err 356 } 357 defer file.Close() 358 359 f, err := w.Create(path) 360 if err != nil { 361 return err 362 } 363 364 _, err = io.Copy(f, file) 365 if err != nil { 366 return err 367 } 368 369 return nil 370 } 371 err = filepath.Walk(folderPath, walker) 372 373 //f, err := w.Create("META-INF/MANIFEST.MF") 374 //_, err = io.Copy(f, file) 375 376 if err != nil { 377 panic(err) 378 } 379 380 } 381 382 func CreateEmptyManifest() (*os.File, error) { 383 // Create a temporary file 384 err := os.Mkdir("META-INF", 0755) 385 if err != nil { 386 log.Fatal(err) 387 } 388 file, err := os.Create("META-INF/MANIFEST.MF") 389 if err != nil { 390 log.Fatal(err) 391 } 392 defer file.Close() 393 fmt.Println(file.Name()) 394 395 // Write some text to the file 396 manifestContent := 397 `Manifest-Version: 1.0 398 Created-By: 17.0.8 (Spring Boot Paketo Buildpack) 399 ` 400 _, err = file.WriteString(manifestContent) 401 if err != nil { 402 fmt.Println(err) 403 return nil, err 404 } 405 406 // Close the file 407 err = file.Close() 408 if err != nil { 409 fmt.Println(err) 410 return nil, err 411 } 412 fmt.Println("The temporary file is created:", file.Name()) 413 return file, err 414 } 415 416 // heavily inspired by https://stackoverflow.com/a/58192644/24069 417 418 func resetAllFilesMtimeAndATime(root string, date time.Time) ([]string, error) { 419 println("Entering resetAllFIles") 420 var files []string 421 err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 422 if !info.IsDir() { 423 println(path) 424 file, err := os.Open(path) 425 if err != nil { 426 log.Printf("Could not open file: %s", path) 427 } 428 sherpa.CopyFile(file, fmt.Sprintf("%s.bak", path)) 429 430 if err := os.Chtimes(path, date, date); err != nil { 431 log.Printf("Could not update atime and mtime for %s\n", fmt.Sprintf("%s.bak", path)) 432 } 433 os.Remove(path) 434 os.Rename(fmt.Sprintf("%s.bak", path), path) 435 files = append(files, path) 436 } 437 return nil 438 }) 439 return files, err 440 }