diff --git a/actionsrunner/runner.go b/actionsrunner/runner.go index c98e511..2e28416 100644 --- a/actionsrunner/runner.go +++ b/actionsrunner/runner.go @@ -64,7 +64,7 @@ type RunnerEnvironment interface { func (run *RunRunner) Run(runnerenv RunnerEnvironment, listenerctx, corectx context.Context) error { settings := run.Settings for i := 0; i < len(settings.Instances); i++ { - if err := settings.Instances[i].EnshurePKey(); err != nil { + if err := settings.Instances[i].EnsurePKey(); err != nil { return err } } @@ -315,14 +315,8 @@ func (run *RunRunner) Run(runnerenv RunnerEnvironment, listenerctx, corectx cont } } session.Status = "Online" - err := vssConnection.RequestWithContext(xctx, "c3a054f6-7a8a-49c0-944e-3a8e5d7adfd7", "5.1-preview", "GET", map[string]string{ - "poolId": fmt.Sprint(instance.PoolID), - }, map[string]string{ - "sessionId": session.TaskAgentSession.SessionID, - "runnerVersion": "3.0.0", - "status": session.Status, - }, nil, message) - // TODO lastMessageId= + var err error + message, err = session.GetSingleMessage(xctx) if err != nil { if errors.Is(err, context.Canceled) { return 0 @@ -355,12 +349,7 @@ func (run *RunRunner) Run(runnerenv RunnerEnvironment, listenerctx, corectx cont return 1 } success = true - err := vssConnection.Request("c3a054f6-7a8a-49c0-944e-3a8e5d7adfd7", "5.1-preview", "DELETE", map[string]string{ - "poolId": fmt.Sprint(instance.PoolID), - "messageId": fmt.Sprint(message.MessageID), - }, map[string]string{ - "sessionId": session.TaskAgentSession.SessionID, - }, nil, nil) + err := session.DeleteMessage(context.Background(), message) if err != nil { runnerenv.Printf("Failed to delete Message\n") success = false @@ -471,7 +460,7 @@ func runJob(runnerenv RunnerEnvironment, joblock *sync.Mutex, vssConnection *pro cancelJob() finishJob() }() - src, err := message.Decrypt(session.Block) + src, err := message.Decrypt(session) if err != nil { plogger.Printf("Failed to decode TaskAgentMessage: %v\n", err) return diff --git a/protocol/connection.go b/protocol/connection.go index a9b4781..3f14755 100644 --- a/protocol/connection.go +++ b/protocol/connection.go @@ -333,36 +333,38 @@ func (vssConnection *VssConnection) GetAgentPools() (*TaskAgentPools, error) { return _taskAgentPools, nil } -func (vssConnection *VssConnection) CreateSession(ctx context.Context) (*AgentMessageConnection, error) { +func (vssConnection *VssConnection) CreateSessionExt(ctx context.Context, serverV2URL string) (*AgentMessageConnection, error) { session := &TaskAgentSession{} session.Agent = *vssConnection.TaskAgent session.UseFipsEncryption = false // Have to be set to false for "GitHub Enterprise Server 3.0.11", github.com reset it to false 24-07-2021 session.OwnerName = "RUNNER" - if err := vssConnection.RequestWithContext(ctx, "134e239e-2df3-4794-a6f6-24f1f19ec8dc", "5.1-preview", "POST", map[string]string{ - "poolId": fmt.Sprint(vssConnection.PoolID), - }, map[string]string{}, session, session); err != nil { - return nil, err + if serverV2URL != "" { + err := vssConnection.RequestWithContext2(ctx, "POST", serverV2URL+"/session", "", session, session) + if err != nil { + return nil, err + } + } else { + if err := vssConnection.RequestWithContext(ctx, "134e239e-2df3-4794-a6f6-24f1f19ec8dc", "5.1-preview", "POST", map[string]string{ + "poolId": fmt.Sprint(vssConnection.PoolID), + }, map[string]string{}, session, session); err != nil { + return nil, err + } + if session.BrokerMigrationMessage != nil { + return vssConnection.CreateSessionExt(ctx, session.BrokerMigrationMessage.BrokerBaseURL) + } } con := &AgentMessageConnection{VssConnection: vssConnection, TaskAgentSession: session} - var err error - con.Block, err = con.TaskAgentSession.GetSessionKey(vssConnection.Key) - if err != nil { - _ = con.Delete(ctx) - return nil, err - } con.Status = statusOnline return con, nil } +func (vssConnection *VssConnection) CreateSession(ctx context.Context) (*AgentMessageConnection, error) { + return vssConnection.CreateSessionExt(ctx, vssConnection.TaskAgent.ServerV2URL) +} + func (vssConnection *VssConnection) LoadSession(ctx context.Context, session *TaskAgentSession) (*AgentMessageConnection, error) { con := &AgentMessageConnection{VssConnection: vssConnection, TaskAgentSession: session} - var err error - con.Block, err = con.TaskAgentSession.GetSessionKey(vssConnection.Key) - if err != nil { - _ = con.Delete(ctx) - return nil, err - } con.Status = statusOnline return con, nil } diff --git a/protocol/misc.go b/protocol/misc.go index 7438cb8..7bc1b0c 100644 --- a/protocol/misc.go +++ b/protocol/misc.go @@ -19,6 +19,7 @@ type GitHubAuthResult struct { TenantURL string `json:"url"` TokenSchema string `json:"token_schema"` Token string `json:"token"` + UseV2FLow bool `json:"use_v2_flow"` } type TaskOrchestrationPlanReference struct { diff --git a/protocol/runner_admin.go b/protocol/runner_admin.go new file mode 100644 index 0000000..7ddae88 --- /dev/null +++ b/protocol/runner_admin.go @@ -0,0 +1,28 @@ +package protocol + +type Runner struct { + Name string `json:"name"` + Id int64 `json:"id"` + Authorization struct { + AuthorizationURL string `json:"authorization_url"` + ServerURL string `json:"server_url"` + ClientId string `json:"client_id"` + } `json:"authorization"` +} + +type RunnerGroup struct { + Id int32 `json:"id,omitempty"` + Name string `json:"name,omitempty"` + IsDefault bool `json:"default,omitempty"` + IsHosted bool `json:"is_hosted,omitempty"` +} + +type RunnerGroupList struct { + RunnerGroups []RunnerGroup `json:"runner_groups"` + Count int `json:"total_count"` +} + +type ListRunnersResponse struct { + TotalCount int `json:"total_count"` + Runners []Runner `json:"runners"` +} diff --git a/protocol/session.go b/protocol/session.go index c63e361..1c538fb 100644 --- a/protocol/session.go +++ b/protocol/session.go @@ -14,6 +14,7 @@ import ( "fmt" "hash" "io" + "net/url" "strings" "time" ) @@ -30,7 +31,7 @@ type TaskAgentMessage struct { Body string } -func (message *TaskAgentMessage) Decrypt(block cipher.Block) ([]byte, error) { +func (message *TaskAgentMessage) Decrypt(session *AgentMessageConnection) ([]byte, error) { if message.IV == "" { return []byte(message.Body), nil } @@ -42,9 +43,17 @@ func (message *TaskAgentMessage) Decrypt(block cipher.Block) ([]byte, error) { if err != nil { return nil, err } - cbcdec := cipher.NewCBCDecrypter(block, iv) + if session.Block == nil { + // Parse Key + var err error + session.Block, err = session.TaskAgentSession.GetSessionKey(session.VssConnection.Key) + if err != nil { + return nil, err + } + } + cbcdec := cipher.NewCBCDecrypter(session.Block, iv) cbcdec.CryptBlocks(src, src) - maxlen := block.BlockSize() + maxlen := session.Block.BlockSize() validlen := len(src) // <= is needed if the message ends within a block boundary and maxlen=16 // then we get 16 times char 16 appended, one whole extra block @@ -76,7 +85,7 @@ func (message *TaskAgentMessage) FetchBrokerIfNeeded(xctx context.Context, sessi if strings.EqualFold(message.MessageType, "BrokerMigration") { vssConnection := session.VssConnection rjrr := &BrokerMigration{} - raw, err := message.Decrypt(session.Block) + raw, err := message.Decrypt(session) if err != nil { return err } @@ -117,11 +126,12 @@ type TaskAgentSessionKey struct { } type TaskAgentSession struct { - SessionID string `json:",omitempty"` - EncryptionKey TaskAgentSessionKey - OwnerName string - Agent TaskAgent - UseFipsEncryption bool + SessionID string `json:",omitempty"` + EncryptionKey TaskAgentSessionKey + OwnerName string + Agent TaskAgent + UseFipsEncryption bool + BrokerMigrationMessage *BrokerMigration `json:",omitempty"` } func (session *TaskAgentSession) GetSessionKey(key *rsa.PrivateKey) (cipher.Block, error) { @@ -149,30 +159,50 @@ type AgentMessageConnection struct { TaskAgentSession *TaskAgentSession Block cipher.Block Status string + ServerV2URL string } func (session *AgentMessageConnection) Delete(ctx context.Context) error { + if session.ServerV2URL != "" { + return session.VssConnection.RequestWithContext2(ctx, "DELETE", session.ServerV2URL+"/session", "", nil, nil) + } return session.VssConnection.RequestWithContext(ctx, "134e239e-2df3-4794-a6f6-24f1f19ec8dc", "5.1-preview", "DELETE", map[string]string{ "poolId": fmt.Sprint(session.VssConnection.PoolID), "sessionId": session.TaskAgentSession.SessionID, }, map[string]string{}, session.TaskAgentSession, nil) } -func (session *AgentMessageConnection) GetNextMessage(ctx context.Context) (*TaskAgentMessage, error) { +func (session *AgentMessageConnection) GetSingleMessage(ctx context.Context) (*TaskAgentMessage, error) { message := &TaskAgentMessage{} - for { - select { - case <-ctx.Done(): - return nil, context.Canceled - default: - } - err := session.VssConnection.RequestWithContext(ctx, "c3a054f6-7a8a-49c0-944e-3a8e5d7adfd7", "5.1-preview", "GET", map[string]string{ + var err error + if session.ServerV2URL != "" { + query := url.Values{} + query.Set("sessionId", session.TaskAgentSession.SessionID) + query.Set("runnerVersion", "3.0.0") + query.Set("status", session.Status) + query.Set("disableUpdate", fmt.Sprint(session.TaskAgentSession.Agent.DisableUpdate)) + err = session.VssConnection.RequestWithContext2(ctx, "GET", session.ServerV2URL+"/message?"+query.Encode(), "", nil, message) + } else { + err = session.VssConnection.RequestWithContext(ctx, "c3a054f6-7a8a-49c0-944e-3a8e5d7adfd7", "5.1-preview", "GET", map[string]string{ "poolId": fmt.Sprint(session.VssConnection.PoolID), }, map[string]string{ "sessionId": session.TaskAgentSession.SessionID, "runnerVersion": "3.0.0", + "status": session.Status, }, nil, message) // TODO lastMessageId= + } + return message, err +} + +func (session *AgentMessageConnection) GetNextMessage(ctx context.Context) (*TaskAgentMessage, error) { + for { + select { + case <-ctx.Done(): + return nil, context.Canceled + default: + } + message, err := session.GetSingleMessage(ctx) if err == nil { err = session.DeleteMessage(ctx, message) err = errors.Join(err, message.FetchBrokerIfNeeded(ctx, session)) @@ -195,6 +225,10 @@ func (session *AgentMessageConnection) GetNextMessage(ctx context.Context) (*Tas } func (session *AgentMessageConnection) DeleteMessage(ctx context.Context, message *TaskAgentMessage) error { + if session.ServerV2URL != "" { + // V2 no support for deleting messages + return nil + } return session.VssConnection.RequestWithContext(ctx, "c3a054f6-7a8a-49c0-944e-3a8e5d7adfd7", "5.1-preview", "DELETE", map[string]string{ "poolId": fmt.Sprint(session.VssConnection.PoolID), "messageId": fmt.Sprint(message.MessageID), diff --git a/protocol/task_agent.go b/protocol/task_agent.go index 400a9b4..0b2d8b2 100644 --- a/protocol/task_agent.go +++ b/protocol/task_agent.go @@ -43,7 +43,7 @@ type TaskAgent struct { Authorization TaskAgentAuthorization Labels []AgentLabel MaxParallelism int - ID int + ID int64 Name string Version string OSDescription string @@ -53,6 +53,8 @@ type TaskAgent struct { CreatedOn string Ephemeral bool `json:",omitempty"` DisableUpdate bool `json:",omitempty"` + // Just a convenient way to store the URL, not part of the spec + ServerV2URL string `json:",omitempty"` } type TaskAgents struct { diff --git a/runnerconfiguration/add.go b/runnerconfiguration/add.go index 0393ec4..3558d5d 100644 --- a/runnerconfiguration/add.go +++ b/runnerconfiguration/add.go @@ -1,11 +1,13 @@ package runnerconfiguration import ( + "context" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/base64" "encoding/binary" + "encoding/xml" "fmt" "os" "runtime" @@ -77,10 +79,53 @@ func (config *ConfigureRunner) Configure( Token: res.Token, Trace: config.Trace, } + if res.UseV2FLow { + vssConnection = &protocol.VssConnection{ + AuthHeader: "RemoteAuth " + config.Token, + Trace: config.Trace, + Client: c, + } + } + + getRunnerGroups := func() (*protocol.TaskAgentPools, error) { + return vssConnection.GetAgentPools() + } + var apiBuilder *GithubApiUrlBuilder + if res.UseV2FLow { + var err error + apiBuilder, err = NewGithubApiUrlBuilder(config.URL) + if err != nil { + return nil, fmt.Errorf("invalid Url: %v", config.URL) + } + getRunnerGroups = func() (*protocol.TaskAgentPools, error) { + runnerGroupsURL, err := apiBuilder.ScopedApiUrl("actions/runner-groups") + if err != nil { + return nil, err + } + runnerGroups := &protocol.RunnerGroupList{} + err = vssConnection.RequestWithContext2(context.Background(), "GET", runnerGroupsURL, "", nil, runnerGroups) + if err != nil { + return nil, err + } + poolList := &protocol.TaskAgentPools{} + poolList.Count = int64(len(runnerGroups.RunnerGroups)) + for _, val := range runnerGroups.RunnerGroups { + poolList.Value = append(poolList.Value, protocol.TaskAgentPool{ + TaskAgentPoolReference: protocol.TaskAgentPoolReference{ + ID: int64(val.Id), + Name: val.Name, + IsHosted: val.IsHosted, + IsInternal: val.IsDefault, + }, + }) + } + return poolList, nil + } + } { taskAgentPool := "" taskAgentPools := []string{} - _taskAgentPools, err := vssConnection.GetAgentPools() + _taskAgentPools, err := getRunnerGroups() if err != nil { return nil, fmt.Errorf("failed to configure runner: %w", err) } @@ -160,18 +205,38 @@ func (config *ConfigureRunner) Configure( taskAgent.Ephemeral = config.Ephemeral taskAgent.DisableUpdate = config.DisableUpdate { - err := vssConnection.Request("e298ef32-5878-4cab-993c-043836571f42", "6.0-preview.2", "POST", map[string]string{ - "poolId": fmt.Sprint(vssConnection.PoolID), - }, map[string]string{}, taskAgent, taskAgent) + var err error + if res.UseV2FLow { + err = registerOrReplaceRunnerV2(taskAgent, config, vssConnection, apiBuilder, false) + } else { + err = vssConnection.Request("e298ef32-5878-4cab-993c-043836571f42", "6.0-preview.2", "POST", map[string]string{ + "poolId": fmt.Sprint(vssConnection.PoolID), + }, map[string]string{}, taskAgent, taskAgent) + } if err != nil { if !config.Replace { return nil, fmt.Errorf("failed to create taskAgent: %v", err.Error()) } // Try replaceing runner if creation failed taskAgents := &protocol.TaskAgents{} - err := vssConnection.Request("e298ef32-5878-4cab-993c-043836571f42", "6.0-preview.2", "GET", map[string]string{ - "poolId": fmt.Sprint(vssConnection.PoolID), - }, map[string]string{}, nil, taskAgents) + if res.UseV2FLow { + resv2 := &protocol.ListRunnersResponse{} + runnersURL, _ := apiBuilder.ScopedApiUrl("actions/runners") + err = vssConnection.RequestWithContext2(context.Background(), "GET", runnersURL, "", nil, resv2) + if err == nil { + for _, runner := range resv2.Runners { + taskAgents.Value = append(taskAgents.Value, protocol.TaskAgent{ + ID: runner.Id, + Name: runner.Name, + }) + } + taskAgents.Count = int64(len(taskAgents.Value)) + } + } else { + err = vssConnection.Request("e298ef32-5878-4cab-993c-043836571f42", "6.0-preview.2", "GET", map[string]string{ + "poolId": fmt.Sprint(vssConnection.PoolID), + }, map[string]string{}, nil, taskAgents) + } if err != nil { return nil, fmt.Errorf("failed to update taskAgent: %v", err.Error()) } @@ -186,10 +251,14 @@ func (config *ConfigureRunner) Configure( if invalid { return nil, fmt.Errorf("failed to update taskAgent: failed to find agent") } - err = vssConnection.Request("e298ef32-5878-4cab-993c-043836571f42", "6.0-preview.2", "PUT", map[string]string{ - "poolId": fmt.Sprint(vssConnection.PoolID), - "agentId": fmt.Sprint(taskAgent.ID), - }, map[string]string{}, taskAgent, taskAgent) + if res.UseV2FLow { + err = registerOrReplaceRunnerV2(taskAgent, config, vssConnection, apiBuilder, true) + } else { + err = vssConnection.Request("e298ef32-5878-4cab-993c-043836571f42", "6.0-preview.2", "PUT", map[string]string{ + "poolId": fmt.Sprint(vssConnection.PoolID), + "agentId": fmt.Sprint(taskAgent.ID), + }, map[string]string{}, taskAgent, taskAgent) + } if err != nil { return nil, fmt.Errorf("failed to update taskAgent: %v", err.Error()) } @@ -201,6 +270,49 @@ func (config *ConfigureRunner) Configure( return settings, nil } +type RSAKeyValue struct { + Modulus string + Exponent string +} + +func registerOrReplaceRunnerV2(taskAgent *protocol.TaskAgent, config *ConfigureRunner, + vssConnection *protocol.VssConnection, apiBuilder *GithubApiUrlBuilder, replace bool, +) error { + runnerResp := &protocol.Runner{} + pubKeyXml, err := xml.Marshal(&RSAKeyValue{ + Modulus: taskAgent.Authorization.PublicKey.Modulus, + Exponent: taskAgent.Authorization.PublicKey.Exponent, + }) + if err != nil { + return err + } + v2Register := map[string]interface{}{ + "url": config.URL, + "group_id": vssConnection.PoolID, + "name": config.Name, + "version": taskAgent.Version, + "updates_disabled": taskAgent.DisableUpdate, + "ephemeral": taskAgent.Ephemeral, + "labels": taskAgent.Labels, + "public_key": string(pubKeyXml), + } + if replace { + v2Register["runner_id"] = taskAgent.ID + v2Register["replace"] = true + } + err = vssConnection.RequestWithContext2(context.Background(), "POST", + apiBuilder.AbsoluteApiUrl("actions/runners/register"), "", v2Register, runnerResp) + if err != nil { + return err + } + taskAgent.ID = runnerResp.Id + taskAgent.Name = runnerResp.Name + taskAgent.Authorization.AuthorizationURL = runnerResp.Authorization.AuthorizationURL + taskAgent.Authorization.ClientID = runnerResp.Authorization.ClientId + taskAgent.ServerV2URL = runnerResp.Authorization.ServerURL + return nil +} + func (config *ConfigureRunner) ReadFromEnvironment() { config.ConfigureRemoveRunner.ReadFromEnvironment() if !config.Ephemeral { diff --git a/runnerconfiguration/common.go b/runnerconfiguration/common.go index 56c35b6..2575103 100644 --- a/runnerconfiguration/common.go +++ b/runnerconfiguration/common.go @@ -80,7 +80,7 @@ type RunnerInstance struct { WorkFolder string // Currently unused for actions/runner compat } -func (instance *RunnerInstance) EnshurePKey() error { +func (instance *RunnerInstance) EnsurePKey() error { if instance.PKey == nil { key, err := base64.StdEncoding.DecodeString(instance.Key) if err != nil { @@ -101,40 +101,64 @@ type RunnerSettings struct { Instances []*RunnerInstance } -func gitHubAuth( - config *ConfigureRemoveRunner, c *http.Client, runnerEvent, apiEndpoint string, survey Survey, +type GithubApiUrlBuilder struct { + URL *url.URL + ApiScope string +} + +func NewGithubApiUrlBuilder(baseURLString string) (*GithubApiUrlBuilder, error) { + baseUrl, err := url.Parse(baseURLString) + if err != nil { + return nil, err + } + apiBuilder := &GithubApiUrlBuilder{ + URL: baseUrl, + } + if strings.EqualFold(apiBuilder.URL.Host, "github.com") || strings.HasSuffix(strings.ToLower(apiBuilder.URL.Host), ".ghe.com") { + apiBuilder.URL.Host = "api." + apiBuilder.URL.Host + } else { + apiBuilder.ApiScope = "/api/v3" + } + return apiBuilder, nil +} + +func (apiBuilder *GithubApiUrlBuilder) AbsoluteApiUrl(p string) string { + apiURL := *apiBuilder.URL + apiURL.Path = path.Join(apiBuilder.ApiScope, p) + return apiURL.String() +} + +func (apiBuilder *GithubApiUrlBuilder) ScopedApiUrl(p string) (string, error) { + apiURL := *apiBuilder.URL + paths := strings.Split(strings.TrimPrefix(apiURL.Path, "/"), "/") + if len(paths) == 1 { + apiURL.Path = path.Join(apiBuilder.ApiScope, "orgs", paths[0], p) + } else if len(paths) == repositoryPathSegments { + scope := "repos" + if strings.EqualFold(paths[0], "enterprises") { + scope = "" + } + apiURL.Path = path.Join(apiBuilder.ApiScope, scope, paths[0], paths[1], p) + } else { + return "", fmt.Errorf("unsupported registration url") + } + return apiURL.String(), nil +} + +func gitHubAuth(config *ConfigureRemoveRunner, c *http.Client, runnerEvent, apiEndpoint string, survey Survey, ) (*protocol.GitHubAuthResult, error) { if config.URL == "" && !config.Unattended { config.URL = survey.GetInput("Which GitHub Url is associated with this runner (Normally this isn't missing):", "") } - registerURL, err := url.Parse(config.URL) + apiBuilder, err := NewGithubApiUrlBuilder(config.URL) if err != nil { return nil, fmt.Errorf("invalid Url: %v", config.URL) } - if registerURL.Hostname() == "" { - return nil, fmt.Errorf("invalid Url missing Hostname: %v", config.URL) - } - apiscope := "/" - if strings.EqualFold(registerURL.Host, "github.com") { - registerURL.Host = "api." + registerURL.Host - } else { - apiscope = "/api/v3" - } - if config.Token == "" { if config.Pat != "" { - paths := strings.Split(strings.TrimPrefix(registerURL.Path, "/"), "/") - repoURL := *registerURL - if len(paths) == 1 { - repoURL.Path = path.Join(apiscope, "orgs", paths[0], "actions/runners", apiEndpoint) - } else if len(paths) == repositoryPathSegments { - scope := "repos" - if strings.EqualFold(paths[0], "enterprises") { - scope = "" - } - repoURL.Path = path.Join(apiscope, scope, paths[0], paths[1], "actions/runners", apiEndpoint) - } else { - return nil, fmt.Errorf("unsupported registration url") + apiURL, serr := apiBuilder.ScopedApiUrl(path.Join("actions/runners", apiEndpoint)) + if serr != nil { + return nil, serr } client := &protocol.VssConnection{ AuthHeader: fmt.Sprintf("Basic %v", base64.StdEncoding.EncodeToString([]byte("GitHub:"+config.Pat))), @@ -142,7 +166,7 @@ func gitHubAuth( Client: c, } tokenresp := &protocol.GitHubRunnerRegisterToken{} - err = client.RequestWithContext2(context.Background(), "POST", repoURL.String(), "", nil, tokenresp) + err = client.RequestWithContext2(context.Background(), "POST", apiURL, "", nil, tokenresp) if err != nil { return nil, fmt.Errorf("failed to retrieve %v token via pat: %v", apiEndpoint, err.Error()) } @@ -154,9 +178,8 @@ func gitHubAuth( if config.Token == "" { return nil, fmt.Errorf("no runner registration token provided") } - registerURL.Path = path.Join(apiscope, "actions/runner-registration") - finalregisterURL := registerURL.String() + finalregisterURL := apiBuilder.AbsoluteApiUrl("actions/runner-registration") client := &protocol.VssConnection{ AuthHeader: "RemoteAuth " + config.Token, diff --git a/runnerconfiguration/compat/actions_runner_compat.go b/runnerconfiguration/compat/actions_runner_compat.go index de10361..a5aeb76 100644 --- a/runnerconfiguration/compat/actions_runner_compat.go +++ b/runnerconfiguration/compat/actions_runner_compat.go @@ -4,7 +4,6 @@ import ( "crypto/rsa" "encoding/base64" "encoding/json" - "encoding/xml" "fmt" "math/big" "strconv" @@ -35,6 +34,8 @@ type DotnetAgent struct { ServerURL string `json:"ServerUrl"` WorkFolder string `json:"WorkFolder"` GitHubURL string `json:"GitHubUrl"` + UseV2Flow bool `json:"UseV2Flow"` + ServerURLV2 string `json:"ServerUrlV2"` } type DotnetCredentials struct { @@ -131,7 +132,7 @@ func ToRunnerInstance(fileAccess ConfigFileAccess) (*runnerconfiguration.RunnerI if err != nil { return nil, err } - agentID, err := strconv.ParseInt(agent.AgentID, 10, 32) + agentID, err := strconv.ParseInt(agent.AgentID, 10, 64) if err != nil { return nil, err } @@ -144,7 +145,7 @@ func ToRunnerInstance(fileAccess ConfigFileAccess) (*runnerconfiguration.RunnerI }, PKey: FromRsaParameters(rsaParameters), Agent: &protocol.TaskAgent{ - ID: int(agentID), + ID: agentID, Ephemeral: ephemeral, Name: agent.AgentName, MaxParallelism: 1, @@ -154,6 +155,7 @@ func ToRunnerInstance(fileAccess ConfigFileAccess) (*runnerconfiguration.RunnerI }, DisableUpdate: disableUpdate, Version: "3.0.0", + ServerV2URL: agent.ServerURLV2, }, WorkFolder: agent.WorkFolder, RegistrationURL: agent.GitHubURL, @@ -170,6 +172,8 @@ func FromRunnerInstance(instance *runnerconfiguration.RunnerInstance, fileAccess ServerURL: instance.Auth.TenantURL, WorkFolder: instance.WorkFolder, GitHubURL: instance.RegistrationURL, + UseV2Flow: instance.Auth.UseV2FLow, + ServerURLV2: instance.Agent.ServerV2URL, } if agent.WorkFolder == "" { agent.WorkFolder = "_work" @@ -187,7 +191,7 @@ func FromRunnerInstance(instance *runnerconfiguration.RunnerInstance, fileAccess if err := fileAccess.Write(".credentials", credentials); err != nil { return err } - if err := instance.EnshurePKey(); err != nil { + if err := instance.EnsurePKey(); err != nil { return err } if err := fileAccess.Write(".credentials_rsaparams", ToRsaParameters(instance.PKey)); err != nil { @@ -206,10 +210,6 @@ func ParseJitRunnerConfig(conf string) (*runnerconfiguration.RunnerSettings, err return nil, unmarshalErr } ret, err := ToRunnerInstance(JITConfigFileAccess(files)) - _, xmlErr := ToXMLString(&ret.PKey.PublicKey) - if xmlErr != nil { - fmt.Printf("convert xml string: %v", xmlErr) - } return &runnerconfiguration.RunnerSettings{ Instances: []*runnerconfiguration.RunnerInstance{ ret, @@ -228,16 +228,3 @@ func ToJitRunnerConfig(instance *runnerconfiguration.RunnerInstance) (string, er } return base64.StdEncoding.EncodeToString(rawfiles), nil } - -type RSAKeyValue struct { - Modulus string - Exponent string -} - -func ToXMLString(publicKey *rsa.PublicKey) (string, error) { - res, err := xml.Marshal(&RSAKeyValue{ - Modulus: base64.StdEncoding.EncodeToString(publicKey.N.Bytes()), - Exponent: base64.StdEncoding.EncodeToString(big.NewInt(int64(publicKey.E)).Bytes()), - }) - return string(res), err -} diff --git a/runnerconfiguration/remove.go b/runnerconfiguration/remove.go index 1bded3d..96afa30 100644 --- a/runnerconfiguration/remove.go +++ b/runnerconfiguration/remove.go @@ -1,6 +1,7 @@ package runnerconfiguration import ( + "context" "fmt" "github.com/ChristopherHX/github-act-runner/protocol" @@ -75,16 +76,39 @@ func (config *RemoveRunner) Remove(settings *RunnerSettings, survey Survey, auth res = authres } - vssConnection := &protocol.VssConnection{ - Client: c, - TenantURL: res.TenantURL, - Token: res.Token, - PoolID: instance.PoolID, - Trace: config.Trace, - } - if err := vssConnection.DeleteAgent(instance.Agent); err != nil { - return fmt.Errorf("failed to remove Runner from server: %w", err) + if res.UseV2FLow { + // This is not based on any code of actions/runner + // it uses the pipelines api to remove the runner + vssConnection := &protocol.VssConnection{ + AuthHeader: "RemoteAuth " + config.Token, + Trace: config.Trace, + Client: c, + } + apiBuilder, err := NewGithubApiUrlBuilder(config.URL) + if err != nil { + return fmt.Errorf("invalid Url: %v", config.URL) + } + runnerURL, err := apiBuilder.ScopedApiUrl(fmt.Sprintf("actions/runners/%d", instance.Agent.ID)) + if err != nil { + return fmt.Errorf("failed to remove Runner from server: %w", err) + } + err = vssConnection.RequestWithContext2(context.Background(), "DELETE", runnerURL, "", nil, nil) + if err != nil { + return fmt.Errorf("failed to remove Runner from server: %w", err) + } + } else { + vssConnection := &protocol.VssConnection{ + Client: c, + TenantURL: res.TenantURL, + Token: res.Token, + PoolID: instance.PoolID, + Trace: config.Trace, + } + if err := vssConnection.DeleteAgent(instance.Agent); err != nil { + return fmt.Errorf("failed to remove Runner from server: %w", err) + } } + return nil }() if result != nil && !config.Force {