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  }