mirror of
https://github.com/actions/actions-runner-controller.git
synced 2025-12-10 19:50:30 +00:00
e2e: Install and run workflow and verify the result (#661)
This enhances the E2E test suite introduced in #658 to also include the following steps: - Install GitHub Actions workflow - Trigger a workflow run via a git commit - Verify the workflow run result In the workflow, we use `kubectl create cm --from-literal` to create a configmap that contains an unique test ID. In the last step we obtain the configmap from within the E2E test and check the test ID to match the expected one. To install a GitHub Actions workflow, we clone a GitHub repository denoted by the TEST_REPO envvar, progmatically generate a few files with some Go code, run `git-add`, `git-commit`, and then `git-push` to actually push the files to the repository. A single commit containing an updated workflow definition and an updated file seems to run a workflow derived to the definition introduced in the commit, which was a bit surpirising and useful behaviour. At this point, the E2E test fully covers all the steps for a GitHub token based installation. We need to add scenarios for more deployment options, like GitHub App, RunnerDeployment, HRA, and so on. But each of them would worth another pull request.
This commit is contained in:
112
testing/git.go
Normal file
112
testing/git.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type GitRepo struct {
|
||||
Dir string
|
||||
Name string
|
||||
CommitMessage string
|
||||
Contents map[string][]byte
|
||||
}
|
||||
|
||||
func (g *GitRepo) Sync(ctx context.Context) error {
|
||||
repoName := g.Name
|
||||
if repoName == "" {
|
||||
return errors.New("missing git repo name")
|
||||
}
|
||||
|
||||
repoURL := fmt.Sprintf("git@github.com:%s.git", repoName)
|
||||
|
||||
if g.Dir == "" {
|
||||
return errors.New("missing git dir")
|
||||
}
|
||||
|
||||
dir, err := filepath.Abs(g.Dir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting abs path for %q: %w", g.Dir, err)
|
||||
}
|
||||
|
||||
if _, err := g.combinedOutput(g.gitCloneCmd(ctx, repoURL, dir)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for path, content := range g.Contents {
|
||||
absPath := filepath.Join(dir, path)
|
||||
|
||||
if err := os.WriteFile(absPath, content, 0755); err != nil {
|
||||
return fmt.Errorf("error writing %s: %w", path, err)
|
||||
}
|
||||
|
||||
if _, err := g.combinedOutput(g.gitAddCmd(ctx, dir, path)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := g.combinedOutput(g.gitDiffCmd(ctx, dir)); err != nil {
|
||||
if _, err := g.combinedOutput(g.gitCommitCmd(ctx, dir, g.CommitMessage)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := g.combinedOutput(g.gitPushCmd(ctx, dir)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GitRepo) gitCloneCmd(ctx context.Context, repo, dir string) *exec.Cmd {
|
||||
return exec.CommandContext(ctx, "git", "clone", repo, dir)
|
||||
}
|
||||
|
||||
func (g *GitRepo) gitDiffCmd(ctx context.Context, dir string) *exec.Cmd {
|
||||
cmd := exec.CommandContext(ctx, "git", "diff", "--exit-code", "--cached")
|
||||
cmd.Dir = dir
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (g *GitRepo) gitAddCmd(ctx context.Context, dir, path string) *exec.Cmd {
|
||||
cmd := exec.CommandContext(ctx, "git", "add", path)
|
||||
cmd.Dir = dir
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (g *GitRepo) gitCommitCmd(ctx context.Context, dir, msg string) *exec.Cmd {
|
||||
cmd := exec.CommandContext(ctx, "git", "commit", "-m", msg)
|
||||
cmd.Dir = dir
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (g *GitRepo) gitPushCmd(ctx context.Context, dir string) *exec.Cmd {
|
||||
cmd := exec.CommandContext(ctx, "git", "push", "origin", "main")
|
||||
cmd.Dir = dir
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (g *GitRepo) combinedOutput(cmd *exec.Cmd) (string, error) {
|
||||
o, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
args := append([]string{}, cmd.Args...)
|
||||
args[0] = cmd.Path
|
||||
|
||||
cs := strings.Join(args, " ")
|
||||
s := string(o)
|
||||
g.errorf("%s failed with output:\n%s", cs, s)
|
||||
|
||||
return s, err
|
||||
}
|
||||
|
||||
return string(o), nil
|
||||
}
|
||||
|
||||
func (g *GitRepo) errorf(f string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, f+"\n", args...)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package testing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -342,6 +343,55 @@ func (k *Cluster) RunKubectlEnsureNS(ctx context.Context, name string, cfg Kubec
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Cluster) GetClusterRoleBinding(ctx context.Context, name string, cfg KubectlConfig) (string, error) {
|
||||
o, err := k.combinedOutput(k.kubectlCmd(ctx, "get", []string{"clusterrolebinding", name}, cfg))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
func (k *Cluster) CreateClusterRoleBindingServiceAccount(ctx context.Context, name string, clusterrole string, sa string, cfg KubectlConfig) error {
|
||||
_, err := k.combinedOutput(k.kubectlCmd(ctx, "create", []string{"clusterrolebinding", name, "--clusterrole=" + clusterrole, "--serviceaccount=" + sa}, cfg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Cluster) GetCMLiterals(ctx context.Context, name string, cfg KubectlConfig) (map[string]string, error) {
|
||||
o, err := k.combinedOutput(k.kubectlCmd(ctx, "get", []string{"cm", name, "-o=json"}, cfg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cm struct {
|
||||
Data map[string]string `json:"data"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(o), &cm); err != nil {
|
||||
k.errorf("Failed unmarshalling this data to JSON:\n%s\n", o)
|
||||
|
||||
return nil, fmt.Errorf("unmarshalling json: %w", err)
|
||||
}
|
||||
|
||||
return cm.Data, nil
|
||||
}
|
||||
|
||||
func (k *Cluster) CreateCMLiterals(ctx context.Context, name string, literals map[string]string, cfg KubectlConfig) error {
|
||||
args := []string{"cm", name}
|
||||
|
||||
for k, v := range literals {
|
||||
args = append(args, fmt.Sprintf("--from-literal=%s=%s", k, v))
|
||||
}
|
||||
|
||||
if _, err := k.combinedOutput(k.kubectlCmd(ctx, "create", args, cfg)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Cluster) Apply(ctx context.Context, path string, cfg KubectlConfig) error {
|
||||
if _, err := k.combinedOutput(k.kubectlCmd(ctx, "apply", []string{"-f", path}, cfg)); err != nil {
|
||||
return err
|
||||
|
||||
46
testing/workflow.go
Normal file
46
testing/workflow.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package testing
|
||||
|
||||
const (
|
||||
ActionsCheckoutV2 = "actions/checkout@v2"
|
||||
)
|
||||
|
||||
type Workflow struct {
|
||||
Name string `json:"name"`
|
||||
On On `json:"on"`
|
||||
Jobs map[string]Job `json:"jobs"`
|
||||
}
|
||||
|
||||
type On struct {
|
||||
Push *Push `json:"push,omitempty"`
|
||||
WorkflowDispatch *WorkflowDispatch `json:"workflow_dispatch,omitempty"`
|
||||
}
|
||||
|
||||
type Push struct {
|
||||
Branches []string `json:"branches,omitempty"`
|
||||
}
|
||||
|
||||
type WorkflowDispatch struct {
|
||||
Inputs map[string]InputSpec `json:"inputs,omitempty"`
|
||||
}
|
||||
|
||||
type InputSpec struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
Default string `json:"default,omitempty"`
|
||||
}
|
||||
|
||||
type Job struct {
|
||||
RunsOn string `json:"runs-on"`
|
||||
Steps []Step `json:"steps"`
|
||||
}
|
||||
|
||||
type Step struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Uses string `json:"uses,omitempty"`
|
||||
With *With `json:"with,omitempty"`
|
||||
Run string `json:"run,omitempty"`
|
||||
}
|
||||
|
||||
type With struct {
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
Reference in New Issue
Block a user