Moves common code out of Jenkins plugin test to utility package.
Adds tests to build pipeline to validate new pipeline examples.
... | ... |
@@ -34,9 +34,9 @@ objects: |
34 | 34 |
node { |
35 | 35 |
project = env.PROJECT_NAME |
36 | 36 |
stage("Initialize") { |
37 |
- sh "oc get route ${appName} -n ${project} -o jsonpath='{ .spec.to.weight }' > blueweight" |
|
38 |
- blueWeight = readFile('blueweight').trim() |
|
39 |
- if (blueWeight == "100") { |
|
37 |
+ sh "oc get route ${appName} -n ${project} -o jsonpath='{ .spec.to.name }' > activeservice" |
|
38 |
+ activeService = readFile('activeservice').trim() |
|
39 |
+ if (activeService == "${appName}-blue") { |
|
40 | 40 |
tag = "green" |
41 | 41 |
altTag = "blue" |
42 | 42 |
} |
... | ... |
@@ -55,7 +55,7 @@ objects: |
55 | 55 |
} |
56 | 56 |
|
57 | 57 |
stage("Test") { |
58 |
- input "Test deployment: http://${routeHost}. Approve?" |
|
58 |
+ input message: "Test deployment: http://${routeHost}. Approve?", id: "approval" |
|
59 | 59 |
} |
60 | 60 |
|
61 | 61 |
stage("Go Live") { |
... | ... |
@@ -4894,9 +4894,9 @@ objects: |
4894 | 4894 |
node { |
4895 | 4895 |
project = env.PROJECT_NAME |
4896 | 4896 |
stage("Initialize") { |
4897 |
- sh "oc get route ${appName} -n ${project} -o jsonpath='{ .spec.to.weight }' > blueweight" |
|
4898 |
- blueWeight = readFile('blueweight').trim() |
|
4899 |
- if (blueWeight == "100") { |
|
4897 |
+ sh "oc get route ${appName} -n ${project} -o jsonpath='{ .spec.to.name }' > activeservice" |
|
4898 |
+ activeService = readFile('activeservice').trim() |
|
4899 |
+ if (activeService == "${appName}-blue") { |
|
4900 | 4900 |
tag = "green" |
4901 | 4901 |
altTag = "blue" |
4902 | 4902 |
} |
... | ... |
@@ -4915,7 +4915,7 @@ objects: |
4915 | 4915 |
} |
4916 | 4916 |
|
4917 | 4917 |
stage("Test") { |
4918 |
- input "Test deployment: http://${routeHost}. Approve?" |
|
4918 |
+ input message: "Test deployment: http://${routeHost}. Approve?", id: "approval" |
|
4919 | 4919 |
} |
4920 | 4920 |
|
4921 | 4921 |
stage("Go Live") { |
... | ... |
@@ -2,52 +2,164 @@ package builds |
2 | 2 |
|
3 | 3 |
import ( |
4 | 4 |
"fmt" |
5 |
+ "strings" |
|
6 |
+ "sync" |
|
7 |
+ "time" |
|
5 | 8 |
|
6 | 9 |
g "github.com/onsi/ginkgo" |
7 | 10 |
o "github.com/onsi/gomega" |
8 | 11 |
|
9 | 12 |
exutil "github.com/openshift/origin/test/extended/util" |
13 |
+ "github.com/openshift/origin/test/extended/util/jenkins" |
|
10 | 14 |
) |
11 | 15 |
|
12 | 16 |
var _ = g.Describe("[builds][Slow] openshift pipeline build", func() { |
13 | 17 |
defer g.GinkgoRecover() |
14 | 18 |
var ( |
15 |
- pipelineTemplatePath = exutil.FixturePath("testdata", "test-pipeline.json") |
|
16 |
- jenkinsTemplatePath = exutil.FixturePath("..", "..", "examples", "jenkins", "jenkins-ephemeral-template.json") |
|
17 |
- oc = exutil.NewCLI("jenkins-pipeline", exutil.KubeConfigPath()) |
|
19 |
+ jenkinsTemplatePath = exutil.FixturePath("..", "..", "examples", "jenkins", "jenkins-ephemeral-template.json") |
|
20 |
+ mavenSlavePipelinePath = exutil.FixturePath("..", "..", "examples", "jenkins", "pipeline", "maven-pipeline.yaml") |
|
21 |
+ orchestrationPipelinePath = exutil.FixturePath("..", "..", "examples", "jenkins", "pipeline", "mapsapp-pipeline.yaml") |
|
22 |
+ blueGreenPipelinePath = exutil.FixturePath("..", "..", "examples", "jenkins", "pipeline", "bluegreen-pipeline.yaml") |
|
23 |
+ |
|
24 |
+ oc = exutil.NewCLI("jenkins-pipeline", exutil.KubeConfigPath()) |
|
18 | 25 |
) |
19 | 26 |
g.JustBeforeEach(func() { |
20 | 27 |
g.By("waiting for builder service account") |
21 | 28 |
err := exutil.WaitForBuilderAccount(oc.KubeClient().Core().ServiceAccounts(oc.Namespace())) |
22 | 29 |
o.Expect(err).NotTo(o.HaveOccurred()) |
23 | 30 |
}) |
24 |
- g.Context("Manual deploy the jenkins and trigger a jenkins pipeline build", func() { |
|
25 |
- g.It("JenkinsPipeline build should succeed when manual deploy the jenkins service", func() { |
|
26 |
- oc.SetOutputDir(exutil.TestContext.OutputDir) |
|
27 |
- |
|
31 |
+ g.Context("Pipeline with maven slave", func() { |
|
32 |
+ g.It("Should build and complete successfully", func() { |
|
33 |
+ // Deploy Jenkins |
|
28 | 34 |
g.By(fmt.Sprintf("calling oc new-app -f %q", jenkinsTemplatePath)) |
29 | 35 |
err := oc.Run("new-app").Args("-f", jenkinsTemplatePath).Execute() |
30 | 36 |
o.Expect(err).NotTo(o.HaveOccurred()) |
31 | 37 |
|
32 |
- //wait for the jenkins deployment complete |
|
33 |
- g.By("waiting the jenkins service deployed") |
|
34 |
- err = exutil.WaitForADeploymentToComplete(oc.KubeClient().Core().ReplicationControllers(oc.Namespace()), "jenkins", oc) |
|
35 |
- if err != nil { |
|
36 |
- exutil.DumpDeploymentLogs("jenkins", oc) |
|
37 |
- } |
|
38 |
+ // instantiate the template |
|
39 |
+ g.By(fmt.Sprintf("calling oc new-app -f %q", mavenSlavePipelinePath)) |
|
40 |
+ err = oc.Run("new-app").Args("-f", mavenSlavePipelinePath).Execute() |
|
41 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
42 |
+ |
|
43 |
+ // start the build |
|
44 |
+ g.By("starting the pipeline build and waiting for it to complete") |
|
45 |
+ br, _ := exutil.StartBuildAndWait(oc, "openshift-jee-sample") |
|
46 |
+ br.AssertSuccess() |
|
47 |
+ |
|
48 |
+ // wait for the service to be running |
|
49 |
+ g.By("expecting the openshift-jee-sample service to be deployed and running") |
|
50 |
+ _, err = exutil.GetEndpointAddress(oc, "openshift-jee-sample") |
|
38 | 51 |
o.Expect(err).NotTo(o.HaveOccurred()) |
39 |
- // create the pipeline build example |
|
40 |
- g.By(fmt.Sprintf("calling oc new-app -f %q", pipelineTemplatePath)) |
|
41 |
- err = oc.Run("new-app").Args("-f", pipelineTemplatePath).Execute() |
|
52 |
+ }) |
|
53 |
+ }) |
|
54 |
+ |
|
55 |
+ g.Context("Orchestration pipeline", func() { |
|
56 |
+ g.It("Should build and complete successfully", func() { |
|
57 |
+ // Deploy Jenkins |
|
58 |
+ g.By(fmt.Sprintf("calling oc new-app -f %q", jenkinsTemplatePath)) |
|
59 |
+ err := oc.Run("new-app").Args("-f", jenkinsTemplatePath).Execute() |
|
42 | 60 |
o.Expect(err).NotTo(o.HaveOccurred()) |
43 | 61 |
|
44 |
- g.By("starting a pipeline build") |
|
45 |
- br, _ := exutil.StartBuildAndWait(oc, "sample-pipeline") |
|
46 |
- if !br.BuildSuccess { |
|
47 |
- exutil.DumpDeploymentLogs("jenkins", oc) |
|
48 |
- } |
|
62 |
+ // instantiate the template |
|
63 |
+ g.By(fmt.Sprintf("calling oc new-app -f %q", orchestrationPipelinePath)) |
|
64 |
+ err = oc.Run("new-app").Args("-f", orchestrationPipelinePath).Execute() |
|
65 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
66 |
+ |
|
67 |
+ // start the build |
|
68 |
+ g.By("starting the pipeline build and waiting for it to complete") |
|
69 |
+ br, _ := exutil.StartBuildAndWait(oc, "mapsapp-pipeline") |
|
49 | 70 |
br.AssertSuccess() |
71 |
+ |
|
72 |
+ // wait for the service to be running |
|
73 |
+ g.By("expecting the parksmap-web service to be deployed and running") |
|
74 |
+ _, err = exutil.GetEndpointAddress(oc, "parksmap") |
|
75 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
50 | 76 |
}) |
51 | 77 |
}) |
52 | 78 |
|
79 |
+ g.Context("Blue-green pipeline", func() { |
|
80 |
+ g.It("Should build and complete successfully", func() { |
|
81 |
+ // Deploy Jenkins without oauth |
|
82 |
+ g.By(fmt.Sprintf("calling oc new-app -f %q -p ENABLE_OAUTH=false", jenkinsTemplatePath)) |
|
83 |
+ err := oc.Run("new-app").Args("-f", jenkinsTemplatePath, "-p", "ENABLE_OAUTH=false").Execute() |
|
84 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
85 |
+ |
|
86 |
+ j := jenkins.NewRef(oc) |
|
87 |
+ |
|
88 |
+ // instantiate the template |
|
89 |
+ g.By(fmt.Sprintf("calling oc new-app -f %q", blueGreenPipelinePath)) |
|
90 |
+ err = oc.Run("new-app").Args("-f", blueGreenPipelinePath).Execute() |
|
91 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
92 |
+ |
|
93 |
+ wg := &sync.WaitGroup{} |
|
94 |
+ wg.Add(1) |
|
95 |
+ |
|
96 |
+ // start the build |
|
97 |
+ go func() { |
|
98 |
+ g.By("starting the bluegreen pipeline build and waiting for it to complete") |
|
99 |
+ br, _ := exutil.StartBuildAndWait(oc, "bluegreen-pipeline") |
|
100 |
+ br.AssertSuccess() |
|
101 |
+ |
|
102 |
+ g.By("verifying that the main route has been switched to green") |
|
103 |
+ value, err := oc.Run("get").Args("route", "nodejs-mongodb-example", "-o", "jsonpath={ .spec.to.name }").Output() |
|
104 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
105 |
+ activeRoute := strings.TrimSpace(value) |
|
106 |
+ g.By("verifying that the active route is 'nodejs-mongodb-example-green'") |
|
107 |
+ o.Expect(activeRoute).To(o.Equal("nodejs-mongodb-example-green")) |
|
108 |
+ wg.Done() |
|
109 |
+ }() |
|
110 |
+ |
|
111 |
+ // wait for the green service to be available |
|
112 |
+ g.By("waiting for the nodejs-mongodb-example-green service to be available") |
|
113 |
+ _, err = exutil.GetEndpointAddress(oc, "nodejs-mongodb-example-green") |
|
114 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
115 |
+ |
|
116 |
+ // approve the Jenkins pipeline |
|
117 |
+ g.By("Waiting for the approval prompt") |
|
118 |
+ jobName := oc.Namespace() + "-bluegreen-pipeline" |
|
119 |
+ _, err = j.WaitForContent("Approve?", 200, 10*time.Minute, "job/%s/lastBuild/consoleText", jobName) |
|
120 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
121 |
+ |
|
122 |
+ g.By("Approving the current build") |
|
123 |
+ _, _, err = j.Post(nil, fmt.Sprintf("job/%s/lastBuild/input/Approval/proceedEmpty", jobName), "") |
|
124 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
125 |
+ |
|
126 |
+ // wait for first build completion and verification |
|
127 |
+ g.By("Waiting for the build to complete successfully") |
|
128 |
+ wg.Wait() |
|
129 |
+ |
|
130 |
+ wg = &sync.WaitGroup{} |
|
131 |
+ wg.Add(1) |
|
132 |
+ |
|
133 |
+ // start the build again |
|
134 |
+ go func() { |
|
135 |
+ g.By("starting the bluegreen pipeline build and waiting for it to complete") |
|
136 |
+ br, _ := exutil.StartBuildAndWait(oc, "bluegreen-pipeline") |
|
137 |
+ br.AssertSuccess() |
|
138 |
+ |
|
139 |
+ g.By("verifying that the main route has been switched to blue") |
|
140 |
+ value, err := oc.Run("get").Args("route", "nodejs-mongodb-example", "-o", "jsonpath={ .spec.to.name }").Output() |
|
141 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
142 |
+ activeRoute := strings.TrimSpace(value) |
|
143 |
+ g.By("verifying that the active route is 'nodejs-mongodb-example-blue'") |
|
144 |
+ o.Expect(activeRoute).To(o.Equal("nodejs-mongodb-example-blue")) |
|
145 |
+ wg.Done() |
|
146 |
+ }() |
|
147 |
+ |
|
148 |
+ // wait for the blue service to be available |
|
149 |
+ g.By("waiting for the nodejs-mongodb-example-blue service to be available") |
|
150 |
+ _, err = exutil.GetEndpointAddress(oc, "nodejs-mongodb-example-blue") |
|
151 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
152 |
+ |
|
153 |
+ // approve the Jenkins pipeline |
|
154 |
+ g.By("Waiting for the approval prompt") |
|
155 |
+ _, err = j.WaitForContent("Approve?", 200, 10*time.Minute, "job/%s/lastBuild/consoleText", jobName) |
|
156 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
157 |
+ |
|
158 |
+ g.By("Approving the current build") |
|
159 |
+ _, _, err = j.Post(nil, fmt.Sprintf("job/%s/lastBuild/input/Approval/proceedEmpty", jobName), "") |
|
160 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
161 |
+ |
|
162 |
+ wg.Wait() |
|
163 |
+ }) |
|
164 |
+ }) |
|
53 | 165 |
}) |
... | ... |
@@ -2,12 +2,10 @@ package image_ecosystem |
2 | 2 |
|
3 | 3 |
import ( |
4 | 4 |
"bytes" |
5 |
- "encoding/xml" |
|
6 | 5 |
"fmt" |
7 |
- "io" |
|
8 |
- "io/ioutil" |
|
9 |
- "net/http" |
|
10 | 6 |
"net/url" |
7 |
+ "os" |
|
8 |
+ "os/exec" |
|
11 | 9 |
"regexp" |
12 | 10 |
"strings" |
13 | 11 |
"time" |
... | ... |
@@ -18,333 +16,21 @@ import ( |
18 | 18 |
kapi "k8s.io/kubernetes/pkg/api" |
19 | 19 |
"k8s.io/kubernetes/pkg/util/wait" |
20 | 20 |
|
21 |
- "os" |
|
22 |
- "os/exec" |
|
23 |
- |
|
24 | 21 |
exutil "github.com/openshift/origin/test/extended/util" |
22 |
+ "github.com/openshift/origin/test/extended/util/jenkins" |
|
25 | 23 |
) |
26 | 24 |
|
27 |
-type JenkinsRef struct { |
|
28 |
- oc *exutil.CLI |
|
29 |
- host string |
|
30 |
- port string |
|
31 |
- // The namespace in which the Jenkins server is running |
|
32 |
- namespace string |
|
33 |
- password string |
|
34 |
-} |
|
35 |
- |
|
36 | 25 |
const ( |
37 | 26 |
useLocalPluginSnapshotEnvVarName = "USE_SNAPSHOT_JENKINS_IMAGE" |
38 | 27 |
disableJenkinsMemoryStats = "DISABLE_JENKINS_MEMORY_MONITORING" |
39 | 28 |
localPluginSnapshotImage = "openshift/jenkins-plugin-snapshot-test:latest" |
40 | 29 |
) |
41 | 30 |
|
42 |
-// Struct can be marshalled into XML to represent a Jenkins workflow job definition. |
|
43 |
-type FlowDefinition struct { |
|
44 |
- XMLName xml.Name `xml:"flow-definition"` |
|
45 |
- Plugin string `xml:"plugin,attr"` |
|
46 |
- KeepDependencies bool `xml:"keepDependencies"` |
|
47 |
- Definition Definition |
|
48 |
-} |
|
49 |
- |
|
50 |
-type Definition struct { |
|
51 |
- XMLName xml.Name `xml:"definition"` |
|
52 |
- Class string `xml:"class,attr"` |
|
53 |
- Plugin string `xml:"plugin,attr"` |
|
54 |
- Script string `xml:"script"` |
|
55 |
-} |
|
56 |
- |
|
57 |
-// Builds a URI for the Jenkins server. |
|
58 |
-func (j *JenkinsRef) buildURI(resourcePathFormat string, a ...interface{}) string { |
|
59 |
- resourcePath := fmt.Sprintf(resourcePathFormat, a...) |
|
60 |
- return fmt.Sprintf("http://%s:%v/%s", j.host, j.port, resourcePath) |
|
61 |
-} |
|
62 |
- |
|
63 |
-// Submits a GET request to this Jenkins server. |
|
64 |
-// Returns a response body and status code or an error. |
|
65 |
-func (j *JenkinsRef) getResource(resourcePathFormat string, a ...interface{}) (string, int, error) { |
|
66 |
- uri := j.buildURI(resourcePathFormat, a...) |
|
67 |
- ginkgolog("Retrieving Jenkins resource: %q", uri) |
|
68 |
- req, err := http.NewRequest("GET", uri, nil) |
|
69 |
- if err != nil { |
|
70 |
- return "", 0, fmt.Errorf("Unable to build request for uri %q: %v", uri, err) |
|
71 |
- } |
|
72 |
- |
|
73 |
- // http://stackoverflow.com/questions/17714494/golang-http-request-results-in-eof-errors-when-making-multiple-requests-successi |
|
74 |
- req.Close = true |
|
75 |
- |
|
76 |
- req.SetBasicAuth("admin", j.password) |
|
77 |
- client := &http.Client{} |
|
78 |
- resp, err := client.Do(req) |
|
79 |
- |
|
80 |
- if err != nil { |
|
81 |
- return "", 0, fmt.Errorf("Unable to GET uri %q: %v", uri, err) |
|
82 |
- } |
|
83 |
- |
|
84 |
- defer resp.Body.Close() |
|
85 |
- status := resp.StatusCode |
|
86 |
- |
|
87 |
- body, err := ioutil.ReadAll(resp.Body) |
|
88 |
- if err != nil { |
|
89 |
- return "", 0, fmt.Errorf("Error reading GET response %q: %v", uri, err) |
|
90 |
- } |
|
91 |
- |
|
92 |
- return string(body), status, nil |
|
93 |
-} |
|
94 |
- |
|
95 |
-// Sends a POST to the Jenkins server. If a body is specified, it should be XML. |
|
96 |
-// Returns response body and status code or an error. |
|
97 |
-func (j *JenkinsRef) postXML(reqBody io.Reader, resourcePathFormat string, a ...interface{}) (string, int, error) { |
|
98 |
- return j.post(reqBody, resourcePathFormat, "application/xml", a...) |
|
99 |
-} |
|
100 |
- |
|
101 |
-// Sends a POST to the Jenkins server. Returns response body and status code or an error. |
|
102 |
-func (j *JenkinsRef) post(reqBody io.Reader, resourcePathFormat, contentType string, a ...interface{}) (string, int, error) { |
|
103 |
- uri := j.buildURI(resourcePathFormat, a...) |
|
104 |
- |
|
105 |
- req, err := http.NewRequest("POST", uri, reqBody) |
|
106 |
- o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred()) |
|
107 |
- |
|
108 |
- // http://stackoverflow.com/questions/17714494/golang-http-request-results-in-eof-errors-when-making-multiple-requests-successi |
|
109 |
- req.Close = true |
|
110 |
- |
|
111 |
- if reqBody != nil { |
|
112 |
- req.Header.Set("Content-Type", contentType) |
|
113 |
- req.Header.Del("Expect") // jenkins will return 417 if we have an expect hdr |
|
114 |
- } |
|
115 |
- req.SetBasicAuth("admin", j.password) |
|
116 |
- |
|
117 |
- client := &http.Client{} |
|
118 |
- resp, err := client.Do(req) |
|
119 |
- if err != nil { |
|
120 |
- return "", 0, fmt.Errorf("Error posting request to %q: %v", uri, err) |
|
121 |
- } |
|
122 |
- |
|
123 |
- defer resp.Body.Close() |
|
124 |
- status := resp.StatusCode |
|
125 |
- |
|
126 |
- body, err := ioutil.ReadAll(resp.Body) |
|
127 |
- if err != nil { |
|
128 |
- return "", 0, fmt.Errorf("Error reading post response body %q: %v", uri, err) |
|
129 |
- } |
|
130 |
- |
|
131 |
- return string(body), status, nil |
|
132 |
-} |
|
133 |
- |
|
134 |
-// Creates simple entry in the GinkgoWriter. |
|
31 |
+// ginkgolog creates simple entry in the GinkgoWriter. |
|
135 | 32 |
func ginkgolog(format string, a ...interface{}) { |
136 | 33 |
fmt.Fprintf(g.GinkgoWriter, format+"\n", a...) |
137 | 34 |
} |
138 | 35 |
|
139 |
-// Repeatedly tries to GET a jenkins resource with an acceptable |
|
140 |
-// HTTP status. Retries for the specified duration. |
|
141 |
-func (j *JenkinsRef) getResourceWithStatus(validStatusList []int, timeout time.Duration, resourcePathFormat string, a ...interface{}) (string, int, error) { |
|
142 |
- var retBody string |
|
143 |
- var retStatus int |
|
144 |
- err := wait.Poll(10*time.Second, timeout, func() (bool, error) { |
|
145 |
- body, status, err := j.getResource(resourcePathFormat, a...) |
|
146 |
- if err != nil { |
|
147 |
- ginkgolog("Error accessing resource: %v", err) |
|
148 |
- return false, nil |
|
149 |
- } |
|
150 |
- var found bool |
|
151 |
- for _, s := range validStatusList { |
|
152 |
- if status == s { |
|
153 |
- found = true |
|
154 |
- break |
|
155 |
- } |
|
156 |
- } |
|
157 |
- if !found { |
|
158 |
- ginkgolog("Expected http status [%v] during GET by recevied [%v]", validStatusList, status) |
|
159 |
- return false, nil |
|
160 |
- } |
|
161 |
- retBody = body |
|
162 |
- retStatus = status |
|
163 |
- return true, nil |
|
164 |
- }) |
|
165 |
- if err != nil { |
|
166 |
- uri := j.buildURI(resourcePathFormat, a...) |
|
167 |
- return "", retStatus, fmt.Errorf("Error waiting for status %v from resource path %s: %v", validStatusList, uri, err) |
|
168 |
- } |
|
169 |
- return retBody, retStatus, nil |
|
170 |
-} |
|
171 |
- |
|
172 |
-// Waits for a particular HTTP status and HTML matching a particular |
|
173 |
-// pattern to be returned by this Jenkins server. An error will be returned |
|
174 |
-// if the condition is not matched within the timeout period. |
|
175 |
-func (j *JenkinsRef) waitForContent(verificationRegEx string, verificationStatus int, timeout time.Duration, resourcePathFormat string, a ...interface{}) (string, error) { |
|
176 |
- var matchingContent = "" |
|
177 |
- err := wait.Poll(10*time.Second, timeout, func() (bool, error) { |
|
178 |
- |
|
179 |
- content, _, err := j.getResourceWithStatus([]int{verificationStatus}, timeout, resourcePathFormat, a...) |
|
180 |
- if err != nil { |
|
181 |
- return false, nil |
|
182 |
- } |
|
183 |
- |
|
184 |
- if len(verificationRegEx) > 0 { |
|
185 |
- re := regexp.MustCompile(verificationRegEx) |
|
186 |
- if re.MatchString(content) { |
|
187 |
- matchingContent = content |
|
188 |
- return true, nil |
|
189 |
- } else { |
|
190 |
- ginkgolog("Content did not match verification regex %q:\n %v", verificationRegEx, content) |
|
191 |
- return false, nil |
|
192 |
- } |
|
193 |
- } else { |
|
194 |
- matchingContent = content |
|
195 |
- return true, nil |
|
196 |
- } |
|
197 |
- }) |
|
198 |
- |
|
199 |
- if err != nil { |
|
200 |
- uri := j.buildURI(resourcePathFormat, a...) |
|
201 |
- return "", fmt.Errorf("Error waiting for status %v and verification regex %q from resource path %s: %v", verificationStatus, verificationRegEx, uri, err) |
|
202 |
- } else { |
|
203 |
- return matchingContent, nil |
|
204 |
- } |
|
205 |
-} |
|
206 |
- |
|
207 |
-// Submits XML to create a named item on the Jenkins server. |
|
208 |
-func (j *JenkinsRef) createItem(name string, itemDefXML string) { |
|
209 |
- g.By(fmt.Sprintf("Creating new jenkins item: %s", name)) |
|
210 |
- _, status, err := j.postXML(bytes.NewBufferString(itemDefXML), "createItem?name=%s", name) |
|
211 |
- o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred()) |
|
212 |
- o.ExpectWithOffset(1, status).To(o.Equal(200)) |
|
213 |
-} |
|
214 |
- |
|
215 |
-type JobMon struct { |
|
216 |
- j *JenkinsRef |
|
217 |
- lastBuildNumber string |
|
218 |
- buildNumber string |
|
219 |
- jobName string |
|
220 |
-} |
|
221 |
- |
|
222 |
-// Returns the current buildNumber on the named project OR "new" if |
|
223 |
-// there are no builds against a job yet. |
|
224 |
-func (j *JenkinsRef) getJobBuildNumber(name string, timeout time.Duration) (string, error) { |
|
225 |
- body, status, err := j.getResourceWithStatus([]int{200, 404}, timeout, "job/%s/lastBuild/buildNumber", name) |
|
226 |
- if err != nil { |
|
227 |
- return "", err |
|
228 |
- } |
|
229 |
- if status != 200 { |
|
230 |
- return "new", nil |
|
231 |
- } |
|
232 |
- return body, nil |
|
233 |
-} |
|
234 |
- |
|
235 |
-// Waits for the timestamp on the Jenkins job to change. Returns |
|
236 |
-// and error if the timeout expires. |
|
237 |
-func (jmon *JobMon) await(timeout time.Duration) error { |
|
238 |
- err := wait.Poll(10*time.Second, timeout, func() (bool, error) { |
|
239 |
- |
|
240 |
- buildNumber, err := jmon.j.getJobBuildNumber(jmon.jobName, time.Minute) |
|
241 |
- o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred()) |
|
242 |
- |
|
243 |
- ginkgolog("Checking build number for job %q current[%v] vs last[%v]", jmon.jobName, buildNumber, jmon.lastBuildNumber) |
|
244 |
- if buildNumber == jmon.lastBuildNumber { |
|
245 |
- return false, nil |
|
246 |
- } |
|
247 |
- |
|
248 |
- if jmon.buildNumber == "" { |
|
249 |
- jmon.buildNumber = buildNumber |
|
250 |
- } |
|
251 |
- body, status, err := jmon.j.getResource("job/%s/%s/api/json?depth=1", jmon.jobName, jmon.buildNumber) |
|
252 |
- o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred()) |
|
253 |
- o.ExpectWithOffset(1, status).To(o.Equal(200)) |
|
254 |
- |
|
255 |
- body = strings.ToLower(body) |
|
256 |
- if strings.Contains(body, "\"building\":true") { |
|
257 |
- ginkgolog("Jenkins job %q still building:\n%s\n\n", jmon.jobName, body) |
|
258 |
- return false, nil |
|
259 |
- } |
|
260 |
- |
|
261 |
- if strings.Contains(body, "\"result\":null") { |
|
262 |
- ginkgolog("Jenkins job %q still building result:\n%s\n\n", jmon.jobName, body) |
|
263 |
- return false, nil |
|
264 |
- } |
|
265 |
- |
|
266 |
- ginkgolog("Jenkins job %q build complete:\n%s\n\n", jmon.jobName, body) |
|
267 |
- return true, nil |
|
268 |
- }) |
|
269 |
- return err |
|
270 |
-} |
|
271 |
- |
|
272 |
-// Triggers a named Jenkins job. The job can be monitored with the |
|
273 |
-// returned object. |
|
274 |
-func (j *JenkinsRef) startJob(jobName string) *JobMon { |
|
275 |
- lastBuildNumber, err := j.getJobBuildNumber(jobName, time.Minute) |
|
276 |
- o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred()) |
|
277 |
- |
|
278 |
- jmon := &JobMon{ |
|
279 |
- j: j, |
|
280 |
- lastBuildNumber: lastBuildNumber, |
|
281 |
- buildNumber: "", |
|
282 |
- jobName: jobName, |
|
283 |
- } |
|
284 |
- |
|
285 |
- ginkgolog("Current timestamp for [%s]: %q", jobName, jmon.lastBuildNumber) |
|
286 |
- g.By(fmt.Sprintf("Starting jenkins job: %s", jobName)) |
|
287 |
- _, status, err := j.postXML(nil, "job/%s/build?delay=0sec", jobName) |
|
288 |
- o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred()) |
|
289 |
- o.ExpectWithOffset(1, status).To(o.Equal(201)) |
|
290 |
- |
|
291 |
- return jmon |
|
292 |
-} |
|
293 |
- |
|
294 |
-// Returns the content of a Jenkins job XML file. Instances of the |
|
295 |
-// string "PROJECT_NAME" are replaced with the specified namespace. |
|
296 |
-// Variables named in the vars map will also be replaced with their |
|
297 |
-// corresponding value. |
|
298 |
-func (j *JenkinsRef) readJenkinsJobUsingVars(filename, namespace string, vars map[string]string) string { |
|
299 |
- pre := exutil.FixturePath("testdata", "jenkins-plugin", filename) |
|
300 |
- post := exutil.ArtifactPath(filename) |
|
301 |
- |
|
302 |
- if vars == nil { |
|
303 |
- vars = map[string]string{} |
|
304 |
- } |
|
305 |
- vars["PROJECT_NAME"] = namespace |
|
306 |
- err := exutil.VarSubOnFile(pre, post, vars) |
|
307 |
- o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred()) |
|
308 |
- |
|
309 |
- data, err := ioutil.ReadFile(post) |
|
310 |
- o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred()) |
|
311 |
- return string(data) |
|
312 |
-} |
|
313 |
- |
|
314 |
-// Returns the content of a Jenkins job XML file. Instances of the |
|
315 |
-// string "PROJECT_NAME" are replaced with the specified namespace. |
|
316 |
-func (j *JenkinsRef) readJenkinsJob(filename, namespace string) string { |
|
317 |
- return j.readJenkinsJobUsingVars(filename, namespace, nil) |
|
318 |
-} |
|
319 |
- |
|
320 |
-// Returns an XML string defining a Jenkins workflow/pipeline DSL job. Instances of the |
|
321 |
-// string "PROJECT_NAME" are replaced with the specified namespace. |
|
322 |
-func (j *JenkinsRef) buildDSLJob(namespace string, scriptLines ...string) (string, error) { |
|
323 |
- script := strings.Join(scriptLines, "\n") |
|
324 |
- script = strings.Replace(script, "PROJECT_NAME", namespace, -1) |
|
325 |
- fd := FlowDefinition{ |
|
326 |
- Plugin: "workflow-job@2.7", |
|
327 |
- Definition: Definition{ |
|
328 |
- Class: "org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition", |
|
329 |
- Plugin: "workflow-cps@2.18", |
|
330 |
- Script: script, |
|
331 |
- }, |
|
332 |
- } |
|
333 |
- output, err := xml.MarshalIndent(fd, " ", " ") |
|
334 |
- ginkgolog("Formulated DSL Project XML:\n%s\n\n", output) |
|
335 |
- return string(output), err |
|
336 |
-} |
|
337 |
- |
|
338 |
-// Returns the console logs of a particular buildNumber. |
|
339 |
-func (j *JenkinsRef) getJobConsoleLogs(jobName, buildNumber string) (string, error) { |
|
340 |
- return j.waitForContent("", 200, 10*time.Minute, "job/%s/%s/consoleText", jobName, buildNumber) |
|
341 |
-} |
|
342 |
- |
|
343 |
-// Returns the last build associated with a Jenkins job. |
|
344 |
-func (j *JenkinsRef) getLastJobConsoleLogs(jobName string) (string, error) { |
|
345 |
- return j.getJobConsoleLogs(jobName, "lastBuild") |
|
346 |
-} |
|
347 |
- |
|
348 | 36 |
// Loads a Jenkins related template using new-app. |
349 | 37 |
func loadFixture(oc *exutil.CLI, filename string) { |
350 | 38 |
resourcePath := exutil.FixturePath("testdata", "jenkins-plugin", filename) |
... | ... |
@@ -385,33 +71,6 @@ func assertEnvVars(oc *exutil.CLI, buildPrefix string, varsToFind map[string]str |
385 | 385 |
} |
386 | 386 |
} |
387 | 387 |
|
388 |
-func getAdminPassword(oc *exutil.CLI) string { |
|
389 |
- envs, err := oc.Run("set").Args("env", "dc/jenkins", "--list").Output() |
|
390 |
- o.Expect(err).NotTo(o.HaveOccurred()) |
|
391 |
- kvs := strings.Split(envs, "\n") |
|
392 |
- for _, kv := range kvs { |
|
393 |
- if strings.HasPrefix(kv, "JENKINS_PASSWORD=") { |
|
394 |
- s := strings.Split(kv, "=") |
|
395 |
- fmt.Fprintf(g.GinkgoWriter, "\nJenkins admin password %s\n", s[1]) |
|
396 |
- return s[1] |
|
397 |
- } |
|
398 |
- } |
|
399 |
- return "password" |
|
400 |
-} |
|
401 |
- |
|
402 |
-// Finds the pod running Jenkins |
|
403 |
-func findJenkinsPod(oc *exutil.CLI) *kapi.Pod { |
|
404 |
- pods, err := exutil.GetDeploymentConfigPods(oc, "jenkins") |
|
405 |
- o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred()) |
|
406 |
- |
|
407 |
- if pods == nil || pods.Items == nil { |
|
408 |
- g.Fail("No pods matching jenkins deploymentconfig in namespace " + oc.Namespace()) |
|
409 |
- } |
|
410 |
- |
|
411 |
- o.ExpectWithOffset(1, len(pods.Items)).To(o.Equal(1)) |
|
412 |
- return &pods.Items[0] |
|
413 |
-} |
|
414 |
- |
|
415 | 388 |
// Stands up a simple pod which can be used for exec commands |
416 | 389 |
func initExecPod(oc *exutil.CLI) *kapi.Pod { |
417 | 390 |
// Create a running pod in which we can execute our commands |
... | ... |
@@ -456,7 +115,7 @@ var memoryOverragePattern = regexp.MustCompile(`\s+rss\s+5\d\d\d\d\d\d\d\d`) |
456 | 456 |
var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", func() { |
457 | 457 |
defer g.GinkgoRecover() |
458 | 458 |
var oc = exutil.NewCLI("jenkins-plugin", exutil.KubeConfigPath()) |
459 |
- var j *JenkinsRef |
|
459 |
+ var j *jenkins.JenkinsRef |
|
460 | 460 |
var dcLogFollow *exec.Cmd |
461 | 461 |
var dcLogStdOut, dcLogStdErr *bytes.Buffer |
462 | 462 |
var ticker *time.Ticker |
... | ... |
@@ -467,12 +126,12 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
467 | 467 |
ticker.Stop() |
468 | 468 |
} |
469 | 469 |
|
470 |
- oc.SetNamespace(j.namespace) |
|
470 |
+ oc.SetNamespace(j.Namespace()) |
|
471 | 471 |
ginkgolog("Jenkins DC description follows. If there were issues, check to see if there were any restarts in the jenkins pod.") |
472 | 472 |
exutil.DumpDeploymentLogs("jenkins", oc) |
473 | 473 |
|
474 | 474 |
// Destroy the Jenkins namespace |
475 |
- oc.Run("delete").Args("project", j.namespace).Execute() |
|
475 |
+ oc.Run("delete").Args("project", j.Namespace()).Execute() |
|
476 | 476 |
if dcLogFollow != nil && dcLogStdOut != nil && dcLogStdErr != nil { |
477 | 477 |
ginkgolog("Waiting for Jenkins DC log follow to terminate") |
478 | 478 |
dcLogFollow.Process.Wait() |
... | ... |
@@ -548,26 +207,10 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
548 | 548 |
err = exutil.WaitForADeploymentToComplete(oc.KubeClient().Core().ReplicationControllers(oc.Namespace()), "jenkins", oc) |
549 | 549 |
o.Expect(err).NotTo(o.HaveOccurred()) |
550 | 550 |
|
551 |
- g.By("get ip and port for jenkins service") |
|
552 |
- serviceIP, err := oc.Run("get").Args("svc", "jenkins", "--config", exutil.KubeConfigPath()).Template("{{.spec.clusterIP}}").Output() |
|
553 |
- o.Expect(err).NotTo(o.HaveOccurred()) |
|
554 |
- port, err := oc.Run("get").Args("svc", "jenkins", "--config", exutil.KubeConfigPath()).Template("{{ $x := index .spec.ports 0}}{{$x.port}}").Output() |
|
555 |
- o.Expect(err).NotTo(o.HaveOccurred()) |
|
556 |
- |
|
557 |
- g.By("get admin password") |
|
558 |
- password := getAdminPassword(oc) |
|
559 |
- o.Expect(password).ShouldNot(o.BeEmpty()) |
|
560 |
- |
|
561 |
- j = &JenkinsRef{ |
|
562 |
- oc: oc, |
|
563 |
- host: serviceIP, |
|
564 |
- port: port, |
|
565 |
- namespace: jenkinsNamespace, |
|
566 |
- password: password, |
|
567 |
- } |
|
551 |
+ j = jenkins.NewRef(oc) |
|
568 | 552 |
|
569 | 553 |
g.By("wait for jenkins to come up") |
570 |
- _, err = j.waitForContent("", 200, 10*time.Minute, "") |
|
554 |
+ _, err = j.WaitForContent("", 200, 10*time.Minute, "") |
|
571 | 555 |
|
572 | 556 |
if err != nil { |
573 | 557 |
exutil.DumpDeploymentLogs("jenkins", oc) |
... | ... |
@@ -578,11 +221,11 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
578 | 578 |
if useSnapshotImage { |
579 | 579 |
g.By("verifying the test image is being used") |
580 | 580 |
// for the test image, confirm that a snapshot version of the plugin is running in the jenkins image we'll test against |
581 |
- _, err = j.waitForContent(`About OpenShift Pipeline Jenkins Plugin ([0-9\.]+)-SNAPSHOT`, 200, 10*time.Minute, "/pluginManager/plugin/openshift-pipeline/thirdPartyLicenses") |
|
581 |
+ _, err = j.WaitForContent(`About OpenShift Pipeline Jenkins Plugin ([0-9\.]+)-SNAPSHOT`, 200, 10*time.Minute, "/pluginManager/plugin/openshift-pipeline/thirdPartyLicenses") |
|
582 | 582 |
o.Expect(err).NotTo(o.HaveOccurred()) |
583 | 583 |
} |
584 | 584 |
|
585 |
- jenkinsPod := findJenkinsPod(oc) |
|
585 |
+ jenkinsPod := jenkins.FindJenkinsPod(oc) |
|
586 | 586 |
|
587 | 587 |
if os.Getenv(disableJenkinsMemoryStats) == "" { |
588 | 588 |
ticker = time.NewTicker(10 * time.Second) |
... | ... |
@@ -630,7 +273,7 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
630 | 630 |
oc.SetNamespace(testNamespace) |
631 | 631 |
|
632 | 632 |
g.By("set up policy for jenkins jobs in " + oc.Namespace()) |
633 |
- err = oc.Run("policy").Args("add-role-to-user", "edit", "system:serviceaccount:"+j.namespace+":jenkins").Execute() |
|
633 |
+ err = oc.Run("policy").Args("add-role-to-user", "edit", "system:serviceaccount:"+j.Namespace()+":jenkins").Execute() |
|
634 | 634 |
o.Expect(err).NotTo(o.HaveOccurred()) |
635 | 635 |
|
636 | 636 |
// Populate shared Jenkins namespace with artifacts that can be used by all tests |
... | ... |
@@ -646,10 +289,10 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
646 | 646 |
g.It("jenkins-plugin test trigger build including clone", func() { |
647 | 647 |
|
648 | 648 |
jobName := "test-build-job" |
649 |
- data := j.readJenkinsJob("build-job.xml", oc.Namespace()) |
|
650 |
- j.createItem(jobName, data) |
|
651 |
- jmon := j.startJob(jobName) |
|
652 |
- jmon.await(10 * time.Minute) |
|
649 |
+ data := j.ReadJenkinsJob("build-job.xml", oc.Namespace()) |
|
650 |
+ j.CreateItem(jobName, data) |
|
651 |
+ jmon := j.StartJob(jobName) |
|
652 |
+ jmon.Await(10 * time.Minute) |
|
653 | 653 |
|
654 | 654 |
// the build and deployment is by far the most time consuming portion of the test jenkins job; |
655 | 655 |
// we leverage some of the openshift utilities for waiting for the deployment before we poll |
... | ... |
@@ -661,7 +304,7 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
661 | 661 |
o.Expect(err).NotTo(o.HaveOccurred()) |
662 | 662 |
|
663 | 663 |
g.By("get build console logs and see if succeeded") |
664 |
- logs, err := j.waitForContent("Finished: SUCCESS", 200, 10*time.Minute, "job/%s/lastBuild/consoleText", jobName) |
|
664 |
+ logs, err := j.WaitForContent("Finished: SUCCESS", 200, 10*time.Minute, "job/%s/lastBuild/consoleText", jobName) |
|
665 | 665 |
ginkgolog("\n\nJenkins logs>\n%s\n\n", logs) |
666 | 666 |
o.Expect(err).NotTo(o.HaveOccurred()) |
667 | 667 |
|
... | ... |
@@ -671,12 +314,12 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
671 | 671 |
o.Expect(strings.Contains(out, "Jenkins job")).To(o.BeTrue()) |
672 | 672 |
|
673 | 673 |
jobName = "test-build-clone-job" |
674 |
- data = j.readJenkinsJob("build-job-clone.xml", oc.Namespace()) |
|
675 |
- j.createItem(jobName, data) |
|
676 |
- jmon = j.startJob(jobName) |
|
677 |
- jmon.await(10 * time.Minute) |
|
674 |
+ data = j.ReadJenkinsJob("build-job-clone.xml", oc.Namespace()) |
|
675 |
+ j.CreateItem(jobName, data) |
|
676 |
+ jmon = j.StartJob(jobName) |
|
677 |
+ jmon.Await(10 * time.Minute) |
|
678 | 678 |
g.By("get clone build console logs and see if succeeded") |
679 |
- logs, err = j.waitForContent("Finished: SUCCESS", 200, 10*time.Minute, "job/%s/lastBuild/consoleText", jobName) |
|
679 |
+ logs, err = j.WaitForContent("Finished: SUCCESS", 200, 10*time.Minute, "job/%s/lastBuild/consoleText", jobName) |
|
680 | 680 |
ginkgolog("\n\nJenkins logs>\n%s\n\n", logs) |
681 | 681 |
o.Expect(err).NotTo(o.HaveOccurred()) |
682 | 682 |
|
... | ... |
@@ -693,10 +336,10 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
693 | 693 |
g.It("jenkins-plugin test trigger build with slave", func() { |
694 | 694 |
|
695 | 695 |
jobName := "test-build-job-slave" |
696 |
- data := j.readJenkinsJob("build-job-slave.xml", oc.Namespace()) |
|
697 |
- j.createItem(jobName, data) |
|
698 |
- jmon := j.startJob(jobName) |
|
699 |
- jmon.await(10 * time.Minute) |
|
696 |
+ data := j.ReadJenkinsJob("build-job-slave.xml", oc.Namespace()) |
|
697 |
+ j.CreateItem(jobName, data) |
|
698 |
+ jmon := j.StartJob(jobName) |
|
699 |
+ jmon.Await(10 * time.Minute) |
|
700 | 700 |
|
701 | 701 |
// the build and deployment is by far the most time consuming portion of the test jenkins job; |
702 | 702 |
// we leverage some of the openshift utilities for waiting for the deployment before we poll |
... | ... |
@@ -708,12 +351,12 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
708 | 708 |
o.Expect(err).NotTo(o.HaveOccurred()) |
709 | 709 |
|
710 | 710 |
g.By("get build console logs and see if succeeded") |
711 |
- logs, err := j.waitForContent("Finished: SUCCESS", 200, 10*time.Minute, "job/%s/lastBuild/consoleText", jobName) |
|
711 |
+ logs, err := j.WaitForContent("Finished: SUCCESS", 200, 10*time.Minute, "job/%s/lastBuild/consoleText", jobName) |
|
712 | 712 |
ginkgolog("\n\nJenkins logs>\n%s\n\n", logs) |
713 | 713 |
o.Expect(err).NotTo(o.HaveOccurred()) |
714 | 714 |
|
715 | 715 |
g.By("get build console logs and confirm ran on slave") |
716 |
- logs, err = j.waitForContent("Building remotely on", 200, 10*time.Minute, "job/%s/lastBuild/consoleText", jobName) |
|
716 |
+ logs, err = j.WaitForContent("Building remotely on", 200, 10*time.Minute, "job/%s/lastBuild/consoleText", jobName) |
|
717 | 717 |
ginkgolog("\n\nJenkins logs>\n%s\n\n", logs) |
718 | 718 |
o.Expect(err).NotTo(o.HaveOccurred()) |
719 | 719 |
|
... | ... |
@@ -723,17 +366,17 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
723 | 723 |
|
724 | 724 |
jobsToCreate := map[string]string{"test-create-obj": "create-job.xml", "test-delete-obj": "delete-job.xml", "test-delete-obj-labels": "delete-job-labels.xml", "test-delete-obj-keys": "delete-job-keys.xml"} |
725 | 725 |
for jobName, jobConfig := range jobsToCreate { |
726 |
- data := j.readJenkinsJob(jobConfig, oc.Namespace()) |
|
727 |
- j.createItem(jobName, data) |
|
726 |
+ data := j.ReadJenkinsJob(jobConfig, oc.Namespace()) |
|
727 |
+ j.CreateItem(jobName, data) |
|
728 | 728 |
} |
729 | 729 |
|
730 | 730 |
jobsToRun := []apiObjJob{{"test-create-obj", true}, {"test-delete-obj", false}, {"test-create-obj", true}, {"test-delete-obj-labels", false}, {"test-create-obj", true}, {"test-delete-obj-keys", false}} |
731 | 731 |
for _, job := range jobsToRun { |
732 |
- jmon := j.startJob(job.jobName) |
|
733 |
- jmon.await(10 * time.Minute) |
|
732 |
+ jmon := j.StartJob(job.jobName) |
|
733 |
+ jmon.Await(10 * time.Minute) |
|
734 | 734 |
|
735 | 735 |
g.By("get build console logs and see if succeeded") |
736 |
- logs, err := j.waitForContent("Finished: SUCCESS", 200, 10*time.Minute, "job/%s/lastBuild/consoleText", job.jobName) |
|
736 |
+ logs, err := j.WaitForContent("Finished: SUCCESS", 200, 10*time.Minute, "job/%s/lastBuild/consoleText", job.jobName) |
|
737 | 737 |
ginkgolog("\n\nJenkins logs>\n%s\n\n", logs) |
738 | 738 |
o.Expect(err).NotTo(o.HaveOccurred()) |
739 | 739 |
out, err := oc.Run("get").Args("bc", "forcepull-bldr").Output() |
... | ... |
@@ -747,12 +390,12 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
747 | 747 |
g.It("jenkins-plugin test trigger build with envs", func() { |
748 | 748 |
|
749 | 749 |
jobName := "test-build-with-env-job" |
750 |
- data := j.readJenkinsJob("build-with-env-job.xml", oc.Namespace()) |
|
751 |
- j.createItem(jobName, data) |
|
752 |
- jmon := j.startJob(jobName) |
|
753 |
- jmon.await(10 * time.Minute) |
|
750 |
+ data := j.ReadJenkinsJob("build-with-env-job.xml", oc.Namespace()) |
|
751 |
+ j.CreateItem(jobName, data) |
|
752 |
+ jmon := j.StartJob(jobName) |
|
753 |
+ jmon.Await(10 * time.Minute) |
|
754 | 754 |
|
755 |
- logs, err := j.getLastJobConsoleLogs(jobName) |
|
755 |
+ logs, err := j.GetLastJobConsoleLogs(jobName) |
|
756 | 756 |
ginkgolog("\n\nJenkins logs>\n%s\n\n", logs) |
757 | 757 |
o.Expect(err).NotTo(o.HaveOccurred()) |
758 | 758 |
|
... | ... |
@@ -764,7 +407,7 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
764 | 764 |
o.Expect(err).NotTo(o.HaveOccurred()) |
765 | 765 |
|
766 | 766 |
g.By("get build console logs and see if succeeded") |
767 |
- _, err = j.waitForContent("Finished: SUCCESS", 200, 10*time.Minute, "job/%s/lastBuild/consoleText", jobName) |
|
767 |
+ _, err = j.WaitForContent("Finished: SUCCESS", 200, 10*time.Minute, "job/%s/lastBuild/consoleText", jobName) |
|
768 | 768 |
|
769 | 769 |
assertEnvVars(oc, "frontend-", map[string]string{ |
770 | 770 |
"a": "b", |
... | ... |
@@ -779,16 +422,16 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
779 | 779 |
buildsBefore, err := oc.Client().Builds(oc.Namespace()).List(kapi.ListOptions{}) |
780 | 780 |
o.Expect(err).NotTo(o.HaveOccurred()) |
781 | 781 |
|
782 |
- data, err := j.buildDSLJob(oc.Namespace(), |
|
782 |
+ data, err := j.BuildDSLJob(oc.Namespace(), |
|
783 | 783 |
"node{", |
784 | 784 |
"openshiftBuild( namespace:'PROJECT_NAME', bldCfg: 'frontend', env: [ [ name : 'a', value : 'b' ], [ name : 'C', value : 'D' ], [ name : 'e', value : '' ] ] )", |
785 | 785 |
"}", |
786 | 786 |
) |
787 | 787 |
|
788 | 788 |
jobName := "test-build-dsl-job" |
789 |
- j.createItem(jobName, data) |
|
790 |
- monitor := j.startJob(jobName) |
|
791 |
- err = monitor.await(10 * time.Minute) |
|
789 |
+ j.CreateItem(jobName, data) |
|
790 |
+ monitor := j.StartJob(jobName) |
|
791 |
+ err = monitor.Await(10 * time.Minute) |
|
792 | 792 |
o.Expect(err).NotTo(o.HaveOccurred()) |
793 | 793 |
|
794 | 794 |
err = wait.Poll(10*time.Second, 10*time.Minute, func() (bool, error) { |
... | ... |
@@ -801,7 +444,7 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
801 | 801 |
o.Expect(err).NotTo(o.HaveOccurred()) |
802 | 802 |
o.Expect(len(buildsAfter.Items)).To(o.Equal(len(buildsBefore.Items) + 1)) |
803 | 803 |
|
804 |
- log, err := j.getLastJobConsoleLogs(jobName) |
|
804 |
+ log, err := j.GetLastJobConsoleLogs(jobName) |
|
805 | 805 |
ginkgolog("Job logs>>\n%s\n\n", log) |
806 | 806 |
|
807 | 807 |
assertEnvVars(oc, "frontend-", map[string]string{ |
... | ... |
@@ -817,7 +460,7 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
817 | 817 |
targetPod := initExecPod(oc) |
818 | 818 |
targetContainer := targetPod.Spec.Containers[0] |
819 | 819 |
|
820 |
- data, err := j.buildDSLJob(oc.Namespace(), |
|
820 |
+ data, err := j.BuildDSLJob(oc.Namespace(), |
|
821 | 821 |
"node{", |
822 | 822 |
fmt.Sprintf("openshiftExec( namespace:'PROJECT_NAME', pod: '%s', command: [ 'echo', 'hello', 'world', '1' ] )", targetPod.Name), |
823 | 823 |
fmt.Sprintf("openshiftExec( namespace:'PROJECT_NAME', pod: '%s', command: 'echo', arguments : [ 'hello', 'world', '2' ] )", targetPod.Name), |
... | ... |
@@ -829,12 +472,12 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
829 | 829 |
) |
830 | 830 |
|
831 | 831 |
jobName := "test-exec-dsl-job" |
832 |
- j.createItem(jobName, data) |
|
833 |
- monitor := j.startJob(jobName) |
|
834 |
- err = monitor.await(10 * time.Minute) |
|
832 |
+ j.CreateItem(jobName, data) |
|
833 |
+ monitor := j.StartJob(jobName) |
|
834 |
+ err = monitor.Await(10 * time.Minute) |
|
835 | 835 |
o.Expect(err).NotTo(o.HaveOccurred()) |
836 | 836 |
|
837 |
- log, err := j.getLastJobConsoleLogs(jobName) |
|
837 |
+ log, err := j.GetLastJobConsoleLogs(jobName) |
|
838 | 838 |
ginkgolog("Job logs>>\n%s\n\n", log) |
839 | 839 |
|
840 | 840 |
o.Expect(strings.Contains(log, "hello world 1")).To(o.BeTrue()) |
... | ... |
@@ -851,16 +494,16 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
851 | 851 |
targetContainer := targetPod.Spec.Containers[0] |
852 | 852 |
|
853 | 853 |
jobName := "test-build-with-env-steps" |
854 |
- data := j.readJenkinsJobUsingVars("build-with-exec-steps.xml", oc.Namespace(), map[string]string{ |
|
854 |
+ data := j.ReadJenkinsJobUsingVars("build-with-exec-steps.xml", oc.Namespace(), map[string]string{ |
|
855 | 855 |
"POD_NAME": targetPod.Name, |
856 | 856 |
"CONTAINER_NAME": targetContainer.Name, |
857 | 857 |
}) |
858 | 858 |
|
859 |
- j.createItem(jobName, data) |
|
860 |
- jmon := j.startJob(jobName) |
|
861 |
- jmon.await(2 * time.Minute) |
|
859 |
+ j.CreateItem(jobName, data) |
|
860 |
+ jmon := j.StartJob(jobName) |
|
861 |
+ jmon.Await(2 * time.Minute) |
|
862 | 862 |
|
863 |
- log, err := j.getLastJobConsoleLogs(jobName) |
|
863 |
+ log, err := j.GetLastJobConsoleLogs(jobName) |
|
864 | 864 |
ginkgolog("\n\nJenkins logs>\n%s\n\n", log) |
865 | 865 |
o.Expect(err).NotTo(o.HaveOccurred()) |
866 | 866 |
|
... | ... |
@@ -868,16 +511,16 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
868 | 868 |
|
869 | 869 |
// Now run without specifying container |
870 | 870 |
jobName = "test-build-with-env-steps-no-container" |
871 |
- data = j.readJenkinsJobUsingVars("build-with-exec-steps.xml", oc.Namespace(), map[string]string{ |
|
871 |
+ data = j.ReadJenkinsJobUsingVars("build-with-exec-steps.xml", oc.Namespace(), map[string]string{ |
|
872 | 872 |
"POD_NAME": targetPod.Name, |
873 | 873 |
"CONTAINER_NAME": "", |
874 | 874 |
}) |
875 | 875 |
|
876 |
- j.createItem(jobName, data) |
|
877 |
- jmon = j.startJob(jobName) |
|
878 |
- jmon.await(2 * time.Minute) |
|
876 |
+ j.CreateItem(jobName, data) |
|
877 |
+ jmon = j.StartJob(jobName) |
|
878 |
+ jmon.Await(2 * time.Minute) |
|
879 | 879 |
|
880 |
- log, err = j.getLastJobConsoleLogs(jobName) |
|
880 |
+ log, err = j.GetLastJobConsoleLogs(jobName) |
|
881 | 881 |
ginkgolog("\n\nJenkins logs>\n%s\n\n", log) |
882 | 882 |
o.Expect(err).NotTo(o.HaveOccurred()) |
883 | 883 |
|
... | ... |
@@ -898,13 +541,13 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
898 | 898 |
o.Expect(err).NotTo(o.HaveOccurred()) |
899 | 899 |
|
900 | 900 |
jobName := "test-multitag-job" |
901 |
- data := j.readJenkinsJob("multitag-job.xml", oc.Namespace()) |
|
902 |
- j.createItem(jobName, data) |
|
903 |
- monitor := j.startJob(jobName) |
|
904 |
- err = monitor.await(10 * time.Minute) |
|
901 |
+ data := j.ReadJenkinsJob("multitag-job.xml", oc.Namespace()) |
|
902 |
+ j.CreateItem(jobName, data) |
|
903 |
+ monitor := j.StartJob(jobName) |
|
904 |
+ err = monitor.Await(10 * time.Minute) |
|
905 | 905 |
o.Expect(err).NotTo(o.HaveOccurred()) |
906 | 906 |
|
907 |
- log, err := j.getLastJobConsoleLogs(jobName) |
|
907 |
+ log, err := j.GetLastJobConsoleLogs(jobName) |
|
908 | 908 |
ginkgolog("Job logs>>\n%s\n\n", log) |
909 | 909 |
|
910 | 910 |
// Assert stream tagging results |
... | ... |
@@ -966,14 +609,14 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
966 | 966 |
|
967 | 967 |
// Allow jenkins service account to edit the new namespace |
968 | 968 |
oc.SetNamespace(anotherNamespace) |
969 |
- err = oc.Run("policy").Args("add-role-to-user", "edit", "system:serviceaccount:"+j.namespace+":jenkins").Execute() |
|
969 |
+ err = oc.Run("policy").Args("add-role-to-user", "edit", "system:serviceaccount:"+j.Namespace()+":jenkins").Execute() |
|
970 | 970 |
o.Expect(err).NotTo(o.HaveOccurred()) |
971 | 971 |
|
972 | 972 |
oc.SetNamespace(testNamespace) |
973 | 973 |
|
974 | 974 |
ginkgolog("Using testNamespace: %q and currentNamespace: %q", testNamespace, oc.Namespace()) |
975 | 975 |
|
976 |
- data, err := j.buildDSLJob(oc.Namespace(), |
|
976 |
+ data, err := j.BuildDSLJob(oc.Namespace(), |
|
977 | 977 |
"node{", |
978 | 978 |
"openshiftTag( namespace:'PROJECT_NAME', srcStream: 'multitag', srcTag: 'orig', destStream: 'multitag', destTag: 'prod' )", |
979 | 979 |
"openshiftTag( namespace:'PROJECT_NAME', srcStream: 'multitag', srcTag: 'orig', destStream: 'multitag2', destTag: 'prod1, prod2, prod3' )", |
... | ... |
@@ -987,14 +630,14 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
987 | 987 |
) |
988 | 988 |
|
989 | 989 |
jobName := "test-multitag-dsl-job" |
990 |
- j.createItem(jobName, data) |
|
991 |
- monitor := j.startJob(jobName) |
|
992 |
- err = monitor.await(10 * time.Minute) |
|
990 |
+ j.CreateItem(jobName, data) |
|
991 |
+ monitor := j.StartJob(jobName) |
|
992 |
+ err = monitor.Await(10 * time.Minute) |
|
993 | 993 |
o.Expect(err).NotTo(o.HaveOccurred()) |
994 | 994 |
|
995 | 995 |
time.Sleep(10 * time.Second) |
996 | 996 |
|
997 |
- log, err := j.getLastJobConsoleLogs(jobName) |
|
997 |
+ log, err := j.GetLastJobConsoleLogs(jobName) |
|
998 | 998 |
o.Expect(err).NotTo(o.HaveOccurred()) |
999 | 999 |
ginkgolog("Job logs>>\n%s\n\n", log) |
1000 | 1000 |
|
... | ... |
@@ -1028,8 +671,8 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
1028 | 1028 |
testImageStreamSCM := func(jobXMLFile string) { |
1029 | 1029 |
jobName := "test-imagestream-scm" |
1030 | 1030 |
g.By("creating a jenkins job with an imagestream SCM") |
1031 |
- data := j.readJenkinsJob(jobXMLFile, oc.Namespace()) |
|
1032 |
- j.createItem(jobName, data) |
|
1031 |
+ data := j.ReadJenkinsJob(jobXMLFile, oc.Namespace()) |
|
1032 |
+ j.CreateItem(jobName, data) |
|
1033 | 1033 |
|
1034 | 1034 |
// Because polling is enabled, a job should start automatically and fail |
1035 | 1035 |
// Wait for it to run and fail |
... | ... |
@@ -1038,7 +681,7 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
1038 | 1038 |
jobStatusURI := "api/xml?tree=%s&xpath=%s" |
1039 | 1039 |
g.By("waiting for initial job to complete") |
1040 | 1040 |
wait.Poll(10*time.Second, 10*time.Minute, func() (bool, error) { |
1041 |
- result, status, err := j.getResource(jobStatusURI, tree, xpath) |
|
1041 |
+ result, status, err := j.GetResource(jobStatusURI, tree, xpath) |
|
1042 | 1042 |
o.Expect(err).NotTo(o.HaveOccurred()) |
1043 | 1043 |
if status == 200 && strings.Contains(result, "red") { |
1044 | 1044 |
return true, nil |
... | ... |
@@ -1069,12 +712,12 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
1069 | 1069 |
g.It("jenkins-plugin test connection test", func() { |
1070 | 1070 |
|
1071 | 1071 |
jobName := "test-build-job" |
1072 |
- data := j.readJenkinsJob("build-job.xml", oc.Namespace()) |
|
1073 |
- j.createItem(jobName, data) |
|
1072 |
+ data := j.ReadJenkinsJob("build-job.xml", oc.Namespace()) |
|
1073 |
+ j.CreateItem(jobName, data) |
|
1074 | 1074 |
|
1075 | 1075 |
g.By("trigger test connection logic, check for success") |
1076 | 1076 |
testConnectionBody := bytes.NewBufferString("apiURL=&authToken=") |
1077 |
- result, code, err := j.post(testConnectionBody, "job/test-build-job/descriptorByName/com.openshift.jenkins.plugins.pipeline.OpenShiftBuilder/testConnection", "application/x-www-form-urlencoded") |
|
1077 |
+ result, code, err := j.Post(testConnectionBody, "job/test-build-job/descriptorByName/com.openshift.jenkins.plugins.pipeline.OpenShiftBuilder/testConnection", "application/x-www-form-urlencoded") |
|
1078 | 1078 |
if code != 200 { |
1079 | 1079 |
err = fmt.Errorf("Expected return code of 200") |
1080 | 1080 |
} |
... | ... |
@@ -1085,7 +728,7 @@ var _ = g.Describe("[image_ecosystem][jenkins][Slow] openshift pipeline plugin", |
1085 | 1085 |
|
1086 | 1086 |
g.By("trigger test connection logic, check for failure") |
1087 | 1087 |
testConnectionBody = bytes.NewBufferString("apiURL=https%3A%2F%2F1.2.3.4&authToken=") |
1088 |
- result, code, err = j.post(testConnectionBody, "job/test-build-job/descriptorByName/com.openshift.jenkins.plugins.pipeline.OpenShiftBuilder/testConnection", "application/x-www-form-urlencoded") |
|
1088 |
+ result, code, err = j.Post(testConnectionBody, "job/test-build-job/descriptorByName/com.openshift.jenkins.plugins.pipeline.OpenShiftBuilder/testConnection", "application/x-www-form-urlencoded") |
|
1089 | 1089 |
if code != 200 { |
1090 | 1090 |
err = fmt.Errorf("Expected return code of 200") |
1091 | 1091 |
} |
1092 | 1092 |
deleted file mode 100644 |
... | ... |
@@ -1,88 +0,0 @@ |
1 |
-{ |
|
2 |
- "kind": "Template", |
|
3 |
- "apiVersion": "v1", |
|
4 |
- "metadata": { |
|
5 |
- "name": "jenkins-pipeline-example", |
|
6 |
- "annotations": { |
|
7 |
- "description": "This is used to test the jenkins pipeline strategy", |
|
8 |
- "iconClass": "icon-jenkins" |
|
9 |
- } |
|
10 |
- }, |
|
11 |
- "objects": [ |
|
12 |
- { |
|
13 |
- "kind": "BuildConfig", |
|
14 |
- "apiVersion": "v1", |
|
15 |
- "metadata": { |
|
16 |
- "name": "sample-pipeline", |
|
17 |
- "labels": { |
|
18 |
- "name": "sample-pipeline" |
|
19 |
- }, |
|
20 |
- "annotations": { |
|
21 |
- "pipeline.alpha.openshift.io/uses": "[{\"name\": \"frontend\", \"namespace\": \"\", \"kind\": \"DeploymentConfig\"}]" |
|
22 |
- } |
|
23 |
- }, |
|
24 |
- "spec": { |
|
25 |
- "strategy": { |
|
26 |
- "type": "JenkinsPipeline", |
|
27 |
- "jenkinsPipelineStrategy": { |
|
28 |
- "jenkinsfile": "node{\nstage 'build'\nopenshiftBuild(buildConfig: 'ruby-sample-build', showBuildLogs: 'true')\n}" |
|
29 |
- } |
|
30 |
- } |
|
31 |
- } |
|
32 |
- }, |
|
33 |
- { |
|
34 |
- "kind": "ImageStream", |
|
35 |
- "apiVersion": "v1", |
|
36 |
- "metadata": { |
|
37 |
- "name": "origin-ruby-sample" |
|
38 |
- }, |
|
39 |
- "spec": {}, |
|
40 |
- "status": { |
|
41 |
- "dockerImageRepository": "" |
|
42 |
- } |
|
43 |
- }, |
|
44 |
- { |
|
45 |
- "kind": "BuildConfig", |
|
46 |
- "apiVersion": "v1", |
|
47 |
- "metadata": { |
|
48 |
- "name": "ruby-sample-build", |
|
49 |
- "labels": { |
|
50 |
- "name": "ruby-sample-build" |
|
51 |
- } |
|
52 |
- }, |
|
53 |
- "spec": { |
|
54 |
- "source": { |
|
55 |
- "type": "Git", |
|
56 |
- "git": { |
|
57 |
- "uri": "https://github.com/openshift/ruby-hello-world.git" |
|
58 |
- } |
|
59 |
- }, |
|
60 |
- "strategy": { |
|
61 |
- "type": "Source", |
|
62 |
- "sourceStrategy": { |
|
63 |
- "from": { |
|
64 |
- "kind": "ImageStreamTag", |
|
65 |
- "name": "ruby:2.2", |
|
66 |
- "namespace": "openshift" |
|
67 |
- } |
|
68 |
- } |
|
69 |
- }, |
|
70 |
- "output": { |
|
71 |
- "to": { |
|
72 |
- "kind": "ImageStreamTag", |
|
73 |
- "name": "origin-ruby-sample:latest" |
|
74 |
- } |
|
75 |
- }, |
|
76 |
- "resources": {} |
|
77 |
- }, |
|
78 |
- "status": { |
|
79 |
- "lastVersion": 0 |
|
80 |
- } |
|
81 |
- } |
|
82 |
- ], |
|
83 |
- "parameters": [ |
|
84 |
- ], |
|
85 |
- "labels": { |
|
86 |
- "template": "jenkins-pipeline-test" |
|
87 |
- } |
|
88 |
-} |
89 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,55 @@ |
0 |
+package jenkins |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "strings" |
|
4 |
+ "time" |
|
5 |
+ |
|
6 |
+ o "github.com/onsi/gomega" |
|
7 |
+ |
|
8 |
+ "k8s.io/kubernetes/pkg/util/wait" |
|
9 |
+) |
|
10 |
+ |
|
11 |
+// JobMon is a Jenkins job monitor |
|
12 |
+type JobMon struct { |
|
13 |
+ j *JenkinsRef |
|
14 |
+ lastBuildNumber string |
|
15 |
+ buildNumber string |
|
16 |
+ jobName string |
|
17 |
+} |
|
18 |
+ |
|
19 |
+// Await waits for the timestamp on the Jenkins job to change. Returns |
|
20 |
+// and error if the timeout expires. |
|
21 |
+func (jmon *JobMon) Await(timeout time.Duration) error { |
|
22 |
+ err := wait.Poll(10*time.Second, timeout, func() (bool, error) { |
|
23 |
+ |
|
24 |
+ buildNumber, err := jmon.j.GetJobBuildNumber(jmon.jobName, time.Minute) |
|
25 |
+ o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred()) |
|
26 |
+ |
|
27 |
+ ginkgolog("Checking build number for job %q current[%v] vs last[%v]", jmon.jobName, buildNumber, jmon.lastBuildNumber) |
|
28 |
+ if buildNumber == jmon.lastBuildNumber { |
|
29 |
+ return false, nil |
|
30 |
+ } |
|
31 |
+ |
|
32 |
+ if jmon.buildNumber == "" { |
|
33 |
+ jmon.buildNumber = buildNumber |
|
34 |
+ } |
|
35 |
+ body, status, err := jmon.j.GetResource("job/%s/%s/api/json?depth=1", jmon.jobName, jmon.buildNumber) |
|
36 |
+ o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred()) |
|
37 |
+ o.ExpectWithOffset(1, status).To(o.Equal(200)) |
|
38 |
+ |
|
39 |
+ body = strings.ToLower(body) |
|
40 |
+ if strings.Contains(body, "\"building\":true") { |
|
41 |
+ ginkgolog("Jenkins job %q still building:\n%s\n\n", jmon.jobName, body) |
|
42 |
+ return false, nil |
|
43 |
+ } |
|
44 |
+ |
|
45 |
+ if strings.Contains(body, "\"result\":null") { |
|
46 |
+ ginkgolog("Jenkins job %q still building result:\n%s\n\n", jmon.jobName, body) |
|
47 |
+ return false, nil |
|
48 |
+ } |
|
49 |
+ |
|
50 |
+ ginkgolog("Jenkins job %q build complete:\n%s\n\n", jmon.jobName, body) |
|
51 |
+ return true, nil |
|
52 |
+ }) |
|
53 |
+ return err |
|
54 |
+} |
0 | 55 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,349 @@ |
0 |
+package jenkins |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "bytes" |
|
4 |
+ "encoding/xml" |
|
5 |
+ "fmt" |
|
6 |
+ "io" |
|
7 |
+ "io/ioutil" |
|
8 |
+ "net/http" |
|
9 |
+ "regexp" |
|
10 |
+ "strings" |
|
11 |
+ "time" |
|
12 |
+ |
|
13 |
+ g "github.com/onsi/ginkgo" |
|
14 |
+ o "github.com/onsi/gomega" |
|
15 |
+ |
|
16 |
+ kapi "k8s.io/kubernetes/pkg/api" |
|
17 |
+ "k8s.io/kubernetes/pkg/util/wait" |
|
18 |
+ |
|
19 |
+ exutil "github.com/openshift/origin/test/extended/util" |
|
20 |
+) |
|
21 |
+ |
|
22 |
+// JenkinsRef represents a Jenkins instance running on an OpenShift server |
|
23 |
+type JenkinsRef struct { |
|
24 |
+ oc *exutil.CLI |
|
25 |
+ host string |
|
26 |
+ port string |
|
27 |
+ // The namespace in which the Jenkins server is running |
|
28 |
+ namespace string |
|
29 |
+ password string |
|
30 |
+} |
|
31 |
+ |
|
32 |
+// FlowDefinition can be marshalled into XML to represent a Jenkins workflow job definition. |
|
33 |
+type FlowDefinition struct { |
|
34 |
+ XMLName xml.Name `xml:"flow-definition"` |
|
35 |
+ Plugin string `xml:"plugin,attr"` |
|
36 |
+ KeepDependencies bool `xml:"keepDependencies"` |
|
37 |
+ Definition Definition |
|
38 |
+} |
|
39 |
+ |
|
40 |
+// Definition is part of a FlowDefinition |
|
41 |
+type Definition struct { |
|
42 |
+ XMLName xml.Name `xml:"definition"` |
|
43 |
+ Class string `xml:"class,attr"` |
|
44 |
+ Plugin string `xml:"plugin,attr"` |
|
45 |
+ Script string `xml:"script"` |
|
46 |
+} |
|
47 |
+ |
|
48 |
+// ginkgolog creates simple entry in the GinkgoWriter. |
|
49 |
+func ginkgolog(format string, a ...interface{}) { |
|
50 |
+ fmt.Fprintf(g.GinkgoWriter, format+"\n", a...) |
|
51 |
+} |
|
52 |
+ |
|
53 |
+// NewRef creates a jenkins reference from an OC client |
|
54 |
+func NewRef(oc *exutil.CLI) *JenkinsRef { |
|
55 |
+ g.By("get ip and port for jenkins service") |
|
56 |
+ serviceIP, err := oc.Run("get").Args("svc", "jenkins", "--config", exutil.KubeConfigPath()).Template("{{.spec.clusterIP}}").Output() |
|
57 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
58 |
+ port, err := oc.Run("get").Args("svc", "jenkins", "--config", exutil.KubeConfigPath()).Template("{{ $x := index .spec.ports 0}}{{$x.port}}").Output() |
|
59 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
60 |
+ |
|
61 |
+ g.By("get admin password") |
|
62 |
+ password := GetAdminPassword(oc) |
|
63 |
+ o.Expect(password).ShouldNot(o.BeEmpty()) |
|
64 |
+ |
|
65 |
+ j := &JenkinsRef{ |
|
66 |
+ oc: oc, |
|
67 |
+ host: serviceIP, |
|
68 |
+ port: port, |
|
69 |
+ namespace: oc.Namespace(), |
|
70 |
+ password: password, |
|
71 |
+ } |
|
72 |
+ return j |
|
73 |
+} |
|
74 |
+ |
|
75 |
+// Namespace returns the Jenkins namespace |
|
76 |
+func (j *JenkinsRef) Namespace() string { |
|
77 |
+ return j.namespace |
|
78 |
+} |
|
79 |
+ |
|
80 |
+// BuildURI builds a URI for the Jenkins server. |
|
81 |
+func (j *JenkinsRef) BuildURI(resourcePathFormat string, a ...interface{}) string { |
|
82 |
+ resourcePath := fmt.Sprintf(resourcePathFormat, a...) |
|
83 |
+ return fmt.Sprintf("http://%s:%v/%s", j.host, j.port, resourcePath) |
|
84 |
+} |
|
85 |
+ |
|
86 |
+// GetResource submits a GET request to this Jenkins server. |
|
87 |
+// Returns a response body and status code or an error. |
|
88 |
+func (j *JenkinsRef) GetResource(resourcePathFormat string, a ...interface{}) (string, int, error) { |
|
89 |
+ uri := j.BuildURI(resourcePathFormat, a...) |
|
90 |
+ ginkgolog("Retrieving Jenkins resource: %q", uri) |
|
91 |
+ req, err := http.NewRequest("GET", uri, nil) |
|
92 |
+ if err != nil { |
|
93 |
+ return "", 0, fmt.Errorf("Unable to build request for uri %q: %v", uri, err) |
|
94 |
+ } |
|
95 |
+ |
|
96 |
+ // http://stackoverflow.com/questions/17714494/golang-http-request-results-in-eof-errors-when-making-multiple-requests-successi |
|
97 |
+ req.Close = true |
|
98 |
+ |
|
99 |
+ req.SetBasicAuth("admin", j.password) |
|
100 |
+ client := &http.Client{} |
|
101 |
+ resp, err := client.Do(req) |
|
102 |
+ |
|
103 |
+ if err != nil { |
|
104 |
+ return "", 0, fmt.Errorf("Unable to GET uri %q: %v", uri, err) |
|
105 |
+ } |
|
106 |
+ |
|
107 |
+ defer resp.Body.Close() |
|
108 |
+ status := resp.StatusCode |
|
109 |
+ |
|
110 |
+ body, err := ioutil.ReadAll(resp.Body) |
|
111 |
+ if err != nil { |
|
112 |
+ return "", 0, fmt.Errorf("Error reading GET response %q: %v", uri, err) |
|
113 |
+ } |
|
114 |
+ |
|
115 |
+ return string(body), status, nil |
|
116 |
+} |
|
117 |
+ |
|
118 |
+// Post sends a POST to the Jenkins server. Returns response body and status code or an error. |
|
119 |
+func (j *JenkinsRef) Post(reqBody io.Reader, resourcePathFormat, contentType string, a ...interface{}) (string, int, error) { |
|
120 |
+ uri := j.BuildURI(resourcePathFormat, a...) |
|
121 |
+ |
|
122 |
+ req, err := http.NewRequest("POST", uri, reqBody) |
|
123 |
+ o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred()) |
|
124 |
+ |
|
125 |
+ // http://stackoverflow.com/questions/17714494/golang-http-request-results-in-eof-errors-when-making-multiple-requests-successi |
|
126 |
+ req.Close = true |
|
127 |
+ |
|
128 |
+ if reqBody != nil { |
|
129 |
+ req.Header.Set("Content-Type", contentType) |
|
130 |
+ req.Header.Del("Expect") // jenkins will return 417 if we have an expect hdr |
|
131 |
+ } |
|
132 |
+ req.SetBasicAuth("admin", j.password) |
|
133 |
+ |
|
134 |
+ client := &http.Client{} |
|
135 |
+ ginkgolog("Posting to Jenkins resource: %q", uri) |
|
136 |
+ resp, err := client.Do(req) |
|
137 |
+ if err != nil { |
|
138 |
+ return "", 0, fmt.Errorf("Error posting request to %q: %v", uri, err) |
|
139 |
+ } |
|
140 |
+ |
|
141 |
+ defer resp.Body.Close() |
|
142 |
+ status := resp.StatusCode |
|
143 |
+ |
|
144 |
+ body, err := ioutil.ReadAll(resp.Body) |
|
145 |
+ if err != nil { |
|
146 |
+ return "", 0, fmt.Errorf("Error reading Post response body %q: %v", uri, err) |
|
147 |
+ } |
|
148 |
+ |
|
149 |
+ return string(body), status, nil |
|
150 |
+} |
|
151 |
+ |
|
152 |
+// PostXML sends a POST to the Jenkins server. If a body is specified, it should be XML. |
|
153 |
+// Returns response body and status code or an error. |
|
154 |
+func (j *JenkinsRef) PostXML(reqBody io.Reader, resourcePathFormat string, a ...interface{}) (string, int, error) { |
|
155 |
+ return j.Post(reqBody, resourcePathFormat, "application/xml", a...) |
|
156 |
+} |
|
157 |
+ |
|
158 |
+// GetResourceWithStatus repeatedly tries to GET a jenkins resource with an acceptable |
|
159 |
+// HTTP status. Retries for the specified duration. |
|
160 |
+func (j *JenkinsRef) GetResourceWithStatus(validStatusList []int, timeout time.Duration, resourcePathFormat string, a ...interface{}) (string, int, error) { |
|
161 |
+ var retBody string |
|
162 |
+ var retStatus int |
|
163 |
+ err := wait.Poll(10*time.Second, timeout, func() (bool, error) { |
|
164 |
+ body, status, err := j.GetResource(resourcePathFormat, a...) |
|
165 |
+ if err != nil { |
|
166 |
+ ginkgolog("Error accessing resource: %v", err) |
|
167 |
+ return false, nil |
|
168 |
+ } |
|
169 |
+ var found bool |
|
170 |
+ for _, s := range validStatusList { |
|
171 |
+ if status == s { |
|
172 |
+ found = true |
|
173 |
+ break |
|
174 |
+ } |
|
175 |
+ } |
|
176 |
+ if !found { |
|
177 |
+ ginkgolog("Expected http status [%v] during GET by recevied [%v]", validStatusList, status) |
|
178 |
+ return false, nil |
|
179 |
+ } |
|
180 |
+ retBody = body |
|
181 |
+ retStatus = status |
|
182 |
+ return true, nil |
|
183 |
+ }) |
|
184 |
+ if err != nil { |
|
185 |
+ uri := j.BuildURI(resourcePathFormat, a...) |
|
186 |
+ return "", retStatus, fmt.Errorf("Error waiting for status %v from resource path %s: %v", validStatusList, uri, err) |
|
187 |
+ } |
|
188 |
+ return retBody, retStatus, nil |
|
189 |
+} |
|
190 |
+ |
|
191 |
+// WaitForContent waits for a particular HTTP status and HTML matching a particular |
|
192 |
+// pattern to be returned by this Jenkins server. An error will be returned |
|
193 |
+// if the condition is not matched within the timeout period. |
|
194 |
+func (j *JenkinsRef) WaitForContent(verificationRegEx string, verificationStatus int, timeout time.Duration, resourcePathFormat string, a ...interface{}) (string, error) { |
|
195 |
+ var matchingContent = "" |
|
196 |
+ err := wait.Poll(10*time.Second, timeout, func() (bool, error) { |
|
197 |
+ |
|
198 |
+ content, _, err := j.GetResourceWithStatus([]int{verificationStatus}, timeout, resourcePathFormat, a...) |
|
199 |
+ if err != nil { |
|
200 |
+ return false, nil |
|
201 |
+ } |
|
202 |
+ |
|
203 |
+ if len(verificationRegEx) > 0 { |
|
204 |
+ re := regexp.MustCompile(verificationRegEx) |
|
205 |
+ if re.MatchString(content) { |
|
206 |
+ matchingContent = content |
|
207 |
+ return true, nil |
|
208 |
+ } else { |
|
209 |
+ ginkgolog("Content did not match verification regex %q:\n %v", verificationRegEx, content) |
|
210 |
+ return false, nil |
|
211 |
+ } |
|
212 |
+ } else { |
|
213 |
+ matchingContent = content |
|
214 |
+ return true, nil |
|
215 |
+ } |
|
216 |
+ }) |
|
217 |
+ |
|
218 |
+ if err != nil { |
|
219 |
+ uri := j.BuildURI(resourcePathFormat, a...) |
|
220 |
+ return "", fmt.Errorf("Error waiting for status %v and verification regex %q from resource path %s: %v", verificationStatus, verificationRegEx, uri, err) |
|
221 |
+ } else { |
|
222 |
+ return matchingContent, nil |
|
223 |
+ } |
|
224 |
+} |
|
225 |
+ |
|
226 |
+// CreateItem submits XML to create a named item on the Jenkins server. |
|
227 |
+func (j *JenkinsRef) CreateItem(name string, itemDefXML string) { |
|
228 |
+ g.By(fmt.Sprintf("Creating new jenkins item: %s", name)) |
|
229 |
+ _, status, err := j.PostXML(bytes.NewBufferString(itemDefXML), "CreateItem?name=%s", name) |
|
230 |
+ o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred()) |
|
231 |
+ o.ExpectWithOffset(1, status).To(o.Equal(200)) |
|
232 |
+} |
|
233 |
+ |
|
234 |
+// GetJobBuildNumber returns the current buildNumber on the named project OR "new" if |
|
235 |
+// there are no builds against a job yet. |
|
236 |
+func (j *JenkinsRef) GetJobBuildNumber(name string, timeout time.Duration) (string, error) { |
|
237 |
+ body, status, err := j.GetResourceWithStatus([]int{200, 404}, timeout, "job/%s/lastBuild/buildNumber", name) |
|
238 |
+ if err != nil { |
|
239 |
+ return "", err |
|
240 |
+ } |
|
241 |
+ if status != 200 { |
|
242 |
+ return "new", nil |
|
243 |
+ } |
|
244 |
+ return body, nil |
|
245 |
+} |
|
246 |
+ |
|
247 |
+// StartJob triggers a named Jenkins job. The job can be monitored with the |
|
248 |
+// returned object. |
|
249 |
+func (j *JenkinsRef) StartJob(jobName string) *JobMon { |
|
250 |
+ lastBuildNumber, err := j.GetJobBuildNumber(jobName, time.Minute) |
|
251 |
+ o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred()) |
|
252 |
+ |
|
253 |
+ jmon := &JobMon{ |
|
254 |
+ j: j, |
|
255 |
+ lastBuildNumber: lastBuildNumber, |
|
256 |
+ buildNumber: "", |
|
257 |
+ jobName: jobName, |
|
258 |
+ } |
|
259 |
+ |
|
260 |
+ ginkgolog("Current timestamp for [%s]: %q", jobName, jmon.lastBuildNumber) |
|
261 |
+ g.By(fmt.Sprintf("Starting jenkins job: %s", jobName)) |
|
262 |
+ _, status, err := j.PostXML(nil, "job/%s/build?delay=0sec", jobName) |
|
263 |
+ o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred()) |
|
264 |
+ o.ExpectWithOffset(1, status).To(o.Equal(201)) |
|
265 |
+ |
|
266 |
+ return jmon |
|
267 |
+} |
|
268 |
+ |
|
269 |
+// ReadJenkinsJobUsingVars returns the content of a Jenkins job XML file. Instances of the |
|
270 |
+// string "PROJECT_NAME" are replaced with the specified namespace. |
|
271 |
+// Variables named in the vars map will also be replaced with their |
|
272 |
+// corresponding value. |
|
273 |
+func (j *JenkinsRef) ReadJenkinsJobUsingVars(filename, namespace string, vars map[string]string) string { |
|
274 |
+ pre := exutil.FixturePath("testdata", "jenkins-plugin", filename) |
|
275 |
+ post := exutil.ArtifactPath(filename) |
|
276 |
+ |
|
277 |
+ if vars == nil { |
|
278 |
+ vars = map[string]string{} |
|
279 |
+ } |
|
280 |
+ vars["PROJECT_NAME"] = namespace |
|
281 |
+ err := exutil.VarSubOnFile(pre, post, vars) |
|
282 |
+ o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred()) |
|
283 |
+ |
|
284 |
+ data, err := ioutil.ReadFile(post) |
|
285 |
+ o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred()) |
|
286 |
+ return string(data) |
|
287 |
+} |
|
288 |
+ |
|
289 |
+// ReadJenkinsJob returns the content of a Jenkins job XML file. Instances of the |
|
290 |
+// string "PROJECT_NAME" are replaced with the specified namespace. |
|
291 |
+func (j *JenkinsRef) ReadJenkinsJob(filename, namespace string) string { |
|
292 |
+ return j.ReadJenkinsJobUsingVars(filename, namespace, nil) |
|
293 |
+} |
|
294 |
+ |
|
295 |
+// BuildDSLJob returns an XML string defining a Jenkins workflow/pipeline DSL job. Instances of the |
|
296 |
+// string "PROJECT_NAME" are replaced with the specified namespace. |
|
297 |
+func (j *JenkinsRef) BuildDSLJob(namespace string, scriptLines ...string) (string, error) { |
|
298 |
+ script := strings.Join(scriptLines, "\n") |
|
299 |
+ script = strings.Replace(script, "PROJECT_NAME", namespace, -1) |
|
300 |
+ fd := FlowDefinition{ |
|
301 |
+ Plugin: "workflow-job@2.7", |
|
302 |
+ Definition: Definition{ |
|
303 |
+ Class: "org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition", |
|
304 |
+ Plugin: "workflow-cps@2.18", |
|
305 |
+ Script: script, |
|
306 |
+ }, |
|
307 |
+ } |
|
308 |
+ output, err := xml.MarshalIndent(fd, " ", " ") |
|
309 |
+ ginkgolog("Formulated DSL Project XML:\n%s\n\n", output) |
|
310 |
+ return string(output), err |
|
311 |
+} |
|
312 |
+ |
|
313 |
+// GetJobConsoleLogs returns the console logs of a particular buildNumber. |
|
314 |
+func (j *JenkinsRef) GetJobConsoleLogs(jobName, buildNumber string) (string, error) { |
|
315 |
+ return j.WaitForContent("", 200, 10*time.Minute, "job/%s/%s/consoleText", jobName, buildNumber) |
|
316 |
+} |
|
317 |
+ |
|
318 |
+// GetLastJobConsoleLogs returns the last build associated with a Jenkins job. |
|
319 |
+func (j *JenkinsRef) GetLastJobConsoleLogs(jobName string) (string, error) { |
|
320 |
+ return j.GetJobConsoleLogs(jobName, "lastBuild") |
|
321 |
+} |
|
322 |
+ |
|
323 |
+func GetAdminPassword(oc *exutil.CLI) string { |
|
324 |
+ envs, err := oc.Run("set").Args("env", "dc/jenkins", "--list").Output() |
|
325 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
326 |
+ kvs := strings.Split(envs, "\n") |
|
327 |
+ for _, kv := range kvs { |
|
328 |
+ if strings.HasPrefix(kv, "JENKINS_PASSWORD=") { |
|
329 |
+ s := strings.Split(kv, "=") |
|
330 |
+ fmt.Fprintf(g.GinkgoWriter, "\nJenkins admin password %s\n", s[1]) |
|
331 |
+ return s[1] |
|
332 |
+ } |
|
333 |
+ } |
|
334 |
+ return "password" |
|
335 |
+} |
|
336 |
+ |
|
337 |
+// Finds the pod running Jenkins |
|
338 |
+func FindJenkinsPod(oc *exutil.CLI) *kapi.Pod { |
|
339 |
+ pods, err := exutil.GetDeploymentConfigPods(oc, "jenkins") |
|
340 |
+ o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred()) |
|
341 |
+ |
|
342 |
+ if pods == nil || pods.Items == nil { |
|
343 |
+ g.Fail("No pods matching jenkins deploymentconfig in namespace " + oc.Namespace()) |
|
344 |
+ } |
|
345 |
+ |
|
346 |
+ o.ExpectWithOffset(1, len(pods.Items)).To(o.Equal(1)) |
|
347 |
+ return &pods.Items[0] |
|
348 |
+} |