internal/platform/qdenv/qdenv.go (255 lines of code) (raw):
/*
* Copyright 2021-2024 JetBrains s.r.o.
*
* 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
*
* https://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 qdenv
import (
"fmt"
"net/url"
"os"
"strings"
"github.com/JetBrains/qodana-cli/internal/platform/strutil"
"github.com/JetBrains/qodana-cli/internal/platform/version"
cienvironment "github.com/cucumber/ci-environment/go"
log "github.com/sirupsen/logrus"
)
const (
// QodanaLicenseOnlyToken !!! QODANA_LICENSE_ONLY_TOKEN is accessed only by env !!!
QodanaLicenseOnlyToken = "QODANA_LICENSE_ONLY_TOKEN"
QodanaRemoteUrl = "QODANA_REMOTE_URL"
QodanaDockerEnv = "QODANA_DOCKER"
QodanaToolEnv = "QODANA_TOOL"
QodanaConfEnv = "QODANA_CONF"
QodanaClearKeyring = "QODANA_CLEAR_KEYRING"
QodanaEnv = "QODANA_ENV"
QodanaJobUrl = "QODANA_JOB_URL"
QodanaBranch = "QODANA_BRANCH"
QodanaRevision = "QODANA_REVISION"
QodanaCliContainerName = "QODANA_CLI_CONTAINER_NAME"
QodanaCliContainerKeep = "QODANA_CLI_CONTAINER_KEEP"
QodanaDistEnv = "QODANA_DIST"
QodanaCorettoSdk = "QODANA_CORETTO_SDK"
AndroidSdkRoot = "ANDROID_SDK_ROOT"
QodanaLicense = "QODANA_LICENSE"
QodanaTreatAsRelease = "QODANA_TREAT_AS_RELEASE"
QodanaProjectIdHash = "QODANA_PROJECT_ID_HASH"
QodanaOrganisationIdHash = "QODANA_ORGANISATION_ID_HASH"
QodanaNugetUrl = "QODANA_NUGET_URL"
QodanaNugetUser = "QODANA_NUGET_USER"
QodanaNugetPassword = "QODANA_NUGET_PASSWORD"
QodanaNugetName = "QODANA_NUGET_NAME"
GemHome = "GEM_HOME"
BundleAppConfig = "BUNDLE_APP_CONFIG"
QodanaCloudRequestCooldownEnv = "QODANA_CLOUD_REQUEST_COOLDOWN"
QodanaCloudRequestTimeoutEnv = "QODANA_CLOUD_REQUEST_TIMEOUT"
QodanaCloudRequestRetriesEnv = "QODANA_CLOUD_REQUEST_RETRIES"
// QodanaEndpointEnv QodanaToken properties accessed only by GetQodanaGlobalEnv
QodanaEndpointEnv = "QODANA_ENDPOINT"
QodanaToken = "QODANA_TOKEN"
)
type qodanaGlobalEnv struct {
env map[string]string
}
// globalEnv some env values which are relevant both for CLI and docker runs are stored and accessed with global variable
var globalEnv *qodanaGlobalEnv
func InitializeQodanaGlobalEnv(provider EnvProvider) {
globalEnv = &qodanaGlobalEnv{
env: map[string]string{
QodanaEndpointEnv: GetEnvWithOsEnv(provider, QodanaEndpointEnv),
QodanaToken: GetEnvWithOsEnv(provider, QodanaToken),
},
}
}
func GetQodanaGlobalEnv(key string) string {
if globalEnv == nil {
log.Fatal("Qodana inner env is not initialized")
}
if value, exists := globalEnv.env[key]; exists {
return value
}
return ""
}
type EnvProvider interface {
Env() []string
}
type emptyEnvProvider struct {
}
func (e emptyEnvProvider) Env() []string {
return []string{}
}
func EmptyEnvProvider() EnvProvider {
return emptyEnvProvider{}
}
func GetEnv(provider EnvProvider, key string) string {
for _, e := range provider.Env() {
if strings.HasPrefix(e, key) {
return strings.TrimPrefix(e, key+"=")
}
}
return ""
}
func GetEnvWithOsEnv(provider EnvProvider, key string) string {
envFromProvider := GetEnv(provider, key)
if envFromProvider != "" {
return envFromProvider
}
return os.Getenv(key)
}
func SetEnv(key string, value string) {
log.Debugf("Setting %s=%s", key, value)
if os.Getenv(key) == "" && value != "" {
err := os.Setenv(key, value)
if err != nil {
return
}
log.Debugf("Set %s=%s", key, value)
}
}
func IsContainer() bool {
return os.Getenv(QodanaDockerEnv) != ""
}
// ExtractQodanaEnvironment extracts Qodana environment variables from the current environment.
func ExtractQodanaEnvironment(setEnvironmentFunc func(string, string)) {
if license := os.Getenv(QodanaLicense); license != "" {
setEnvironmentFunc(QodanaLicense, license)
}
if endpoint := GetQodanaGlobalEnv(QodanaEndpointEnv); endpoint != "" {
setEnvironmentFunc(QodanaEndpointEnv, endpoint)
}
if remoteUrl := os.Getenv(QodanaRemoteUrl); remoteUrl != "" {
setEnvironmentFunc(QodanaRemoteUrl, remoteUrl)
}
if branch := os.Getenv(QodanaBranch); branch != "" {
setEnvironmentFunc(QodanaBranch, branch)
}
if revision := os.Getenv(QodanaRevision); revision != "" {
setEnvironmentFunc(QodanaRevision, revision)
}
ci := cienvironment.DetectCIEnvironment()
qEnv := "cli"
if ci != nil {
qEnv = GetCIName(ci)
setEnvironmentFunc(QodanaJobUrl, validateJobUrl(ci.URL, qEnv))
if ci.Git != nil {
setEnvironmentFunc(QodanaRemoteUrl, validateRemoteUrl(ci.Git.Remote, qEnv))
setEnvironmentFunc(QodanaBranch, validateBranch(ci.Git.Branch, qEnv))
setEnvironmentFunc(QodanaRevision, ci.Git.Revision)
}
setEnvironmentFunc(QodanaNugetUrl, os.Getenv(QodanaNugetUrl))
setEnvironmentFunc(QodanaNugetUser, os.Getenv(QodanaNugetUser))
setEnvironmentFunc(QodanaNugetPassword, os.Getenv(QodanaNugetPassword))
setEnvironmentFunc(QodanaNugetName, os.Getenv(QodanaNugetName))
} else if space := os.Getenv("JB_SPACE_API_URL"); space != "" {
qEnv = "space"
setEnvironmentFunc(QodanaJobUrl, os.Getenv("JB_SPACE_EXECUTION_URL"))
setEnvironmentFunc(QodanaRemoteUrl, getSpaceRemoteUrl())
setEnvironmentFunc(QodanaBranch, os.Getenv("JB_SPACE_GIT_BRANCH"))
setEnvironmentFunc(QodanaRevision, os.Getenv("JB_SPACE_GIT_REVISION"))
} else if IsBitBucket() {
qEnv = "bitbucket"
setEnvironmentFunc(QodanaJobUrl, GetBitBucketJobUrl())
}
setEnvironmentFunc(QodanaEnv, fmt.Sprintf("%s:%s", qEnv, version.Version))
}
func GetCIName(ci *cienvironment.CiEnvironment) string {
return strings.ReplaceAll(strings.ToLower(ci.Name), " ", "-")
}
func validateRemoteUrl(remote string, qEnv string) string {
if strings.HasPrefix(qEnv, "space") {
return getSpaceRemoteUrl()
}
_, err := url.ParseRequestURI(remote)
if remote == "" || err != nil {
log.Warnf(
"Unable to parse git remote URL %s, set %s env variable for proper qodana.cloud reporting",
remote,
QodanaRemoteUrl,
)
return ""
}
return remote
}
func validateBranch(branch string, env string) string {
if branch == "" {
switch env {
case "github-actions":
branch = os.Getenv("GITHUB_REF_NAME")
case "azure-pipelines":
branch = os.Getenv("BUILD_SOURCEBRANCHNAME")
case "jenkins":
branch = os.Getenv("GIT_BRANCH")
}
}
if branch == "" {
log.Warnf("Unable to parse git branch, set %s env variable for proper qodana.cloud reporting", QodanaBranch)
return ""
}
return branch
}
func validateJobUrl(ciUrl string, qEnv string) string {
if strings.HasPrefix(qEnv, "azure") { // temporary workaround for Azure Pipelines
return getAzureJobUrl()
}
_, err := url.ParseRequestURI(ciUrl)
if err != nil {
return ""
}
return ciUrl
}
func UnsetRubyVariables() {
variables := []string{GemHome, BundleAppConfig}
for _, variable := range variables {
if err := os.Unsetenv(variable); err != nil {
log.Warnf("couldn't unset env variable %s", err.Error())
}
}
}
// getAzureJobUrl returns the Azure Pipelines job URL.
func getAzureJobUrl() string {
if server := os.Getenv("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI"); server != "" {
return strings.Join(
[]string{
server,
os.Getenv("SYSTEM_TEAMPROJECT"),
"/_build/results?buildId=",
os.Getenv("BUILD_BUILDID"),
}, "",
)
}
return ""
}
// getSpaceJobUrl returns the Space job URL.
func getSpaceRemoteUrl() string {
if server := os.Getenv("JB_SPACE_API_URL"); server != "" {
return strings.Join(
[]string{
"ssh://git@git.",
server,
"/",
os.Getenv("JB_SPACE_PROJECT_KEY"),
"/",
os.Getenv("JB_SPACE_GIT_REPOSITORY_NAME"),
".git",
}, "",
)
}
return ""
}
// IsGitLab returns true if the current environment is GitLab CI.
func IsGitLab() bool {
return os.Getenv("GITLAB_CI") == "true"
}
// IsBitBucket returns true if the current environment is BitBucket Pipelines.
func IsBitBucket() bool {
return os.Getenv("BITBUCKET_PIPELINE_UUID") != ""
}
// IsBitBucketPipe returns true if the current environment is in a working BitBucket Pipe.
func IsBitBucketPipe() bool {
return os.Getenv("BITBUCKET_PIPE_STORAGE_DIR") != "" || os.Getenv("BITBUCKET_PIPE_SHARED_STORAGE_DIR") != ""
}
func GetBitBucketJobUrl() string {
return fmt.Sprintf(
"https://bitbucket.org/%s/pipelines/results/%s",
GetBitBucketRepoFullName(),
os.Getenv("BITBUCKET_BUILD_NUMBER"),
)
}
// GetBitBucketCommit returns the BitBucket commit hash.
func GetBitBucketCommit() string {
return os.Getenv("BITBUCKET_COMMIT")
}
// GetBitBucketRepoFullName returns the BitBucket repository slug in the form "owner/repo".
func GetBitBucketRepoFullName() string {
return os.Getenv("BITBUCKET_REPO_FULL_NAME")
}
func GetBitBucketRepoOwner() string {
return strutil.SafeSplit(GetBitBucketRepoFullName(), "/", 0)
}
func GetBitBucketRepoName() string {
return strutil.SafeSplit(GetBitBucketRepoFullName(), "/", 1)
}