github.com/jfrog/jfrog-cli-core/v2@v2.51.0/general/project/projectinit.go (about)

     1  package project
     2  
     3  import (
     4  	"os"
     5  	"os/exec"
     6  	"path"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	commonCommands "github.com/jfrog/jfrog-cli-core/v2/common/commands"
    11  	"github.com/jfrog/jfrog-cli-core/v2/common/project"
    12  	"github.com/jfrog/jfrog-cli-core/v2/utils/config"
    13  	"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
    14  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
    15  	"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
    16  	"gopkg.in/yaml.v3"
    17  )
    18  
    19  const (
    20  	buildFileName = "build.yaml"
    21  )
    22  
    23  type ProjectInitCommand struct {
    24  	projectPath string
    25  	serverId    string
    26  	serverUrl   string
    27  }
    28  
    29  func NewProjectInitCommand() *ProjectInitCommand {
    30  	return &ProjectInitCommand{}
    31  }
    32  
    33  func (pic *ProjectInitCommand) SetProjectPath(path string) *ProjectInitCommand {
    34  	pic.projectPath = path
    35  	return pic
    36  }
    37  
    38  func (pic *ProjectInitCommand) SetServerId(id string) *ProjectInitCommand {
    39  	pic.serverId = id
    40  	return pic
    41  }
    42  
    43  func (pic *ProjectInitCommand) Run() (err error) {
    44  	if pic.serverId == "" {
    45  		defaultServer, err := config.GetSpecificConfig("", true, false)
    46  		if err != nil {
    47  			return err
    48  		}
    49  		pic.serverId = defaultServer.ServerId
    50  		pic.serverUrl = defaultServer.Url
    51  	}
    52  	technologiesMap, err := pic.detectTechnologies()
    53  	if err != nil {
    54  		return err
    55  	}
    56  	if _, errNotFound := exec.LookPath("docker"); errNotFound == nil {
    57  		technologiesMap[coreutils.Docker] = true
    58  	}
    59  	// First create repositories for the detected technologies.
    60  	for techName := range technologiesMap {
    61  		// First create repositories for the detected technology.
    62  		err = createDefaultReposIfNeeded(techName, pic.serverId)
    63  		if err != nil {
    64  			return err
    65  		}
    66  		err = createProjectBuildConfigs(techName, pic.projectPath, pic.serverId)
    67  		if err != nil {
    68  			return err
    69  		}
    70  	}
    71  	// Create build config
    72  	if err = pic.createBuildConfig(); err != nil {
    73  		return
    74  	}
    75  
    76  	err = coreutils.PrintTable("", "", pic.createSummarizeMessage(technologiesMap), false)
    77  	return
    78  }
    79  
    80  func (pic *ProjectInitCommand) createSummarizeMessage(technologiesMap map[coreutils.Technology]bool) string {
    81  	return coreutils.PrintBold("This project is initialized!\n") +
    82  		coreutils.PrintBold("The project config is stored inside the .jfrog directory.") +
    83  		"\n\n" +
    84  		coreutils.PrintTitle("🔍 Scan the dependencies of this project for security vulnerabilities by running") +
    85  		"\n" +
    86  		"jf audit\n\n" +
    87  		coreutils.PrintTitle("📦 Scan any software package on you machine for security vulnerabilities by running") +
    88  		"\n" +
    89  		"jf scan path/to/dir/or/package\n\n" +
    90  		coreutils.PrintTitle("🐳 Scan any local docker image on you machine for security vulnerabilities by running") +
    91  		"\n" +
    92  		"jf docker scan <image name>:<image tag>\n\n" +
    93  		coreutils.PrintTitle("💻 If you're using VS Code, IntelliJ IDEA, WebStorm, PyCharm, Android Studio or GoLand") +
    94  		"\n" +
    95  		"Open the IDE 👉 Install the JFrog extension or plugin 👉 View the JFrog panel" +
    96  		"\n\n" +
    97  		pic.createBuildMessage(technologiesMap) +
    98  		coreutils.PrintTitle("📚 Read more using this link:") +
    99  		"\n" +
   100  		coreutils.PrintLink(coreutils.GettingStartedGuideUrl)
   101  }
   102  
   103  // Return a string message, which includes all the build and deployment commands, matching the technologiesMap sent.
   104  func (pic *ProjectInitCommand) createBuildMessage(technologiesMap map[coreutils.Technology]bool) string {
   105  	message := ""
   106  	for tech := range technologiesMap {
   107  		switch tech {
   108  		case coreutils.Maven:
   109  			message += "jf mvn install deploy\n"
   110  		case coreutils.Gradle:
   111  			message += "jf gradle artifactoryP\n"
   112  		case coreutils.Npm:
   113  			message += "jf npm install\n"
   114  			message += "jf npm publish\n"
   115  		case coreutils.Go:
   116  			message +=
   117  				"jf go build\n" +
   118  					"jf go-publish v1.0.0\n"
   119  		case coreutils.Pip, coreutils.Pipenv:
   120  			message +=
   121  				"jf " + string(tech) + " install\n" +
   122  					"jf rt upload path/to/package/file default-pypi-local" +
   123  					coreutils.PrintComment(" #Publish your "+string(tech)+" package") +
   124  					"\n"
   125  		case coreutils.Dotnet:
   126  			executableName := coreutils.Nuget
   127  			_, errNotFound := exec.LookPath("dotnet")
   128  			if errNotFound == nil {
   129  				// dotnet exists in path, So use it in the instruction message.
   130  				executableName = coreutils.Dotnet
   131  			}
   132  			message +=
   133  				"jf " + string(executableName) + " restore\n" +
   134  					"jf rt upload '*.nupkg'" + RepoDefaultName[tech][Virtual] + "\n"
   135  		}
   136  	}
   137  	if message != "" {
   138  		message = coreutils.PrintTitle("🚧 Build the code & deploy the packages by running") +
   139  			"\n" +
   140  			message +
   141  			"\n"
   142  	}
   143  	if ok := technologiesMap[coreutils.Docker]; ok {
   144  		baseurl := strings.TrimPrefix(strings.TrimSpace(pic.serverUrl), "https://")
   145  		baseurl = strings.TrimPrefix(baseurl, "http://")
   146  		imageUrl := path.Join(baseurl, DockerVirtualDefaultName, "<image>:<tag>")
   147  		message += coreutils.PrintTitle("🐳 Pull and push any docker image using Artifactory") +
   148  			"\n" +
   149  			"jf docker tag <image>:<tag> " + imageUrl + "\n" +
   150  			"jf docker push " + imageUrl + "\n" +
   151  			"jf docker pull " + imageUrl + "\n\n"
   152  	}
   153  
   154  	if message != "" {
   155  		message += coreutils.PrintTitle("📤 Publish the build-info to Artifactory") +
   156  			"\n" +
   157  			"jf rt build-publish\n\n"
   158  	}
   159  	return message
   160  }
   161  
   162  // Returns all detected technologies found in the project directory.
   163  // First, try to return only the technologies that detected according to files in the root directory.
   164  // In case no indication found in the root directory, the search continue recursively.
   165  func (pic *ProjectInitCommand) detectTechnologies() (technologiesMap map[coreutils.Technology]bool, err error) {
   166  	technologiesMap, err = coreutils.DetectTechnologies(pic.projectPath, false, false)
   167  	if err != nil {
   168  		return
   169  	}
   170  	// In case no technologies were detected in the root directory, try again recursively.
   171  	if len(technologiesMap) == 0 {
   172  		technologiesMap, err = coreutils.DetectTechnologies(pic.projectPath, false, true)
   173  		if err != nil {
   174  			return
   175  		}
   176  	}
   177  	return
   178  }
   179  
   180  type BuildConfigFile struct {
   181  	Version    int    `yaml:"version,omitempty"`
   182  	ConfigType string `yaml:"type,omitempty"`
   183  	BuildName  string `yaml:"name,omitempty"`
   184  }
   185  
   186  func (pic *ProjectInitCommand) createBuildConfig() error {
   187  	jfrogProjectDir := filepath.Join(pic.projectPath, ".jfrog", "projects")
   188  	if err := fileutils.CreateDirIfNotExist(jfrogProjectDir); err != nil {
   189  		return errorutils.CheckError(err)
   190  	}
   191  	configFilePath := filepath.Join(jfrogProjectDir, buildFileName)
   192  	projectDirName := filepath.Base(filepath.Dir(pic.projectPath))
   193  	buildConfigFile := &BuildConfigFile{Version: 1, ConfigType: "build", BuildName: projectDirName}
   194  	resBytes, err := yaml.Marshal(&buildConfigFile)
   195  	if err != nil {
   196  		return errorutils.CheckError(err)
   197  	}
   198  	return errorutils.CheckError(os.WriteFile(configFilePath, resBytes, 0644))
   199  }
   200  
   201  func createDefaultReposIfNeeded(tech coreutils.Technology, serverId string) error {
   202  	err := CreateDefaultLocalRepo(tech, serverId)
   203  	if err != nil {
   204  		return err
   205  	}
   206  	err = CreateDefaultRemoteRepo(tech, serverId)
   207  	if err != nil {
   208  		return err
   209  	}
   210  
   211  	return CreateDefaultVirtualRepo(tech, serverId)
   212  }
   213  
   214  func createProjectBuildConfigs(tech coreutils.Technology, projectPath string, serverId string) error {
   215  	jfrogProjectDir := filepath.Join(projectPath, ".jfrog", "projects")
   216  	if err := fileutils.CreateDirIfNotExist(jfrogProjectDir); err != nil {
   217  		return errorutils.CheckError(err)
   218  	}
   219  	techName := strings.ToLower(string(tech))
   220  	configFilePath := filepath.Join(jfrogProjectDir, techName+".yaml")
   221  	configFile := commonCommands.ConfigFile{
   222  		Version:    commonCommands.BuildConfVersion,
   223  		ConfigType: techName,
   224  	}
   225  	configFile.Resolver = project.Repository{ServerId: serverId}
   226  	configFile.Deployer = project.Repository{ServerId: serverId}
   227  	switch tech {
   228  	case coreutils.Maven:
   229  		configFile.Resolver.ReleaseRepo = MavenVirtualDefaultName
   230  		configFile.Resolver.SnapshotRepo = MavenVirtualDefaultName
   231  		configFile.Deployer.ReleaseRepo = MavenVirtualDefaultName
   232  		configFile.Deployer.SnapshotRepo = MavenVirtualDefaultName
   233  	case coreutils.Dotnet:
   234  		fallthrough
   235  	case coreutils.Nuget:
   236  		configFile.Resolver.NugetV2 = true
   237  		fallthrough
   238  	default:
   239  		configFile.Resolver.Repo = RepoDefaultName[tech][Virtual]
   240  		configFile.Deployer.Repo = RepoDefaultName[tech][Virtual]
   241  
   242  	}
   243  	resBytes, err := yaml.Marshal(&configFile)
   244  	if err != nil {
   245  		return errorutils.CheckError(err)
   246  	}
   247  
   248  	return errorutils.CheckError(os.WriteFile(configFilePath, resBytes, 0644))
   249  }
   250  
   251  func (pic *ProjectInitCommand) CommandName() string {
   252  	return "project_init"
   253  }
   254  
   255  func (pic *ProjectInitCommand) ServerDetails() (*config.ServerDetails, error) {
   256  	return config.GetSpecificConfig("", true, false)
   257  }