Skip to content

Commit

Permalink
Added intall plugin and credentials management
Browse files Browse the repository at this point in the history
  • Loading branch information
Piotr Jaromin committed Feb 4, 2018
1 parent 16a895f commit 034124c
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 9 deletions.
73 changes: 65 additions & 8 deletions credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const deleteCredentialURL = baseCredentialsURL + "credential/%s/delete"
const configCredentialURL = baseCredentialsURL + "credential/%s/config.xml"
const credentialsListURL = baseCredentialsURL + "api/json"

var listQuery = map[string]string{
"tree": "credentials[id]",
}

//ClassUsernameCredentials is name if java class which implements credentials that store username-password pair
const ClassUsernameCredentials = "com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl"

Expand All @@ -31,15 +35,63 @@ type credentialIDs struct {

//UsernameCredentials struct representing credential for storing username-password pair
type UsernameCredentials struct {
XMLName xml.Name `xml:"com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl"`
ID string `xml:"id"`
Scope string `xml:"scope"`
Username string `xml:"username"`
Password string `xml:"password"`
XMLName xml.Name `xml:"com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl"`
ID string `xml:"id"`
Scope string `xml:"scope"`
Description string `xml:"description"`
Username string `xml:"username"`
Password string `xml:"password"`
}

var listQuery = map[string]string{
"tree": "credentials[id]",
//StringCredentials store only secret text
type StringCredentials struct {
XMLName xml.Name `xml:"org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl"`
ID string `xml:"id"`
Scope string `xml:"scope"`
Description string `xml:"description"`
Secret string `xml:"secret"`
}

//SSHCredentials store credentials for ssh keys.
type SSHCredentials struct {
XMLName xml.Name `xml:"com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey"`
ID string `xml:"id"`
Scope string `xml:"scope"`
Username string `xml:"username"`
Description string `xml:"description,omitempty"`
PrivateKeySource interface{} `xml:"privateKeySource"`
Passphrase string `xml:"passphrase,omitempty"`
}

//DockerServerCredentials store credentials for docker keys.
type DockerServerCredentials struct {
XMLName xml.Name `xml:"org.jenkinsci.plugins.docker.commons.credentials.DockerServerCredentials"`
ID string `xml:"id"`
Scope string `xml:"scope"`
Username string `xml:"username"`
Description string `xml:"description,omitempty"`
ClientKey string `xml:"clientKey"`
ClientCertificate string `xml:"clientCertificate"`
ServerCaCertificate string `xml:"serverCaCertificate"`
}

//KeySourceDirectEntryType is used when secret in provided directly as private key value
const KeySourceDirectEntryType = "com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey$DirectEntryPrivateKeySource"

//KeySourceOnMasterType is used when private key value is path to file on jenkins master
const KeySourceOnMasterType = "com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey$FileOnMasterPrivateKeySource"

//PrivateKey used in SSHCredentials type, type can be either:
//KeySourceDirectEntryType - then value should be text with secret
//KeySourceOnMasterType - then value should be path on master jenkins where secret is stored
type PrivateKey struct {
Value string `xml:"privateKey"`
Class string `xml:"class,attr"`
}

type PrivateKeyFile struct {
Value string `xml:"privateKeyFile"`
Class string `xml:"class,attr"`
}

//List ids if credentials stored inside provided domain
Expand All @@ -62,8 +114,13 @@ func (cm CredentialsManager) List(domain string) ([]string, error) {
//GetSingle searches for credential in given domain with given id, if credential is found
//it will be parsed as xml to creds parameter(creds must be pointer to struct)
func (cm CredentialsManager) GetSingle(domain string, id string, creds interface{}) error {
str := ""
err := cm.handleResponse(cm.J.Requester.Get(fmt.Sprintf(configCredentialURL, domain, id), &str, map[string]string{}))
if err != nil {
return err
}

return cm.handleResponse(cm.J.Requester.Get(fmt.Sprintf(configCredentialURL, domain, id), creds, map[string]string{}))
return xml.Unmarshal([]byte(str), &creds)
}

//Add credential to given domain, creds must be struct which is parsable to xml
Expand Down
106 changes: 106 additions & 0 deletions credentials_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package gojenkins

import (
"github.com/stretchr/testify/assert"
"os"
"testing"
)

var (
cm CredentialsManager
domain = "_"
dockerID = "dockerIDCred"
sshID = "sshIdCred"
usernameID = "usernameIDcred"
scope = "GLOBAL"
)

func TestCreateUsernameCredentials(t *testing.T) {

cred := UsernameCredentials{
ID: usernameID,
Scope: scope,
Username: "usernameTest",
Password: "pass",
}

err := cm.Add(domain, cred)
assert.Nil(t, err, "Could not create credential")

getCred := UsernameCredentials{}
err = cm.GetSingle(domain, cred.ID, &getCred)
assert.Nil(t, err, "Could not get credential")

assert.Equal(t, cred.Scope, getCred.Scope, "Scope is not equal")
assert.Equal(t, cred.ID, cred.ID, "ID is not equal")
assert.Equal(t, cred.Username, cred.Username, "Username is not equal")
}

func TestCreateDockerCredentials(t *testing.T) {

cred := DockerServerCredentials{
Scope: scope,
ID: dockerID,
Username: "docker-name",
ClientCertificate: "some secret value",
ClientKey: "client key",
}

err := cm.Add(domain, cred)
assert.Nil(t, err, "Could not create credential")

getCred := DockerServerCredentials{}
err = cm.GetSingle(domain, cred.ID, &getCred)
assert.Nil(t, err, "Could not get credential")

assert.Equal(t, cred.Scope, getCred.Scope, "Scope is not equal")
assert.Equal(t, cred.ID, cred.ID, "ID is not equal")
assert.Equal(t, cred.Username, cred.Username, "Username is not equal")
assert.Equal(t, cred.ClientCertificate, cred.ClientCertificate, "ClientCertificate is not equal")
assert.Equal(t, cred.ClientKey, cred.ClientKey, "Username is not equal")

}

func TestCreateSSHCredentialsFullFlow(t *testing.T) {
sshCred := SSHCredentials{
Scope: scope,
ID: sshID,
Username: "RANDONMANE",
Passphrase: "password",
PrivateKeySource: &PrivateKeyFile{
Value: "testValueofkey",
Class: KeySourceOnMasterType,
},
}

err := cm.Add(domain, sshCred)
assert.Nil(t, err, "Could not create credential")

sshCred.Username = "new_username"
err = cm.Update(domain, sshCred.ID, sshCred)
assert.Nil(t, err, "Could not update credential")

getSSH := SSHCredentials{}
err = cm.GetSingle(domain, sshCred.ID, &getSSH)
assert.Nil(t, err, "Could not get ssh credential")

assert.Equal(t, sshCred.Scope, getSSH.Scope, "Scope is not equal")
assert.Equal(t, sshCred.ID, getSSH.ID, "ID is not equal")
assert.Equal(t, sshCred.Username, getSSH.Username, "Username is not equal")
assert.Equal(t, sshCred.Scope, getSSH.Scope, "Scope is not equal")

err = cm.Delete(domain, getSSH.ID)
assert.Nil(t, err, "Could not delete credentials")

}

func TestMain(m *testing.M) {
//setup
jenkins := CreateJenkins(nil, "http://localhost:8080", "admin", "admin")
jenkins.Init()

cm = CredentialsManager{J: jenkins}

//execute tests
os.Exit(m.Run())
}
27 changes: 27 additions & 0 deletions jenkins.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ func (j *Jenkins) Info() (*ExecutorResponse, error) {
return j.Raw, nil
}

// SafeRestart jenkins, restart will be done when there are no jobs running
func (j *Jenkins) SafeRestart() error {
_, err := j.Requester.Post("/safeRestart", strings.NewReader(""), struct{}{}, map[string]string{})
return err
}

// Create a new Node
// Can be JNLPLauncher or SSHLauncher
// Example : jenkins.CreateNode("nodeName", 1, "Description", "/var/lib/jenkins", "jdk8 docker", map[string]string{"method": "JNLPLauncher"})
Expand Down Expand Up @@ -426,6 +432,16 @@ func (j *Jenkins) GetPlugins(depth int) (*Plugins, error) {
return &p, nil
}

// UninstallPlugin plugin otherwise returns error
func (j *Jenkins) UninstallPlugin(name string) error {
url := fmt.Sprintf("/pluginManager/plugin/%s/doUninstall", name)
resp, err := j.Requester.Post(url, strings.NewReader(""), struct{}{}, map[string]string{})
if resp.StatusCode != 200 {
return fmt.Errorf("Invalid status code returned: %d", resp.StatusCode)
}
return err
}

// Check if the plugin is installed on the server.
// Depth level 1 is used. If you need to go deeper, you can use GetPlugins, and iterate through them.
func (j *Jenkins) HasPlugin(name string) (*Plugin, error) {
Expand All @@ -437,6 +453,17 @@ func (j *Jenkins) HasPlugin(name string) (*Plugin, error) {
return p.Contains(name), nil
}

//InstallPlugin with given version and name
func (j *Jenkins) InstallPlugin(name string, version string) error {
xml := fmt.Sprintf(`<jenkins><install plugin="%s@%s" /></jenkins>`, name, version)
resp, err := j.Requester.PostXML("/pluginManager/installNecessaryPlugins", xml, j.Raw, map[string]string{})

if resp.StatusCode != 200 {
return fmt.Errorf("Invalid status code returned: %d", resp.StatusCode)
}
return err
}

// Verify FingerPrint
func (j *Jenkins) ValidateFingerPrint(id string) (bool, error) {
fp := FingerPrint{Jenkins: j, Base: "/fingerprint/", Id: id, Raw: new(FingerPrintResponse)}
Expand Down
9 changes: 8 additions & 1 deletion jenkins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,18 @@ func TestConcurrentRequests(t *testing.T) {
}
}

func TestInstallPlugin(t *testing.T) {

err := jenkins.InstallPlugin("packer", "1.4")

assert.Nil(t, err, "Could not install plugin")
}

func getFileAsString(path string) string {
buf, err := ioutil.ReadFile("_tests/" + path)
if err != nil {
panic(err)
}

return string(buf)
}
}

0 comments on commit 034124c

Please sign in to comment.