common/support.go (405 lines of code) (raw):

//nolint:goconst package common import ( "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "fmt" "math/big" "net/http" "os" "os/exec" "path" "runtime" "strings" "sync/atomic" "time" yamlconv "sigs.k8s.io/yaml" ) const ( repoRemoteURL = "https://gitlab.com/gitlab-org/ci-cd/gitlab-runner-pipeline-tests/gitlab-test.git" repoRefType = RefTypeBranch repoSHA = "69b18e5ed3610cf646119c3e38f462c64ec462b7" repoBeforeSHA = "1ea27a9695f80d7816d9e8ce025d9b2df83d0dd7" repoRefName = "main" repoLFSSHA = "c8f2a61def956871b91f73fcd0c320afb257fd6e" repoLFSBeforeSHA = "86002a2304d89a193f91b8b0907c4cf2f95a6d28" repoLFSRefName = "add-lfs-object" repoSubmoduleLFSSHA = "86002a2304d89a193f91b8b0907c4cf2f95a6d28" repoSubmoduleLFSBeforeSHA = "1ea27a9695f80d7816d9e8ce025d9b2df83d0dd7" repoSubmoduleLFSRefName = "add-lfs-submodule" repoStepsSHA = "1142c6530a1eb81f0a5476db25fbfbf9a4e08f30" repoStepsBeforeSHA = "1ea27a9695f80d7816d9e8ce025d9b2df83d0dd7" repoStepsRefName = "add-steps" FilesLFSFile1LFSsize = int64(2097152) ) var ( gitLabComChain string gitLabComChainFetched atomic.Bool ) func GetGitInfo(url string) GitInfo { return GitInfo{ RepoURL: url, Sha: repoSHA, BeforeSha: repoBeforeSHA, Ref: repoRefName, RefType: repoRefType, Refspecs: []string{"+refs/heads/*:refs/origin/heads/*", "+refs/tags/*:refs/tags/*"}, } } func GetLFSGitInfo(url string) GitInfo { return GitInfo{ RepoURL: url, Sha: repoLFSSHA, BeforeSha: repoLFSBeforeSHA, Ref: repoLFSRefName, RefType: repoRefType, Refspecs: []string{"+refs/heads/*:refs/origin/heads/*", "+refs/tags/*:refs/tags/*"}, } } func GetSubmoduleLFSGitInfo(url string) GitInfo { return GitInfo{ RepoURL: url, Sha: repoSubmoduleLFSSHA, BeforeSha: repoSubmoduleLFSBeforeSHA, Ref: repoSubmoduleLFSRefName, RefType: repoRefType, Refspecs: []string{"+refs/heads/*:refs/origin/heads/*", "+refs/tags/*:refs/tags/*"}, } } func GetStepsGitInfo(url string) GitInfo { return GitInfo{ RepoURL: url, Sha: repoStepsSHA, BeforeSha: repoStepsBeforeSHA, Ref: repoStepsRefName, RefType: repoRefType, Refspecs: []string{"+refs/heads/*:refs/origin/heads/*", "+refs/tags/*:refs/tags/*"}, } } func GetSuccessfulBuild() (JobResponse, error) { return GetLocalBuildResponse("echo Hello World") } func GetSuccessfulMultilineCommandBuild() (JobResponse, error) { return GetLocalBuildResponse(`echo "Hello World"`) } func GetRemoteSuccessfulBuild() (JobResponse, error) { return GetRemoteBuildResponse("echo Hello World") } func GetRemoteSuccessfulLFSBuild() (JobResponse, error) { response, err := GetRemoteBuildResponse("echo Hello World") response.GitInfo = GetLFSGitInfo(repoRemoteURL) return response, err } func GetRemoteSuccessfulBuildWithAfterScript() (JobResponse, error) { jobResponse, err := GetRemoteBuildResponse("echo Hello World") jobResponse.Steps = append( jobResponse.Steps, Step{ Name: StepNameAfterScript, Script: []string{"echo Hello World"}, When: StepWhenAlways, }, ) return jobResponse, err } func GetRemoteSuccessfulBuildPrintVars(shell string, vars ...string) (JobResponse, error) { printVarsCmd := getShellPrintVars(shell, vars...) return GetRemoteBuildResponse(printVarsCmd...) } func GetRemoteSuccessfulMultistepBuild() (JobResponse, error) { jobResponse, err := GetRemoteBuildResponse("echo Hello World") if err != nil { return JobResponse{}, err } jobResponse.Steps = append( jobResponse.Steps, Step{ Name: "release", Script: []string{"echo Release"}, When: StepWhenOnSuccess, }, Step{ Name: StepNameAfterScript, Script: []string{"echo After Script"}, When: StepWhenAlways, }, ) return jobResponse, nil } func GetRemoteFailingMultistepBuild(failingStepName StepName) (JobResponse, error) { jobResponse, err := GetRemoteSuccessfulMultistepBuild() if err != nil { return JobResponse{}, err } for i, step := range jobResponse.Steps { if step.Name == failingStepName { jobResponse.Steps[i].Script = append(step.Script, "exit 1") //nolint:gocritic } } return jobResponse, nil } func GetRemoteFailingMultistepBuildPrintVars(shell string, fail bool, vars ...string) (JobResponse, error) { jobResponse, err := GetRemoteBuildResponse("echo 'Hello World'") if err != nil { return JobResponse{}, err } printVarsCmd := getShellPrintVars(shell, vars...) exitCommand := "exit 0" if fail { exitCommand = "exit 1" } jobResponse.Steps = append( jobResponse.Steps, Step{ Name: "env", Script: append(printVarsCmd, exitCommand), When: StepWhenOnSuccess, }, Step{ Name: StepNameAfterScript, Script: printVarsCmd, When: StepWhenAlways, }, ) return jobResponse, nil } func getShellPrintVars(shell string, vars ...string) []string { var envCommand []string var fmtStr string switch shell { case "powershell", "pwsh": fmtStr = "echo %s=$env:%s" default: fmtStr = "echo %s=$%s" } for _, v := range vars { envCommand = append(envCommand, fmt.Sprintf(fmtStr, v, v)) } return envCommand } func GetRemoteSuccessfulBuildWithDumpedVariables() (JobResponse, error) { variableName := "test_dump" variableValue := "test" response, err := GetRemoteBuildResponse( fmt.Sprintf("[[ \"${%s}\" != \"\" ]]", variableName), fmt.Sprintf("[[ $(cat $%s) == \"%s\" ]]", variableName, variableValue), ) if err != nil { return JobResponse{}, err } dumpedVariable := JobVariable{ Key: variableName, Value: variableValue, Internal: true, Public: true, File: true, } response.Variables = append(response.Variables, dumpedVariable) return response, nil } func GetFailedBuild() (JobResponse, error) { return GetLocalBuildResponse("exit 1") } func GetRemoteFailedBuild() (JobResponse, error) { return GetRemoteBuildResponse("exit 1") } func GetLongRunningBuild() (JobResponse, error) { return GetLocalBuildResponse("sleep 3600") } func GetRemoteLongRunningBuild() (JobResponse, error) { return GetRemoteBuildResponse("sleep 3600") } func GetRemoteLongRunningBuildWithAfterScript(shell string) (JobResponse, error) { var jobResponse JobResponse var err error jobResponse, err = GetRemoteLongRunningBuild() if err != nil { return JobResponse{}, err } switch shell { default: jobResponse.Steps = append(jobResponse.Steps, Step{ Name: StepNameAfterScript, Script: []string{ "echo \"Hello World from after_script\"", "echo \"job status $CI_JOB_STATUS\"", }, }) case "pwsh": jobResponse.Steps = append(jobResponse.Steps, Step{ Name: StepNameAfterScript, Script: []string{ "echo \"Hello World from after_script\"", "echo \"job status $env:CI_JOB_STATUS\"", }, }) case "cmd": jobResponse.Steps = append(jobResponse.Steps, Step{ Name: StepNameAfterScript, Script: []string{ "echo \"Hello World from after_script\"", "echo \"job status %CI_JOB_STATUS%\"", }, }) } return jobResponse, nil } func GetMultilineBashBuild() (JobResponse, error) { return GetRemoteBuildResponse(`if true; then echo 'Hello World' fi `) } func GetMultilineBashBuildPowerShell() (JobResponse, error) { return GetRemoteBuildResponse("if (0 -eq 0) {\n\recho \"Hello World\"\n\r}") } func GetRemoteBrokenTLSBuild() (JobResponse, error) { invalidCert, err := buildSnakeOilCert() if err != nil { return JobResponse{}, err } return getRemoteCustomTLSBuild(invalidCert) } func GetRemoteGitLabComTLSBuild() (JobResponse, error) { cert, err := getGitLabComTLSChain() if err != nil { return JobResponse{}, err } return getRemoteCustomTLSBuild(cert) } func getRemoteCustomTLSBuild(chain string) (JobResponse, error) { job, err := GetRemoteBuildResponse("echo Hello World") if err != nil { return JobResponse{}, err } job.TLSCAChain = chain job.Variables = append( job.Variables, JobVariable{Key: "GIT_STRATEGY", Value: "clone"}, JobVariable{Key: "GIT_SUBMODULE_STRATEGY", Value: "normal"}, ) return job, nil } func getBuildResponse(repoURL string, commands []string) JobResponse { return JobResponse{ Variables: JobVariables{ JobVariable{Key: "CI_JOB_TOKEN", Value: "test-job-token"}, }, GitInfo: GetGitInfo(repoURL), Steps: Steps{ Step{ Name: StepNameScript, Script: commands, When: StepWhenAlways, AllowFailure: false, }, }, RunnerInfo: RunnerInfo{ Timeout: DefaultTimeout, }, } } func getStepsBuildResponse(repoURL string, stepsYAML string) (JobResponse, error) { // steps come from GitLab in json, but it's a whole lot more convenient to write them in yaml in tests. Let's // support the latter and convert the yaml to json here so everything else downstream works as if the steps were // specified in json. stepsJSON, err := yamlconv.YAMLToJSON([]byte(stepsYAML)) if err != nil { return JobResponse{}, fmt.Errorf("converting json to yaml: %w", err) } return JobResponse{ GitInfo: GetStepsGitInfo(repoURL), Run: string(stepsJSON), Steps: Steps{Step{Name: StepNameRun}}, RunnerInfo: RunnerInfo{ Timeout: DefaultTimeout, }, }, nil } func GetRemoteStepsBuildResponse(stepsYAML string) (JobResponse, error) { return getStepsBuildResponse(repoRemoteURL, stepsYAML) } func GetRemoteBuildResponse(commands ...string) (JobResponse, error) { return getBuildResponse(repoRemoteURL, commands), nil } func GetLocalBuildResponse(commands ...string) (JobResponse, error) { localRepoURL, err := getLocalRepoURL() if err != nil { if os.IsNotExist(err) { panic("Local repo not found, please run `make development_setup`") } return JobResponse{}, err } return getBuildResponse(localRepoURL, commands), nil } func getLocalRepoURL() (string, error) { _, filename, _, _ := runtime.Caller(0) //nolint:dogsled directory := path.Dir(filename) if strings.Contains(directory, "_test/_obj_test") { pwd, err := os.Getwd() if err != nil { return "", err } directory = pwd } localRepoURL := path.Clean(directory + "/../tmp/gitlab-test/.git") _, err := os.Stat(localRepoURL) if err != nil { return "", err } return localRepoURL, nil } func RunLocalRepoGitCommand(arguments ...string) error { url, err := getLocalRepoURL() if err != nil { return err } cmd := exec.Command("git", arguments...) cmd.Dir = path.Dir(url) return cmd.Run() } func buildSnakeOilCert() (string, error) { priv, err := rsa.GenerateKey(rand.Reader, 1024) if err != nil { return "", err } notBefore := time.Now() notAfter := notBefore.Add(time.Hour) template := x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ Organization: []string{"Snake Oil Co"}, }, NotBefore: notBefore, NotAfter: notAfter, IsCA: true, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, } derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) if err != nil { return "", err } certificate := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) return string(certificate), nil } func getGitLabComTLSChain() (string, error) { if gitLabComChainFetched.Load() { return gitLabComChain, nil } resp, err := http.Head("https://gitlab.com/users/sign_in") if err != nil { return "", err } defer func() { _ = resp.Body.Close() }() var buff strings.Builder for _, certs := range resp.TLS.VerifiedChains { for _, cert := range certs { err = pem.Encode(&buff, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) if err != nil { return "", err } } } gitLabComChain = buff.String() gitLabComChainFetched.Store(true) return gitLabComChain, nil }