github.com/jfrog/jfrog-cli-core@v1.12.1/artifactory/commands/dotnet/dotnetcommand.go (about) 1 package dotnet 2 3 import ( 4 "fmt" 5 "net/url" 6 "os" 7 "path" 8 "path/filepath" 9 "strings" 10 11 "github.com/jfrog/gofrog/io" 12 "github.com/jfrog/jfrog-cli-core/artifactory/utils" 13 "github.com/jfrog/jfrog-cli-core/artifactory/utils/dotnet" 14 "github.com/jfrog/jfrog-cli-core/artifactory/utils/dotnet/solution" 15 "github.com/jfrog/jfrog-cli-core/utils/config" 16 coreutils "github.com/jfrog/jfrog-cli-core/utils/coreutils" 17 "github.com/jfrog/jfrog-client-go/auth" 18 "github.com/jfrog/jfrog-client-go/utils/errorutils" 19 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 20 "github.com/jfrog/jfrog-client-go/utils/log" 21 ) 22 23 const SourceName = "JFrogCli" 24 25 type DotnetCommand struct { 26 toolchainType dotnet.ToolchainType 27 subCommand string 28 argAndFlags []string 29 repoName string 30 solutionPath string 31 useNugetAddSource bool 32 useNugetV2 bool 33 buildConfiguration *utils.BuildConfiguration 34 serverDetails *config.ServerDetails 35 // Indicates the command is run through the legacy syntax, before the 'native' syntax introduction. 36 legacy bool 37 } 38 39 func (dc *DotnetCommand) SetServerDetails(serverDetails *config.ServerDetails) *DotnetCommand { 40 dc.serverDetails = serverDetails 41 return dc 42 } 43 44 func (dc *DotnetCommand) SetBuildConfiguration(buildConfiguration *utils.BuildConfiguration) *DotnetCommand { 45 dc.buildConfiguration = buildConfiguration 46 return dc 47 } 48 49 func (dc *DotnetCommand) SetToolchainType(toolchainType dotnet.ToolchainType) *DotnetCommand { 50 dc.toolchainType = toolchainType 51 return dc 52 } 53 54 func (dc *DotnetCommand) SetSolutionPath(solutionPath string) *DotnetCommand { 55 dc.solutionPath = solutionPath 56 return dc 57 } 58 59 func (dc *DotnetCommand) SetRepoName(repoName string) *DotnetCommand { 60 dc.repoName = repoName 61 return dc 62 } 63 64 func (dc *DotnetCommand) SetUseNugetV2(useNugetV2 bool) *DotnetCommand { 65 dc.useNugetV2 = useNugetV2 66 return dc 67 } 68 69 func (dc *DotnetCommand) SetArgAndFlags(argAndFlags []string) *DotnetCommand { 70 dc.argAndFlags = argAndFlags 71 return dc 72 } 73 74 func (dc *DotnetCommand) SetBasicCommand(subCommand string) *DotnetCommand { 75 dc.subCommand = subCommand 76 return dc 77 } 78 79 func (dc *DotnetCommand) SetUseNugetAddSource(useNugetAddSource bool) *DotnetCommand { 80 dc.useNugetAddSource = useNugetAddSource 81 return dc 82 } 83 84 func (dc *DotnetCommand) ServerDetails() (*config.ServerDetails, error) { 85 return dc.serverDetails, nil 86 } 87 88 func (dc *DotnetCommand) CommandName() string { 89 return "rt_" + dc.toolchainType.String() 90 } 91 92 // Exec all consume type nuget commands, install, update, add, restore. 93 func (dc *DotnetCommand) Exec() error { 94 log.Info("Running " + dc.toolchainType.String() + "...") 95 // Use temp dir to save config file, so that config will be removed at the end. 96 tempDirPath, err := fileutils.CreateTempDir() 97 if err != nil { 98 return err 99 } 100 defer fileutils.RemoveTempDir(tempDirPath) 101 102 dc.solutionPath, err = changeWorkingDir(dc.solutionPath) 103 if err != nil { 104 return err 105 } 106 107 err = dc.prepareAndRunCmd(tempDirPath) 108 if err != nil { 109 return err 110 } 111 112 isCollectBuildInfo := len(dc.buildConfiguration.BuildName) > 0 && len(dc.buildConfiguration.BuildNumber) > 0 113 if !isCollectBuildInfo { 114 return nil 115 } 116 117 slnFile, err := dc.updateSolutionPathAndGetFileName() 118 if err != nil { 119 return err 120 } 121 sol, err := solution.Load(dc.solutionPath, slnFile) 122 if err != nil { 123 return err 124 } 125 126 if err = utils.SaveBuildGeneralDetails(dc.buildConfiguration.BuildName, dc.buildConfiguration.BuildNumber, dc.buildConfiguration.Project); err != nil { 127 return err 128 } 129 buildInfo, err := sol.BuildInfo(dc.buildConfiguration.Module) 130 if err != nil { 131 return err 132 } 133 return utils.SaveBuildInfo(dc.buildConfiguration.BuildName, dc.buildConfiguration.BuildNumber, dc.buildConfiguration.Project, buildInfo) 134 } 135 136 func (dc *DotnetCommand) updateSolutionPathAndGetFileName() (string, error) { 137 // The path argument wasn't provided, sln file will be searched under working directory. 138 if len(dc.argAndFlags) == 0 || strings.HasPrefix(dc.argAndFlags[0], "-") { 139 return "", nil 140 } 141 cmdFirstArg := dc.argAndFlags[0] 142 exist, err := fileutils.IsDirExists(cmdFirstArg, false) 143 if err != nil { 144 return "", err 145 } 146 // The path argument is a directory. sln/project file will be searched under this directory. 147 if exist { 148 dc.updateSolutionPath(cmdFirstArg) 149 return "", err 150 } 151 exist, err = fileutils.IsFileExists(cmdFirstArg, false) 152 if err != nil { 153 return "", err 154 } 155 if exist { 156 // The path argument is a .sln file. 157 if strings.HasSuffix(cmdFirstArg, ".sln") { 158 dc.updateSolutionPath(filepath.Dir(cmdFirstArg)) 159 return filepath.Base(cmdFirstArg), nil 160 } 161 // The path argument is a .*proj/packages.config file. 162 if strings.HasSuffix(filepath.Ext(cmdFirstArg), "proj") || strings.HasSuffix(cmdFirstArg, "packages.config") { 163 dc.updateSolutionPath(filepath.Dir(cmdFirstArg)) 164 } 165 } 166 return "", nil 167 } 168 169 func (dc *DotnetCommand) updateSolutionPath(slnRootPath string) { 170 if filepath.IsAbs(slnRootPath) { 171 dc.solutionPath = slnRootPath 172 } else { 173 dc.solutionPath = filepath.Join(dc.solutionPath, slnRootPath) 174 } 175 } 176 177 // Changes the working directory if provided. 178 // Returns the path to the solution 179 func changeWorkingDir(newWorkingDir string) (string, error) { 180 var err error 181 if newWorkingDir != "" { 182 err = os.Chdir(newWorkingDir) 183 } else { 184 newWorkingDir, err = os.Getwd() 185 } 186 187 return newWorkingDir, errorutils.CheckError(err) 188 } 189 190 // Prepares the nuget configuration file within the temp directory 191 // Runs NuGet itself with the arguments and flags provided. 192 func (dc *DotnetCommand) prepareAndRunCmd(configDirPath string) error { 193 cmd, err := dc.createCmd() 194 if err != nil { 195 return err 196 } 197 // To prevent NuGet prompting for credentials 198 err = os.Setenv("NUGET_EXE_NO_PROMPT", "true") 199 if err != nil { 200 return errorutils.CheckError(err) 201 } 202 203 err = dc.prepareConfigFile(cmd, configDirPath) 204 if err != nil { 205 return err 206 } 207 err = io.RunCmd(cmd) 208 if err != nil { 209 return err 210 } 211 212 return nil 213 } 214 215 // Checks if the user provided input such as -configfile flag or -Source flag. 216 // If those flags were provided, NuGet will use the provided configs (default config file or the one with -configfile) 217 // If neither provided, we are initializing our own config. 218 func (dc *DotnetCommand) prepareConfigFile(cmd *dotnet.Cmd, configDirPath string) error { 219 cmdFlag := cmd.GetToolchain().GetTypeFlagPrefix() + "configfile" 220 currentConfigPath, err := getFlagValueIfExists(cmdFlag, cmd) 221 if err != nil { 222 return err 223 } 224 if currentConfigPath != "" { 225 return nil 226 } 227 228 cmdFlag = cmd.GetToolchain().GetTypeFlagPrefix() + "source" 229 sourceCommandValue, err := getFlagValueIfExists(cmdFlag, cmd) 230 if err != nil { 231 return err 232 } 233 if sourceCommandValue != "" { 234 return nil 235 } 236 237 configFile, err := dc.InitNewConfig(configDirPath) 238 if err == nil { 239 cmd.CommandFlags = append(cmd.CommandFlags, cmd.GetToolchain().GetTypeFlagPrefix()+"configfile", configFile.Name()) 240 } 241 return err 242 } 243 244 // Returns the value of the flag if exists 245 func getFlagValueIfExists(cmdFlag string, cmd *dotnet.Cmd) (string, error) { 246 for i := 0; i < len(cmd.CommandFlags); i++ { 247 if !strings.EqualFold(cmd.CommandFlags[i], cmdFlag) { 248 continue 249 } 250 if i+1 == len(cmd.CommandFlags) { 251 return "", errorutils.CheckError(errorutils.CheckError(fmt.Errorf(cmdFlag, " flag was provided without value"))) 252 } 253 return cmd.CommandFlags[i+1], nil 254 } 255 256 return "", nil 257 } 258 259 // Got to here, means that neither of the flags provided and we need to init our own config. 260 func (dc *DotnetCommand) InitNewConfig(configDirPath string) (configFile *os.File, err error) { 261 // Initializing a new NuGet config file that NuGet will use into a temp file 262 configFile, err = os.CreateTemp(configDirPath, "jfrog.cli.nuget.") 263 if errorutils.CheckError(err) != nil { 264 return 265 } 266 log.Debug("Nuget config file created at:", configFile.Name()) 267 defer configFile.Close() 268 269 // We will prefer to write the NuGet configuration using the `nuget add source` command (addSourceToNugetConfig) 270 // Currently the NuGet configuration utility doesn't allow setting protocolVersion. 271 // Until that is supported, the templated method must be used. 272 err = dc.addSourceToNugetTemplate(configFile) 273 return 274 } 275 276 // Set Artifactory repo as source using the toolchain's `add source` command 277 func (dc *DotnetCommand) AddNugetAuthToConfig(cmdType dotnet.ToolchainType, configFile *os.File, sourceUrl, user, password string) error { 278 content := dotnet.ConfigFileTemplate 279 _, err := configFile.WriteString(content) 280 if err != nil { 281 return errorutils.CheckError(err) 282 } 283 // We need to close the config file to let the toolchain modify it. 284 configFile.Close() 285 return addSourceToNugetConfig(cmdType, configFile.Name(), sourceUrl, user, password) 286 } 287 288 // Runs nuget sources add command 289 func addSourceToNugetConfig(cmdType dotnet.ToolchainType, configFileName, sourceUrl, user, password string) error { 290 cmd, err := dotnet.CreateDotnetAddSourceCmd(cmdType, sourceUrl) 291 if err != nil { 292 return err 293 } 294 295 flagPrefix := cmdType.GetTypeFlagPrefix() 296 cmd.CommandFlags = append(cmd.CommandFlags, flagPrefix+"configfile", configFileName) 297 cmd.CommandFlags = append(cmd.CommandFlags, flagPrefix+"name", SourceName) 298 cmd.CommandFlags = append(cmd.CommandFlags, flagPrefix+"username", user) 299 cmd.CommandFlags = append(cmd.CommandFlags, flagPrefix+"password", password) 300 output, err := io.RunCmdOutput(cmd) 301 log.Debug("Running command: Add sources. Output:", output) 302 return err 303 } 304 305 // Adds a source to the nuget config template 306 func (dc *DotnetCommand) addSourceToNugetTemplate(configFile *os.File) error { 307 sourceUrl, user, password, err := dc.getSourceDetails() 308 if err != nil { 309 return err 310 } 311 312 // Specify the protocolVersion 313 protoVer := "3" 314 if dc.useNugetV2 { 315 protoVer = "2" 316 } 317 318 // Format the templates 319 _, err = fmt.Fprintf(configFile, dotnet.ConfigFileFormat, sourceUrl, protoVer, user, password) 320 return err 321 } 322 323 func (dc *DotnetCommand) getSourceDetails() (sourceURL, user, password string, err error) { 324 var u *url.URL 325 u, err = url.Parse(dc.serverDetails.ArtifactoryUrl) 326 if errorutils.CheckError(err) != nil { 327 return 328 } 329 nugetApi := "api/nuget/v3" 330 if dc.useNugetV2 { 331 nugetApi = "api/nuget" 332 } 333 u.Path = path.Join(u.Path, nugetApi, dc.repoName) 334 sourceURL = u.String() 335 336 user = dc.serverDetails.User 337 password = dc.serverDetails.Password 338 // If access-token is defined, extract user from it. 339 serverDetails, err := dc.ServerDetails() 340 if errorutils.CheckError(err) != nil { 341 return 342 } 343 if serverDetails.AccessToken != "" { 344 log.Debug("Using access-token details for nuget authentication.") 345 user, err = auth.ExtractUsernameFromAccessToken(serverDetails.AccessToken) 346 if err != nil { 347 return 348 } 349 password = serverDetails.AccessToken 350 } 351 return 352 } 353 354 func (dc *DotnetCommand) createCmd() (*dotnet.Cmd, error) { 355 c, err := dotnet.NewToolchainCmd(dc.toolchainType) 356 if err != nil { 357 return nil, err 358 } 359 if dc.legacy { 360 return dc.createLegacyCmd(c) 361 } 362 if dc.subCommand != "" { 363 c.Command = append(c.Command, strings.Split(dc.subCommand, " ")...) 364 } 365 c.CommandFlags = dc.argAndFlags 366 return c, nil 367 } 368 369 // In the legacy syntax, parsing args is required since they might include environment variables, or need escaping. 370 func (dc *DotnetCommand) createLegacyCmd(c *dotnet.Cmd) (*dotnet.Cmd, error) { 371 if dc.subCommand != "" { 372 subCommand, err := coreutils.ParseArgs(strings.Split(dc.subCommand, " ")) 373 if err != nil { 374 return nil, errorutils.CheckError(err) 375 } 376 c.Command = append(c.Command, subCommand...) 377 } 378 var err error 379 if len(dc.argAndFlags) > 0 { 380 c.CommandFlags, err = coreutils.ParseArgs(dc.argAndFlags) 381 } 382 return c, errorutils.CheckError(err) 383 }