Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c9a4b05
feat(ibmcloud): add GitHub Actions runner support for IBM Power and I…
deekay2310 Jun 9, 2026
9af3e13
fix: harden --ghactions-runner-image-repo input
deekay2310 Jun 9, 2026
f68b509
feat(ibmcloud): auto-generate GitHub Actions runner registration token
deekay2310 Jun 9, 2026
68f31f4
fix(ibmcloud): install prerequisites before runner image build
deekay2310 Jun 10, 2026
78d423b
ci: add s390x runner smoke test workflow
deekay2310 Jun 10, 2026
753b7ef
fix(ibmcloud): tolerate flaky upstream test failures in runner build
deekay2310 Jun 10, 2026
9e03696
fix(ibmcloud): preserve runner binary if post-build installer fails
deekay2310 Jun 11, 2026
7829062
revert: remove unnecessary runner binary watcher from snippets
deekay2310 Jun 11, 2026
a6893f0
fix(ibmcloud): run GitHub Actions runner as non-root user
deekay2310 Jun 11, 2026
60e93b8
fix(ibmcloud): repair PAM config after runner build to preserve sshd
deekay2310 Jun 11, 2026
ef87753
debug(ibmcloud): add sshd watchdog and diagnostic logging to ppc64le …
deekay2310 Jun 11, 2026
265345e
fix(ibmcloud): restore sshd privsep dir after runner build + COS diag…
deekay2310 Jun 11, 2026
de8fc1f
fix(ibmcloud): remove nonexistent /opt/dotnet from ppc64le snippet
deekay2310 Jun 11, 2026
9f02ad6
feat(ibmcloud): auto-discover Power VS system type based on pool capa…
deekay2310 Jun 17, 2026
246c1c0
fix(ibmcloud): harden GH runner params and system pool selection
deekay2310 Jun 17, 2026
28e55f7
fix(ibmcloud): remove nonexistent /opt/dotnet from s390x snippet
deekay2310 Jun 22, 2026
107ab7f
feat(ibmcloud): zone-level system type discovery with retry on capacity
deekay2310 Jun 22, 2026
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 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,
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

func validateRunnerImageRepo(repo string) error {
if !strings.HasPrefix(repo, "https://") {
return fmt.Errorf("only HTTPS URLs are allowed, got: %s", repo)
}
return nil
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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
68 changes: 68 additions & 0 deletions pkg/integrations/github/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package github

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

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")

client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.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