├── .gitignore
├── src
└── main
│ ├── webapp
│ └── images
│ │ └── logo-96.png
│ ├── resources
│ ├── org
│ │ └── jenkinsci
│ │ │ └── plugins
│ │ │ └── appetize
│ │ │ ├── AppetizeCredentials
│ │ │ ├── help-description.html
│ │ │ ├── help-apiToken.html
│ │ │ ├── credentials.jelly
│ │ │ └── help-id.html
│ │ │ ├── AppetizeRecorder
│ │ │ ├── help-platform.html
│ │ │ ├── help-apiTokenId.html
│ │ │ ├── help-appPath.html
│ │ │ └── config.jelly
│ │ │ ├── AppetizeBuildAction
│ │ │ └── summary.jelly
│ │ │ └── AppetizeProjectAction
│ │ │ ├── floatingBox.jelly
│ │ │ └── jobMain.jelly
│ └── index.jelly
│ └── java
│ └── org
│ └── jenkinsci
│ └── plugins
│ └── appetize
│ ├── AppetizeProjectAction.java
│ ├── AppetizeBuildAction.java
│ ├── AppetizeCredentials.java
│ ├── AppetizeApp.java
│ ├── AppetizeApiService.java
│ └── AppetizeRecorder.java
├── README.md
├── LICENSE.txt
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | work/
3 | .idea/
4 | *.iml
5 |
--------------------------------------------------------------------------------
/src/main/webapp/images/logo-96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/appetize-plugin/master/src/main/webapp/images/logo-96.png
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/appetize/AppetizeCredentials/help-description.html:
--------------------------------------------------------------------------------
1 |
2 | An optional description to help tell similar credentials apart.
3 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/appetize/AppetizeRecorder/help-platform.html:
--------------------------------------------------------------------------------
1 |
2 |
Appetize.io currently supports streaming iOS and Android apps.
3 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/appetize/AppetizeCredentials/help-apiToken.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 | Stream iOS & Android builds directly within Jenkins via
4 |
Appetize.io's
5 | cloud-based iOS Simulators & Android Emulators.
6 |
7 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/appetize/AppetizeCredentials/credentials.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/appetize/AppetizeCredentials/help-id.html:
--------------------------------------------------------------------------------
1 |
2 | An internal unique ID by which these credentials are identified from jobs and other configuration.
3 | Normally left blank, in which case an ID will be generated, which is fine for jobs created using visual forms.
4 | Useful to specify explicitly when using credentials from scripted configuration.
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Deprecated
2 | ## This plugin is deprecated and no longer supported
3 |
4 | # appetize-plugin
5 |
6 | Stream iOS & Android builds directly within Jenkins via Appetize.io's cloud-based iOS Simulators & Android Emulators.
7 |
8 | Demos: [iPhone](https://appetize.io/demo?device=iphone), [iPad](https://appetize.io/demo?device=ipad), [Nexus 5](https://appetize.io/demo?device=nexus5)
9 |
10 | 
11 |
12 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/appetize/AppetizeRecorder/help-apiTokenId.html:
--------------------------------------------------------------------------------
1 |
2 |
The API Token to use when connecting to Appetize.io. Note that all apps, including those uploaded
3 | with the placeholder token, are private and secure.
4 |
5 |
The placeholder token is intended for temporary use only. Request your own at
6 | https://appetize.io/api and enter it at
7 | Jenkins → Credentials → Global credentials.
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/appetize/AppetizeBuildAction/summary.jelly:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 | View on Appetize.io
6 |
7 | Manage App Settings
8 |
9 |
10 | ${it.getEmbedHtml()}
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/appetize/AppetizeProjectAction/floatingBox.jelly:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 | ${action.getLastAppetizeBuild().getEmbedHtml()}
8 |
Last Successful Build (#${action.getLastAppetizeBuild().getBuildNumber()})
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/appetize/AppetizeRecorder/help-appPath.html:
--------------------------------------------------------------------------------
1 |
2 |
The output location of your app, relative to the project workspace.
3 |
4 |
iOS
5 |
Specify the ".app" folder that contains the simulator build, e.g.
6 | build/Release-iphonesimulator/example.app
7 |
8 |
Note that iOS apps require a simulator build. To create a build with a script, use the command:
9 |
10 |
xcodebuild -sdk iphonesimulator
11 |
12 |
13 | If you are using the Jenkins Xcode plugin, specify iphonesimulator for SDK
14 | under "Advanced Xcode build options".
15 |
16 |
17 |
Android
18 |
Specify the location of your apk, e.g. app/build/outputs/apk/app-release.apk
19 |
20 |
Android apps that use the NDK (very few do) must include the x86 instruction set.
21 |
22 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Appetize.io
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/appetize/AppetizeProjectAction/jobMain.jelly:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/appetize/AppetizeRecorder/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | iOS
8 |
9 |
10 | iOS
11 |
12 |
13 |
14 |
15 | Android
16 |
17 |
18 | Android
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/appetize/AppetizeProjectAction.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2015 Appetize.io
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.appetize;
26 |
27 | import hudson.model.AbstractBuild;
28 | import hudson.model.AbstractProject;
29 | import hudson.model.Action;
30 |
31 | /**
32 | * Developers: Weiyin He and John Snyder
33 | */
34 | public class AppetizeProjectAction implements Action {
35 | private final AbstractProject,?> project;
36 |
37 | public AppetizeProjectAction(AbstractProject, ?> project) {
38 | this.project = project;
39 | }
40 |
41 | public AppetizeBuildAction getLastAppetizeBuild() {
42 | AbstractBuild,?> build = project.getLastSuccessfulBuild();
43 | return build == null ? null : build.getAction(AppetizeBuildAction.class);
44 | }
45 |
46 | @Override
47 | public String getIconFileName() {
48 | return null;
49 | }
50 |
51 | @Override
52 | public String getDisplayName() {
53 | return null;
54 | }
55 |
56 | @Override
57 | public String getUrlName() {
58 | return null;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | org.jenkins-ci.plugins
7 | plugin
8 | 1.480
9 |
10 |
11 | org.jenkins-ci.plugins
12 | appetize
13 | 1.1.1-SNAPSHOT
14 | hpi
15 |
16 | Appetize.io Plugin
17 | Stream iOS & Android builds directly within Jenkins via Appetize.io's cloud-based iOS Simulators & Android Emulators.
18 | https://wiki.jenkins-ci.org/display/JENKINS/Appetize.io+Plugin
19 |
20 |
21 | MIT License
22 | http://opensource.org/licenses/MIT
23 |
24 |
25 |
26 |
27 | weiyin
28 | Weiyin He
29 | weiyin@appetize.io
30 |
31 |
32 | jcsnyder
33 | John Snyder
34 | john@appetize.io
35 |
36 |
37 |
38 | scm:git:git://github.com/jenkinsci/${project.artifactId}-plugin.git
39 | scm:git:git@github.com:jenkinsci/${project.artifactId}-plugin.git
40 | http://github.com/jenkinsci/${project.artifactId}-plugin
41 | HEAD
42 |
43 |
44 |
45 | repo.jenkins-ci.org
46 | https://repo.jenkins-ci.org/public/
47 |
48 |
49 |
50 |
51 | repo.jenkins-ci.org
52 | https://repo.jenkins-ci.org/public/
53 |
54 |
55 |
56 |
57 | org.jenkins-ci.plugins
58 | credentials
59 | 1.21
60 |
61 |
62 | com.google.code.gson
63 | gson
64 | 2.3.1
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/appetize/AppetizeBuildAction.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2015 Appetize.io
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.appetize;
26 |
27 | import hudson.EnvVars;
28 | import hudson.model.AbstractBuild;
29 | import hudson.model.EnvironmentContributingAction;
30 |
31 | /**
32 | * Developers: Weiyin He and John Snyder
33 | */
34 | public class AppetizeBuildAction extends AppetizeApp implements EnvironmentContributingAction {
35 | private int buildNumber;
36 |
37 | public AppetizeBuildAction(String platform, String privateKey, String publicKey, String publicUrl, String manageUrl, int buildNumber) {
38 | super(platform, privateKey, publicKey, publicUrl, manageUrl);
39 | this.buildNumber = buildNumber;
40 | }
41 |
42 | public void buildEnvVars(AbstractBuild, ?> abstractBuild, EnvVars envVars) {
43 | if (envVars == null) return;
44 |
45 | envVars.put("APPETIZEIO_PUBLIC_KEY", getPublicKey());
46 | envVars.put("APPETIZEIO_PRIVATE_KEY", getPrivateKey());
47 | envVars.put("APPETIZEIO_PUBLIC_URL", getPublicUrl());
48 | envVars.put("APPETIZEIO_MANAGE_URL", getManageUrl());
49 | }
50 |
51 | @Override
52 | public String getIconFileName() {
53 | // don't show anything on left sidebar
54 | return null;
55 | }
56 |
57 | @Override
58 | public String getDisplayName() {
59 | return null;
60 | }
61 |
62 | @Override
63 | public String getUrlName() {
64 | return null;
65 | }
66 |
67 | public int getBuildNumber() {
68 | return buildNumber;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/appetize/AppetizeCredentials.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2015 Appetize.io
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 | package org.jenkinsci.plugins.appetize;
25 |
26 | import com.cloudbees.plugins.credentials.CredentialsNameProvider;
27 | import com.cloudbees.plugins.credentials.CredentialsScope;
28 | import com.cloudbees.plugins.credentials.NameWith;
29 | import com.cloudbees.plugins.credentials.common.StandardCredentials;
30 | import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials;
31 | import edu.umd.cs.findbugs.annotations.NonNull;
32 | import hudson.Extension;
33 | import hudson.Util;
34 | import hudson.util.Secret;
35 | import org.kohsuke.stapler.DataBoundConstructor;
36 |
37 | /**
38 | * Developers: Weiyin He and John Snyder
39 | */
40 | @NameWith(value=AppetizeCredentials.NameProvider.class)
41 | public class AppetizeCredentials extends BaseStandardCredentials {
42 | private static final long serialVersionUID = 1L;
43 |
44 | // Appetize.io API token
45 | private final Secret apiToken;
46 |
47 | @DataBoundConstructor
48 | public AppetizeCredentials(CredentialsScope scope, String id, String description, String apiToken) {
49 | super(scope, id, description);
50 | this.apiToken = Secret.fromString(apiToken);
51 | }
52 |
53 | public Secret getApiToken() {
54 | return apiToken;
55 | }
56 |
57 | public String toString() {
58 | String description = Util.fixEmptyAndTrim(getDescription());
59 | return "API Token" + (description != null ? " (" + description + ")" : "");
60 | }
61 |
62 | @Extension
63 | public static class DescriptorImpl extends BaseStandardCredentialsDescriptor {
64 | @Override
65 | public String getDisplayName() {
66 | return "Appetize.io Credentials";
67 | }
68 | }
69 |
70 | public static class NameProvider extends CredentialsNameProvider {
71 | @NonNull
72 | @Override
73 | public String getName(StandardCredentials appetizeCredentials) {
74 | return appetizeCredentials.toString();
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/appetize/AppetizeApp.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2015 Appetize.io
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.appetize;
26 |
27 | /**
28 | * Developers: Weiyin He and John Snyder
29 | */
30 | public class AppetizeApp {
31 | private String platform;
32 | private String privateKey;
33 | private String publicKey;
34 | private String publicUrl;
35 | private String manageUrl;
36 |
37 | public AppetizeApp(String platform, String privateKey, String publicKey, String publicUrl, String manageUrl) {
38 | this.platform = platform;
39 | this.privateKey = privateKey;
40 | this.publicKey = publicKey;
41 | this.publicUrl = publicUrl;
42 | this.manageUrl = manageUrl;
43 | }
44 |
45 | public String getEmbedHtml() {
46 | String device = null;
47 | String width = null;
48 | String height = null;
49 |
50 | if (platform.equalsIgnoreCase("ios")) {
51 | device = "iphone";
52 | width = "284px";
53 | height = "600px";
54 | }
55 | else if (platform.equalsIgnoreCase("android")) {
56 | device = "nexus5";
57 | width = "300px";
58 | height = "597px";
59 | }
60 |
61 | if (device != null && privateKey != null) {
62 | String sourceUrl = "https://appetize.io/embed/" + publicKey +
63 | "?device=" + device + "&scale=75&autoplay=false&orientation=portrait&deviceColor=black";
64 | String iframe = String.format("",
65 | sourceUrl, width, height);
66 | return iframe;
67 | } else {
68 | return "";
69 | }
70 | }
71 |
72 | public String getPlatform() {
73 | return platform;
74 | }
75 |
76 | public String getPrivateKey() {
77 | return privateKey;
78 | }
79 |
80 | public String getPublicKey() {
81 | return publicKey;
82 | }
83 |
84 | public String getPublicUrl() {
85 | return publicUrl;
86 | }
87 |
88 | public String getManageUrl() {
89 | return manageUrl;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/appetize/AppetizeApiService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2015 Appetize.io
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.appetize;
26 |
27 | import com.google.gson.Gson;
28 |
29 | import java.io.*;
30 | import java.net.HttpURLConnection;
31 | import java.net.Proxy;
32 | import java.net.URL;
33 | import hudson.ProxyConfiguration;
34 | import jenkins.model.Jenkins;
35 |
36 | /**
37 | * Developers: Weiyin He and John Snyder
38 | */
39 | public class AppetizeApiService {
40 | private final String PRESIGN_URL = "https://api.appetize.io/v1/jenkins/presigned";
41 | private final String UPDATE_URL = "https://api.appetize.io/v1/app/update";
42 |
43 | private PrintStream logger;
44 | private Gson gson;
45 |
46 | public AppetizeApiService(PrintStream logger) {
47 | this.logger = logger;
48 | this.gson = new Gson();
49 | }
50 |
51 | public static class AppetizePresignedUrls {
52 | public String iosUrl;
53 | public String androidUrl;
54 | }
55 |
56 | public static class AppetizeUpdateParams {
57 | public String url;
58 | public String platform;
59 | public String token;
60 | public String privateKey;
61 | public String source;
62 | public String jenkinsUUID;
63 | public String jobUUID;
64 | public Integer buildNumber;
65 | }
66 |
67 | public static class AppetizeUpdateResult {
68 | public String publicKey;
69 | public String privateKey;
70 | public String publicURL;
71 | public String appURL;
72 | public String manageURL;
73 | }
74 |
75 | public AppetizePresignedUrls getPresignedUrls() {
76 | HttpURLConnection connection = null;
77 | try {
78 | URL url = new URL(PRESIGN_URL);
79 | connection = getConnection(url);
80 | connection.setRequestMethod("GET");
81 | connection.connect();
82 | int status = connection.getResponseCode();
83 |
84 | if (status >= 200 && status <= 299) {
85 | InputStreamReader reader = new InputStreamReader(connection.getInputStream());
86 | return gson.fromJson(reader, AppetizePresignedUrls.class);
87 | } else {
88 | String errorMessage = readToString(connection.getErrorStream());
89 | throw new Exception("Status " + status + ": " + errorMessage);
90 | }
91 | } catch (Exception e) {
92 | println("Error getting Appetize.io upload URLs");
93 | println(e.getMessage());
94 | return null;
95 | } finally {
96 | if (connection != null) {
97 | connection.disconnect();
98 | }
99 | }
100 | }
101 |
102 | public boolean uploadData(InputStream in, String urlString) {
103 | HttpURLConnection connection = null;
104 | try {
105 | URL url = new URL(urlString);
106 | connection = getConnection(url);
107 | connection.setDoOutput(true);
108 | connection.setRequestMethod("PUT");
109 |
110 | OutputStream out = connection.getOutputStream();
111 | copy(in, out);
112 | in.close();
113 | out.close();
114 |
115 | int status = connection.getResponseCode();
116 | if (status >= 200 && status <= 299) {
117 | return true;
118 | } else {
119 | throw new Exception("Status " + status);
120 | }
121 | } catch (Exception e) {
122 | println("Error uploading to " + urlString);
123 | println(e.getMessage());
124 | return false;
125 | } finally {
126 | if (connection != null) connection.disconnect();
127 | }
128 | }
129 |
130 | public AppetizeUpdateResult updateApp(AppetizeUpdateParams params) {
131 |
132 | HttpURLConnection connection = null;
133 | try {
134 | URL url = new URL(UPDATE_URL);
135 | connection = getConnection(url);
136 | connection.setRequestMethod("POST");
137 | connection.setRequestProperty("Content-Type", "application/json");
138 | connection.setDoOutput(true);
139 |
140 | // upload json
141 | String json = gson.toJson(params);
142 | OutputStream out = connection.getOutputStream();
143 | OutputStreamWriter writer = new OutputStreamWriter(out);
144 | writer.write(json);
145 | writer.flush();
146 | writer.close();
147 |
148 | // get response
149 | int status = connection.getResponseCode();
150 | if (status >= 200 && status <= 299) {
151 | InputStreamReader reader = new InputStreamReader(connection.getInputStream());
152 | return gson.fromJson(reader, AppetizeUpdateResult.class);
153 | } else {
154 | String errorMessage = readToString(connection.getErrorStream());
155 | throw new Exception("Status " + status + ": " + errorMessage);
156 | }
157 | } catch (Exception e) {
158 | println("Error calling Appetize.io API");
159 | println(e.getMessage());
160 | return null;
161 | } finally {
162 | if (connection != null) {
163 | connection.disconnect();
164 | }
165 | }
166 | }
167 |
168 | void println(String message) {
169 | if (this.logger != null) logger.println(message);
170 | }
171 |
172 | String readToString(InputStream is) {
173 | if (is == null) return null;
174 |
175 | InputStreamReader reader = new InputStreamReader(is);
176 | StringWriter writer = new StringWriter();
177 | char[] buf = new char[1024];
178 | try {
179 | while (reader.read(buf) > 0) {
180 | writer.write(buf);
181 | }
182 | is.close();
183 |
184 | return writer.toString();
185 | } catch (IOException e) {
186 | return null;
187 | }
188 | }
189 |
190 | private static void copy(InputStream in, OutputStream out) throws IOException{
191 | byte[] buf = new byte[1024 * 10];
192 | int len;
193 | while ((len = in.read(buf)) > 0) {
194 | out.write(buf, 0, len);
195 | }
196 | }
197 |
198 | /**
199 | * Returns an HttpURLConnection using the Jenkins global proxy if set
200 | * @param url URL to open the connection
201 | * @return Instanciated connection
202 | * @throws IOException
203 | */
204 | private HttpURLConnection getConnection(URL url) throws IOException {
205 |
206 | HttpURLConnection connection;
207 | ProxyConfiguration proxyConfig = Jenkins.getInstance().proxy;
208 | if (proxyConfig != null) {
209 | Proxy proxy = proxyConfig.createProxy(url.getHost());
210 | if (proxy != null && proxy.type() == Proxy.Type.HTTP) {
211 | connection = (HttpURLConnection)url.openConnection(proxy);
212 | }
213 | else {
214 | connection = (HttpURLConnection)url.openConnection();
215 | }
216 | }
217 | else {
218 | connection = (HttpURLConnection)url.openConnection();
219 | }
220 |
221 | return connection;
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/appetize/AppetizeRecorder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2015 Appetize.io
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.appetize;
26 |
27 | import com.cloudbees.plugins.credentials.CredentialsProvider;
28 | import com.cloudbees.plugins.credentials.domains.DomainRequirement;
29 | import hudson.Extension;
30 | import hudson.FilePath;
31 | import hudson.Launcher;
32 | import hudson.model.*;
33 | import hudson.security.ACL;
34 | import hudson.tasks.BuildStepDescriptor;
35 | import hudson.tasks.BuildStepMonitor;
36 | import hudson.tasks.Publisher;
37 | import hudson.tasks.Recorder;
38 | import hudson.util.ListBoxModel;
39 | import org.kohsuke.stapler.DataBoundConstructor;
40 |
41 | import java.io.File;
42 | import java.io.IOException;
43 | import java.io.PrintStream;
44 | import java.security.MessageDigest;
45 | import java.security.NoSuchAlgorithmException;
46 | import java.util.*;
47 |
48 | /**
49 | * Developers: Weiyin He and John Snyder
50 | */
51 | public class AppetizeRecorder extends Recorder {
52 | // For production use, please request an API token. This token is for temporary use when
53 | // first setting up projects.
54 | private static final String PLACEHOLDER_API_TOKEN = "tok_7vkmr5quwwjjxy4rv1q1h0rn08";
55 | private static final String PLACEHOLDER_ID = "placeholder";
56 |
57 | private final String platform;
58 | private final String appPath;
59 | private final String apiTokenId;
60 |
61 | @DataBoundConstructor
62 | public AppetizeRecorder(String platform, String appPath, String apiTokenId) {
63 | this.platform = platform;
64 | this.appPath = appPath;
65 | this.apiTokenId = apiTokenId;
66 | }
67 |
68 | public String getPlatform() {
69 | return platform;
70 | }
71 |
72 | public String getApiTokenId() {
73 | return apiTokenId;
74 | }
75 |
76 | public String getAppPath() {
77 | return appPath;
78 | }
79 |
80 | @Override
81 | public DescriptorImpl getDescriptor() {
82 | return (DescriptorImpl)super.getDescriptor();
83 | }
84 |
85 | @Override
86 | public BuildStepMonitor getRequiredMonitorService() {
87 | return BuildStepMonitor.NONE;
88 | }
89 |
90 | @Override
91 | public Collection extends Action> getProjectActions(AbstractProject, ?> project) {
92 | ArrayList collection = new ArrayList(1);
93 | AppetizeProjectAction action = new AppetizeProjectAction(project);
94 | if (action != null) collection.add(action);
95 | return collection;
96 | }
97 |
98 | @Override
99 | public boolean perform(AbstractBuild, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
100 | // only run on SUCCESS
101 | if (!build.getResult().isBetterOrEqualTo(Result.SUCCESS)) return false;
102 |
103 | PrintStream logger = listener.getLogger();
104 |
105 | // check platform
106 | if (!platform.equalsIgnoreCase("ios") && !platform.equalsIgnoreCase("android")) {
107 | logger.println("Error: Invalid platform " + platform);
108 | return false;
109 | }
110 |
111 | // check appPath exists
112 | if (appPath == null || appPath.isEmpty()) {
113 | logger.println("Error: Empty appPath");
114 | return false;
115 | }
116 | FilePath appLocation = new FilePath(build.getWorkspace(), appPath);
117 | if ((platform.equalsIgnoreCase("ios") && !appLocation.isDirectory()) ||
118 | (platform.equalsIgnoreCase("android") && !appLocation.exists())) {
119 | logger.println("Error: could not find app in " + appLocation.getRemote());
120 | return false;
121 | }
122 |
123 | // get api token
124 | String apiToken = null;
125 | if (apiTokenId == null || apiTokenId.isEmpty() || apiTokenId.equals(PLACEHOLDER_ID)) {
126 | apiToken = PLACEHOLDER_API_TOKEN;
127 | } else {
128 | List credentialsList = CredentialsProvider.lookupCredentials(
129 | AppetizeCredentials.class, (Item)null,
130 | ACL.SYSTEM, Collections.emptyList());
131 | for(AppetizeCredentials credentials : credentialsList) {
132 | if (apiTokenId.equals(credentials.getId())) {
133 | apiToken = credentials.getApiToken().getPlainText();
134 | break;
135 | }
136 | }
137 |
138 | if (apiToken == null) {
139 | logger.println("Error looking up appetize.io credentials. Please reconfigure the appetize.io post-build action");
140 | return false;
141 | }
142 | }
143 |
144 | // get pre-signed url
145 | AppetizeApiService appetize = new AppetizeApiService(logger);
146 | AppetizeApiService.AppetizePresignedUrls urls = appetize.getPresignedUrls();
147 | if (urls == null) {
148 | logger.println("Error getting appetize.io upload URL");
149 | return false;
150 | }
151 |
152 | // prepare upload
153 | FilePath uploadFile;
154 | File zipFile = null;
155 | String uploadUrl = null;
156 | if (platform.equalsIgnoreCase("ios")) {
157 | try {
158 | zipFile = File.createTempFile("appetize", ".zip");
159 | uploadFile = new FilePath(zipFile);
160 | appLocation.zip(uploadFile);
161 | } catch (Exception e) {
162 | logger.println("Error creating zip file in " + zipFile.toString());
163 | return false;
164 | }
165 | uploadUrl = urls.iosUrl;
166 | }
167 | else {
168 | uploadFile = appLocation;
169 | uploadUrl = urls.androidUrl;
170 | }
171 |
172 | // upload file
173 | if (!appetize.uploadData(uploadFile.read(), uploadUrl)) {
174 | return false;
175 | }
176 |
177 | String jobUUID;
178 | try {
179 | String projectName = build.getProject().getName();
180 | MessageDigest digest = MessageDigest.getInstance("SHA-256");
181 | String jenkinsUUID = getDescriptor().getJenkinsUUID();
182 | if (jenkinsUUID != null) digest.update(jenkinsUUID.getBytes());
183 | if (projectName != null) digest.update(projectName.getBytes());
184 | if (jenkinsUUID != null) digest.update(jenkinsUUID.getBytes());
185 |
186 | byte[] hash = digest.digest();
187 | StringBuffer hexString = new StringBuffer();
188 | for (int i = 0; i < hash.length; i++) {
189 | String hex = Integer.toHexString(0xff & hash[i]);
190 | if(hex.length() == 1) hexString.append('0');
191 | hexString.append(hex);
192 | }
193 | jobUUID = hexString.toString();
194 | } catch (NoSuchAlgorithmException e) {
195 | return false;
196 | }
197 |
198 | // hit api to create app
199 | AppetizeApiService.AppetizeUpdateParams params = new AppetizeApiService.AppetizeUpdateParams();
200 | params.url = uploadUrl;
201 | params.platform = platform;
202 | params.token = apiToken;
203 | params.source = "appetize-jenkins-plugin";
204 | params.jenkinsUUID = getDescriptor().getJenkinsUUID();
205 | params.jobUUID = jobUUID;
206 | params.buildNumber = build.getNumber();
207 | AppetizeApiService.AppetizeUpdateResult result = appetize.updateApp(params);
208 | if (result == null) {
209 | logger.println("Error calling Appetize.io API");
210 | return false;
211 | }
212 |
213 | logger.println("Success uploading to Appetize.io");
214 | logger.println("You can view your app at " + result.publicURL);
215 | logger.println("You can manage your app at " + result.manageURL);
216 |
217 | // add action to build
218 | build.addAction(new AppetizeBuildAction(platform, result.privateKey, result.publicKey,
219 | result.publicURL, result.manageURL, build.getNumber()));
220 |
221 | return true;
222 | }
223 |
224 | @Extension
225 | public static class DescriptorImpl extends BuildStepDescriptor {
226 | private String jenkinsUUID;
227 |
228 | public DescriptorImpl() {
229 | load();
230 | if (jenkinsUUID == null || jenkinsUUID.isEmpty()) {
231 | jenkinsUUID = UUID.randomUUID().toString();
232 | save();
233 | }
234 | }
235 |
236 | @Override
237 | public boolean isApplicable(Class extends AbstractProject> aClass) {
238 | return true;
239 | }
240 |
241 | @Override
242 | public String getDisplayName() {
243 | return "Upload to Appetize.io";
244 | }
245 |
246 | public String getJenkinsUUID() {
247 | return jenkinsUUID;
248 | }
249 |
250 | public ListBoxModel doFillApiTokenIdItems() {
251 | ListBoxModel items = new ListBoxModel();
252 | items.add("Placeholder API Token", PLACEHOLDER_ID);
253 |
254 | List credentialsList = CredentialsProvider.lookupCredentials(
255 | AppetizeCredentials.class, (Item)null,
256 | ACL.SYSTEM, Collections.emptyList());
257 | for (AppetizeCredentials credentials : credentialsList) {
258 | items.add(credentials.toString(), credentials.getId());
259 | }
260 |
261 | return items;
262 | }
263 | }
264 | }
265 |
--------------------------------------------------------------------------------