Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions .github/workflows/smoke-test-s390x.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: s390x Runner Smoke Test
on: workflow_dispatch
jobs:
smoke-test:
runs-on: [self-hosted, S390X]
steps:
- name: Check architecture
run: |
echo "Architecture: $(uname -m)"
cat /etc/os-release | grep PRETTY_NAME
echo "Runner is alive on $(arch)"
7 changes: 6 additions & 1 deletion cmd/mapt/cmd/ibmcloud/hosts/ibm-power.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package hosts

import (
"github.com/redhat-developer/mapt/cmd/mapt/cmd/params"
"github.com/redhat-developer/mapt/pkg/integrations/github"
"github.com/redhat-developer/mapt/pkg/integrations/gitlab"
maptContext "github.com/redhat-developer/mapt/pkg/manager/context"
ibmpower "github.com/redhat-developer/mapt/pkg/provider/ibmcloud/action/ibm-power"
Expand Down Expand Up @@ -43,6 +44,10 @@ func ibmPowerCreate() *cobra.Command {
if err := viper.BindPFlags(cmd.Flags()); err != nil {
return err
}
ghRunnerArgs := params.GithubRunnerArgs()
if ghRunnerArgs != nil {
ghRunnerArgs.Arch = &github.Ppc64le
}
return ibmpower.New(
&maptContext.ContextArgs{
Context: cmd.Context(),
Expand All @@ -52,7 +57,7 @@ func ibmPowerCreate() *cobra.Command {
Debug: viper.IsSet(params.Debug),
DebugLevel: viper.GetUint(params.DebugLevel),
CirrusPWArgs: params.CirrusPersistentWorkerArgs(),
GHRunnerArgs: params.GithubRunnerArgs(),
GHRunnerArgs: ghRunnerArgs,
GLRunnerArgs: params.GitLabRunnerArgs(&gitlab.Ppc64le),
Tags: viper.GetStringMapString(params.Tags),
},
Expand Down
7 changes: 6 additions & 1 deletion cmd/mapt/cmd/ibmcloud/hosts/ibm-z.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package hosts

import (
"github.com/redhat-developer/mapt/cmd/mapt/cmd/params"
"github.com/redhat-developer/mapt/pkg/integrations/github"
"github.com/redhat-developer/mapt/pkg/integrations/gitlab"
maptContext "github.com/redhat-developer/mapt/pkg/manager/context"
ibmz "github.com/redhat-developer/mapt/pkg/provider/ibmcloud/action/ibm-z"
Expand Down Expand Up @@ -43,6 +44,10 @@ func ibmZCreate() *cobra.Command {
if err := viper.BindPFlags(cmd.Flags()); err != nil {
return err
}
ghRunnerArgs := params.GithubRunnerArgs()
if ghRunnerArgs != nil {
ghRunnerArgs.Arch = &github.S390x
}
return ibmz.New(
&maptContext.ContextArgs{
Context: cmd.Context(),
Expand All @@ -52,7 +57,7 @@ func ibmZCreate() *cobra.Command {
Debug: viper.IsSet(params.Debug),
DebugLevel: viper.GetUint(params.DebugLevel),
CirrusPWArgs: params.CirrusPersistentWorkerArgs(),
GHRunnerArgs: params.GithubRunnerArgs(),
GHRunnerArgs: ghRunnerArgs,
GLRunnerArgs: params.GitLabRunnerArgs(&gitlab.S390x),
Tags: viper.GetStringMapString(params.Tags),
},
Expand Down
71 changes: 60 additions & 11 deletions cmd/mapt/cmd/params/params.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package params

import (
"fmt"
"os"
"strings"

"github.com/redhat-developer/mapt/pkg/integrations/cirrus"
"github.com/redhat-developer/mapt/pkg/integrations/github"
"github.com/redhat-developer/mapt/pkg/integrations/gitlab"
Expand Down Expand Up @@ -73,9 +77,14 @@ const (
CreateCmdName string = "create"
DestroyCmdName string = "destroy"

ghActionsRunnerToken string = "ghactions-runner-token"
ghActionsRunnerRepo string = "ghactions-runner-repo"
ghActionsRunnerLabels string = "ghactions-runner-labels"
ghActionsRunnerToken string = "ghactions-runner-token"
ghActionsRunnerRepo string = "ghactions-runner-repo"
ghActionsRunnerLabels string = "ghactions-runner-labels"
ghActionsRunnerImageRepo string = "ghactions-runner-image-repo"
// TODO: once the RHEL script is merged to https://github.com/IBM/action-runner-image-pz,
// switch default from deekay2310 fork to IBM upstream.
ghActionsRunnerImageRepoDefault string = "https://github.com/deekay2310/action-runner-image-pz.git"
GHActionsRunnerImageRepoDesc string = "Git clone URL for the action-runner-image-pz repository, used to build the GitHub Actions runner from source on ppc64le/s390x (no official binaries exist for these architectures)"

cirrusPWToken string = "it-cirrus-pw-token"
cirrusPWTokenDesc string = "Add mapt target as a cirrus persistent worker. The value will hold a valid token to be used by cirrus cli to join the project."
Expand Down Expand Up @@ -278,18 +287,54 @@ func AddGHActionsFlags(fs *pflag.FlagSet) {
fs.StringP(ghActionsRunnerToken, "", "", GHActionsRunnerTokenDesc)
fs.StringP(ghActionsRunnerRepo, "", "", GHActionsRunnerRepoDesc)
fs.StringSlice(ghActionsRunnerLabels, nil, GHActionsRunnerLabelsDesc)
fs.StringP(ghActionsRunnerImageRepo, "", ghActionsRunnerImageRepoDefault, GHActionsRunnerImageRepoDesc)
}

func GithubRunnerArgs() *github.GithubRunnerArgs {
if viper.IsSet(ghActionsRunnerToken) {
return &github.GithubRunnerArgs{
Token: viper.GetString(ghActionsRunnerToken),
RepoURL: viper.GetString(ghActionsRunnerRepo),
Labels: viper.GetStringSlice(ghActionsRunnerLabels),
Platform: &github.Linux,
Arch: linuxArchAsGithubActionsArch(
viper.GetString(LinuxArch)),
token := viper.GetString(ghActionsRunnerToken)
repoURL := viper.GetString(ghActionsRunnerRepo)
pat := os.Getenv("GITHUB_TOKEN")

if token == "" && pat == "" {
return nil
}

if token == "" && repoURL == "" {
logging.Error("--ghactions-runner-repo is required for GitHub Actions runner setup")
return nil
}

if token == "" {
logging.Info("no --ghactions-runner-token provided, auto-generating from GITHUB_TOKEN")
var err error
token, err = github.GenerateRegistrationToken(pat, repoURL)
if err != nil {
logging.Errorf("failed to auto-generate runner registration token: %v", err)
return nil
}
logging.Info("runner registration token generated successfully")
}

imageRepo := viper.GetString(ghActionsRunnerImageRepo)
if imageRepo != "" {
if err := validateRunnerImageRepo(imageRepo); err != nil {
logging.Errorf("invalid --ghactions-runner-image-repo: %v", err)
return nil
}
}
return &github.GithubRunnerArgs{
Token: token,
RepoURL: repoURL,
Labels: viper.GetStringSlice(ghActionsRunnerLabels),
Platform: &github.Linux,
Arch: linuxArchAsGithubActionsArch(viper.GetString(LinuxArch)),
RunnerImageRepo: imageRepo,
}
}

func validateRunnerImageRepo(repo string) error {
if !strings.HasPrefix(repo, "https://") {
return fmt.Errorf("only HTTPS URLs are allowed, got: %s", repo)
}
return nil
}
Expand Down Expand Up @@ -359,6 +404,10 @@ func linuxArchAsGithubActionsArch(arch string) *github.Arch {
switch arch {
case "x86_64":
return &github.Amd64
case "ppc64le":
return &github.Ppc64le
case "s390x":
return &github.S390x
}
return &github.Arm64
}
Expand Down
66 changes: 66 additions & 0 deletions pkg/integrations/github/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package github

import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)

type registrationTokenResponse struct {
Token string `json:"token"`
ExpiresAt string `json:"expires_at"`
}

// GenerateRegistrationToken calls the GitHub API to create a short-lived
// runner registration token for the given repository.
// pat is a Personal Access Token with repo admin scope.
// repoURL is in the form "owner/repo" or "https://github.com/owner/repo".
func GenerateRegistrationToken(pat, repoURL string) (string, error) {
ownerRepo := repoURL
ownerRepo = strings.TrimPrefix(ownerRepo, "https://github.com/")
ownerRepo = strings.TrimPrefix(ownerRepo, "http://github.com/")
ownerRepo = strings.TrimSuffix(ownerRepo, "/")

parts := strings.Split(ownerRepo, "/")
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return "", fmt.Errorf("invalid repo format %q, expected owner/repo", repoURL)
}

url := fmt.Sprintf("https://api.github.com/repos/%s/%s/actions/runners/registration-token", parts[0], parts[1])

req, err := http.NewRequest(http.MethodPost, url, nil)
if err != nil {
return "", fmt.Errorf("creating request: %w", err)
}
req.Header.Set("Authorization", "token "+pat)
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("calling GitHub API: %w", err)
}
defer func() { _ = resp.Body.Close() }()

body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("reading response: %w", err)
}

if resp.StatusCode != http.StatusCreated {
return "", fmt.Errorf("GitHub API returned %d: %s (ensure GITHUB_TOKEN has admin scope on the repo)", resp.StatusCode, string(body))
}

var tokenResp registrationTokenResponse
if err := json.Unmarshal(body, &tokenResp); err != nil {
return "", fmt.Errorf("parsing response: %w", err)
}

if tokenResp.Token == "" {
return "", fmt.Errorf("empty token in GitHub API response")
}

return tokenResp.Token, nil
}
30 changes: 23 additions & 7 deletions pkg/integrations/github/ghrunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,23 @@ var snippetLinux []byte
//go:embed snippet-windows.ps1
var snippetWindows []byte

//go:embed snippet-linux-ppc64le.sh
var snippetLinuxPpc64le []byte

//go:embed snippet-linux-s390x.sh
var snippetLinuxS390x []byte

var snippets map[Platform][]byte = map[Platform][]byte{
Darwin: snippetDarwin,
Linux: snippetLinux,
Windows: snippetWindows,
}

var archSnippets map[Arch][]byte = map[Arch][]byte{
Ppc64le: snippetLinuxPpc64le,
S390x: snippetLinuxS390x,
}

var runnerArgs *GithubRunnerArgs

func Init(args *GithubRunnerArgs) {
Expand All @@ -40,17 +51,22 @@ func (args *GithubRunnerArgs) GetUserDataValues() *integrations.UserDataValues {
return nil
}
return &integrations.UserDataValues{
Name: args.Name,
Token: args.Token,
Labels: getLabels(),
RepoURL: args.RepoURL,
CliURL: downloadURL(),
Name: args.Name,
Token: args.Token,
Labels: getLabels(),
RepoURL: args.RepoURL,
CliURL: downloadURL(),
RunnerImageRepo: args.RunnerImageRepo,
}
}

func (args *GithubRunnerArgs) GetSetupScriptTemplate() string {
templateConfig := string(snippets[*runnerArgs.Platform][:])
return templateConfig
if *runnerArgs.Platform == Linux && runnerArgs.Arch != nil {
if archSnippet, ok := archSnippets[*runnerArgs.Arch]; ok {
return string(archSnippet[:])
}
}
return string(snippets[*runnerArgs.Platform][:])
}

func GetRunnerArgs() *GithubRunnerArgs {
Expand Down
Loading