diff --git a/cmd/create.go b/cmd/create.go index 53ca7d4f..9c96e500 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -199,7 +199,7 @@ func (cc *createCmd) detectLanguage() (*config.DraftConfig, string, error) { selection := &promptui.Select{ Label: "Linguist detected Java, are you using maven or gradle?", - Items: []string{"gradle", "maven"}, + Items: []string{"gradle", "maven", "gradlew"}, } _, selectResponse, err := selection.Run() @@ -209,6 +209,8 @@ func (cc *createCmd) detectLanguage() (*config.DraftConfig, string, error) { if selectResponse == "gradle" { lang.Language = "Gradle" + } else if selectResponse == "gradlew" { + lang.Language = "Gradlew" } } } diff --git a/pkg/languages/defaults/gradle.go b/pkg/languages/defaults/gradle.go new file mode 100644 index 00000000..80f607f7 --- /dev/null +++ b/pkg/languages/defaults/gradle.go @@ -0,0 +1,115 @@ +package defaults + +import ( + "fmt" + "strings" + + "github.com/Azure/draft/pkg/reporeader" + log "github.com/sirupsen/logrus" +) + +const SOURCE_COMPATIBILITY = "sourceCompatibility" +const TARGET_COMPATIBILITY = "targetCompatibility" +const SERVER_PORT = "server.port" +const GRADLE_FILE_FORMAT = "*.gradle" + +type GradleExtractor struct { +} + +// GetName implements reporeader.VariableExtractor +func (*GradleExtractor) GetName() string { + return "gradle" +} + +// MatchesLanguage implements reporeader.VariableExtractor +func (*GradleExtractor) MatchesLanguage(lowerlang string) bool { + return lowerlang == "gradle" || lowerlang == "gradlew" +} + +// ReadDefaults implements reporeader.VariableExtractor +func (*GradleExtractor) ReadDefaults(r reporeader.RepoReader) (map[string]string, error) { + separatorsSet := createSeparatorsSet() + cutSet := createCutSet() + extractedValues := make(map[string]string) + files, err := r.FindFiles(".", []string{GRADLE_FILE_FORMAT}, 2) + if err != nil { + return nil, fmt.Errorf("error finding gradle files: %v", err) + } + if len(files) > 0 { + f, err := r.ReadFile(files[0]) + if err != nil { + log.Warn("Unable to read gradle file, skipping detection") + return nil, nil + } + content := string(f) + // this separator is used to split the line from build.gradle ex: sourceCompatibility = '1.8' + // output will be ['sourceCompatibility', '1.8'] or ["sourceCompatibility", "1.8"] + separatorFunc := func(c rune) bool { + return separatorsSet.Contains(c) + } + // this func takes care of removing the single or double quotes from split array output + cutset := func(c rune) bool { return cutSet.Contains(c) } + if strings.Contains(content, SOURCE_COMPATIBILITY) || strings.Contains(content, TARGET_COMPATIBILITY) || strings.Contains(content, SERVER_PORT) { + stringAfterSplit := strings.FieldsFunc(content, separatorFunc) + for i, s := range stringAfterSplit { + if i+1 >= len(stringAfterSplit) { + break + } + if s == SOURCE_COMPATIBILITY { + detectedVersion := strings.TrimFunc(stringAfterSplit[i+1], cutset) + detectedVersion = detectedVersion + "-jre" + extractedValues["VERSION"] = detectedVersion + } else if s == TARGET_COMPATIBILITY { + detectedBuilderVersion := strings.TrimFunc(stringAfterSplit[i+1], cutset) + detectedBuilderVersion = "jdk" + detectedBuilderVersion + extractedValues["BUILDERVERSION"] = detectedBuilderVersion + } else if s == SERVER_PORT { + detectedPort := strings.TrimFunc(stringAfterSplit[i+1], cutset) + extractedValues["PORT"] = detectedPort + } + } + } + } + + return extractedValues, nil +} + +func createSeparatorsSet() Set { + separatorsSet := NewSet() + separatorsSet.Add(' ') + separatorsSet.Add('=') + separatorsSet.Add('\n') + separatorsSet.Add('\r') + separatorsSet.Add('\t') + separatorsSet.Add('{') + separatorsSet.Add('}') + separatorsSet.Add('[') + separatorsSet.Add(']') + separatorsSet.Add('-') + separatorsSet.Add(':') + return separatorsSet +} + +func createCutSet() Set { + cutSet := NewSet() + cutSet.Add('\'') + cutSet.Add('"') + return cutSet +} + +type Set map[interface{}]struct{} + +func NewSet() Set { + return make(Set) +} + +func (s Set) Add(item interface{}) { + s[item] = struct{}{} +} + +func (s Set) Contains(item interface{}) bool { + _, ok := s[item] + return ok +} + +var _ reporeader.VariableExtractor = &GradleExtractor{} diff --git a/pkg/languages/defaults/gradle_test.go b/pkg/languages/defaults/gradle_test.go new file mode 100644 index 00000000..e4dc0d3d --- /dev/null +++ b/pkg/languages/defaults/gradle_test.go @@ -0,0 +1,116 @@ +package defaults + +import ( + "io/ioutil" + "reflect" + "testing" + + "github.com/Azure/draft/pkg/reporeader" +) + +func TestGradleExtractor_ReadDefaults(t *testing.T) { + content, err := ioutil.ReadFile("testdata/sample.gradle") + if err != nil { + t.Errorf("error reading sample_build.gradle: %v", err) + } + type args struct { + r reporeader.RepoReader + } + tests := []struct { + name string + args args + want map[string]string + wantErr bool + }{ + { + name: "extract gradle jre version with spaces", + args: args{ + r: reporeader.TestRepoReader{ + Files: map[string][]byte{ + "build.gradle": []byte("group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility=\"11\" targetCompatibility='11'"), + }, + }, + }, + want: map[string]string{ + "VERSION": "11-jre", + "BUILDERVERSION": "jdk11", + }, + wantErr: false, + }, + { + name: "extract gradle jre version with new lines", + args: args{ + r: reporeader.TestRepoReader{ + Files: map[string][]byte{ + "build.gradle": []byte("group = 'com.example'\nversion = '0.0.1-SNAPSHOT'\nsourceCompatibility=\"11\"\ntargetCompatibility='11'"), + }, + }, + }, + want: map[string]string{ + "VERSION": "11-jre", + "BUILDERVERSION": "jdk11", + }, + wantErr: false, + }, + { + name: "extract gradle jre version with tabs", + args: args{ + r: reporeader.TestRepoReader{ + Files: map[string][]byte{ + "build.gradle": []byte("group = 'com.example'\tversion = '0.0.1-SNAPSHOT'\tsourceCompatibility= \"12\" \ntargetCompatibility='11'"), + }, + }, + }, + want: map[string]string{ + "VERSION": "12-jre", + "BUILDERVERSION": "jdk11", + }, + wantErr: false, + }, + { + name: "extract gradle jre version with double spaces", + args: args{ + r: reporeader.TestRepoReader{ + Files: map[string][]byte{ + "build.gradle": []byte("group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility=\"12\"\ntargetCompatibility='11'"), + }, + }, + }, + want: map[string]string{ + "VERSION": "12-jre", + "BUILDERVERSION": "jdk11", + }, + wantErr: false, + }, + { + name: "extract gradle jre version reading from a file", + args: args{ + r: reporeader.TestRepoReader{ + Files: map[string][]byte{ + "build.gradle": content, + }, + }, + }, + want: map[string]string{ + "VERSION": "11-jre", + "BUILDERVERSION": "jdk11", + "PORT": "8081", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := GradleExtractor{} + got, err := p.ReadDefaults(tt.args.r) + if (err != nil) != tt.wantErr { + t.Errorf("ReadDefaults() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ReadDefaults() got = %v, want %v", got, tt.want) + } + }) + } + +} diff --git a/pkg/languages/defaults/testdata/sample.gradle b/pkg/languages/defaults/testdata/sample.gradle new file mode 100644 index 00000000..82016342 --- /dev/null +++ b/pkg/languages/defaults/testdata/sample.gradle @@ -0,0 +1,39 @@ +plugins { + id 'org.springframework.boot' version '2.6.3' + id 'io.spring.dependency-management' version '1.0.11.RELEASE' + id 'java' +} + +group = 'com.example' +version = '0.0.1-SNAPSHOT' +sourceCompatibility = "11" +targetCompatibility = '11' + +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + +repositories { + mavenCentral() +} + +bootRun { + args = ['--server.port=8081'] +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml' + implementation 'com.networknt:json-schema-validator:1.0.66' + compileOnly 'org.projectlombok:lombok' + runtimeOnly 'com.h2database:h2' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.named('test') { + useJUnitPlatform() +} \ No newline at end of file diff --git a/pkg/languages/languages.go b/pkg/languages/languages.go index c57b9e02..b709e5fd 100644 --- a/pkg/languages/languages.go +++ b/pkg/languages/languages.go @@ -120,6 +120,7 @@ func CreateLanguagesFromEmbedFS(dockerfileTemplates embed.FS, dest string) *Lang func (l *Languages) ExtractDefaults(lowerLang string, r reporeader.RepoReader) ([]config.BuilderVarDefault, error) { extractors := []reporeader.VariableExtractor{ &defaults.PythonExtractor{}, + &defaults.GradleExtractor{}, } extractedValues := make(map[string]string) var extractedDefaults []config.BuilderVarDefault diff --git a/template/dockerfiles/gradlew/.dockerignore b/template/dockerfiles/gradlew/.dockerignore new file mode 100644 index 00000000..843dec4f --- /dev/null +++ b/template/dockerfiles/gradlew/.dockerignore @@ -0,0 +1,2 @@ +Dockerfile +charts/ diff --git a/template/dockerfiles/gradlew/Dockerfile b/template/dockerfiles/gradlew/Dockerfile new file mode 100644 index 00000000..4522aa0e --- /dev/null +++ b/template/dockerfiles/gradlew/Dockerfile @@ -0,0 +1,17 @@ +FROM gradle:{{BUILDERVERSION}} as BUILD + +COPY --chown=gradle:gradle . /project +COPY gradlew gradlew +COPY gradle/wrapper gradle/wrapper +RUN chmod +x gradle/wrapper +RUN chmod +x gradlew +RUN ./gradlew -i -s -b /project/build.gradle clean build + +FROM eclipse-temurin:{{VERSION}} +ENV PORT {{PORT}} +EXPOSE {{PORT}} + +COPY --from=BUILD /project/build/libs/* /opt/ +WORKDIR /opt/ +RUN ls -l +CMD ["/bin/bash", "-c", "find -type f -name '*SNAPSHOT.jar' | xargs java -jar"] \ No newline at end of file diff --git a/template/dockerfiles/gradlew/draft.yaml b/template/dockerfiles/gradlew/draft.yaml new file mode 100644 index 00000000..9439b3fb --- /dev/null +++ b/template/dockerfiles/gradlew/draft.yaml @@ -0,0 +1,22 @@ +language: gradle +displayName: Gradle +nameOverrides: + - path: "dockerignore" + prefix: "." +variables: + - name: "PORT" + description: "the port exposed in the application" + type: int + - name: "BUILDERVERSION" + description: "the version of gradle used during the builder stage to generate the executable" + exampleValues: ["jdk8","jdk11","jdk17","jdk19"] + - name: "VERSION" + description: "the version of openjdk used by the application" + exampleValues: ["8-jre","11-jre","17-jre","19-jre"] +variableDefaults: + - name: "BUILDERVERSION" + value: "jdk11" + - name: "VERSION" + value: "11-jre" + - name: "PORT" + value: "80" \ No newline at end of file