Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance local backend #2017

Merged
merged 43 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
c4bc2ba
makefile add exe suffix on build-* targets too
6543 Jul 19, 2023
e35a89e
make sure file endings are still LF encoded
6543 Jul 19, 2023
05d9622
wip
6543 Jul 19, 2023
6ab9399
Merge branch 'main' into enhance_local_backend_for_windows
6543 Jul 20, 2023
ce5d2c5
wip
6543 Jul 20, 2023
305dd6b
Merge branch 'enhance_local_backend_for_windows' of github.com:6543-f…
6543 Jul 20, 2023
be94759
init backend only once
6543 Jul 20, 2023
2e91ef4
better log
6543 Jul 20, 2023
74764aa
simplify and correct comment
6543 Jul 20, 2023
d9df770
add TODOs
6543 Jul 20, 2023
6d5216c
add TODOs
6543 Jul 20, 2023
7c46d41
make pluginGitBinary per workflow if non global
6543 Jul 20, 2023
9d6c934
nit
6543 Jul 20, 2023
a9af3ff
refactor logger
6543 Jul 20, 2023
33cc4df
load backend engien only once
6543 Jul 20, 2023
b98cb26
add TODOs
6543 Jul 20, 2023
86cf5a8
rename backend endigne interface funcs
6543 Jul 20, 2023
8274e62
pass a taskUUID to the backend
6543 Jul 20, 2023
85c1557
fix lint and more logging in trace mode
6543 Jul 20, 2023
9f53acd
Merge branch 'refactor_agent' into enhance_local_backend_for_windows
6543 Jul 20, 2023
34bb3ea
local backend: use taskUUID to store and load workflowState
6543 Jul 20, 2023
cb6efdc
make sure git store files with LF on windows too
6543 Jul 20, 2023
aa01bed
append .exe on windows as target for build targets too
6543 Jul 20, 2023
c174035
Merge remote-tracking branch 'own/enhance_windows_dev' into enhance_l…
6543 Jul 20, 2023
cf786bf
fix lint
6543 Jul 20, 2023
8ee93a9
gofumpt
6543 Jul 20, 2023
efd1554
fix
6543 Jul 20, 2023
1e4a222
make public clone work again
6543 Jul 20, 2023
338cabf
Merge branch 'main' into enhance_local_backend_for_windows
6543 Jul 20, 2023
1286590
Merge branch 'main' into enhance_local_backend_for_windows
6543 Jul 20, 2023
cd65c44
Merge branch 'main' into enhance_local_backend_for_windows
6543 Jul 20, 2023
2c476eb
Merge branch 'main' into enhance_local_backend_for_windows
6543 Jul 25, 2023
503ab90
wip
6543 Jul 25, 2023
1ed56c9
wip2
6543 Jul 26, 2023
3345675
works
6543 Jul 26, 2023
5f9b822
Merge branch 'main' into enhance_local_backend_for_windows
6543 Jul 27, 2023
409f100
Merge branch 'main' into enhance_local_backend_for_windows
6543 Jul 28, 2023
5500a97
Merge branch 'main' into enhance_local_backend_for_windows
6543 Aug 4, 2023
3bad2aa
code comment and refactor
6543 Aug 4, 2023
485ad54
Update pipeline/backend/local/const.go
6543 Aug 4, 2023
0875cca
Merge branch 'main' into enhance_local_backend_for_windows
qwerty287 Aug 7, 2023
f4a6e2f
Merge branch 'main' into enhance_local_backend_for_windows
6543 Aug 7, 2023
c1fffb1
Merge branch 'main' into enhance_local_backend_for_windows
6543 Aug 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions pipeline/backend/local/clone.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Copyright 2023 Woodpecker Authors
//
// 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 local

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"

"github.com/rs/zerolog/log"
"github.com/woodpecker-ci/woodpecker/pipeline/backend/types"
"github.com/woodpecker-ci/woodpecker/shared/constant"
)

// checkGitCloneCap check if we have the git binary on hand
func checkGitCloneCap() error {
_, err := exec.LookPath("git")
return err
}

// loadClone on backend start determine if there is a global plugin-git binary
func (e *local) loadClone() {
binary, err := exec.LookPath("plugin-git")
if err != nil || binary == "" {
// could not found global git plugin, just ignore it
return
}
e.pluginGitBinary = binary
}

// setupClone prepare the clone environment before exec
func (e *local) setupClone(state *workflowState) error {
if e.pluginGitBinary != "" {
state.pluginGitBinary = e.pluginGitBinary
return nil
}

log.Info().Msg("no global 'plugin-git' installed, try to download for current workflow")
state.pluginGitBinary = filepath.Join(state.homeDir, "plugin-git")
if runtime.GOOS == "windows" {
state.pluginGitBinary += ".exe"
}
return downloadLatestGitPluginBinary(state.pluginGitBinary)
}

// execClone executes a clone-step locally
func (e *local) execClone(ctx context.Context, step *types.Step, state *workflowState, env []string) error {
if err := e.setupClone(state); err != nil {
return fmt.Errorf("setup clone step failed: %w", err)
}

if err := checkGitCloneCap(); err != nil {
return fmt.Errorf("check for git clone capabilities failed: %w", err)
}

if step.Image != constant.DefaultCloneImage {
// TODO: write message into log
log.Warn().Msgf("clone step image '%s' does not match default git clone image. We ignore it assume git.", step.Image)
}

rmCmd, err := writeNetRC(step, state)
if err != nil {
return err
}

env = append(env, "CI_WORKSPACE="+state.workspaceDir)

// Prepare command
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
pwsh, err := exec.LookPath("powershell.exe")
if err != nil {
return err
}
cmd = exec.CommandContext(ctx, pwsh, "-Command", fmt.Sprintf("%s ; $code=$? ; %s ; if (!$code) {[Environment]::Exit(1)}", state.pluginGitBinary, rmCmd))
} else {
cmd = exec.CommandContext(ctx, "/bin/sh", "-c", fmt.Sprintf("%s ; $code=$? ; %s ; exit $code", state.pluginGitBinary, rmCmd))
}
cmd.Env = env
cmd.Dir = state.workspaceDir

// Get output and redirect Stderr to Stdout
e.output, _ = cmd.StdoutPipe()
cmd.Stderr = cmd.Stdout

state.stepCMDs[step.Name] = cmd

return cmd.Start()
}

// writeNetRC write a netrc file into the home dir of a given workflow state
func writeNetRC(step *types.Step, state *workflowState) (string, error) {
if step.Environment["CI_NETRC_MACHINE"] == "" {
return "", nil
}

file := filepath.Join(state.homeDir, ".netrc")
rmCmd := fmt.Sprintf("rm \"%s\"", file)
if runtime.GOOS == "windows" {
file = filepath.Join(state.homeDir, "_netrc")
rmCmd = fmt.Sprintf("del \"%s\"", file)
}

return rmCmd, os.WriteFile(file, []byte(fmt.Sprintf(
netrcFile,
step.Environment["CI_NETRC_MACHINE"],
step.Environment["CI_NETRC_USERNAME"],
step.Environment["CI_NETRC_PASSWORD"],
)), 0o600)
}

// downloadLatestGitPluginBinary download the latest plugin-git binary based on runtime OS and Arch
// and saves it to dest
func downloadLatestGitPluginBinary(dest string) error {
qwerty287 marked this conversation as resolved.
Show resolved Hide resolved
type asset struct {
Name string
BrowserDownloadURL string `json:"browser_download_url"`
}

type release struct {
Assets []asset
}

// get latest release
req, _ := http.NewRequest(http.MethodGet, "https://api.github.com/repos/woodpecker-ci/plugin-git/releases/latest", nil)
6543 marked this conversation as resolved.
Show resolved Hide resolved
req.Header.Set("Accept", "application/vnd.github+json")
req.Header.Set("X-GitHub-Api-Version", "2022-11-28")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("could not get latest release: %w", err)
}
raw, _ := io.ReadAll(resp.Body)
_ = resp.Body.Close()
var rel release
if err := json.Unmarshal(raw, &rel); err != nil {
return fmt.Errorf("could not unmarshal github response: %w", err)
}

for _, at := range rel.Assets {
if strings.Contains(at.Name, runtime.GOOS) && strings.Contains(at.Name, runtime.GOARCH) {
resp2, err := http.Get(at.BrowserDownloadURL)
if err != nil {
return fmt.Errorf("could not download plugin-git: %w", err)
}
defer resp2.Body.Close()

file, err := os.Create(dest)
if err != nil {
return fmt.Errorf("could not create plugin-git: %w", err)
}
defer file.Close()

if _, err := io.Copy(file, resp2.Body); err != nil {
return fmt.Errorf("could not download plugin-git: %w", err)
}
if err := os.Chmod(dest, 0o755); err != nil {
return err
}

// download successful
return nil
}
}

return fmt.Errorf("could not download plugin-git, binary for this os/arch not found")
}
38 changes: 38 additions & 0 deletions pipeline/backend/local/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2023 Woodpecker Authors
//
// 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 local

import "errors"

// notAllowedEnvVarOverwrites are all env vars that can not be overwritten by step config
var notAllowedEnvVarOverwrites = []string{
"CI_NETRC_MACHINE",
"CI_NETRC_USERNAME",
"CI_NETRC_PASSWORD",
"CI_SCRIPT",
"HOME",
"SHELL",
}

var (
ErrUnsupportedStepType = errors.New("unsupported step type")
ErrWorkflowStateNotFound = errors.New("workflow state not found")
)

const netrcFile = `
machine %s
login %s
password %s
`
Loading