Skip to content

Commit

Permalink
changes for python file entry point detection (#217)
Browse files Browse the repository at this point in the history
  • Loading branch information
bosesuneha authored Jul 7, 2023
1 parent d419927 commit 5602b8a
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 76 deletions.
76 changes: 38 additions & 38 deletions .github/workflows/integration-linux.yml

Large diffs are not rendered by default.

21 changes: 18 additions & 3 deletions cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,12 +260,27 @@ func (cc *createCmd) generateDockerfile(langConfig *config.DraftConfig, lowerLan
}

// Extract language-specific defaults from repo
extractedDefaults, err := cc.supportedLangs.ExtractDefaults(lowerLang, cc.repoReader)
extractedValues, err := cc.supportedLangs.ExtractDefaults(lowerLang, cc.repoReader)
if err != nil {
return err
}
for _, d := range extractedDefaults {
langConfig.VariableDefaults = append(langConfig.VariableDefaults, d)

// Check for existing duplicate defualts
for k, v := range extractedValues {
variableExists := false
for i, varD := range langConfig.VariableDefaults {
if k == varD.Name {
variableExists = true
langConfig.VariableDefaults[i].Value = v
break
}
}
if !variableExists {
langConfig.VariableDefaults = append(langConfig.VariableDefaults, config.BuilderVarDefault{
Name: k,
Value: v,
})
}
}

var inputs map[string]string
Expand Down
49 changes: 37 additions & 12 deletions cmd/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,15 @@ import (
"github.com/Azure/draft/pkg/config"
"github.com/Azure/draft/pkg/languages"
"github.com/Azure/draft/pkg/linguist"
"github.com/Azure/draft/pkg/reporeader"
"github.com/Azure/draft/pkg/templatewriter/writers"
"github.com/Azure/draft/template"
)

func TestRun(t *testing.T) {
mockCC := &createCmd{}
mockCC.createConfig = &CreateConfig{}
mockCC.dest = "./.."
mockCC.createConfig.DeployType = ""
mockCC.createConfig.LanguageVariables = []UserInputs{}
mockCC.createConfig.DeployVariables = []UserInputs{}
mockPortInput := UserInputs{Name: "PORT", Value: "8080"}
mockAppNameInput := UserInputs{Name: "APPNAME", Value: "testingCreateCommand"}
mockCC.createConfig.DeployVariables = append(mockCC.createConfig.DeployVariables, mockPortInput, mockAppNameInput)
mockCC.createConfig.LanguageVariables = append(mockCC.createConfig.LanguageVariables, mockPortInput)
mockCC.templateWriter = &writers.LocalFSWriter{}
testCreateConfig := CreateConfig{LanguageVariables: []UserInputs{{Name: "PORT", Value: "8080"}}, DeployVariables: []UserInputs{{Name: "PORT", Value: "8080"}, {Name: "APPNAME", Value: "testingCreateCommand"}}}
flagVariablesMap = map[string]string{"PORT": "8080", "APPNAME": "testingCreateCommand", "VERSION": "1.18", "SERVICEPORT": "8080", "NAMESPACE": "testNamespace", "IMAGENAME": "testImage", "IMAGETAG": "latest"}
mockCC := createCmd{dest: "./..", createConfig: &testCreateConfig, templateWriter: &writers.LocalFSWriter{}}
deployTypes := []string{"helm", "kustomize", "manifests"}
oldDockerfile, _ := ioutil.ReadFile("./../Dockerfile")
oldDockerignore, _ := ioutil.ReadFile("./../.dockerignore")
Expand All @@ -53,7 +45,6 @@ func TestRun(t *testing.T) {
assert.False(t, lowerLang == "")
assert.True(t, err == nil)
err = mockCC.generateDockerfile(detectedLang, lowerLang)
println(err)
assert.True(t, err == nil)

//Write back old Dockerfile
Expand Down Expand Up @@ -105,6 +96,40 @@ func TestRun(t *testing.T) {
}
}

func TestRunCreateDockerfileWithRepoReader(t *testing.T) {

testRepoReader := &reporeader.TestRepoReader{Files: map[string][]byte{
"foo.py": []byte("print('Hello World')"),
"main.py": []byte("print('Hello World')"),
}}

testCreateConfig := CreateConfig{LanguageType: "python", LanguageVariables: []UserInputs{{Name: "PORT", Value: "8080"}}}
mockCC := createCmd{createConfig: &testCreateConfig, repoReader: testRepoReader, templateWriter: &writers.LocalFSWriter{}}

detectedLang, lowerLang, err := mockCC.mockDetectLanguage()
assert.False(t, detectedLang == nil)
assert.True(t, lowerLang == "python")
assert.Nil(t, err)

err = mockCC.generateDockerfile(detectedLang, lowerLang)
assert.True(t, err == nil)

dockerFileContent, err := ioutil.ReadFile("Dockerfile")
if err != nil {
t.Error(err)
}
assert.Contains(t, string(dockerFileContent), "CMD [\"main.py\"]")

err = os.Remove("Dockerfile")
if err != nil {
t.Error(err)
}
err = os.RemoveAll(".dockerignore")
if err != nil {
t.Error(err)
}
}

func TestInitConfig(t *testing.T) {
mockCC := &createCmd{}
mockCC.createConfig = &CreateConfig{}
Expand Down
32 changes: 30 additions & 2 deletions pkg/languages/defaults/python.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package defaults

import (
"fmt"
"path/filepath"
"regexp"

"github.com/Azure/draft/pkg/reporeader"
)
Expand All @@ -12,12 +14,38 @@ type PythonExtractor struct {
// ReadDefaults reads the default values for the language from the repo files
func (p PythonExtractor) ReadDefaults(r reporeader.RepoReader) (map[string]string, error) {
extractedValues := make(map[string]string)
// Find files with .py extension in the root of the repository or upto depth 0
files, err := r.FindFiles(".", []string{"*.py"}, 0)
if err != nil {
return nil, fmt.Errorf("error finding python files: %v", err)
}
if len(files) > 0 {
extractedValues["ENTRYPOINT"] = files[0]
// Regex for python entrypoint pattern `if __name__ == '__main__'`
entryPointPattern := `if\s*__name__\s*==\s*["']__main__["']`
compiledPattern, err := regexp.Compile(entryPointPattern)
if err != nil {
return nil, fmt.Errorf("error compiling regex pattern: %v", err)
}

for _, filePath := range files {
fileContent, err := r.ReadFile(filePath)
baseFile := filepath.Base(filePath)

if err != nil {
return nil, fmt.Errorf(("error reading python files"))
}
fileContentInString := string(fileContent)
// Check if file contains python entrypoint pattern or name of the file is 'main.py' or 'app.py'
if compiledPattern.MatchString(fileContentInString) || baseFile == "main.py" || baseFile == "app.py" {
extractedValues["ENTRYPOINT"] = baseFile
break
}
}

// Set entrypoint to the first .py file if other conditions do not match
if _, ok := extractedValues["ENTRYPOINT"]; !ok {
if len(files) > 0 {
extractedValues["ENTRYPOINT"] = files[0]
}
}

return extractedValues, nil
Expand Down
48 changes: 47 additions & 1 deletion pkg/languages/defaults/python_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,53 @@ func TestPythonExtractor_ReadDefaults(t *testing.T) {
"ENTRYPOINT": "foo.py",
},
wantErr: false,
}, {
},
{
name: "extract python file containing the string \"if __name__ == '__main__'\" as the entrypoint",
args: args{
r: reporeader.TestRepoReader{
Files: map[string][]byte{
"foo.py": []byte("print('hello world')"),
"bar.py": []byte("if __name__ == '__main__' : print('hello world')"),
},
},
},
want: map[string]string{
"ENTRYPOINT": "bar.py",
},
wantErr: false,
},
{
name: "extract python file containing the string \"if __name__==\"__main__\"\" as the entrypoint",
args: args{
r: reporeader.TestRepoReader{
Files: map[string][]byte{
"foo.py": []byte("print('hello world')"),
"bar.py": []byte("if __name__==\"__main__\": print('hello world')"),
},
},
},
want: map[string]string{
"ENTRYPOINT": "bar.py",
},
wantErr: false,
},
{
name: "extract python file named app.py as the entrypoint",
args: args{
r: reporeader.TestRepoReader{
Files: map[string][]byte{
"foo.py": []byte("print('Hello World')"),
"app.py": []byte("print('Hello World')"),
},
},
},
want: map[string]string{
"ENTRYPOINT": "app.py",
},
wantErr: false,
},
{
name: "no extraction if no python files",
args: args{
r: reporeader.TestRepoReader{
Expand Down
14 changes: 3 additions & 11 deletions pkg/languages/languages.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,16 +117,15 @@ func CreateLanguagesFromEmbedFS(dockerfileTemplates embed.FS, dest string) *Lang
return l
}

func (l *Languages) ExtractDefaults(lowerLang string, r reporeader.RepoReader) ([]config.BuilderVarDefault, error) {
func (l *Languages) ExtractDefaults(lowerLang string, r reporeader.RepoReader) (map[string]string, error) {
extractors := []reporeader.VariableExtractor{
&defaults.PythonExtractor{},
&defaults.GradleExtractor{},
}
extractedValues := make(map[string]string)
var extractedDefaults []config.BuilderVarDefault
if r == nil {
log.Debugf("no repo reader provided, returning empty list of defaults")
return extractedDefaults, nil
return extractedValues, nil
}
for _, extractor := range extractors {
if extractor.MatchesLanguage(lowerLang) {
Expand All @@ -144,12 +143,5 @@ func (l *Languages) ExtractDefaults(lowerLang string, r reporeader.RepoReader) (
}
}

for k, v := range extractedValues {
extractedDefaults = append(extractedDefaults, config.BuilderVarDefault{
Name: k,
Value: v,
})
}

return extractedDefaults, nil
return extractedValues, nil
}
2 changes: 1 addition & 1 deletion template/dockerfiles/python/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ RUN pip install --no-cache-dir -r requirements.txt
COPY . .

ENTRYPOINT ["python"]
CMD ["app.py"]
CMD ["{{ENTRYPOINT}}"]
6 changes: 6 additions & 0 deletions template/dockerfiles/python/draft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@ variables:
- name: "VERSION"
description: "the version of python used by the application"
exampleValues: ["3.9", "3.8", "3.7", "3.6"]
- name: "ENTRYPOINT"
description: "the entrypoint file of the repository"
type: string
exampleValues: ["app.py", "main.py"]
variableDefaults:
- name: "VERSION"
value: "3"
- name: "PORT"
value: "80"
- name: "ENTRYPOINT"
value: "app.py"
38 changes: 30 additions & 8 deletions test/gen_integration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ do
# addon integration testing vars
ingress_test_args="-a webapp_routing --variable ingress-tls-cert-keyvault-uri=test.cert.keyvault.uri --variable ingress-use-osm-mtls=true --variable ingress-host=host1"
create_config_args="--variable PORT=8080 --variable APPNAME=testingCreateCommand --variable VERSION=1.11 --variable BUILDERVERSION=1.11 --variable SERVICEPORT=8080 --variable NAMESPACE=testNamespace --variable IMAGENAME=testImage --variable IMAGETAG=latest"
python_create_config_args="--variable PORT=8080 --variable APPNAME=testingCreateCommand --variable VERSION=1.11 --variable BUILDERVERSION=1.11 --variable SERVICEPORT=8080 --variable NAMESPACE=testNamespace --variable IMAGENAME=testImage --variable IMAGETAG=latest --variable ENTRYPOINT=testapp.py"
echo "Adding $lang with port $port"

mkdir ./integration/$lang
Expand Down Expand Up @@ -204,12 +205,19 @@ languageVariables:
run: |
npm install -g ajv-cli@5.0.0
ajv validate -s test/dry_run_schema.json -d test/temp/dry-run.json
- name: Execute Dry Run with variables passed through flag
run: |
- name: Execute Dry Run with variables passed through flag
$(if [ "$lang" = "python" ]
then echo "run: |
mkdir -p test/temp
./draft --dry-run --dry-run-file test/temp/dry-run.json \
create -d ./langtest/ -l $lang --skip-file-detection --deploy-type helm \
$python_create_config_args"
else echo "run: |
mkdir -p test/temp
./draft --dry-run --dry-run-file test/temp/dry-run.json \
create -d ./langtest/ -l $lang --skip-file-detection --deploy-type helm \
$create_config_args
$create_config_args"
fi)
- name: Validate JSON
run: |
npm install -g ajv-cli@5.0.0
Expand Down Expand Up @@ -355,11 +363,18 @@ languageVariables:
npm install -g ajv-cli@5.0.0
ajv validate -s test/dry_run_schema.json -d test/temp/dry-run.json
- name: Execute Dry Run with variables passed through flag
run: |
$(if [ "$lang" = "python" ];
then echo "run: |
mkdir -p test/temp
./draft --dry-run --dry-run-file test/temp/dry-run.json \
create -d ./langtest/ -l $lang --skip-file-detection --deploy-type kustomize \
$python_create_config_args";
else echo "run: |
mkdir -p test/temp
./draft --dry-run --dry-run-file test/temp/dry-run.json \
create -d ./langtest/ -l $lang --skip-file-detection --deploy-type kustomize \
$create_config_args
$create_config_args";
fi)
- name: Validate JSON
run: |
npm install -g ajv-cli@5.0.0
Expand Down Expand Up @@ -495,12 +510,19 @@ languageVariables:
run: |
npm install -g ajv-cli@5.0.0
ajv validate -s test/dry_run_schema.json -d test/temp/dry-run.json
- name: Execute Dry Run with variables passed through flag
run: |
- name: Execute Dry Run with variables passed through flag
$(if [ "$lang" = "python" ];
then echo "run: |
mkdir -p test/temp
./draft --dry-run --dry-run-file test/temp/dry-run.json \
create -d ./langtest/ -l $lang --skip-file-detection --deploy-type manifests \
$python_create_config_args";
else echo "run: |
mkdir -p test/temp
./draft --dry-run --dry-run-file test/temp/dry-run.json \
create -d ./langtest/ -l $lang --skip-file-detection --deploy-type manifests \
$create_config_args
$create_config_args";
fi)
- name: Validate JSON
run: |
npm install -g ajv-cli@5.0.0
Expand Down

0 comments on commit 5602b8a

Please sign in to comment.