Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
25 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
43b2865
fix(ibmcloud): harden Power VS provisioning and address PR review
deekay2310 Jun 23, 2026
93c91ee
fix(ibmcloud): default pi-sys-type to auto-discovery and retry on tim…
deekay2310 Jun 24, 2026
7c2ee65
ci: retrigger after transient quay.io pull failure
deekay2310 Jun 24, 2026
a1d6416
Merge branch 'main' into ibmcloud-gh-runners
deekay2310 Jun 25, 2026
baad7fb
refactor(github): pass arch to GithubRunnerArgs, internalize runner i…
deekay2310 Jun 30, 2026
d75b79c
refactor(ibmcloud): remove PowerVS capacity retry logic and default t…
deekay2310 Jun 30, 2026
9761706
fix(github): point runner image repo to aipcc-cicd org
deekay2310 Jun 30, 2026
a8e6f9c
refactor(github): pin runner image to aipcc-cicd v2.0.0 tag and pass …
deekay2310 Jul 1, 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()
Comment thread
deekay2310 marked this conversation as resolved.
Outdated
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()
Comment thread
deekay2310 marked this conversation as resolved.
Outdated
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"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably we dont want to expose this...you can set it in the logic (not in cmd) and also instead of download the zip pointing to main...use tags and download source from releases

@deekay2310 deekay2310 Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the --ghactions-runner-image-repo CLI flag and hardcoded the repo URL internally in ghrunner.go. For switching to tags/releases instead of cloning main, I can take care of that in a follow-up PR once the fork is moved into aipcc-cicd and we have tagged releases to point at. Does that work?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since the repo was moved into the aipcc-cicd org, I did a v2.0.0 release to be consumed in this section, which has now been implemented.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user doesn't provide --ghactions-runner-token, the code uses GITHUB_TOKEN from their environment to auto-generate a runner registration token via the GitHub API (GenerateRegistrationToken). This way users don't have to manually create a short-lived registration token before each run.


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,
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

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.
Outdated
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])
Comment thread
deekay2310 marked this conversation as resolved.

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)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
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