[DeploymentManager] Deploy test envs using Murano

* deploy environement using custom object model
 * store openstack credentials
 * use object model from the config within application
 repository

Change-Id: I18f1eb590845acbd7a8287797b29cbd727b113da
This commit is contained in:
Alexey Khivin 2016-10-03 18:48:18 +03:00
parent d7be94d354
commit 2110eec0e8
24 changed files with 1594 additions and 0 deletions

7
deployment-manager/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
*.classpath
*.project
*.iml
*.settings
.idea/
work/
target/

View File

201
deployment-manager/LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,29 @@
# Openstack Murano Deployment Manager Plugin
This plugin manages Openstack Murano environments
# Build
mvn clean verify
Creates the plugin HPI package for use with Jenkins.
# Run by Maven
mvn hpi:run
Runs Jenkins with the plugin installed
# License
(The Apache v2 License)
Copyright 2016 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

139
deployment-manager/pom.xml Normal file
View File

@ -0,0 +1,139 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>2.15</version>
</parent>
<name>Murano Deployment Manager Jenkins Plugin</name>
<description>
This plugin provides integration with Openstack Murano
</description>
<url>https://wiki.jenkins-ci.org/display/JENKINS/Murano+Deployment+Manager+Plugin</url>
<artifactId>murano-deployment-manager</artifactId>
<packaging>hpi</packaging>
<version>0.1-SNAPSHOT</version>
<licenses>
<license>
<name>The Apache V2 License</name>
<url>http://www.apache.org/licenses/LICENSE-2.0</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<name>Alexey Khivin</name>
<email>akhivin@gmail.com</email>
</developer>
</developers>
<properties>
<java.level>8</java.level>
<jenkins.version>2.23</jenkins.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<openstack4jversion>3.0.2</openstack4jversion>
</properties>
<repositories>
<repository>
<id>repo.jenkins-ci.org</id>
<name>Jenkins Repository</name>
<url>http://repo.jenkins-ci.org/public/</url>
</repository>
<repository>
<id>jgit-repository</id>
<name>Eclipse JGit Repository</name>
<url>http://download.eclipse.org/jgit/maven</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
<dependencies>
<dependency>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-http</artifactId>
<version>1.0-beta-6</version>
<type>jar</type>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<scm>
<connection>scm:git:git://github.com/jenkinsci/${project.artifactId}-plugin.git</connection>
<developerConnection>scm:git:git@github.com:jenkinsci/${project.artifactId}-plugin.git</developerConnection>
<url>https://github.com/jenkinsci/${project.artifactId}-plugin</url>
<tag>HEAD</tag>
</scm>
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.pacesys</groupId>
<artifactId>openstack4j-core</artifactId>
<version>${openstack4jversion}</version>
</dependency>
<dependency>
<groupId>org.pacesys.openstack4j.connectors</groupId>
<artifactId>openstack4j-httpclient</artifactId>
<version>${openstack4jversion}</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>credentials</artifactId>
<version>LATEST</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,28 @@
package org.openstack.murano.jenkins_plugins.muranoci.deploy;
import hudson.model.Descriptor;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
import static java.util.Objects.requireNonNull;
public abstract class AbstractMuranoDeploymentDescriptor
extends Descriptor<MuranoDeployment> {
protected AbstractMuranoDeploymentDescriptor(Class<? extends MuranoDeployment> clazz) {
super(requireNonNull(clazz));
load();
}
/**
* {@inheritDoc}
*/
@Override
public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
save();
return true;
}
public abstract boolean isApplicable(Descriptor descriptor);
public abstract String getDisplayName();
}

View File

@ -0,0 +1,36 @@
package org.openstack.murano.jenkins_plugins.muranoci.deploy;
import hudson.Extension;
import hudson.ExtensionPoint;
import hudson.model.Describable;
import hudson.tasks.BuildStep;
import hudson.tasks.Shell;
public abstract class BuildStepDetailsProvider<T extends BuildStep> implements ExtensionPoint {
protected static String defaultName(BuildStep bs) {
return bs instanceof Describable<?> ? ((Describable<?>) bs).getDescriptor().getDisplayName()
: null;
}
/**
* @param bs A given {@link BuildStep}.
* @return the details of the build step.
*/
public abstract String getDetails(T bs);
/**
* {@link BuildStepDetailsProvider} for {@link Shell}.
*/
@Extension
public static class ShellBuildStepDetailsProvider extends BuildStepDetailsProvider<Shell> {
/**
* {@inheritDoc}
*/
@Override
public String getDetails(Shell shell) {
return shell.getCommand();
}
}
}

View File

@ -0,0 +1,77 @@
package org.openstack.murano.jenkins_plugins.muranoci.deploy;
import hudson.DescriptorExtensionList;
import hudson.ExtensionPoint;
import hudson.model.Describable;
import hudson.model.Descriptor;
import jenkins.model.Jenkins;
import java.util.LinkedList;
import java.util.List;
import static java.util.Objects.requireNonNull;
public abstract class MuranoDeployment
implements Describable<MuranoDeployment>, ExtensionPoint {
/**
* Json data that describes Murano Environment applications
*/
private String objectModel;
public MuranoDeployment() {
super();
}
/**
* Contains data that describes Environment within Openstack Cloud
* and connection credentials.
*
* @param objectModel description of environment to be deployed
*/
public MuranoDeployment(String objectModel) {
this.objectModel = requireNonNull(objectModel, "Object Model should not be Null");
}
/**
* Boilerplate, see:
* https://wiki.jenkins-ci.org/display/JENKINS/Defining+a+new+extension+point
*
* @return all registered {@link MuranoDeployment}s
*/
public static DescriptorExtensionList<MuranoDeployment, AbstractMuranoDeploymentDescriptor> all() {
return Jenkins.getInstance().getDescriptorList(MuranoDeployment.class);
}
public static List<AbstractMuranoDeploymentDescriptor> getCompatibleDeployments(Descriptor descriptor) {
LinkedList<AbstractMuranoDeploymentDescriptor> cloudDeployments =
new LinkedList<>();
for (AbstractMuranoDeploymentDescriptor deployment : all()) {
if (!deployment.isApplicable(descriptor)) {
continue;
}
cloudDeployments.add(deployment);
}
return cloudDeployments;
}
public String getObjectModel() {
return objectModel;
}
public void setObjectModel(String objectModel) {
this.objectModel = objectModel;
}
/**
* Boilerplate, see: https://wiki.jenkins-ci.org/display/JENKINS/Defining+a+new+extension+point
*/
@Override
public Descriptor<MuranoDeployment> getDescriptor() {
return (Descriptor<MuranoDeployment>) Jenkins.getInstance().getDescriptor(getClass());
}
}

View File

@ -0,0 +1,363 @@
package org.openstack.murano.jenkins_plugins.muranoci.deploy;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.kohsuke.stapler.DataBoundConstructor;
import org.openstack4j.api.OSClient;
import org.openstack4j.api.exceptions.AuthenticationException;
import org.openstack4j.connectors.httpclient.HttpCommand;
import org.openstack4j.core.transport.HttpMethod;
import org.openstack4j.core.transport.HttpRequest;
import org.openstack4j.openstack.OSFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.time.Instant;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
public class MuranoHelper {
public static final int MURANO_DEFAULT_PORT = 8082;
private final static Logger LOG = Logger.getLogger(MuranoHelper.class.getName());
private OSClient.OSClientV2 os = null;
/**
* Suppose this is keystone Url
*/
private String serverUrl;
private String username;
private String password;
private String tenantName;
/**
* Default timeout for waiting deployment success
*/
private int timeout = 3600*1000;
@DataBoundConstructor
public MuranoHelper(String serverUrl,
String username,
String password,
String tenantName) {
this.serverUrl = serverUrl;
this.username = username;
this.password = password;
this.tenantName = tenantName;
}
public String getServerUrl() {
return serverUrl;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getTenantName() {
return tenantName;
}
/**
*
* @param name New environment name
* @param objectModel object model describing new environment
* @return new environment id
* @throws AuthenticationException in case credentials
*/
public String deployNewFromObjectModel(String name, String objectModel)
throws AuthenticationException {
this.authenticate();
String token = getOSClient().getAccess().getToken().getId();
String envId = checkIfDeploymentExists(token, name);
if (envId == null) {
// Create Env
envId = this.createEnvironment(token, name);
// Create Session
String sessionId = this.createEnvironmentSession(token, envId);
// Add App to Environment
addApplicationToEnvironment(token, envId, sessionId, objectModel);
// Deploy
deployEnvironment(token, envId, sessionId);
}
return envId;
}
/**
* Loop around to see if the deployment is a success. This waits for about 10 secs 300 times hoping that
* it finishes. This all depends on teh number of nodes and the speed of the boxes. But seems sufficient.
*
* @param envId Environemnt Id
* @return whether the deployment is a success
* @throws TimeoutException if deployment process still in progress after deadline
*/
public boolean waitDeploymentResult(String envId) throws TimeoutException {
String token = getOSClient().getAccess().getToken().getId();
boolean status = false;
Instant deadline = Instant.now().plusMillis(timeout);
while(true) {
try {
Thread.sleep(10000);
String payload = getResponseForJson(
getMuranoEnpoint(),
MURANO_DEFAULT_PORT,
"/v1/environments/" + envId + "/deployments",
HttpMethod.GET,
token,
null,
null);
JSONParser parser = new JSONParser();
try {
JSONObject deployments = (JSONObject) parser.parse(payload);
JSONArray deploymentList = (JSONArray) deployments.get("deployments");
JSONObject thisDeployment = (JSONObject) deploymentList.get(0);
if ("success".equals(thisDeployment.get("state"))) {
status = true;
break;
}
} catch (ParseException pe) {
}
} catch (Exception ex) {
status = false;
break;
}
if (Instant.now().isAfter(deadline)){
throw new TimeoutException("Environment was not ready in time.");
}
}
return status;
}
/**
* Return the Environment id if it exists
*
* @param token
* @param name
* @return
*/
private String checkIfDeploymentExists(String token, String name) {
// TODO: remove string manipulation
String payload = getResponseForJson(
getMuranoEnpoint(),
MURANO_DEFAULT_PORT,
"/v1/environments",
HttpMethod.GET,
token,
null,
null);
String envId = null;
JSONParser parser = new JSONParser();
try{
Object obj = parser.parse(payload);
JSONObject response = (JSONObject)obj;
JSONArray environmentArray = (JSONArray) response.get("environments");
for (Object env: environmentArray) {
JSONObject thisEnv = (JSONObject) env;
String envName = (String) thisEnv.get("name");
if (envName.equals(name)) {
envId = (String) thisEnv.get("id");
break;
}
}
}catch(ParseException pe){
LogRecord logRecord = new LogRecord(Level.WARNING, "Parse exception: position: " + pe.getPosition());
logRecord.setThrown(pe);
LOG.log(logRecord);
}
return envId;
}
/**
* Deploy the environment given the environment id and Session Token
*/
private void deployEnvironment(String token, String envId, String sessionId) {
String response = getResponseForJson(
getMuranoEnpoint(),
MURANO_DEFAULT_PORT,
"/v1/environments/" + envId + "/sessions/" + sessionId + "/deploy",
HttpMethod.POST,
token,
null,
null);
}
public String getMuranoEnpoint() {
/*
* TODO: This is temporary decision. Murano URL should be obtained from Keystone
*/
String string[] = this.serverUrl.split(":");
return string[0] + ":" + string[1];
}
/**
* Add the app(K8S) to the environment
* @param token
* @param envId
* @param sessionId
* @param jsonReq
*/
private void addApplicationToEnvironment(String token, String envId, String sessionId, String jsonReq) {
String response = getResponseForJson(this.getMuranoEnpoint(),
MURANO_DEFAULT_PORT,
"/v1/environments/" + envId + "/services",
HttpMethod.POST,
token,
jsonReq,
sessionId);
}
private String createEnvironmentSession(String token, String envId) {
String payload = getResponseForJson(this.getMuranoEnpoint(),
MURANO_DEFAULT_PORT,
"/v1/environments/" + envId + "/configure",
HttpMethod.POST,
token,
null,
null);
String sessionId = "";
JSONParser parser = new JSONParser();
try{
Object obj = parser.parse(payload);
JSONObject response = (JSONObject)obj;
sessionId = (String)response.get("id");
}catch(ParseException pe){
System.out.println("position: " + pe.getPosition());
System.out.println(pe);
}
return sessionId;
}
private String createEnvironment(String token, String envname) {
String reqPayload = "{\"name\":\"" + envname + "\"}";
String payload = getResponseForJson(
getMuranoEnpoint(),
MURANO_DEFAULT_PORT,
"/v1/environments",
HttpMethod.POST, token, reqPayload, null);
String envId = "";
JSONParser parser = new JSONParser();
try{
Object obj = parser.parse(payload);
JSONObject response = (JSONObject)obj;
envId = (String)response.get("id");
}catch(ParseException pe){
System.out.println("position: " + pe.getPosition());
System.out.println(pe);
}
return envId;
}
/**
* Main helper method to call the Murano API and return the response. Accepts both GET and POST.
*
* @param url Base URL to connect
* @param port Which port is murano listening on
* @param requestPath Path on Murano URL
* @param method GET or POST
* @param token Auth Token
* @param jsonPayload Payload for the message
* @param muranoSessionId Optional Session Id
* @return Response from the Call
*/
private String getResponseForJson(String url,
int port,
String requestPath,
HttpMethod method,
String token,
String jsonPayload,
String muranoSessionId) {
HttpRequest request = HttpRequest.builder().method(method)
.endpoint(url + ":" + port)
.path(requestPath)
.header("X-Auth-Token", token)
.json(jsonPayload)
.build();
if (muranoSessionId != null) {
request.getHeaders().put("X-Configuration-Session", muranoSessionId);
}
if (jsonPayload != null) {
request = request.toBuilder().json(jsonPayload).build();
}
HttpCommand command = HttpCommand.create(request);
CloseableHttpResponse response = null;
try {
response = command.execute();
} catch(Exception ex) {
ex.printStackTrace();
return null;
}
StringBuffer jsonString = new StringBuffer();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(
response.getEntity().getContent()));
//Print the raw output of murano api from the server
String output;
while ((output = br.readLine()) != null) {
jsonString.append(output + "\n");
}
} catch(Exception ex) {
return null;
}
return jsonString.toString();
}
/**
* Authenticate to the Openstack instance given the credentials in constructor
*
* @throws AuthenticationException in case of authentication failure.
*/
public void authenticate() throws AuthenticationException {
this.os = OSFactory.builderV2()
.endpoint(this.serverUrl)
.credentials(this.username, this.password)
.tenantName(this.tenantName)
.authenticate();
}
/**
* Helper object to return the OSClient
* @return OSClient V2
*/
public OSClient.OSClientV2 getOSClient() {
return this.os;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
}

View File

@ -0,0 +1,211 @@
package org.openstack.murano.jenkins_plugins.muranoci.deploy;
import com.cloudbees.plugins.credentials.CredentialsMatchers;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
import com.cloudbees.plugins.credentials.domains.DomainRequirement;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLParser;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.*;
import hudson.security.ACL;
import hudson.tasks.BuildWrapper;
import hudson.tasks.BuildWrapperDescriptor;
import hudson.util.ListBoxModel;
import jenkins.model.Jenkins;
import org.apache.http.impl.client.SystemDefaultCredentialsProvider;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.export.Exported;
import org.openstack.murano.jenkins_plugins.muranoci.deploy.credentials.OpenstackCredentials;
import java.io.IOException;
import java.io.Serializable;
import java.math.BigInteger;
import java.net.URL;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.google.common.collect.Lists.newArrayList;
import static java.util.Objects.requireNonNull;
public class MuranoManagerBuildWrapper extends BuildWrapper implements Serializable {
private static String MURANO_ENV_NAME = "MuranoCI-";
private final MuranoDeployment deployment;
private String credentialsId;
@DataBoundConstructor
public MuranoManagerBuildWrapper(MuranoDeployment deployment,
String credentialsId) {
this.deployment = requireNonNull(deployment);
this.credentialsId = requireNonNull(credentialsId);
}
public String getCredentialsId() {
return credentialsId;
}
/**
* {@inheritDoc}
*/
@Override
public BuildWrapper.Environment setUp(AbstractBuild build, Launcher launcher, BuildListener listener)
throws IOException, InterruptedException {
EnvVars env = build.getEnvironment(listener);
OpenstackCredentials credentials = getOpenstackCredentials(getCredentialsId());
try {
MuranoHelper helper = new MuranoHelper(
credentials.getIdentityServiceEndpoint(),
credentials.getUsername(),
credentials.getPassword().getPlainText(),
credentials.getTenant()
);
if (env.containsKey("BUILD_ENVIRONMENT_TIMEOUT")) {
int timeout = Integer.parseInt(env.get("BUILD_ENVIRONMENT_TIMEOUT"));
helper.setTimeout(timeout);
}
//TODO: Remove
try {
((RepositoryTemplatedDeployment) deployment).readObjectModel(build.getWorkspace());
} catch (Exception io) {
}
String name = generateEnvName();
String envId = helper.deployNewFromObjectModel(
name, deployment.getObjectModel());
boolean result = helper.waitDeploymentResult(envId);
if (!result) {
build.setResult(Result.FAILURE);
}
} catch (Exception e) {
e.printStackTrace();
build.setResult(Result.FAILURE);
}
return new JenkinsEnvironmentImpl(env);
}
private String generateEnvName() {
return MURANO_ENV_NAME + new BigInteger(
32,
new SecureRandom())
.toString(16);
}
private OpenstackCredentials getOpenstackCredentials(String credentialsId) {
List<OpenstackCredentials> openstackCredentialsList =
CredentialsProvider.lookupCredentials(
OpenstackCredentials.class,
Jenkins.getInstance(),
ACL.SYSTEM);
OpenstackCredentials openstackCredentials = CredentialsMatchers.firstOrNull(
openstackCredentialsList,
CredentialsMatchers.allOf(
CredentialsMatchers.withId(credentialsId)));
return openstackCredentials;
}
@Exported
public MuranoDeployment getDeployment() {
return deployment;
}
/**
* {@inheritDoc}
*/
@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl) super.getDescriptor();
}
/**
* The descriptor for our {@code MuranoManagerBuildWrapper} plugin.
*/
@Extension
public static final class DescriptorImpl extends BuildWrapperDescriptor {
/**
* {@inheritDoc}
*/
@Override
public boolean isApplicable(AbstractProject<?, ?> project) {
return true;
}
/**
* {@inheritDoc}
*/
@Override
public String getDisplayName() {
return Messages.MuranoManagerBuildWrapper_DisplayName();
}
@SuppressWarnings("unused") // used by stapler
public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Jenkins context,
@QueryParameter String remoteBase) {
if (context == null || !context.hasPermission(Item.CONFIGURE)) {
return new StandardListBoxModel();
}
List<DomainRequirement> domainRequirements = newArrayList();
return new StandardListBoxModel()
.withEmptySelection()
.withMatching(
CredentialsMatchers.anyOf(
CredentialsMatchers.instanceOf(OpenstackCredentials.class)),
CredentialsProvider.lookupCredentials(
StandardCredentials.class,
context,
ACL.SYSTEM,
domainRequirements));
}
}
private final class JenkinsEnvironmentImpl extends Environment {
private final EnvVars envVars;
/**
* Construct the instance with a snapshot of the environment within which it was created in case
* values that were used to configure it at the start of the build change before the end.
*
* @param envVars The set of environment variables used to spin up the ephemeral deployment, so
* we can tear it down with the same.
*/
public JenkinsEnvironmentImpl(EnvVars envVars) {
this.envVars = requireNonNull(envVars);
}
@Override
public void buildEnvVars(Map<String, String> env) {
super.buildEnvVars(env);
}
/**
* {@inheritDoc}
*/
@Override
public boolean tearDown(AbstractBuild build, BuildListener listener)
throws IOException, InterruptedException {
return true;
}
}
}

View File

@ -0,0 +1,92 @@
package org.openstack.murano.jenkins_plugins.muranoci.deploy;
import hudson.EnvVars;
import hudson.Extension;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Result;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import org.kohsuke.stapler.DataBoundConstructor;
import java.io.IOException;
/**
* After a successful build, this plugin deploys to Murano Environment via the
* Deployment Manager API.
*/
public class MuranoManagerDeployer extends Recorder {
@DataBoundConstructor
public MuranoManagerDeployer() {
}
/**
* {@inheritDoc}
*/
@Override
public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener)
throws IOException, InterruptedException {
if (build.getResult() != Result.SUCCESS) {
return true;
}
try {
build.getEnvironment(listener);
} catch (IOException e) {
e.printStackTrace(listener.error(Messages.MuranoManagerDeployer_EnvironmentException()));
build.setResult(Result.FAILURE);
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
}
@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
/**
* {@inheritDoc}
*/
@Override
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
// Indicates that this builder can be used with all kinds of project types
return true;
}
/**
* {@inheritDoc}
*/
@Override
public String getDisplayName() {
return Messages.MuranoManagerDeployer_DisplayName();
}
}
/**
* {@link BuildStepDetailsProvider} for the Cloud Manager Deployer.
*/
@Extension
public static class DetailsProvider extends BuildStepDetailsProvider<MuranoManagerDeployer> {
/**
* {@inheritDoc}
*/
@Override
public String getDetails(MuranoManagerDeployer deployer) {
return "MuranoManagerDeployer";
}
}
}

View File

@ -0,0 +1,101 @@
package org.openstack.murano.jenkins_plugins.muranoci.deploy;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLParser;
import hudson.Extension;
import hudson.FilePath;
import hudson.model.Descriptor;
import org.kohsuke.stapler.DataBoundConstructor;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class RepositoryTemplatedDeployment extends MuranoDeployment {
/**
* The file in the repository that contains muranoci configuration.
*/
public static final String CI_CONFG_FILENAME = ".murano.yml";
/**
*
*/
private final String environment;
/**
* The specific Implemenation of <code>MuranoDeployment</code> that
* gets object model from the file within the repo.
*
* @param environment The name of the environment within the .murano.yml config
*/
@DataBoundConstructor
public RepositoryTemplatedDeployment(
String environment) {
this.environment = environment;
}
public String getEnvironment() {
return environment;
}
/**
* Denotes that this is a cloud deployment plugin.
*/
@Extension
public static class DescriptorImpl extends AbstractMuranoDeploymentDescriptor {
public DescriptorImpl() {
this(RepositoryTemplatedDeployment.class);
}
public DescriptorImpl(Class<? extends RepositoryTemplatedDeployment> clazz) {
super(clazz);
}
/**
* {@inheritDoc}
*/
@Override
public String getDisplayName() {
return Messages.RepositoryTemplatedMuranoDeployment_DisplayName();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isApplicable(Descriptor descriptor) {
return true;
}
}
public void readObjectModel(FilePath workspace) throws IOException {
String config = null;
try {
config = new FilePath(workspace, ".murano.yml").readToString();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
YAMLFactory factory = new YAMLFactory();
ObjectMapper mapper = new ObjectMapper(factory);
HashMap<String, Object> map = mapper.readValue(config, HashMap.class);
Object model = ((Map<String,Object>)((Map<String,Object>)map).get("environments")).get(this.environment);
JsonFactory jsonFactory = new JsonFactory();
ObjectMapper mapperModel = new ObjectMapper(jsonFactory);
String string = mapperModel.writeValueAsString(model);
System.out.println(string);
this.setObjectModel(string);
}
}

View File

@ -0,0 +1,60 @@
package org.openstack.murano.jenkins_plugins.muranoci.deploy;
import hudson.Extension;
import hudson.model.Descriptor;
import hudson.util.FormValidation;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.openstack4j.api.exceptions.AuthenticationException;
import javax.servlet.ServletException;
import java.io.IOException;
import static java.util.Objects.requireNonNull;
public class TemplatedDeployment extends MuranoDeployment {
/**
* The specific Implemenation of <code>MuranoDeployment</code> that
* gets object model from the contructor parameter
* (the direct textarea on Jenkins form)
*
* @param objectModel Object model for Murano environment to be deployed
*/
@DataBoundConstructor
public TemplatedDeployment(
String objectModel) {
super(requireNonNull(objectModel));
}
/**
* Denotes that this is a cloud deployment plugin.
*/
@Extension
public static class DescriptorImpl extends AbstractMuranoDeploymentDescriptor {
public DescriptorImpl() {
this(TemplatedDeployment.class);
}
public DescriptorImpl(Class<? extends TemplatedDeployment> clazz) {
super(clazz);
}
/**
* {@inheritDoc}
*/
@Override
public String getDisplayName() {
return Messages.TemplatedMuranoDeployment_DisplayName();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isApplicable(Descriptor descriptor) {
return true;
}
}
}

View File

@ -0,0 +1,14 @@
package org.openstack.murano.jenkins_plugins.muranoci.deploy.credentials;
import com.cloudbees.plugins.credentials.Credentials;
import hudson.util.Secret;
public interface OpenstackCredentials extends Credentials {
String getName();
String getDescription();
String getUsername();
Secret getPassword();
String getTenant();
String getIdentityServiceEndpoint();
}

View File

@ -0,0 +1,109 @@
package org.openstack.murano.jenkins_plugins.muranoci.deploy.credentials;
import com.cloudbees.plugins.credentials.CredentialsDescriptor;
import com.cloudbees.plugins.credentials.NameWith;
import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials;
import com.cloudbees.plugins.credentials.impl.Messages;
import hudson.Extension;
import hudson.util.FormValidation;
import hudson.util.Secret;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.openstack.murano.jenkins_plugins.muranoci.deploy.MuranoHelper;
import org.openstack4j.api.exceptions.AuthenticationException;
import javax.servlet.ServletException;
import java.io.IOException;
import static java.util.Objects.requireNonNull;
@NameWith(value = OpenstackCredentialsNameProvider.class, priority = 50)
public class OpenstackCredentialsImpl extends BaseStandardCredentials implements OpenstackCredentials {
private final String name;
private final String identityServiceEndpoint;
private final String username;
private final Secret password;
private final String tenant;
@DataBoundConstructor
public OpenstackCredentialsImpl(
String id,
String name,
String description,
String identityServiceEndpoint,
String tenant,
String username,
String password) {
super(id, description);
this.name = name;
this.identityServiceEndpoint = identityServiceEndpoint;
this.username = username;
this.password = Secret.fromString(requireNonNull(password));
this.tenant = tenant;
}
public String getName() {
return this.name;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public Secret getPassword() {
return this.password;
}
@Override
public String getTenant() {
return this.tenant;
}
@Override
public String getIdentityServiceEndpoint() {
return this.identityServiceEndpoint;
}
@Extension
public static class Descriptor
extends CredentialsDescriptor {
public FormValidation doTestConnection(@QueryParameter("identityServiceEndpoint") final String identityServiceEndpoint,
@QueryParameter("tenant") final String tenant,
@QueryParameter("username") final String username,
@QueryParameter("password") final String password)
throws IOException, ServletException {
MuranoHelper client = new MuranoHelper(
identityServiceEndpoint,
username,
password,
tenant);
try {
client.authenticate();
} catch (AuthenticationException ae) {
return FormValidation.error(
"Unable to connect to server. Please check credentials");
} catch (Exception e) {
return FormValidation.error("Error: " + e.getMessage());
}
return FormValidation.ok("Success");
}
/**
* {@inheritDoc}
*/
@Override
public String getDisplayName() {
// return Messages.CertificateCredentialsImpl_DisplayName();
return "Openstack Cloud";
}
}
}

View File

@ -0,0 +1,12 @@
package org.openstack.murano.jenkins_plugins.muranoci.deploy.credentials;
import com.cloudbees.plugins.credentials.CredentialsNameProvider;
public class OpenstackCredentialsNameProvider extends CredentialsNameProvider<OpenstackCredentialsImpl> {
@Override
public String getName(OpenstackCredentialsImpl openstackCredentials) {
return openstackCredentials.getName();
}
}

View File

@ -0,0 +1,26 @@
MuranoDeployment.InvalidDeploySpec=Invalid deployment specification.
MuranoDeployment.DeployComplete=Deployment complete!
MuranoDeployment.InsertRollback=Failure deploying, attempting rollback.
MuranoDeployment.RollbackSuccess=Rollback successful.
MuranoDeployment.DeleteComplete=Deletion complete!
MuranoDeployment.CreatedDeploy=Created new deployment: {0}
MuranoDeployment.CreatedDeployVerbose=Created new deployment: {0} with spec: {1}
MuranoDeployment.DeletedDeploy=Deleted deployment: {0}
MuranoDeployment.CreateDeployException=Exception creating deployment
MuranoDeployment.GetDeployException=Exception getting deployment
MuranoDeployment.DeleteDeployException=Exception deleting deployment
MuranoDeployment.WaitDeploy=Waiting for deployment...
MuranoDeployment.WaitDelete=Waiting for deletion...
MuranoDeployment.WaitTimeoutDeploy=Timed out waiting for deployment.
MuranoDeployment.WaitTimeoutDelete=Timed out waiting for deletion.
MuranoDeployment.DeployFailed=Deployment failed for: {0}
MuranoDeployment.ExecutorExceptionWhileDeploying=Bad state: RPC execution exception while deploying.
MuranoDeployment.IOExceptionWhileDeploying=IO exception while deploying.
MuranoDeployment.LogInsert=Inserting deployment: {0}
MuranoDeployment.LogDelete=Deleting deployment: {0}
MuranoDeploymentDescriptor.BadComponent=Components must match: {0}
MuranoDeploymentDescriptor.NotEmpty=Components cannot be empty
MuranoDeploymentDescriptor.SampleResolution={0}\nSample variable resolution: {1}
MuranoDeploymentModule.AppName=Jenkins Cloud Deployer
MuranoDeploymentModule.ConnectionError=Exception connecting to Deployment Manager
MuranoEnvironmentDeleter_DisplayName=Deployment Turndown

View File

@ -0,0 +1,6 @@
MuranoManagerDeployer.DisplayName=Openstack Murano Deployer
MuranoManagerDeployer.EnvironmentException=Exception accessing build environment
MuranoManagerBuildWrapper.DisplayName=Murano Deployer
BuildStepDetailsProvider.MavenName=Maven
TemplatedMuranoDeployment.DisplayName=Deploy using ready-made template
RepositoryTemplatedMuranoDeployment.DisplayName=Deploy using configuration from project repository

View File

@ -0,0 +1,21 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core"
xmlns:f="/lib/form"
xmlns:c="/lib/credentials">
<f:entry title="${%Openstack Credentials}" field="credentialsId">
<c:select/>
</f:entry>
<j:invokeStatic var="descriptors"
method="getCompatibleDeployments"
className="org.openstack.murano.jenkins_plugins.muranoci.deploy.MuranoDeployment">
<j:arg type="hudson.model.Descriptor" value="${descriptor}"/>
</j:invokeStatic>
<f:dropdownDescriptorSelector field="deployment"
title="${%Deployment Form}"
descriptors="${descriptors}"/>
</j:jelly>

View File

@ -0,0 +1,17 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core"
xmlns:st="jelly:stapler"
xmlns:d="jelly:define"
xmlns:l="/lib/layout"
xmlns:t="/lib/hudson"
xmlns:f="/lib/form"
xmlns:a="/lib/auth">
<f:entry name="action" title="${%Action}" field="action">
<select name="action">
<option value="teardownAction">${%Tear Down}</option>
<option value="doNothingAction">${%Do Nothing}</option>
</select>
</f:entry>
</j:jelly>

View File

@ -0,0 +1,14 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core"
xmlns:st="jelly:stapler"
xmlns:d="jelly:define"
xmlns:l="/lib/layout"
xmlns:t="/lib/hudson"
xmlns:f="/lib/form"
xmlns:a="/lib/auth">
<f:entry title="${%Environment name}" field="environment" >
<f:textbox />
</f:entry>
</j:jelly>

View File

@ -0,0 +1,15 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core"
xmlns:st="jelly:stapler"
xmlns:d="jelly:define"
xmlns:l="/lib/layout"
xmlns:t="/lib/hudson"
xmlns:f="/lib/form"
xmlns:a="/lib/auth">
<f:entry title="${%Application Object Model}" field="objectModel">
<f:textarea>
</f:textarea>
</f:entry>
</j:jelly>

View File

@ -0,0 +1,12 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core"
xmlns:st="jelly:stapler"
xmlns:d="jelly:define"
xmlns:l="/lib/layout"
xmlns:t="/lib/hudson"
xmlns:f="/lib/form"
xmlns:a="/lib/auth">
</j:jelly>

View File

@ -0,0 +1,4 @@
<?jelly escape-by-default='true'?>
<div>
This plugin integrates Openstack Murano to Jenkins.
</div>