diff --git a/assets/filepermissions.yaml b/assets/filepermissions.yaml index 4ac453cdd..29aea0a9b 100644 --- a/assets/filepermissions.yaml +++ b/assets/filepermissions.yaml @@ -55,7 +55,6 @@ "inbuilt/transformers/dockerfile/javamaven/templates/Dockerfile" : 0644 "inbuilt/transformers/dockerfile/nodejs/nodejs.yaml" : 0644 "inbuilt/transformers/dockerfile/nodejs/templates/Dockerfile" : 0644 -"inbuilt/transformers/dockerfile/php/m2kdetect.sh" : 0755 "inbuilt/transformers/dockerfile/php/php.yaml" : 0644 "inbuilt/transformers/dockerfile/php/templates/Dockerfile" : 0644 "inbuilt/transformers/dockerfile/python/m2kdetect.sh" : 0755 diff --git a/assets/inbuilt/transformers/dockerfile/php/m2kdetect.sh b/assets/inbuilt/transformers/dockerfile/php/m2kdetect.sh deleted file mode 100755 index b2add1527..000000000 --- a/assets/inbuilt/transformers/dockerfile/php/m2kdetect.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -# Copyright IBM Corporation 2020 -# -# 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. - -# Takes as input the source folder and returns error if it is not fit -BASE_DIR=$1 - -found=$(find "$BASE_DIR"/. -name "*.php" -print -quit | wc -l) - -if [ ! "$found" -eq 1 ]; then - exit 1 -fi - -echo '{"generates":"ContainerBuild","generatedBases":"ContainerBuild","port": 8080, "binding": "0.0.0.0:8080", "app_name": "app"}' diff --git a/assets/inbuilt/transformers/dockerfile/php/php.yaml b/assets/inbuilt/transformers/dockerfile/php/php.yaml index adacc320f..9b4db7d16 100644 --- a/assets/inbuilt/transformers/dockerfile/php/php.yaml +++ b/assets/inbuilt/transformers/dockerfile/php/php.yaml @@ -1,10 +1,7 @@ apiVersion: move2kube.konveyor.io/v1alpha1 kind: Transformer metadata: - name: PHP + name: PHP-Dockerfile spec: mode: "Container" - class: "Executable" - config: - directoryDetectCMD: ["./m2kdetect.sh"] - platforms: ["linux", "darwin"] + class: "PHPDockerfileGenerator" diff --git a/assets/inbuilt/transformers/dockerfile/php/templates/Dockerfile b/assets/inbuilt/transformers/dockerfile/php/templates/Dockerfile index c5405676a..01e8b8c8a 100644 --- a/assets/inbuilt/transformers/dockerfile/php/templates/Dockerfile +++ b/assets/inbuilt/transformers/dockerfile/php/templates/Dockerfile @@ -12,9 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3-201 -RUN microdnf update && microdnf install -y php && microdnf clean all -WORKDIR /{{ .app_name }} -COPY . . -EXPOSE {{ .port }} -CMD ["php", "-S", "{{ .binding }}"] +FROM registry.access.redhat.com/ubi8/php-74:latest +{{- if .ConfFile }} +COPY {{ .ConfFile }} /etc/httpd/conf.d/ +{{- else}} +RUN printf "ServerName localhost\n\nListen {{ .ConfFilePort }}\n\n ServerAdmin webmaster@localhost\n DocumentRoot /var/www/html\n \n Options Indexes FollowSymLinks\n AllowOverride All\n Require all granted\n\n" > /etc/httpd/conf.d/site.conf +{{- end }} +COPY . /var/www/html/ +EXPOSE {{ .ConfFilePort }} +CMD ["httpd", "-D", "FOREGROUND"] diff --git a/go.mod b/go.mod index dd0b1efc9..1c9d7ed93 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.16 require ( code.cloudfoundry.org/bytefmt v0.0.0-20210608160410-67692ebc98de // indirect code.cloudfoundry.org/cli v7.1.0+incompatible + github.com/Akash-Nayak/GopacheConfig v0.0.0-20210730101443-d5bfa3109be4 github.com/AlecAivazis/survey/v2 v2.2.12 github.com/Masterminds/semver/v3 v3.1.1 github.com/cloudfoundry/bosh-cli v6.4.1+incompatible @@ -38,7 +39,7 @@ require ( github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 go.starlark.net v0.0.0-20210602144842-1cdb82c9e17a golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e - golang.org/x/mod v0.4.2 // indirect + golang.org/x/mod v0.4.2 google.golang.org/grpc v1.38.0 google.golang.org/protobuf v1.26.0 gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 diff --git a/go.sum b/go.sum index d1a2ab96c..40c2daaf5 100644 --- a/go.sum +++ b/go.sum @@ -89,6 +89,8 @@ git.apache.org/thrift.git v0.12.0 h1:CMxsZlAmxKs+VAZMlDDL0wXciMblJcutQbEe3A9CYUM git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/360EntSecGroup-Skylar/excelize v1.4.1 h1:l55mJb6rkkaUzOpSsgEeKYtS6/0gHwBYyfo5Jcjv/Ks= github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE= +github.com/Akash-Nayak/GopacheConfig v0.0.0-20210730101443-d5bfa3109be4 h1:h6Gvq//5x92mMX/CWrVNAOl7Oi4wdsRzhVgqKIMHDX8= +github.com/Akash-Nayak/GopacheConfig v0.0.0-20210730101443-d5bfa3109be4/go.mod h1:mioqvD+p/slck6WEOtQq+nWaV4Zw84fQjK4npdGRg7M= github.com/AkihiroSuda/containerd-fuse-overlayfs v1.0.0 h1:LhS8BiMh7ULa6zkkF5XI6piLV5XVTR7mSm9j3hTUB/k= github.com/AkihiroSuda/containerd-fuse-overlayfs v1.0.0/go.mod h1:0mMDvQFeLbbn1Wy8P2j3hwFhqBq+FKn8OZPno8WLmp8= github.com/AlecAivazis/survey/v2 v2.2.12 h1:5a07y93zA6SZ09gOa9wLVLznF5zTJMQ+pJ3cZK4IuO8= diff --git a/internal/common/constants.go b/internal/common/constants.go index e987315fa..382833196 100644 --- a/internal/common/constants.go +++ b/internal/common/constants.go @@ -81,8 +81,14 @@ const ( ConfigMinReplicasKey = BaseKey + d + "minreplicas" //ConfigPortsForServiceKeySegment represents the ports used for service ConfigPortsForServiceKeySegment = "ports" + //ConfigPortForServiceKeySegment represents the port used for service + ConfigPortForServiceKeySegment = "port" //ConfigAdditionalPortsForServiceKeySegment represents the ports used for service ConfigAdditionalPortsForServiceKeySegment = "additionalports" + //ConfigAdditionalPortForServiceKeySegment represents the port used for service + ConfigAdditionalPortForServiceKeySegment = "additionalport" + //ConfigApacheConfFileForServiceKeySegment represents the conf file used for service + ConfigApacheConfFileForServiceKeySegment = "apacheconfig" //ConfigModesKey represents modes Key ConfigModesKey = BaseKey + d + "modes" //ConfigSpawmContainersKey represents modes Key diff --git a/internal/transformer/classes/generators/dockerfilegenerators/phpdockerfilegenerator.go b/internal/transformer/classes/generators/dockerfilegenerators/phpdockerfilegenerator.go new file mode 100644 index 000000000..eaf7784d7 --- /dev/null +++ b/internal/transformer/classes/generators/dockerfilegenerators/phpdockerfilegenerator.go @@ -0,0 +1,250 @@ +/* + * Copyright IBM Corporation 2021 + * + * 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 dockerfilegenerators + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + gopache "github.com/Akash-Nayak/GopacheConfig" + "github.com/konveyor/move2kube/environment" + "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/qaengine" + "github.com/konveyor/move2kube/types/qaengine/commonqa" + transformertypes "github.com/konveyor/move2kube/types/transformer" + "github.com/konveyor/move2kube/types/transformer/artifacts" + "github.com/sirupsen/logrus" +) + +const ( + phpExt = ".php" + virtualHost = "VirtualHost" + confExt = ".conf" +) + +// PHPDockerfileGenerator implements the Transformer interface +type PHPDockerfileGenerator struct { + Config transformertypes.Transformer + Env *environment.Environment +} + +// PhpTemplateConfig implements Php config interface +type PhpTemplateConfig struct { + ConfFile string + ConfFilePort int32 +} + +// Init Initializes the transformer +func (t *PHPDockerfileGenerator) Init(tc transformertypes.Transformer, env *environment.Environment) (err error) { + t.Config = tc + t.Env = env + return nil +} + +// GetConfig returns the transformer config +func (t *PHPDockerfileGenerator) GetConfig() (transformertypes.Transformer, *environment.Environment) { + return t.Config, t.Env +} + +// BaseDirectoryDetect runs detect in base directory +func (t *PHPDockerfileGenerator) BaseDirectoryDetect(dir string) (namedServices map[string]transformertypes.ServicePlan, unnamedServices []transformertypes.TransformerPlan, err error) { + return nil, nil, nil +} + +// parseConfFile parses the conf file to detect the port +func parseConfFile(confFilePath string) (int32, error) { + var port int32 + confFile, err := os.Open(confFilePath) + if err != nil { + logrus.Errorf("Could not open the apache config file: %s", err) + return port, err + } + defer confFile.Close() + root, err := gopache.Parse(confFile) + if err != nil { + logrus.Errorf("Error while parsing apache config file : %s", err) + return port, err + } + match, err := root.FindOne(virtualHost) + if err != nil { + logrus.Debugf("Could not find the VirtualHost in apache config file: %s", err) + return port, err + } + tokens := strings.Split(match.Content, ":") + if len(tokens) > 1 { + detectedPort, err := strconv.ParseInt(tokens[1], 10, 32) + if err != nil { + logrus.Errorf("Error while converting the port from string to int : %s", err) + return port, err + } + return int32(detectedPort), nil + } + return port, err +} + +// detectConfFiles detects if conf files are present or not +func detectConfFiles(dir string) ([]string, error) { + var confFilesPaths []string + confFiles, err := common.GetFilesByExt(dir, []string{confExt}) + if err != nil { + logrus.Debugf("Could not find conf files %s", err) + return confFilesPaths, err + } + for _, confFilePath := range confFiles { + confFile, err := os.Open(confFilePath) + if err != nil { + logrus.Debugf("Could not open the conf file: %s", err) + confFile.Close() + continue + } + defer confFile.Close() + _, err = gopache.Parse(confFile) + if err != nil { + logrus.Debugf("Error while parsing conf file : %s", err) + continue + } + confFileRelPath, err := filepath.Rel(dir, confFilePath) + if err != nil { + logrus.Errorf("Unable to resolve apache config file path %s as rel path : %s", confFilePath, err) + continue + } + confFilesPaths = append(confFilesPaths, confFileRelPath) + } + return confFilesPaths, nil +} + +// GetConfFileForService returns ports used by a service +func GetConfFileForService(confFiles []string, serviceName string) string { + noAnswer := "none of the above" + confFiles = append(confFiles, noAnswer) + selectedConfFile := qaengine.FetchSelectAnswer(common.ConfigServicesKey+common.Delim+serviceName+common.Delim+common.ConfigApacheConfFileForServiceKeySegment, fmt.Sprintf("Choose the apache config file to be used for the service %s", serviceName), []string{fmt.Sprintf("Selected apache config file will be used for identifying the port to be exposed for the service %s", serviceName)}, confFiles[0], confFiles) + if selectedConfFile == noAnswer { + logrus.Debugf("No apache config file selected for the service %s", serviceName) + return "" + } + return selectedConfFile +} + +// DirectoryDetect runs detect in each sub directory +func (t *PHPDockerfileGenerator) DirectoryDetect(dir string) (namedServices map[string]transformertypes.ServicePlan, unnamedServices []transformertypes.TransformerPlan, err error) { + dirEntries, err := os.ReadDir(dir) + if err != nil { + logrus.Errorf("Error while trying to read directory : %s", err) + return nil, nil, err + } + for _, de := range dirEntries { + if de.IsDir() { + continue + } + if filepath.Ext(de.Name()) != phpExt { + continue + } + unnamedServices = []transformertypes.TransformerPlan{{ + Mode: t.Config.Spec.Mode, + ArtifactTypes: []transformertypes.ArtifactType{artifacts.ContainerBuildArtifactType}, + BaseArtifactTypes: []transformertypes.ArtifactType{artifacts.ContainerBuildArtifactType}, + Paths: map[string][]string{ + artifacts.ProjectPathPathType: {dir}, + }, + }} + return nil, unnamedServices, nil + } + return nil, nil, nil +} + +// Transform transforms the artifacts +func (t *PHPDockerfileGenerator) Transform(newArtifacts []transformertypes.Artifact, oldArtifacts []transformertypes.Artifact) ([]transformertypes.PathMapping, []transformertypes.Artifact, error) { + pathMappings := []transformertypes.PathMapping{} + artifactsCreated := []transformertypes.Artifact{} + for _, a := range newArtifacts { + if a.Artifact != artifacts.ServiceArtifactType { + continue + } + relSrcPath, err := filepath.Rel(t.Env.GetEnvironmentSource(), a.Paths[artifacts.ProjectPathPathType][0]) + if err != nil { + logrus.Errorf("Unable to convert source path %s to be relative : %s", a.Paths[artifacts.ProjectPathPathType][0], err) + } + var sConfig artifacts.ServiceConfig + err = a.GetConfig(artifacts.ServiceConfigType, &sConfig) + if err != nil { + logrus.Errorf("unable to load config for Transformer into %T : %s", sConfig, err) + continue + } + sImageName := artifacts.ImageName{} + err = a.GetConfig(artifacts.ImageNameConfigType, &sImageName) + if err != nil { + logrus.Debugf("unable to load config for Transformer into %T : %s", sImageName, err) + } + var detectedPorts []int32 + var phpConfig PhpTemplateConfig + confFiles, err := detectConfFiles(a.Paths[artifacts.ProjectPathPathType][0]) + if err != nil { + logrus.Debugf("Could not detect any conf files %s", err) + } else { + if len(confFiles) == 1 { + phpConfig.ConfFile = confFiles[0] + } else if len(confFiles) > 1 { + phpConfig.ConfFile = GetConfFileForService(confFiles, a.Name) + } + if phpConfig.ConfFile != "" { + phpConfig.ConfFilePort, err = parseConfFile(filepath.Join(a.Paths[artifacts.ProjectPathPathType][0], phpConfig.ConfFile)) + if err != nil { + logrus.Errorf("Error while parsing configuration file : %s", err) + } + } + if phpConfig.ConfFilePort == 0 { + phpConfig.ConfFilePort = commonqa.GetPortForService(detectedPorts, a.Name) + } + } + if sImageName.ImageName == "" { + sImageName.ImageName = common.MakeStringContainerImageNameCompliant(sConfig.ServiceName) + } + pathMappings = append(pathMappings, transformertypes.PathMapping{ + Type: transformertypes.SourcePathMappingType, + DestPath: common.DefaultSourceDir, + }, transformertypes.PathMapping{ + Type: transformertypes.TemplatePathMappingType, + SrcPath: filepath.Join(t.Env.Context, t.Config.Spec.TemplatesDir), + DestPath: filepath.Join(common.DefaultSourceDir, relSrcPath), + TemplateConfig: phpConfig, + }) + paths := a.Paths + paths[artifacts.DockerfilePathType] = []string{filepath.Join(common.DefaultSourceDir, relSrcPath, "Dockerfile")} + p := transformertypes.Artifact{ + Name: sImageName.ImageName, + Artifact: artifacts.DockerfileArtifactType, + Paths: paths, + Configs: map[string]interface{}{ + artifacts.ImageNameConfigType: sImageName, + }, + } + dfs := transformertypes.Artifact{ + Name: sConfig.ServiceName, + Artifact: artifacts.DockerfileForServiceArtifactType, + Paths: a.Paths, + Configs: map[string]interface{}{ + artifacts.ImageNameConfigType: sImageName, + artifacts.ServiceConfigType: sConfig, + }, + } + artifactsCreated = append(artifactsCreated, p, dfs) + } + return pathMappings, artifactsCreated, nil +} diff --git a/internal/transformer/transformer.go b/internal/transformer/transformer.go index 509d4a80d..4e8ccf066 100644 --- a/internal/transformer/transformer.go +++ b/internal/transformer/transformer.go @@ -81,6 +81,7 @@ func init() { new(dockerfilegenerators.NodejsDockerfileGenerator), new(dockerfilegenerators.GolangDockerfileGenerator), + new(dockerfilegenerators.PHPDockerfileGenerator), new(external.Starlark), new(external.Executable), diff --git a/samples/php/php/index.php b/samples/php/index.php similarity index 97% rename from samples/php/php/index.php rename to samples/php/index.php index 39959edc3..b9f064741 100644 --- a/samples/php/php/index.php +++ b/samples/php/index.php @@ -17,4 +17,6 @@ This is a PHP web app"; + + phpinfo(); ?> diff --git a/samples/php/site.conf b/samples/php/site.conf new file mode 100644 index 000000000..1460ddf25 --- /dev/null +++ b/samples/php/site.conf @@ -0,0 +1,14 @@ +# site.conf +ServerName localhost + +Listen 8082 + + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + + + Options Indexes FollowSymLinks + AllowOverride All + Require all granted + + diff --git a/types/qaengine/commonqa/commonqa.go b/types/qaengine/commonqa/commonqa.go index 83fb9dfb8..b7017359f 100644 --- a/types/qaengine/commonqa/commonqa.go +++ b/types/qaengine/commonqa/commonqa.go @@ -17,6 +17,7 @@ package commonqa import ( + "fmt" "net/url" "strconv" "strings" @@ -124,3 +125,28 @@ func GetPortsForService(detectedPorts []int32, serviceName string) []int32 { } return exposePorts } + +// GetPortForService returns port used by a service +func GetPortForService(detectedPorts []int32, serviceName string) int32 { + var detectedPortsStr []string + var exposePortStr string + var exposePort int32 + if len(detectedPorts) != 0 { + for _, detectedPort := range detectedPorts { + detectedPortsStr = append(detectedPortsStr, strconv.Itoa(int(detectedPort))) + } + allDetectedPortsStr := append(detectedPortsStr, qatypes.OtherAnswer) + exposePortStr = qaengine.FetchSelectAnswer(common.ConfigServicesKey+common.Delim+serviceName+common.Delim+common.ConfigPortForServiceKeySegment, fmt.Sprintf("Select port to be exposed for the service %s :", serviceName), []string{fmt.Sprintf("Select Other if you want to expose the service %s to some other port", serviceName)}, allDetectedPortsStr[0], allDetectedPortsStr) + } else { + exposePortStr = qaengine.FetchStringAnswer(common.ConfigServicesKey+common.Delim+serviceName+common.Delim+common.ConfigAdditionalPortForServiceKeySegment, fmt.Sprintf("Enter the port to be exposed for the service %s: ", serviceName), []string{fmt.Sprintf("The service %s will be exposed to the specified port", serviceName)}, "8080") + } + exposePortStr = strings.TrimSpace(exposePortStr) + if exposePortStr != "" { + port, err := strconv.ParseInt(exposePortStr, 10, 32) + if err != nil { + logrus.Errorf("Error while converting the selected port from string to int : %s", err) + } + return int32(port) + } + return exposePort +}