-
Notifications
You must be signed in to change notification settings - Fork 21
Expand file tree
/
Copy pathjava.go
More file actions
142 lines (129 loc) · 4.66 KB
/
Copy pathjava.go
File metadata and controls
142 lines (129 loc) · 4.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package java contains Java buildpack library code.
package java
import (
"archive/zip"
"fmt"
"io"
"io/ioutil"
"path/filepath"
"regexp"
"strings"
"time"
gcp "github.com/GoogleCloudPlatform/buildpacks/pkg/gcpbuildpack"
"github.com/buildpacks/libcnb"
)
const (
dateFormat = time.RFC3339Nano
// repoExpiration is an arbitrary amount of time of 10 weeks to refresh the cache layer.
// TODO(b/148099877): Investigate proper cache-clearing strategy.
repoExpiration = time.Duration(time.Hour * 24 * 7 * 10)
// ManifestPath specifies the path of MANIFEST.MF relative to the working directory.
ManifestPath = "META-INF/MANIFEST.MF"
expiryTimestampKey = "expiry_timestamp"
)
var (
// re matches lines in the manifest for a Main-Class entry to detect which jar is appropriate for execution. For some reason, it does not like `(?m)^Main-Class: [^\s]+`.
re = regexp.MustCompile("(?m)^Main-Class: [^\r\n\t\f\v ]+")
// jarPaths contains the paths that we search for executable jar files. Order of paths decides precedence.
jarPaths = [][]string{
[]string{"target"},
[]string{"build"},
[]string{"build", "libs"},
// An empty file path searches the application root for jars.
[]string{},
}
)
// ExecutableJar looks for the jar with a Main-Class manifest. If there is not exactly 1 of these jars, throw an error.
func ExecutableJar(ctx *gcp.Context) (string, error) {
for i, path := range jarPaths {
path = append([]string{ctx.ApplicationRoot()}, path...)
path = append(path, "*.jar")
jars := ctx.Glob(filepath.Join(path...))
// There may be multiple jars due to some frameworks like Quarkus creating multiple jars,
// so we look for the jar that contains a Main-Class entry in its manifest.
executables := filterExecutables(ctx, jars)
// We've found a path with exactly 1 jar, so return that jar.
if len(executables) == 1 {
return executables[0], nil
} else if len(executables) > 1 {
return "", gcp.UserErrorf("found more than one jar with a Main-Class manifest entry in %s: %v, please specify an entrypoint", jarPaths[i], executables)
}
}
return "", gcp.UserErrorf("did not find any jar files with a Main-Class manifest entry")
}
func filterExecutables(ctx *gcp.Context, jars []string) []string {
var executables []string
for _, jar := range jars {
if hasMain, err := hasMainManifestEntry(jar); err != nil {
ctx.Warnf("Failed to inspect %s, skipping: %v.", jar, err)
} else if hasMain {
executables = append(executables, jar)
}
}
return executables
}
func hasMainManifestEntry(jar string) (bool, error) {
r, err := zip.OpenReader(jar)
if err != nil {
return false, gcp.UserErrorf("unzipping jar %s: %v", jar, err)
}
defer r.Close()
for _, f := range r.File {
if f.Name != ManifestPath {
continue
}
rc, err := f.Open()
if err != nil {
return false, fmt.Errorf("opening file %s in jar %s: %v", f.FileInfo().Name(), jar, err)
}
return hasMain(rc), nil
}
return false, nil
}
func hasMain(r io.Reader) bool {
content, err := ioutil.ReadAll(r)
if err != nil {
return false
}
return re.Match(content)
}
// MainFromManifest returns the main class specified in the manifest at the input path.
func MainFromManifest(ctx *gcp.Context, manifestPath string) (string, error) {
content := ctx.ReadFile(manifestPath)
match := re.Find(content)
if len(match) != 0 {
return strings.TrimPrefix(string(match), "Main-Class: "), nil
}
return "", gcp.UserErrorf("no Main-Class manifest entry found in %s", manifestPath)
}
// CheckCacheExpiration clears the m2 layer and sets a new expiry timestamp when the cache is past expiration.
func CheckCacheExpiration(ctx *gcp.Context, m2CachedRepo *libcnb.Layer) {
t := time.Now()
expiry := ctx.GetMetadata(m2CachedRepo, expiryTimestampKey)
if expiry != "" {
var err error
t, err = time.Parse(dateFormat, expiry)
if err != nil {
ctx.Debugf("Could not parse expiration date %q, assuming now: %v", expiry, err)
}
}
if t.After(time.Now()) {
return
}
ctx.Debugf("Cache expired on %v, clearing", t)
ctx.ClearLayer(m2CachedRepo)
ctx.SetMetadata(m2CachedRepo, expiryTimestampKey, time.Now().Add(repoExpiration).Format(dateFormat))
}