提交 3efcd0bd 编写于 作者: LinuxSuRen's avatar LinuxSuRen

Add support to create a jnlp agent

上级 099e99b4
......@@ -266,3 +266,8 @@ func getCurrentJenkinsAndClient(jClient *client.JenkinsCore) (jenkins *JenkinsSe
}
return
}
// GetAliasesDel returns the aliases for delete command
func GetAliasesDel() []string {
return []string{"remove", "del"}
}
package cmd
import (
"net/http"
"github.com/jenkins-zh/jenkins-cli/client"
"github.com/jenkins-zh/jenkins-cli/app/i18n"
"github.com/spf13/cobra"
)
// ComputerCreateOption option for config list command
type ComputerCreateOption struct {
OutputOption
RoundTripper http.RoundTripper
}
var computerCreateOption ComputerCreateOption
func init() {
computerCmd.AddCommand(computerCreateCmd)
}
var computerCreateCmd = &cobra.Command{
Use: "create",
Short: i18n.T("Create an Jenkins agent"),
Long: i18n.T(`Create an Jenkins agent
It can only create a JNLP agent.`),
Example: `jcli agent create agent-name`,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) (err error) {
jClient := &client.ComputerClient{
JenkinsCore: client.JenkinsCore{
RoundTripper: computerCreateOption.RoundTripper,
},
}
getCurrentJenkinsAndClient(&(jClient.JenkinsCore))
err = jClient.Create(args[0])
return
},
Annotations: map[string]string{
since: "v0.0.24",
},
}
package cmd
import (
"bytes"
"io"
"io/ioutil"
"os"
"github.com/jenkins-zh/jenkins-cli/client"
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/jenkins-zh/jenkins-cli/mock/mhttp"
)
var _ = Describe("create list command", func() {
var (
ctrl *gomock.Controller
roundTripper *mhttp.MockRoundTripper
buf io.Writer
)
BeforeEach(func() {
ctrl = gomock.NewController(GinkgoT())
roundTripper = mhttp.NewMockRoundTripper(ctrl)
rootCmd.SetArgs([]string{})
buf = new(bytes.Buffer)
rootCmd.SetOutput(buf)
rootOptions.Jenkins = ""
rootOptions.ConfigFile = "test.yaml"
computerCreateOption.RoundTripper = roundTripper
})
AfterEach(func() {
rootCmd.SetArgs([]string{})
os.Remove(rootOptions.ConfigFile)
rootOptions.ConfigFile = ""
ctrl.Finish()
})
Context("basic cases", func() {
var (
err error
)
BeforeEach(func() {
var data []byte
data, err = generateSampleConfig()
Expect(err).To(BeNil())
err = ioutil.WriteFile(rootOptions.ConfigFile, data, 0664)
Expect(err).To(BeNil())
})
It("should success", func() {
name := "fake-name"
client.PrepareForComputerCreateRequest(roundTripper, "http://localhost:8080/jenkins",
"admin", "111e3a2f0231198855dceaff96f20540a9", name)
rootCmd.SetArgs([]string{"computer", "create", name})
_, err = rootCmd.ExecuteC()
Expect(err).NotTo(HaveOccurred())
})
})
})
package cmd
import (
"github.com/jenkins-zh/jenkins-cli/client"
"github.com/jenkins-zh/jenkins-cli/app/i18n"
"github.com/spf13/cobra"
)
// ComputerDeleteOption option for agent delete command
type ComputerDeleteOption struct {
CommonOption
}
var computerDeleteOption ComputerDeleteOption
func init() {
computerCmd.AddCommand(computerDeleteCmd)
}
var computerDeleteCmd = &cobra.Command{
Use: "delete",
Aliases: GetAliasesDel(),
Short: i18n.T("Delete an agent from Jenkins"),
Long: i18n.T("Delete an agent from Jenkins"),
Args: cobra.MinimumNArgs(1),
Example: `jcli agent delete agent-name`,
RunE: func(cmd *cobra.Command, args []string) (err error) {
jClient := &client.ComputerClient{
JenkinsCore: client.JenkinsCore{
RoundTripper: computerDeleteOption.RoundTripper,
},
}
getCurrentJenkinsAndClient(&(jClient.JenkinsCore))
err = jClient.Delete(args[0])
return
},
Annotations: map[string]string{
since: "v0.0.24",
},
}
package cmd
import (
"bytes"
"io"
"io/ioutil"
"os"
"github.com/jenkins-zh/jenkins-cli/client"
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/jenkins-zh/jenkins-cli/mock/mhttp"
)
var _ = Describe("create delete command", func() {
var (
ctrl *gomock.Controller
roundTripper *mhttp.MockRoundTripper
buf io.Writer
)
BeforeEach(func() {
ctrl = gomock.NewController(GinkgoT())
roundTripper = mhttp.NewMockRoundTripper(ctrl)
rootCmd.SetArgs([]string{})
buf = new(bytes.Buffer)
rootCmd.SetOutput(buf)
rootOptions.Jenkins = ""
rootOptions.ConfigFile = "test.yaml"
computerDeleteOption.RoundTripper = roundTripper
})
AfterEach(func() {
rootCmd.SetArgs([]string{})
os.Remove(rootOptions.ConfigFile)
rootOptions.ConfigFile = ""
ctrl.Finish()
})
Context("basic cases", func() {
var (
err error
)
BeforeEach(func() {
var data []byte
data, err = generateSampleConfig()
Expect(err).To(BeNil())
err = ioutil.WriteFile(rootOptions.ConfigFile, data, 0664)
Expect(err).To(BeNil())
})
It("should success", func() {
name := "fake-name"
client.PrepareForComputerDeleteRequest(roundTripper, "http://localhost:8080/jenkins",
"admin", "111e3a2f0231198855dceaff96f20540a9", name)
rootCmd.SetArgs([]string{"computer", "delete", name})
_, err = rootCmd.ExecuteC()
Expect(err).NotTo(HaveOccurred())
})
})
})
package cmd
import (
"fmt"
"github.com/jenkins-zh/jenkins-cli/client"
"github.com/jenkins-zh/jenkins-cli/util"
"go.uber.org/zap"
"io/ioutil"
"os"
"github.com/jenkins-zh/jenkins-cli/app/i18n"
......@@ -11,34 +16,95 @@ import (
// ComputerLaunchOption option for config list command
type ComputerLaunchOption struct {
CommonOption
Type string
ShowProgress bool
/** share info between inner functions */
ComputerClient *client.ComputerClient
CurrentJenkins *JenkinsServer
Output string
}
var computerLaunchOption ComputerLaunchOption
func init() {
computerCmd.AddCommand(computerLaunchCmd)
computerLaunchCmd.Flags().StringVarP(&computerLaunchOption.Type, "type", "", "",
i18n.T("The type of agent, include jnlp"))
computerLaunchCmd.Flags().BoolVarP(&computerLaunchOption.ShowProgress, "show-progress", "", true,
i18n.T("Show the progress of downloading agent.jar"))
}
var computerLaunchCmd = &cobra.Command{
Use: "launch <name>",
Use: "launch",
Aliases: []string{"start"},
Short: i18n.T("Launch the agent of your Jenkins"),
Long: i18n.T("Launch the agent of your Jenkins"),
Args: cobra.MinimumNArgs(1),
Example: `jcli agent launch agent-name
jcli agent launch agent-name --type jnlp`,
PreRunE: func(_ *cobra.Command, args []string) (err error) {
if computerLaunchOption.Type != "jnlp" {
return
}
var f *os.File
if f, err = ioutil.TempFile("/tmp", "agent.jar"); err == nil {
computerLaunchOption.Output = f.Name()
downloader := util.HTTPDownloader{
RoundTripper: computerLaunchOption.RoundTripper,
TargetFilePath: computerLaunchOption.Output,
URL: fmt.Sprintf("%s/jnlpJars/agent.jar", computerLaunchOption.ComputerClient.URL),
ShowProgress: computerLaunchOption.ShowProgress,
}
err = downloader.DownloadFile()
}
return
},
RunE: func(_ *cobra.Command, args []string) (err error) {
name := args[0]
jClient := &client.ComputerClient{
JenkinsCore: client.JenkinsCore{
RoundTripper: computerLaunchOption.RoundTripper,
},
}
getCurrentJenkinsAndClient(&(jClient.JenkinsCore))
computerLaunchOption.ComputerClient = jClient
computerLaunchOption.CurrentJenkins = getCurrentJenkinsAndClient(&(jClient.JenkinsCore))
err = jClient.Launch(args[0])
switch computerLaunchOption.Type {
case "":
err = computerLaunchOption.Launch(name)
case "jnlp":
err = computerLaunchOption.LaunchJnlp(name)
}
return
// /computer/nginx/toggleOffline
// json: {"offlineMessage": "sdd", "Jenkins-Crumb": "c1482407700d5edfeca3d78315fe7ca33ba89caaaf55ae6b3c6f351fcc2f5470"}
// /computer/nginx/changeOfflineCause
//json: {"offlineMessage": "被 jenkins : sdd断开连接", "Jenkins-Crumb": "c1482407700d5edfeca3d78315fe7ca33ba89caaaf55ae6b3c6f351fcc2f5470"}
},
}
// Launch start a normal agent
func (o *ComputerLaunchOption) Launch(name string) (err error) {
err = o.ComputerClient.Launch(name)
return
}
// LaunchJnlp start a JNLP agent
func (o *ComputerLaunchOption) LaunchJnlp(name string) (err error) {
var secret string
if secret, err = o.ComputerClient.GetSecret(name); err == nil {
var binary string
binary, err = util.LookPath("java", centerStartOption.LookPathContext)
if err == nil {
env := os.Environ()
agentArgs := []string{"java", "-jar", computerLaunchOption.Output,
"-jnlpUrl", fmt.Sprintf("%s/computer/%s/slave-agent.jnlp", o.ComputerClient.URL, name),
"-secret", secret, "-workDir", "/tmp"}
logger.Debug("start a jnlp agent", zap.Any("command", agentArgs))
err = util.Exec(binary, agentArgs, env, o.SystemCallExec)
}
}
return
}
......@@ -2,8 +2,10 @@ package cmd
import (
"bytes"
"github.com/jenkins-zh/jenkins-cli/util"
"io"
"io/ioutil"
"net/http"
"os"
"github.com/jenkins-zh/jenkins-cli/client"
......@@ -20,6 +22,8 @@ var _ = Describe("computer launch command", func() {
ctrl *gomock.Controller
roundTripper *mhttp.MockRoundTripper
buf io.Writer
err error
name string
)
BeforeEach(func() {
......@@ -32,6 +36,13 @@ var _ = Describe("computer launch command", func() {
rootOptions.ConfigFile = "test.yaml"
computerLaunchOption.RoundTripper = roundTripper
name = "fake"
var data []byte
data, err = generateSampleConfig()
Expect(err).To(BeNil())
err = ioutil.WriteFile(rootOptions.ConfigFile, data, 0664)
Expect(err).To(BeNil())
})
AfterEach(func() {
......@@ -41,21 +52,8 @@ var _ = Describe("computer launch command", func() {
ctrl.Finish()
})
Context("basic cases", func() {
var (
err error
)
BeforeEach(func() {
var data []byte
data, err = generateSampleConfig()
Expect(err).To(BeNil())
err = ioutil.WriteFile(rootOptions.ConfigFile, data, 0664)
Expect(err).To(BeNil())
})
Context("launch a default type of agent", func() {
It("should success", func() {
name := "fake"
client.PrepareForLaunchComputer(roundTripper, "http://localhost:8080/jenkins",
"admin", "111e3a2f0231198855dceaff96f20540a9", name)
......@@ -65,4 +63,34 @@ var _ = Describe("computer launch command", func() {
Expect(err).To(BeNil())
})
})
Context("launch a jnlp agent", func() {
var (
fakeJar string
)
BeforeEach(func() {
fakeJar = "fake-jar-content"
computerLaunchOption.SystemCallExec = util.FakeSystemCallExecSuccess
computerLaunchOption.LookPathContext = util.FakeLookPath
request, _ := http.NewRequest("GET", "http://localhost:8080/jenkins/jnlpJars/agent.jar", nil)
response := &http.Response{
StatusCode: 200,
Request: request,
Body: ioutil.NopCloser(bytes.NewBufferString(fakeJar)),
}
roundTripper.EXPECT().
RoundTrip(request).Return(response, nil)
secret := "fake-secret"
client.PrepareForComputerAgentSecretRequest(roundTripper,
"http://localhost:8080/jenkins", "admin", "111e3a2f0231198855dceaff96f20540a9", name, secret)
})
It("should success", func() {
rootCmd.SetArgs([]string{"computer", "launch", name, "--type", "jnlp", "--show-progress=false"})
_, err = rootCmd.ExecuteC()
Expect(err).NotTo(HaveOccurred())
})
})
})
......@@ -32,7 +32,7 @@ func init() {
var credentialDeleteCmd = &cobra.Command{
Use: "delete [store] [id]",
Aliases: []string{"remove", "del"},
Aliases: GetAliasesDel(),
Short: i18n.T("Delete a credential from Jenkins"),
Long: i18n.T("Delete a credential from Jenkins"),
PreRunE: func(cmd *cobra.Command, args []string) (err error) {
......
......@@ -26,10 +26,11 @@ func init() {
}
var jobDeleteCmd = &cobra.Command{
Use: "delete <jobName>",
Short: i18n.T("Delete a job in your Jenkins"),
Long: i18n.T("Delete a job in your Jenkins"),
Args: cobra.MinimumNArgs(1),
Use: "delete <jobName>",
Aliases: GetAliasesDel(),
Short: i18n.T("Delete a job in your Jenkins"),
Long: i18n.T("Delete a job in your Jenkins"),
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
jobName := args[0]
if !jobDeleteOption.Confirm(fmt.Sprintf("Are you sure to delete job %s ?", jobName)) {
......
......@@ -24,10 +24,11 @@ func init() {
}
var userDeleteCmd = &cobra.Command{
Use: "delete <username>",
Short: "Delete a user for your Jenkins",
Long: `Delete a user for your Jenkins`,
Args: cobra.MinimumNArgs(1),
Use: "delete <username>",
Aliases: GetAliasesDel(),
Short: "Delete a user for your Jenkins",
Long: `Delete a user for your Jenkins`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
username := args[0]
......
......@@ -2,8 +2,11 @@ package client
import (
"fmt"
"github.com/jenkins-zh/jenkins-cli/util"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
// ComputerClient is client for operate computers
......@@ -25,6 +28,26 @@ func (c *ComputerClient) Launch(name string) (err error) {
return
}
// Delete removes a agent from Jenkins
func (c *ComputerClient) Delete(name string) (err error) {
api := fmt.Sprintf("/computer/%s/doDelete", name)
_, err = c.RequestWithoutData("POST", api, nil, nil, 200)
return
}
// GetSecret returns the secret of an agent
func (c *ComputerClient) GetSecret(name string) (secret string, err error) {
api := fmt.Sprintf("/instance/agentSecret?name=%s", name)
var response *http.Response
if response, err = c.RequestWithResponse("POST", api, nil, nil); err == nil {
var data []byte
if data, err = ioutil.ReadAll(response.Body); err == nil {
secret = string(data)
}
}
return
}
// GetLog fetch the log a computer
func (c *ComputerClient) GetLog(name string) (log string, err error) {
var response *http.Response
......@@ -44,6 +67,52 @@ func (c *ComputerClient) GetLog(name string) (log string, err error) {
return
}
// Create creates a computer by name
func (c *ComputerClient) Create(name string) (err error) {
formData := url.Values{
"name": {name},
"mode": {"hudson.slaves.DumbSlave"},
}
payload := strings.NewReader(formData.Encode())
if _, err = c.RequestWithoutData("POST", "/computer/createItem",
map[string]string{util.ContentType: util.ApplicationForm}, payload, 200); err == nil {
payload = GetPayloadForCreateAgent(name)
_, err = c.RequestWithoutData("POST", "/computer/doCreateItem",
map[string]string{util.ContentType: util.ApplicationForm}, payload, 200)
}
return
}
// GetPayloadForCreateAgent returns a payload for creating an agent
func GetPayloadForCreateAgent(name string) *strings.Reader {
palyLoad := fmt.Sprintf(`{
"name": "%s",
"nodeDescription": "",
"numExecutors": "1",
"remoteFS": "/abc",
"labelString": "",
"mode": "NORMAL",
"launcher": {
"$class": "hudson.slaves.JNLPLauncher",
"workDirSettings": {
"disabled": false,
"workDirPath": "",
"internalDir": "remoting",
"failIfWorkDirIsMissing": false
},
"tunnel": "",
"vmargs": ""
},
"type": "hudson.slaves.DumbSlave"
}`, name)
formData := url.Values{
"name": {name},
"type": {"hudson.slaves.DumbSlave"},
"json": {palyLoad},
}
return strings.NewReader(formData.Encode())
}
// Computer is the agent of Jenkins
type Computer struct {
AssignedLabels []ComputerLabel
......
......@@ -13,6 +13,7 @@ var _ = Describe("computer test", func() {
ctrl *gomock.Controller
computerClient client.ComputerClient
roundTripper *mhttp.MockRoundTripper
name string
)
BeforeEach(func() {
......@@ -22,6 +23,7 @@ var _ = Describe("computer test", func() {
computerClient = client.ComputerClient{}
computerClient.RoundTripper = roundTripper
computerClient.URL = "http://localhost"
name = "fake-name"
})
AfterEach(func() {
......@@ -38,7 +40,6 @@ var _ = Describe("computer test", func() {
})
It("Launch", func() {
name := "fake-name"
client.PrepareForLaunchComputer(roundTripper, computerClient.URL, "", "", name)
err := computerClient.Launch(name)
......@@ -46,7 +47,6 @@ var _ = Describe("computer test", func() {
})
It("GetLog", func() {
name := "fake-name"
client.PrepareForComputerLogRequest(roundTripper, computerClient.URL, "", "", name)
log, err := computerClient.GetLog(name)
......@@ -55,10 +55,33 @@ var _ = Describe("computer test", func() {
})
It("GetLog with 500", func() {
name := "fake-name"
client.PrepareForComputerLogRequestWithCode(roundTripper, computerClient.URL, "", "", name, 500)
_, err := computerClient.GetLog(name)
Expect(err).To(HaveOccurred())
})
It("Delete an agent", func() {
client.PrepareForComputerDeleteRequest(roundTripper, computerClient.URL, "", "", name)
err := computerClient.Delete(name)
Expect(err).NotTo(HaveOccurred())
})
It("GetSecret of an agent", func() {
secret := "fake-secret"
client.PrepareForComputerAgentSecretRequest(roundTripper,
computerClient.URL, "", "", name, secret)
result, err := computerClient.GetSecret(name)
Expect(err).NotTo(HaveOccurred())
Expect(result).To(Equal(secret))
})
It("Create an agent", func() {
client.PrepareForComputerCreateRequest(roundTripper, computerClient.URL, "", "", name)
err := computerClient.Create(name)
Expect(err).NotTo(HaveOccurred())
})
})
......@@ -3,8 +3,11 @@ package client
import (
"bytes"
"fmt"
"github.com/jenkins-zh/jenkins-cli/util"
"io/ioutil"
"net/http"
"net/url"
"strings"
"github.com/jenkins-zh/jenkins-cli/mock/mhttp"
)
......@@ -51,6 +54,35 @@ func PrepareForComputerLogRequestWithCode(roundTripper *mhttp.MockRoundTripper,
}
}
// PrepareForComputerDeleteRequest only for test
func PrepareForComputerDeleteRequest(roundTripper *mhttp.MockRoundTripper, rootURL, user, password, name string) {
request, _ := http.NewRequest("POST", fmt.Sprintf("%s/computer/%s/doDelete", rootURL, name), nil)
PrepareCommonPost(request, "", roundTripper, user, password, rootURL)
}
// PrepareForComputerAgentSecretRequest only for test
func PrepareForComputerAgentSecretRequest(roundTripper *mhttp.MockRoundTripper, rootURL, user, password, name, secret string) {
request, _ := http.NewRequest("POST", fmt.Sprintf("%s/instance/agentSecret?name=%s", rootURL, name), nil)
PrepareCommonPost(request, secret, roundTripper, user, password, rootURL)
}
// PrepareForComputerCreateRequest only for test
func PrepareForComputerCreateRequest(roundTripper *mhttp.MockRoundTripper, rootURL, user, password, name string) {
formData := url.Values{
"name": {name},
"mode": {"hudson.slaves.DumbSlave"},
}
payload := strings.NewReader(formData.Encode())
request, _ := http.NewRequest("POST", fmt.Sprintf("%s/computer/createItem", rootURL), payload)
request.Header.Add(util.ContentType, util.ApplicationForm)
PrepareCommonPost(request, "", roundTripper, user, password, rootURL)
payload = GetPayloadForCreateAgent(name)
request, _ = http.NewRequest("POST", fmt.Sprintf("%s/computer/doCreateItem", rootURL), payload)
request.Header.Add(util.ContentType, util.ApplicationForm)
PrepareCommonPost(request, "", roundTripper, user, password, rootURL)
}
// PrepareForComputerList only for test
func PrepareForComputerList() string {
return `
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册