diff --git a/.gitignore b/.gitignore index 32858aa..a4b6fee 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* +target +.idea +*.iml diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..7c3435f --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,35 @@ +node { + stage name:"Checkout" + checkout scm; + + stage name:"Dependencies" + buildTempDeps() + + stage name:"Build" + build() + +} + + +def build() { + + def mvnHome = tool 'latest' + env.MAVEN_OPTS="-Xmx2G"; + + sh "${mvnHome}/bin/mvn clean install" + +} + +def buildTempDeps() { + // Temporary dependencies to things that have not yet been fixed + dir('docker-java') { + + git url: "https://github.com/magnayn/docker-java.git" + + def mvnHome = tool 'latest' + env.MAVEN_OPTS="-Xmx2G"; + + sh "${mvnHome}/bin/mvn -DskipTests clean install" + } + +} \ No newline at end of file diff --git a/README.md b/README.md index b548dc6..645d6be 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,25 @@ # SnowGlobe -Container Management Tool +Infrastructure as Code + +## What is SnowGlobe + +SnowGlobe is a tool in the same area as Terraform, Cloudformation, docker-compose. + +SnowGlobe is currently very experimental. It is useful for us in our deployment scenarios! + +These tools aim to allow you to 'describe' how infrastructure is to be deployed, and allow incremental changes to be made. Instead of writing scripts that perform steps, your configuration defines what you want the outcome to look like, and the tooling figures out the necessary steps needed to make it work. + + +It is very similar to (and deeply inspired by) Terraform - but with some differences + +* SnowGlobe scripts are code. The script is a DSL script built on top of groovy - so all valid groovy (and thus all valid java) can be used. + - This should make more 'advanced' deployment scenarios such as blue/green deployments easier to achieve without building external tools that must modify the deployment descriptor. + +* SnowGlobe is built on Java/Groovy rather than golang + +* Terraform tends to concentrate more on AWS - we are interested (primarily) in Docker, though other providers could easily be added + +* It is easier to build 'layers' in SnowGlobe (e.g: a 'base' layer that defines a consul-on-docker, and an 'app' layer that then uses it. + +* Terraform is much more mature and has more effort applied to it. + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4ecb494 --- /dev/null +++ b/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + com.nirima + snowglobe + pom + 0.1-SNAPSHOT + + + 1.5 + 2.0 + 2.4.7 + + + + scm:git:git://github.com/nirima/SnowGlobe.git + scm:git:git@github.com:nirima/SnowGlobe.git + http://github.com/nirima/SnowGlobe + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + UTF-8 + true + + + + + + + + + snowglobe-core + snowglobe-jenkins + snowglobe-exe + snowglobe-shaded + snowglobe-jenkins-plugin + + + + + diff --git a/snowglobe-core/pom.xml b/snowglobe-core/pom.xml new file mode 100644 index 0000000..880c544 --- /dev/null +++ b/snowglobe-core/pom.xml @@ -0,0 +1,143 @@ + + + 4.0.0 + com.nirima.snowglobe + snowglobe-core + + + com.nirima + snowglobe + 0.1-SNAPSHOT + + + + + + + + + org.codehaus.gmavenplus + gmavenplus-plugin + ${gmavenVersion} + + + + addSources + addTestSources + + compile + + testCompile + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + junit + junit + 4.12 + test + + + org.codehaus.groovy + groovy-all + + 2.4.7 + + + org.reflections + reflections + 0.9.10 + + + com.google.guava + guava + 19.0 + + + com.github.docker-java + docker-java + 3.0.6-jenkins + + + + commons-io + commons-io + 2.5 + + + + com.orbitz.consul + consul-client + 0.12.7 + + + org.javers + javers-core + 2.1.2 + + + org.slf4j + slf4j-api + 1.7.21 + + + ch.qos.logback + logback-core + 1.1.7 + test + + + ch.qos.logback + logback-classic + 1.1.7 + test + + + + + + false + + central + bintray + http://jcenter.bintray.com + + + + diff --git a/snowglobe-core/src/main/groovy/com/nirima/snowglobe/SGExec.groovy b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/SGExec.groovy new file mode 100644 index 0000000..784340e --- /dev/null +++ b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/SGExec.groovy @@ -0,0 +1,322 @@ +package com.nirima.snowglobe + +import com.nirima.snowglobe.core.* +import com.nirima.snowglobe.graph.Graph +import com.nirima.snowglobe.graph.GraphBuilder +import com.nirima.snowglobe.plan.Plan +import com.nirima.snowglobe.plan.PlanBuilder +import com.nirima.snowglobe.plan.PlanType +import com.nirima.snowglobe.state.* +import org.codehaus.groovy.runtime.InvokerHelper + +import java.lang.reflect.Field +import java.lang.reflect.Modifier +import java.security.AccessController +import java.security.PrivilegedAction + +/** + * Created by magnayn on 06/09/2016. + */ +class SGExec { + + InputStream planFile, stateFile; + + SnowGlobeSystem dsl = new SnowGlobeSystem(); + public SnowGlobe snowGlobe; + public SnowGlobe stateGlobe; + private Graph g,stateGraph; + + private SnowGlobeContext sgContext; + + public SGExec( File plan) { + + this.planFile = new FileInputStream(plan) + + init(); + } + + SGExec( + File file, File file1) { + + this.planFile = new FileInputStream(file) + if( file1 != null && file1.exists()) + this.stateFile = new FileInputStream(file1); + init(); + } + + SGExec( + InputStream file, InputStream state) { + + planFile = file; + stateFile = state; + + + init(); + } + + String save() { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + SGWriter sw = new SGWriter(byteArrayOutputStream); + sw.write(snowGlobe); + byteArrayOutputStream.close(); + return new String(byteArrayOutputStream.toByteArray()); + } + + void init() { + dsl.parseScript( planFile ); + snowGlobe = dsl.runScript(); + + sgContext = new SnowGlobeContext(snowGlobe); + sgContext.initModules(); + + + if( stateFile != null ) { + SnowGlobeSystem dslState = new SnowGlobeSystem(); + + dslState.parseScript(stateFile); + stateGlobe = dslState.runScript(); + + if( stateGlobe != null ) { + SnowGlobeContext stContext = new SnowGlobeContext(stateGlobe); + + stContext.initModules(); + + // Don't init it. + + mergeState(snowGlobe, stateGlobe); + } + } + + sgContext.buildModules(); + + g = new GraphBuilder().build(sgContext); + } + + void mergeState(SnowGlobe globe, SnowGlobe state) { + assert globe != null + assert state != null + + state.modules.each { stateModule -> + + Module globeModule = globe.getModule(stateModule.id); + if( globeModule == null ) { + globe.addModule( stateModule ) + } else { + // it existed + + stateModule.resources.each { stateResource -> + Resource globeResource = globeModule.getResource(stateResource.getClass(), stateResource.id ); + + if( globeResource == null ) { + globeModule.addResource( stateResource ); + stateResource.savedState = stateResource.state; + stateResource.state = null; + } else { + // It existed + globeResource.savedState = stateResource.state; + globeResource.savedState.resource = globeResource; + } + + } + + } + + } + + + } + + public void graph(OutputStream os) { + String out = new GraphBuilder().graphViz(g); + os.write(out.getBytes()); + } + + public void apply() { + PlanBuilder pb = new PlanBuilder(); + + Plan plan = pb.buildPlan(sgContext, PlanType.Apply); + + plan.execute(); + + + } + + public void destroy() { + PlanBuilder pb = new PlanBuilder(); + + Plan plan = pb.buildPlan(sgContext, PlanType.Destroy); + + plan.execute(); + + + } + + +} + +public class SGWriter { + + OutputStream os; + + public SGWriter(OutputStream os) { + this.os = os; + } + + public void write(SnowGlobe sg) { + os << "snowglobe {\n" + + sg.modules.each { + module -> + os << " module(\"${module.id}\") { \n"; + + + module.resources.each { resource -> + + + write(resource); + + } + + os << " }\n"; + } + + os << "}\n" + } + + public void write(Resource resource) { + + State self = resource.savedState; + if( self != null ) { + String name = Core.INSTANCE.getNameForClass(resource.getClass()); + os << " ${name}(\"${resource.id}\") {\n" + os << writeObject(self); + os << " }\n"; + } + } + public String writeObject(Object self) { + + String os = ""; + + Class klass = self.getClass(); + boolean groovyObject = self instanceof GroovyObject; + + while (klass != null && klass != State.class) { + for (final Field field : klass.getDeclaredFields()) { + if ((field.getModifiers() & Modifier.STATIC) == 0) { + if (groovyObject && field.getName().equals("metaClass")) { + continue; + } + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + field.setAccessible(true); + return null; + } + }); + + try { + Object value = field.get(self); + if( value instanceof Context ) + value = ((Context)value).getProxy(); + + if( value instanceof List ) { + + if( value.size() == 0 ) + continue; + // Could introstpect the type maybe + Object item = value.get(0); + if( !item.getClass().isPrimitive() && !(item.getClass() == String.class || item instanceof GString) ) { + + value.each { + os += " ${field.getName()} { \n"; + + os += writeObject(it); + + os += " }\n" + } + + + continue; + } + + } + + + os += " ${field.getName()} = "; + + + if( value instanceof Provider ) { + String name = Core.INSTANCE.getNameForClass( value.getClass() ); + String id = value.id; + if( id == null ) + os += " ${name}(null)"; + else + os += " ${name}(\"${value.id}\")"; + } else { + os += stringObject(value); + + + } + os += "\n"; + } catch (Exception e) { + + } + + } + } + + klass = klass.getSuperclass(); + + } + + return os; + + } + + public String stringObject(Object value) { + if( value == null ) + return "null"; + + if( value instanceof String || value instanceof GString ) { + return getValue(value); + } + if( value instanceof Map ) { + String obj = null; + value.each { + if( obj == null ) obj = "[" else obj += ", "; + + obj = obj + "${stringObject(it.key)} : ${stringObject(it.value)}" + } + return obj + "]"; + } + + if( value instanceof List ) { + String obj = null; + value.each { + if( obj == null ) obj = "[" else obj += ", "; + + obj = obj + "${stringObject(it)}" + } + return obj + "]"; + } + + return InvokerHelper.toString(value) + } + + public String getProperty(property) { + "${property.key} = ${getValue(property.value)}" + } + + public String getValue(Object o) { + if( o instanceof String || o instanceof GString) { + // TODO : quoted string + + String qs = o.replace("\\", "\\\\") + .replace("\$", "\\\$") + .replace("\"", "\\\""); + + return "\"${qs}\""; + } + + return "${o}"; + } +} \ No newline at end of file diff --git a/snowglobe-core/src/main/groovy/com/nirima/snowglobe/consul/Consul.groovy b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/consul/Consul.groovy new file mode 100644 index 0000000..7bb4f83 --- /dev/null +++ b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/consul/Consul.groovy @@ -0,0 +1,116 @@ +package com.nirima.snowglobe.consul + +import com.google.common.net.HostAndPort +import com.nirima.snowglobe.core.* +import com.nirima.snowglobe.plan.PlanAction +import com.orbitz.consul.Consul + +/** + * Created by magnayn on 04/09/2016. + */ +@SGItem("consul_provider") +class ConsulProvider extends Provider { + + public String address; + public String token; + public String scheme; + public String datacenter; + + ConsulProvider(Module module, String id, Closure closure) { + super(module, id, closure) + } + + public Consul getConsulClient() { + + Thread.sleep(5000); + + Consul consul = Consul.builder() + .withHostAndPort(HostAndPort.fromString(address)) + .build(); + + return consul; + } +} + +class ConsulKeyPrefixState extends ResourceState { + String path_prefix; + Map subkeys; + + ConsulKeyPrefixState(Resource parent, Closure closure) { + super(parent, closure); + } + + Closure getDefaults() { + return { + if(provider == null) { + provider = consul_provider(null); + } + } + } +} +@SGItem("consul_key_prefix") +class ConsulKeyPrefix extends Resource { + + + ConsulKeyPrefix(Module module, String id, Closure closure) { + super(module, id, closure) + } + + + public PlanAction assess() { + return new ConsulKeyPrefixAction(this); + } +} + +class ConsulKeysStateKey implements Serializable { + String path; + String value; +} + + +class ConsulKeysState extends ResourceState { + List key = []; + + ConsulKeysState(Resource parent, Closure closure) { + super(parent, closure); + } + + public void key(Closure c) { + ConsulKeysStateKey p = new ConsulKeysStateKey(); + c.delegate = p; + c.resolveStrategy = Closure.DELEGATE_FIRST + + c() + + key << p; + } + + @Override + void accept(Object context) { + + key=[] + + super.accept(context) + } + + Closure getDefaults() { + return { + if(provider == null) { + provider = consul_provider(null); + } + } + } +} +@SGItem("consul_keys") +class ConsulKeys extends Resource { + + + ConsulKeys(Module module, String id, Closure closure) { + super(module, id, closure) + } + + public PlanAction assess() { + return new ConsulKeysAction(this); + } + +} diff --git a/snowglobe-core/src/main/groovy/com/nirima/snowglobe/consul/ConsulActions.groovy b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/consul/ConsulActions.groovy new file mode 100644 index 0000000..f595cfe --- /dev/null +++ b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/consul/ConsulActions.groovy @@ -0,0 +1,65 @@ +package com.nirima.snowglobe.consul + +import com.nirima.snowglobe.plan.PlanActionBase +import com.orbitz.consul.Consul +import com.orbitz.consul.KeyValueClient +import groovy.util.logging.Slf4j + +@Slf4j +class ConsulKeyPrefixAction extends PlanActionBase { + + + ConsulKeyPrefixAction(ConsulKeyPrefix nodePair) { + super(nodePair) + } + + + @Override + ConsulKeyPrefixState create(ConsulKeyPrefixState desiredState) { + ConsulProvider dp = desiredState.getProvider(); + Consul client = dp.getConsulClient(); + KeyValueClient kvClient = client.keyValueClient(); + + String prefix = desiredState.path_prefix; + + desiredState.subkeys.each { + + it -> + String key = prefix + it.key; + log.debug "Setting consul ${key} = ${it.value}" + kvClient.putValue(key, it.value); + } + + + return desiredState; + } + +} + +@Slf4j +class ConsulKeysAction extends PlanActionBase { + + + ConsulKeysAction(ConsulKeys nodePair) { + super(nodePair) + } + + @Override + ConsulKeysState create(ConsulKeysState desiredState) { + ConsulProvider dp = desiredState.getProvider(); + Consul client = dp.getConsulClient(); + KeyValueClient kvClient = client.keyValueClient(); + + desiredState.key.each { + log.debug "Setting consul ${it.path} = ${it.value}" + kvClient.putValue(it.path, it.value); + } + + return desiredState + } + + + + + +} \ No newline at end of file diff --git a/snowglobe-core/src/main/groovy/com/nirima/snowglobe/core/Context.groovy b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/core/Context.groovy new file mode 100644 index 0000000..744510c --- /dev/null +++ b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/core/Context.groovy @@ -0,0 +1,596 @@ +package com.nirima.snowglobe.core +import groovy.util.logging.Slf4j +/** + * Created by magnayn on 04/09/2016. + */ +@Slf4j +abstract class Context { + private T proxy; + Context(T t) { + proxy = t; + } + + public T getProxy() { return proxy; } + + + public String toString() { + return "Context<${getProxy()}>"; + } +} + +public class Dependency { + Context from; + Context to; + + public Dependency(Context from, Context to) { + assert(from != null); + assert(to != null); + assert(from != to); + + this.from = from + this.to = to + } + + boolean equals(o) { + if (this.is(o)) { + return true + } + if (getClass() != o.class) { + return false + } + + Dependency that = (Dependency) o + + if (from != that.from) { + return false + } + if (to != that.to) { + return false + } + + return true + } + + int hashCode() { + int result + result = (from != null ? from.hashCode() : 0) + result = 31 * result + (to != null ? to.hashCode() : 0) + return result + } + + + @Override + public String toString() { + return """\ +Dependency{ + from=$from, + to=$to +}""" + } +} + +@Slf4j +public class SnowGlobeContext extends Context { + + List modules = []; + Set dependencies = []; + + Set processedModules = []; + Set inProcessModules = []; + + + SnowGlobeContext(SnowGlobe proxy) { + super(proxy); + } + + def invokeMethod(String name, Object args) { + Object result = getProxy().invokeMethod(name, args); + if (result instanceof Module) { + ModuleContext child = new ModuleContext(result, this); + modules << child; + result.accept(child) + } + return result; + } + + public void build() { + // Initial visit goes down as far as the modules + initModules(); + + buildModules(); + } + + public void buildModules() { + log.trace 'Build Modules'; + modules.each { + processModule(it); + } + } + + public void initModules() { + modules = []; + dependencies = []; + processedModules = []; + inProcessModules = []; + log.trace "Visit Modules" + getProxy().accept(this); + } + + private void processModule(ModuleContext context) { + log.debug "Build Module ${context}" + + if( processedModules.contains(context)) + return; + + if( inProcessModules.contains(context) ) + throw new IllegalStateException("Circular dependency on ${context}"); + + inProcessModules.add(context); + context.build(); + inProcessModules.remove(context); + + processedModules.add(context); + log.debug "Built Module ${context}" + } + + public Context getElement(ModuleContext from, Class klass, String id) { + ModuleContext result = modules. + find { klass.isAssignableFrom( it.getProxy().getClass() ) && it.getProxy().id == id } + if (result == null) + throw IllegalStateException("Cannot find resource ${id} type ${klass}"); + + dependencies << new Dependency(from, result); + + // + if( !processedModules.contains(result) ) + processModule(result); + + return result; + } + + Context getContextFor(Object o) { + Context result = modules.collect { + it.getContextFor(o) + }.find { + it != null } + return result; + } +} + + +@Slf4j +public class ProviderContext extends Context { + ModuleContext parent; + + ProviderContext(Provider proxy, ModuleContext parent) { + super(proxy); + this.parent = parent + } + + def getProperty(String name) { + try { + Provider resource = getProxy(); + return resource.getProperty(name); + } catch(Exception ex) { + log.error "State for resource " + getProxy() + " does not have property " + name; + throw ex; + } + } + + void setProperty(String name, Object value) { + + log.debug " set ${name}=${value} on ${this}" + + try { + getProxy().setProperty(name,value); + } + catch( MissingPropertyException ex) { + log.error("Resource ${getProxy()} does not have a property named ${name}"); + } + catch( Exception ex ) { + log.error ("Error setting state for resource ${getProxy()} property ${name} with value ${value}", ex); + throw ex; + } + + + + } + +} + + +public class ModuleReferenceContext + //extends Context +{ + ModuleContext referenceFrom, referenceTo; + + ModuleReferenceContext(ModuleContext from, ModuleContext to) { + //super(from.getProxy()); + + assert(from != null); + assert(to!=null); + + this.referenceFrom = from; + this.referenceTo = to; + } + def invokeMethod(String name, Object args) { + Object[] theArgs = (Object[]) args; + + Class c = Core.INSTANCE.getClassForName(name); + + // We can use provider("name") or provider() or provider(null) + String id = null; + if( theArgs.length > 0 ) + id = theArgs[0]; + + referenceTo.getElement(referenceFrom,c,(String)id) + } +} + +public class ModuleImportContext extends Context { + ModuleContext parent; + + ModuleImportContext( ModuleImports moduleImports,ModuleContext parent) { + super(moduleImports) + this.parent = parent; + } + + // Within a resource this is used to refer to another resource + def invokeMethod(String name, Object args) { + Object[] theArgs = (Object[])args; + + if( name == "module") { + // Refers to another module + return parent.getElement(this, Module.class, (String)theArgs[0]); + } + + Class c = Core.INSTANCE.getClassForName(name); + + if( c == null ) { + return getProxy().invokeMethod(name, args); + } + + String id = null; + if( ((Object[])args).length > 0 ) + id = ((Object[])args)[0]; + + return parent.getElement(this, c, id); + } +} + +@Slf4j +public class ModuleContext extends Context{ + SnowGlobeContext parent; + + List imports = []; + List resources = null; + List providers = []; + List dataSources = []; + + Set processedResources = []; + Set inProcessResources = []; + + Set dependencies = []; + + ModuleContext(Module proxy, SnowGlobeContext parent) { + super(proxy); + this.parent = parent; + } + + Context getContextFor(Object o) { + if( o == proxy ) + return this; + + Context result = resources.find { it.getContextFor(o) != null } + + + return result; + } + + Object invokeMethod(String name, Object args) { + Object result = proxy.invokeMethod(name,args); + if( result instanceof Resource ) { + getProxy().addResource(result); + +// ResourceContext child = new ResourceContext(result, this); +// resources << child; + + + // result.accept( child ) DO NOT ACCEPT THIS YET + } + else if( result instanceof DataSource ) { + DataSourceContext child = new DataSourceContext(result, this); + dataSources << child; + } + else if( result instanceof Provider ) { + ProviderContext child = new ProviderContext(result, this); + providers << child; + } else if( result instanceof ModuleImports ) { + ModuleImportContext child = new ModuleImportContext(result, this); + imports << child; + } + return result; + } + + public void build() { + imports.each() { + it.getProxy().accept(it); + } + + providers.each() { + it.getProxy().accept(it) + } + + dataSources.each() { + it.build() + } + + getResources().each() { + processResource(it); + } + } + + public List getResources() { + if( resources == null ) { + resources = []; + + getProxy().resources.each() { + ResourceContext child = new ResourceContext(it, this); + resources << child; + } + } + return resources; + } + + private void processResource(ResourceContext context) { + if( processedResources.contains(context)) + return; + + if( inProcessResources.contains(context) ) + throw new IllegalStateException("Circular dependency on ${context}"); + + inProcessResources.add(context); + + + context.build(); + + + + + inProcessResources.remove(context); + + processedResources.add(context); + } + + public Context findImport(Class klass, String id) { + for(ModuleImportContext mic : imports) { + Object ctx = mic.getProxy().references.find { + klass.isAssignableFrom( it.getProxy().getClass() ) && it.getProxy().id == id + } + if( ctx !=null ) + return (Context)ctx; + } + return null; + } + + public Object getElement(Context from, Class klass, String id) { + + log.debug("ModuleContext.getElement ${from} wants {$klass} of id ${id}"); + + if(klass == Module.class ) { + // Need to ask the parent for a module. + Object context = parent.getElement(this, klass, id); + + return new ModuleReferenceContext(this, context); + } + + ProviderContext provider = providers.find { klass.isAssignableFrom( it.getProxy().getClass() ) && it.getProxy().id == id } + if( provider != null ) { + dependencies << new Dependency(from, provider); + return provider; + } + + DataSourceContext dataSource = dataSources.find { klass.isAssignableFrom( it.getProxy().getClass() ) && it.getProxy().id == id } + if( dataSource != null ) { + dependencies << new Dependency(from, dataSource); + return dataSource; + } + + ResourceContext result = resources.find { klass.isAssignableFrom( it.getProxy().getClass() ) && it.getProxy().id == id } + if( result == null ) { + // Perhaps it was imported in the imports {} section + + Context ctx = findImport(klass, id); + if( ctx != null ) + return ctx; + } + + if( result == null ) + throw new IllegalStateException("Cannot find resource ${id} type ${klass}"); + + dependencies << new Dependency(from, result); + // + if( !processedResources.contains(result) ) + processResource(result); + + return result; + } + +} + +@Slf4j +public class StateContext { + + public Context parent; + public ModuleContext moduleContext; + + // Actual object + private State proxy; + + StateContext( + State state, Context context, ModuleContext moduleContext) { + assert context != null + this.parent = context; + this.moduleContext = moduleContext; + this.proxy = state; + } + + def invokeMethod(String name, Object args) { + Object[] theArgs = (Object[]) args; + + if (name == "module") { + // Refers to another module + return moduleContext.getElement(parent, Module.class, (String) theArgs[0]); + } + + Class c = Core.INSTANCE.getClassForName(name); + + if (c == null) { + return getProxy().invokeMethod(name, args); + } + + String id = null; + if (((Object[]) args).length > 0) + id = ((Object[]) args)[0]; + + ModuleContext mc = moduleContext; + mc.getElement(parent, c, id); + } + + + State getProxy() { + return proxy + } +// To get a value + def getProperty(String name) { + + log.trace " get ${name}? on ${this}" + + try { + State resource = getProxy(); + return resource.getProperty(name); + } catch (Exception ex) { + log.error "State for resource " + getProxy() + " does not have property " + name; + throw ex; + } + } + + void setProperty(String name, Object value) { + + log.debug " set ${name}=${value} on ${this}" + + try { + getProxy().setProperty(name, value); + } + catch(MissingPropertyException e1) { + log.warn( + "Error setting state for resource ${getProxy()} property ${name} with value ${value}"); + } + catch (Exception ex) { + log.error( + "Error setting state for resource ${getProxy()} property ${name} with value ${value}", ex); + throw ex; + } + + log.debug " done set ${name}=${value} on ${this}" + + } + + void build() { + if( getProxy() != null ) + getProxy().accept(this) + } + + + @Override + public String toString() { + return """\ +StateContext{ + parent=$parent +}""" + } +} + +@Slf4j +public class DataSourceContext extends Context { + + public ModuleContext moduleContext; + public StateContext stateContext; + + DataSourceContext(DataSource proxy, ModuleContext moduleContext) { + super(proxy) + this.moduleContext = moduleContext + + stateContext = new StateContext(proxy.state,this, moduleContext); + } + + def build() { + // getProxy().accept(this) + if (stateContext != null) + stateContext.build() + } + + def propertyMissing(String name) { + try { + DataSource resource = getProxy(); + + try { + return resource.getState().getProperty(name); + } catch(MissingPropertyException e) { + return resource.getProperty(name); + } + } catch(Exception ex) { + log.error "State for resource " + getProxy() + " does not have property " + name; + throw ex; + } + + + } + +} + +@Slf4j +public class ResourceContext extends Context { + + public ModuleContext moduleContext; + + public StateContext stateContext; + public StateContext savedStateContext; + + + ResourceContext(Resource proxy, ModuleContext moduleContext) { + super(proxy); + this.moduleContext = moduleContext + + stateContext = new StateContext(proxy.state,this, moduleContext); + if( proxy.savedState != null ) + savedStateContext = new StateContext(proxy.savedState,this, moduleContext); + + } + + Context getContextFor(Object o) { + if( o == proxy ) + return this; + + return null; + } + + def propertyMissing(String name) { + try { + Resource resource = getProxy(); + return resource.getState().getProperty(name); + } catch(Exception ex) { + log.error "State for resource " + getProxy() + " does not have property " + name; + throw ex; + } + } + + + def build() { + // getProxy().accept(this) + if( stateContext != null ) + stateContext.build() + if( savedStateContext != null ) + savedStateContext.build() + } +} \ No newline at end of file diff --git a/snowglobe-core/src/main/groovy/com/nirima/snowglobe/core/DSL.groovy b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/core/DSL.groovy new file mode 100644 index 0000000..5ba8d1b --- /dev/null +++ b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/core/DSL.groovy @@ -0,0 +1,423 @@ +package com.nirima.snowglobe.core + +import com.nirima.snowglobe.plan.PlanAction +import groovy.util.logging.Slf4j +import org.reflections.Reflections + +import java.lang.reflect.ParameterizedType + +@Slf4j +public class Core { + Map classesMap; + + public static Core INSTANCE = new Core(); + + + private Core() { + + } + public void init() { + + log.info "Initializing class map" + classesMap = [:]; + + Reflections reflections = new Reflections("com.nirima.snowglobe"); + + Set> annotated = reflections.getTypesAnnotatedWith(SGItem.class); + + annotated.each { + + it -> try { + String name = it.getAnnotation(SGItem.class).value(); + log.info "Seen ${name}" + + classesMap.put(name, it) + } catch(Exception ex) { + ex.printStackTrace(); + } + } + + + } + + public void register(Class c, String name) { + if( classesMap == null ) + init(); + classesMap[name] = c; + } + + public Class getClassForName(String name) { + if( classesMap == null ) + init(); + + Class aClass = classesMap.get(name); + if( aClass == null ) { + log.debug "I don't know what class ${name} is for"; + + } + + return aClass; + } + + public String getNameForClass(Class klass) { + if( classesMap == null ) + init(); + + String name = null; + + classesMap.each { + if( it.value == klass ) + name = it.key; + } + + return name; + } + + public void dump() { + classesMap.each { + println "Defined ${it.key} as ${it.value}" + } + } +} + +public class SnowGlobe { + + private transient Closure closure; + + public List modules = []; + + SnowGlobe(Closure closure) { + this.closure = closure + } + + public Module module(String id, Closure clos) { + Module newModule = new Module(this, id, clos); + modules << newModule; + return newModule; + } + + public void accept(Object context) { + + closure.delegate = context; + closure.resolveStrategy = Closure.DELEGATE_FIRST + + closure() + } + + Module getModule(String id) { + modules.find { it.id == id } + } + + def addModule(Module sgModule) { + modules << sgModule; + sgModule.parent = this; + } +} + + + + + + + +public class ModuleImports { + Module module; + transient Closure closure; + + List references = []; + + ModuleImports(Module module, Closure closure) { + this.module = module + this.closure = closure + } + + public Object using(Object c) { + assert(c != null) + references << c; + } + + public void accept(Object context) { + + closure.delegate = context; + closure.resolveStrategy = Closure.DELEGATE_FIRST + + closure() + } + + public String toString() { + return "ModuleImports for ${module}"; + } +} + +@Slf4j +public class Module { + protected SnowGlobe parent; + public String id; + private transient Closure closure; + + public List resources = []; + + public Module(SnowGlobe globe, String id, Closure clos) { + this.parent = globe; + this.id = id; + this.closure = clos; + } + + public void accept(Object context) { + log.debug "Accept in ${this}"; + closure.delegate = context; + closure.resolveStrategy = Closure.DELEGATE_FIRST + + closure() + } + + public ModuleImports imports(Closure c) { + return new ModuleImports(this, c); + } + + def methodMissing(String name, def args) { + Class klass = Core.INSTANCE.getClassForName(name); + + Object[] items = (Object)args; + + // Items is either closure or ID, Closure + // We want ID, Closure + Object[] items2 = new Object[3]; + if(items.length == 1) + items2[2] = items[0]; + else if(items.length == 2) { + items2[1] = items[0]; + items2[2] = items[1]; + } + + items2[0] = this; + if( klass == null ) { + // throw new ClassNotFoundException("Missing ${name}"); + return parent.invokeMethod(name, args); + } + return klass.newInstance(items2); + } + + + @Override + public String toString() { + return "module('${id}')"; + } + + def addResource(Resource sgResource) { + sgResource.module = this; + resources << sgResource; + } + + Resource getResource(Class klass, String id) { + resources.find { klass.isAssignableFrom( it.getClass() ) && it.id == id } + } +} + +@Slf4j +public class State { + public Closure closure; + + public State(Closure closure) { + this.closure = closure; + } + + public void accept(Object context) { + log.debug "State Accept in ${this} CTX= ${context} "; + + assert(context != null) + + closure.delegate = context; + closure.resolveStrategy = Closure.DELEGATE_FIRST + closure() + + Closure defaults = getDefaults(); + defaults.delegate = context; + defaults.resolveStrategy = Closure.DELEGATE_FIRST + defaults(); + + } + + public Closure getDefaults() { + return {} + } +} + +public class ResourceState extends State { + + Resource resource; + public Object provider; + + ResourceState(Resource parent, Closure closure) { + super(closure); + } + + @Override + void accept(Object context) { + super.accept(context) + if( provider == null ) + throw new IllegalStateException("Provider missing for ${this}") + } + + public Provider getProvider() { + if( provider == null ) + return null; + if( provider instanceof Context ) { + return provider.getProxy(); + } + return provider; + } +} + +@Slf4j +abstract public class Resource +{ + Module module; + public String id; + + + T state; + T savedState; + + Resource(Module module, String id, Closure closure) { + assert(module != null) + assert(id != null) + + this.module = module; + this.id = id + + //TODO: Not here? + state = newState(closure); + + } + + T newState(Closure closure) { + return ((Class) ((ParameterizedType) this.getClass(). + getGenericSuperclass()).getActualTypeArguments()[0]).newInstance(this,closure); + } + // TODO: I'd like the SGResource to not be aware of Context + public Provider getProvider() { + if( state.provider == null ) + return null; + if( state.provider instanceof Context ) { + return state.provider.getProxy(); + } + return state.provider; + } + + public PlanAction assess() { + // Maybe to another class in config + return null; + } + + public void accept(Object context) { + + log.debug "Accept in ${this}"; + + assert(context != null) + + // Maybe now there is nothing to do here? + + + //state.accept(context); + + /* + closure.delegate = context; + closure.resolveStrategy = Closure.DELEGATE_FIRST + closure() + + Closure defaults = getDefaults(); + defaults.delegate = context; + defaults.resolveStrategy = Closure.DELEGATE_FIRST + defaults(); + + if( provider == null ) + throw new IllegalStateException("Provider missing for ${this}") + */ + } + + // abstract Closure getDefaults(); + + @Override + public String toString() { + return "${getClass()}:${id}" + } +} + + +@Slf4j +public class Provider { + private Module module; + String id; + public transient Closure closure; + + public Object provider; + + Provider(Module module, String id, Closure closure) { + this.module = module; + this.id = id + this.closure = closure + } + + public void accept(Object context) { + log.debug "Accept in ${this}"; + closure.delegate = context; + closure.resolveStrategy = Closure.DELEGATE_FIRST + + closure() + } + + + @Override + public String toString() { + return "provider(${getClass()}):${id}" + } +} + +public class DataSource extends Provider +{ + + T state; + + DataSource(Module module, String id, Closure closure) { + super(module, id, closure) + + // TODO: Not here? + state = newState(closure); + } + + T newState(Closure closure) { + return ((Class) ((ParameterizedType) this.getClass(). + getGenericSuperclass()).getActualTypeArguments()[0]).newInstance(this,closure); + } +} + +public class DataSourceState extends State { + + public Object provider; + DataSource parent; + // Maybe this is a kind of resourceState? hmm. + + DataSourceState(DataSource parent, Closure closure) { + super(closure) + this.parent = parent; + } + + + + @Override + void accept(Object context) { + super.accept(context) + if( provider == null ) + throw new IllegalStateException("Provider missing for ${this}") + } + + public Provider getProvider() { + if( provider == null ) + return null; + if( provider instanceof Context ) { + return provider.getProxy(); + } + return provider; + } +} \ No newline at end of file diff --git a/snowglobe-core/src/main/groovy/com/nirima/snowglobe/core/Interfaces.groovy b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/core/Interfaces.groovy new file mode 100644 index 0000000..6a4fc01 --- /dev/null +++ b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/core/Interfaces.groovy @@ -0,0 +1,40 @@ +package com.nirima.snowglobe.core + +import java.lang.annotation.ElementType +import java.lang.annotation.Retention +import java.lang.annotation.Target + +import static java.lang.annotation.RetentionPolicy.RUNTIME + +@Target(value = [ElementType.TYPE] ) +@Retention(RUNTIME) +public @interface SGItem +{ + String value(); +} + +// Element with an ID +public interface IElement { + String getId(); +} + +public interface ISnowglobe extends IElement { + +} + +public interface IModule extends IElement { + +} + +public interface IModuleElement extends IElement { + +} + +public interface IResource extends IModuleElement { + T getState(); + T getSavedState(); +} + +public interface IProvider extends IModuleElement { + +} \ No newline at end of file diff --git a/snowglobe-core/src/main/groovy/com/nirima/snowglobe/core/System.groovy b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/core/System.groovy new file mode 100644 index 0000000..917e9c1 --- /dev/null +++ b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/core/System.groovy @@ -0,0 +1,85 @@ +package com.nirima.snowglobe.core +/** + * Created by magnayn on 04/09/2016. + */ +public class SnowGlobeSystem { + + Script dslScript; + + SnowGlobe globe; + + public SnowGlobeSystem() { + + } + + void parseScript(File dsl) { + dslScript = new GroovyShell().parse(dsl.text); + + + dslScript.metaClass = createEMC(dslScript.class, + { + ExpandoMetaClass emc -> + + emc.snowglobe = { + Closure cl -> + globe = new SnowGlobe(cl); + + + } + + emc.snowglobeData = { + Closure cl -> + globe = new SnowGlobe(cl); + } + + }) + + + } + + void parseScript(InputStream dsl) { + dslScript = new GroovyShell().parse(dsl.text); + + + dslScript.metaClass = createEMC(dslScript.class, + { + ExpandoMetaClass emc -> + + emc.snowglobe = { + Closure cl -> + globe = new SnowGlobe(cl); + + + } + + emc.snowglobeData = { + Closure cl -> + globe = new SnowGlobe(cl); + } + + }) + + + } + + SnowGlobe runScript() { + + dslScript.run(); + return globe; + } + + static ExpandoMetaClass createEMC(Class scriptClass, Closure cl) { + ExpandoMetaClass emc = new ExpandoMetaClass(scriptClass, false); + cl(emc) + emc.initialize() + return emc + } + + static SnowGlobeContext getState(SnowGlobe x) { + SnowGlobeContext sgc = new SnowGlobeContext(x); + + sgc.build(); + + return sgc; + } +} diff --git a/snowglobe-core/src/main/groovy/com/nirima/snowglobe/docker/Docker.groovy b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/docker/Docker.groovy new file mode 100644 index 0000000..4aeb173 --- /dev/null +++ b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/docker/Docker.groovy @@ -0,0 +1,386 @@ +package com.nirima.snowglobe.docker + +import com.github.dockerjava.api.DockerClient +import com.github.dockerjava.api.command.PullImageCmd +import com.github.dockerjava.api.model.AuthConfig +import com.github.dockerjava.api.model.Image +import com.github.dockerjava.core.DefaultDockerClientConfig +import com.github.dockerjava.core.DockerClientBuilder +import com.github.dockerjava.core.command.PullImageResultCallback +import com.google.common.base.Objects +import com.nirima.snowglobe.core.* +import com.nirima.snowglobe.plan.PlanAction + +/** + * Created by magnayn on 04/09/2016. + */ + +class DockerContainerPort { + int internal; + int external; + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("internal", internal) + .add("external", external) + .toString(); + } + + boolean equals(o) { + if (this.is(o)) { + return true + } + if (getClass() != o.class) { + return false + } + + DockerContainerPort that = (DockerContainerPort) o + + if (external != that.external) { + return false + } + if (internal != that.internal) { + return false + } + + return true + } + + int hashCode() { + int result + result = internal + result = 31 * result + external + return result + } +} + +class DockerContainerVolume { + String from_container; + + boolean equals(o) { + if (this.is(o)) { + return true + } + if (getClass() != o.class) { + return false + } + + DockerContainerVolume that = (DockerContainerVolume) o + + if (from_container != that.from_container) { + return false + } + + return true + } + + int hashCode() { + return (from_container != null ? from_container.hashCode() : 0) + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("from_container", from_container) + .toString(); + } +} + +public class DockerContainerState extends ResourceState implements Comparable { + public String name; + public String image; + public List links; + + public List ports = []; + public List volumes = []; + public List command; + public List env; + + public String restart; + + public boolean publish_all_ports; + public boolean must_run = true; + + public String id; + + DockerContainerState(Resource parent, Closure closure) { + super(parent, closure); + } + + public void ports(Closure c) { + DockerContainerPort p = new DockerContainerPort(); + c.delegate = p; + c.resolveStrategy = Closure.DELEGATE_FIRST + + c() + + ports<< p; + } + + public void volumes(Closure c) { + DockerContainerVolume p = new DockerContainerVolume(); + c.delegate = p; + c.resolveStrategy = Closure.DELEGATE_FIRST + + c() + + volumes << p; + } + + @Override + void accept(Object context) { + + ports = [] + volumes = [] + + super.accept(context) + } + + Closure getDefaults() { + Closure defaults = { + if( provider == null ) + provider = docker_provider(null); + } + return defaults; + } + + @Override + int compareTo(DockerContainerState o) { + + if (this.is(o)) { + return 0 + } + if (getClass() != o.class) { + return -1 + } + + DockerContainerState that = (DockerContainerState) o + + if (must_run != that.must_run) { + return -1 + } + if (publish_all_ports != that.publish_all_ports) { + return -1 + } + if (command != that.command) { + return -1 + } + if (env != that.env) { + return -1 + } + + // Ignore Id as this is the actual container. + + if (image != that.image) { + return -1 + } + if (links != that.links) { + return -1 + } + if (name != that.name) { + return -1 + } + if (ports != that.ports) { + return -1 + } + if (restart != that.restart) { + return -1 + } + if (volumes != that.volumes) { + return -1 + } + + return 0 + } + + int hashCode() { + int result + result = (name != null ? name.hashCode() : 0) + result = 31 * result + (image != null ? image.hashCode() : 0) + result = 31 * result + (links != null ? links.hashCode() : 0) + result = 31 * result + (ports != null ? ports.hashCode() : 0) + result = 31 * result + (volumes != null ? volumes.hashCode() : 0) + result = 31 * result + (command != null ? command.hashCode() : 0) + result = 31 * result + (env != null ? env.hashCode() : 0) + result = 31 * result + (restart != null ? restart.hashCode() : 0) + result = 31 * result + (publish_all_ports ? 1 : 0) + result = 31 * result + (must_run ? 1 : 0) + result = 31 * result + (id != null ? id.hashCode() : 0) + return result + } +} + +@SGItem("docker_container") +public class DockerContainer extends Resource { + + DockerContainer(Module module, String id, + Closure closure) { + super(module, id, closure) + } + + + public PlanAction assess() { + return new DockerContainerAction(this); + } + +} + + + +public class DockerImageState extends ResourceState { + String name; + + String latest; + + String imageId; + + boolean keep_locally; + + public Object registry_provider; + + DockerImageState(Resource parent, Closure closure) { + super(parent, closure); + } + + Closure getDefaults() { + Closure defaults = { + if( provider == null ) + provider = docker_provider(null); + } + return defaults; + } +} + +@SGItem("docker_image") +public class DockerImage extends Resource { + + + DockerImage(Module module, String id, + Closure closure) { + super(module, id, closure) + } + + + Closure getDefaults() { + return { + if(provider == null) { + provider = docker_provider(null); + } + if( name.contains("/") && registry_provider == null ) { + registry_provider = docker_registry(getRegistry()) + } + } + } + + public String getRegistry() { + if( !name.contains("/") ) + return null; + return name.substring(0,name.indexOf("/")); + } + + public PlanAction assess() { + return new DockerImageAction(this); + } + +} + +@SGItem("docker_registry_image") +public class DockerRegistryImage extends DataSource { + + DockerRegistryImage(Module module, String id, Closure closure) { + super(module, id, closure) + } + + public String getSha256_digest() { + + DockerProvider dp = state.getProvider(); + DockerClient dc = dp.getDockerClient(); + PullImageCmd cmd = dc.pullImageCmd(state.name); + + + // TODO: Figure this out + if( state.name.startsWith("registry")) { + AuthConfig ac = new AuthConfig(); + ac.withUsername("admin"); + ac.withPassword("admin123"); + + cmd.withAuthConfig(ac); + } + + PullImageResultCallback pullImageResultCallback = cmd. + exec(PullImageResultCallback.newInstance()); + + pullImageResultCallback.awaitSuccess(); + + List imgs = dc.listImagesCmd().withImageNameFilter(state.name).exec(); + if( imgs.size() != 1 ) + { + throw new IllegalStateException("Image ${state.name} is ambiguous or empty"); + } + + return imgs.get(0).id; + + + } + + +} + +public class DockerRegistryImageState extends DataSourceState { + String name; + + DockerRegistryImageState(DataSource parent, Closure closure) { + super(parent, closure) + } + Closure getDefaults() { + Closure defaults = { + if( provider == null ) + provider = docker_provider(null); + } + return defaults; + } +// public String getSha256_digest() { +// // Do a pull +// } +} + +@SGItem("docker_provider") +public class DockerProvider extends Provider { + public String host; + + DockerProvider(Module module, String id, Closure closure) { + super(module, id, closure) + } + + public DockerClient getDockerClient() { + DefaultDockerClientConfig defaultDockerClientConfig = DefaultDockerClientConfig.createDefaultConfigBuilder() + .withDockerHost(host) + //.withDockerTlsVerify(true) + //.withDockerCertPath("/Users/magnayn/.sdc/docker/jenkins") + .build(); + + DockerClient dockerClient = DockerClientBuilder.getInstance(defaultDockerClientConfig).build(); + + return dockerClient; + } +} + + +@SGItem("docker_registry") +public class DockerRegistry extends Provider { + public String username; + public String password; + + DockerRegistry(Module module, String id, Closure closure) { + super(module, id, closure) + } + + public AuthConfig getAuthConfig() { + AuthConfig ac = new AuthConfig(); + ac.withUsername(username); + ac.withPassword(password); + return ac; + } +} + diff --git a/snowglobe-core/src/main/groovy/com/nirima/snowglobe/docker/DockerActions.groovy b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/docker/DockerActions.groovy new file mode 100644 index 0000000..39816dd --- /dev/null +++ b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/docker/DockerActions.groovy @@ -0,0 +1,178 @@ +package com.nirima.snowglobe.docker + +import com.github.dockerjava.api.DockerClient +import com.github.dockerjava.api.command.CreateContainerCmd +import com.github.dockerjava.api.command.CreateContainerResponse +import com.github.dockerjava.api.exception.NotModifiedException +import com.github.dockerjava.api.model.* +import com.google.common.base.Strings +import com.nirima.snowglobe.plan.PlanActionBase +import groovy.util.logging.Slf4j +import org.slf4j.Logger + +/** + * Created by magnayn on 05/09/2016. + */ + + +public class DockerImageAction extends PlanActionBase { + + DockerImageAction(DockerImage pair) { + super(pair); + } + + public DockerImageState create(DockerImageState state) { + return update(state); // Create == Update + } + + // Functions + public DockerImageState read(DockerImageState state) + { + + DockerProvider dp = state.getProvider(); + DockerClient client = dp.getDockerClient(); + + List images = client.listImagesCmd().withImageNameFilter(state.name).exec(); + if( images.size() == 0 ) + state.latest = null; + state.latest = images.get(0).id; + return state; + } + + + public DockerImageState update(DockerImageState state) { +// DockerProvider dp = state.getProvider(); +// DockerClient dc = dp.getDockerClient(); +// PullImageCmd cmd = dc.pullImageCmd(state.name); +// +// if( state.name.startsWith("registry")) { +// AuthConfig ac = new AuthConfig(); +// ac.withUsername("admin"); +// ac.withPassword("admin123"); +// +// cmd.withAuthConfig(ac); +// } +// +// PullImageResultCallback pullImageResultCallback = cmd. +// exec(PullImageResultCallback.newInstance()); +// +// pullImageResultCallback.awaitSuccess(); +// +// List imgs = dc.listImagesCmd().withImageNameFilter(state.name).exec(); +// if( imgs.size() != 1 ) +// { +// throw new IllegalStateException("Image ${state.name} is ambiguous or empty"); +// } +// +// state.imageId = imgs.get(0).id; +// return state; + } + + +} + + + +@Slf4j +class DockerContainerAction extends PlanActionBase { + + DockerContainerAction(DockerContainer resource) { + super(resource) + } + + @Override + DockerContainerState create(DockerContainerState desiredState) { + DockerProvider dp = desiredState.getProvider(); + DockerClient client = dp.getDockerClient(); + + CreateContainerCmd create = client.createContainerCmd(desiredState.image); + + create.withPublishAllPorts(desiredState.publish_all_ports); + + Ports portBindings = new Ports(); + desiredState.ports.each { + portBindings.bind(ExposedPort.tcp(it.internal), Ports.Binding.bindPort(it.external)); + + } + create.withPortBindings(portBindings); + if (desiredState.env != null) + create.withEnv(desiredState.env); + + if (desiredState.command != null) + create.withCmd(desiredState.command); + + if( !Strings.isNullOrEmpty(desiredState.restart) ) { + RestartPolicy restartPolicy = RestartPolicy.parse(desiredState.restart); + create.withRestartPolicy(restartPolicy); + } + + if (desiredState.name != null) { + create.withName(desiredState.name); + } + + if (desiredState.links != null) { + + def links = desiredState.links.collect { + Link.parse(it) + } + + create.withLinks(links); + } + + if (desiredState.volumes != null) { + + def volumes = desiredState.volumes.collect { + VolumesFrom.parse(it.from_container) + } + + create.withVolumesFrom(volumes) + } + + CreateContainerResponse response = create.exec(); + + if (desiredState.must_run) { + client.startContainerCmd(response.id).exec(); + } + + desiredState.id = response.id; + return desiredState; + + } + + @Override + DockerContainerState update(DockerContainerState old, DockerContainerState newState) { + + + if( old.compareTo(newState) != 0) { + log.info "Docker Container requires re-creation" + // Delete and recreate + delete(old); + return create(newState); + } + + // Old state will have stuff like IDs in it, so return that if it's deemed + // to be the same. + return old; + + } + + @Override + DockerContainerState delete(DockerContainerState dockerContainer) { + DockerProvider dp = dockerContainer.getProvider(); + DockerClient client = dp.getDockerClient(); + + try { + client.stopContainerCmd(dockerContainer.id).exec(); + } catch(NotModifiedException ex ){ + // Don't mind actually if it's not running + } + client.removeContainerCmd(dockerContainer.id).exec(); + + dockerContainer.id = null; + return null; + } + + + + +} \ No newline at end of file diff --git a/snowglobe-core/src/main/groovy/com/nirima/snowglobe/graph/SGNode.groovy b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/graph/SGNode.groovy new file mode 100644 index 0000000..5262f39 --- /dev/null +++ b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/graph/SGNode.groovy @@ -0,0 +1,340 @@ +package com.nirima.snowglobe.graph + +import com.google.common.base.Objects +import com.nirima.snowglobe.core.* +import groovy.util.logging.Slf4j + +/** + * Created by magnayn on 05/09/2016. + */ +class SGNode { + + SGNode parentNode; + + //Context represents; + + final Object item; + + List dependencies = [] + List children = [] + + protected SGNode() {} + + protected SGNode(SGNode parentNode, Object item) { + this.parentNode = parentNode; + this.item = item; + + if( parentNode != null ) + parentNode.children << this + } + + public accept(Object visitor) { + visitor.visitNode(this); + children.each { it.accept(visitor) } + } + + public String getId() { + String id; + try { + id = item.getClass().simpleName; + id += ":" + item.id; + } catch(Exception ex) { + + } + + if( parentNode != null ) + return "${parentNode.id}.${id}" + else + return id; + } + + public String getLabel() { + try { + + return "${item.getClass().getSimpleName()}:${item.id}"; + } + catch(Exception e) { + return item.toString(); + } + } + + boolean equals(o) { + if (this.is(o)) { + return true + } + if (getClass() != o.class) { + return false + } + + SGNode node = (SGNode) o + + if (getId() != node.getId()) { + return false + } + + return true + } + + int hashCode() { + return (item != null ? item.hashCode() : 0) + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("item", item) + .toString(); + } +} + +@Slf4j +class Graph { + SGNode rootNode; + + // Should be transient as can re-calc + private Map graphNodes = [:]; + private Map graphNodesById = [:]; + + + public static Graph Empty() { + new Graph(new SGNode()) + } + + public SGNode newNode(SGNode parentNode, Context represents) { + return newNodeObject(parentNode, represents.getProxy()); + } + + public SGNode newNodeObject(SGNode parentNode, Object object) { + //this.represents = represents + + SGNode node = new SGNode(parentNode, object); + + graphNodes[ object ] = node; + graphNodesById[ idForNode(node) ] = node; + + return node; + } + + public SGNode getNodeForItem(Object item) { + SGNode node = graphNodes[item]; + if( node == null ) + throw new IllegalStateException("No node for ${item}"); + return node; + } + + Graph(SGNode rootNode) { + this.rootNode = rootNode + } + + public void removeNode(SGNode node) { + assert node != null + + String id = idForNode(node); + + if( node.parentNode != null ) { + node.parentNode.children.remove(node); + } + + // Remove the children too??? + if( node.children.size() > 0 ) { + log.warn "CHILDREN ARE HERE. NOT SURE." + } + + node.children.each { + node.parentNode = null; + } + + graphNodes[node.item] = null; + graphNodesById[id] = null; + } + + + + public void replaceNodeWithThisNode(SGNode node) { + assert node != null + + SGNode current = graphNodesById[ idForNode(node) ]; + if( current != null ) + removeNode(current); + + SGNode parent = graphNodesById[ idForNode(node.parentNode) ]; + SGNode inserted = newNode(parent, node.item); + + // place in the node + node.children.each { + inserted + } + + + } + + public accept(Object visitor) { + rootNode.accept(visitor); + } + + List getNodes() { + List items = []; + items.addAll(graphNodes.values()); + return items; + } + + public static String idForNode(SGNode item) { + + String id = ""; + try { + id = item.getClass().simpleName; + id += ":" + item.id; + } catch(Exception ex) { + + } + + if( item.parentNode != null ) + return "${idForNode(item.parentNode)}.${id}" + else + return id; + } +} + +class GViz { + String nodes = ""; + public void visitNode(SGNode n) { + + if( n.item instanceof SnowGlobe ) { + return; + } + + if( n.item instanceof Module ) { + + if( nodes.length() > 0 ) + nodes += " }\n"; + + nodes = nodes + "\nsubgraph cluster_${n.item.id} {\n"; + nodes += " label = \"${n.id}\"\n"; + } + + nodes = nodes + "\"[root] ${n.id}\" [label = \"${n.getLabel()}\"" + if( n.item instanceof Module ) { + nodes += ", shape=box" + } + if( n.item instanceof Provider ) { + nodes += ", style=filled, fillcolor=blue" + } + if( n.item instanceof Resource ) { + Resource res = n.item; + + if( res.savedState == null ) // Create + nodes += ", style=filled, fillcolor=green" + else if( res.state == null ) // Delete + nodes += ", style=filled, fillcolor=red" + } + nodes += "]\n"; + + } + + public String toString() { + return nodes + "}\n"; + } +} + +public class DependencyList_DFS { + List nodes = []; + + public void insert(SGNode node) { + if( nodes.contains(node) ) + return; // already seen + + node.dependencies.each { + insert(it) + } + + // Try the children too + node.children.each { + insert(it) + } + + nodes << node; + } + +} + +public class GraphBuilder { + + public String graphViz(Graph graph) { + GViz viz = new GViz(); + graph.accept(viz); + + + String nodes = ""; + + + + graph.getNodes().each { n -> + n.dependencies.each { n2 -> + nodes = nodes + "\"[root] ${n.getId()}\" -> \"[root] ${n2.getId()}\"\n"; + } + } + + String g = """ +digraph snowglobe { + + ${viz} + + ${nodes} + +} +""" + return g; + } + + public Graph build(SnowGlobeContext snowGlobeContext) { + + // Create all the nodes + SGNode root = new SGNode(null, snowGlobeContext.getProxy()); + + Graph graph = new Graph(root); + + snowGlobeContext.getModules().each { + SGNode moduleNode = graph.newNode(root, it); + + root.dependencies << moduleNode; + + it.providers.each { p -> + graph.newNode(moduleNode, p); + } + + it.dataSources.each { p -> + graph.newNode(moduleNode, p); + } + + it.resources.each { r -> + graph.newNode(moduleNode, r); + } + + } + + + // Create all the edges + + processDependencies(graph, snowGlobeContext.dependencies); + + snowGlobeContext.getModules().each { + + module -> + processDependencies(graph, module.dependencies); + } + + + return graph; + } + + private void processDependencies(Graph g, Collection deps ) { + deps.each { + dep -> + SGNode source = g.getNodeForItem(dep.from.getProxy()); + SGNode target = g.getNodeForItem(dep.to.getProxy()); + + source.dependencies.add(target); + + } + } +} \ No newline at end of file diff --git a/snowglobe-core/src/main/groovy/com/nirima/snowglobe/plan/PlanBuilder.groovy b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/plan/PlanBuilder.groovy new file mode 100644 index 0000000..99d5f65 --- /dev/null +++ b/snowglobe-core/src/main/groovy/com/nirima/snowglobe/plan/PlanBuilder.groovy @@ -0,0 +1,250 @@ +package com.nirima.snowglobe.plan + +import com.nirima.snowglobe.core.Resource +import com.nirima.snowglobe.core.ResourceContext +import com.nirima.snowglobe.core.ResourceState +import com.nirima.snowglobe.core.SnowGlobeContext +import com.nirima.snowglobe.graph.DependencyList_DFS +import com.nirima.snowglobe.graph.Graph +import com.nirima.snowglobe.graph.GraphBuilder +import com.nirima.snowglobe.graph.SGNode + +/** + * Created by magnayn on 05/09/2016. + */ + +enum PlanActionActivity { + Create, + Update, + Delete, + Recreate +} + + +interface PlanAction { + + //void execute(Plan plan, String phase); + + T getResource(); + + + public V create(V t); + public V read(V t); + public V update(V old,V newState); + public V delete(V t); + +} + +abstract class PlanActionBase implements PlanAction { + + private final T resource; + + PlanActionBase(T resource) { + this.resource = resource + } + + T getResource() { + resource; + } + + public V create(V t) { return t; } + public V read(V t) { return t;} + public V update(V old, V newState) { return newState; } + public V delete(V t) { return null;} + + +} + + +public class Plan { + List> actions = []; + PlanType type; + + private final SnowGlobeContext sgContext; + + def phases = ["validate", + "read", + "create", + "update","delete"] + + Plan(SnowGlobeContext sgContext, PlanType type) { + this.sgContext = sgContext + this.type = type + } + + public void execute() { + if (type == PlanType.Apply) { + apply(); + } else { + destroy(); + } + } + + private void apply() { + actions.each() { action -> + + Resource resource = action.getResource(); + + if( resource.savedState != null ) { + action.read( resource.savedState ); + } + } + + actions.each() { action -> + + Resource resource = action.getResource(); + + if( resource.state != null ) { + // Reevaluate as deps may now be ready + processResourceState(resource); + + if( resource.savedState == null ) { + + // nothing saved. these are creates + resource.savedState = action.create(resource.state); + } else { + resource.savedState = action.update(resource.savedState, resource.state); + } + + } else { + // No state, these are deletes. + resource.savedState = action.delete(resource.savedState); + } + + + } + + + } + + private void destroy() { + actions.each() { action -> + + Resource resource = action.getResource(); + + if( resource.savedState != null ) { + action.read( resource.savedState ); + } + } + + // Deletes happen in reverse order + actions.reverse().each() { action -> + + Resource resource = action.getResource(); + + if( resource.savedState != null ) { + // Reevaluate as deps may now be ready + processResourceState(resource); + resource.savedState = action.delete(resource.savedState); + + } else { + + } + } + + + } + + private void processResourceState(Resource resource) { + // Between first eval and now, a value may have changed (or become resolvable). + + // Need to get the ResourceContext for this resource + ResourceContext ctx = sgContext.getContextFor(resource) + + ctx.stateContext.build(); + + + } + +} + +public enum NodePairStatus { + Create,Update,Delete +} + +public class NodePair +{ + final SGNode oldNode, newNode; + + NodePair(SGNode oldNode, SGNode newNode) { + assert( oldNode != null || newNode != null) + this.oldNode = oldNode + this.newNode = newNode + } + + NodePairStatus getStatus() { + if( oldNode == null ) { + return NodePairStatus.Create; + } + if( newNode == null ) + return NodePairStatus.Delete; + + return NodePairStatus.Update; + } + + Object getElement() { + if( newNode != null ) + return newNode.item; + return oldNode.item; + } + + boolean equals(o) { + if (this.is(o)) { + return true + } + if (getClass() != o.class) { + return false + } + + NodePair nodePair = (NodePair) o + + if (newNode != nodePair.newNode) { + return false + } + if (oldNode != nodePair.oldNode) { + return false + } + + return true + } + + int hashCode() { + int result + result = (oldNode != null ? oldNode.hashCode() : 0) + result = 31 * result + (newNode != null ? newNode.hashCode() : 0) + return result + } +} + +public class PlanBuilder { + + Plan plan; + DependencyList_DFS list = new DependencyList_DFS(); + + public Plan buildPlan(SnowGlobeContext sgContext, PlanType type) { + assert sgContext != null + plan = new Plan(sgContext,type); + + Graph stateGraph = new GraphBuilder().build(sgContext); + + list.insert(stateGraph.getRootNode()); + + // Create, Update (or, same) + list.nodes.each { node -> + Object o = node.item + if( o instanceof Resource ) { + + PlanAction action = o.assess() + if( action != null ) + plan.actions << action; + } + } + + + + return plan; + } + + +} + diff --git a/snowglobe-core/src/main/java/com/nirima/snowglobe/plan/PlanType.java b/snowglobe-core/src/main/java/com/nirima/snowglobe/plan/PlanType.java new file mode 100644 index 0000000..021b8aa --- /dev/null +++ b/snowglobe-core/src/main/java/com/nirima/snowglobe/plan/PlanType.java @@ -0,0 +1,9 @@ +package com.nirima.snowglobe.plan; + +/** + * Created by magnayn on 09/09/2016. + */ +public enum PlanType { + Apply, + Destroy +} diff --git a/snowglobe-core/src/test/groovy/com/nirima/snowglobe/test/TestGroovy.groovy b/snowglobe-core/src/test/groovy/com/nirima/snowglobe/test/TestGroovy.groovy new file mode 100644 index 0000000..b1c13b3 --- /dev/null +++ b/snowglobe-core/src/test/groovy/com/nirima/snowglobe/test/TestGroovy.groovy @@ -0,0 +1,17 @@ +package com.nirima.snowglobe.test + +import com.nirima.snowglobe.core.Module +import com.nirima.snowglobe.core.SGItem +import com.nirima.snowglobe.docker.DockerProvider + +/** + * Created by magnayn on 13/09/2016. + */ +@SGItem("test_provider") +public class TestDockerProvider extends DockerProvider { + + TestDockerProvider(Module module, String id, + Closure closure) { + super(module, id, closure) + } +} \ No newline at end of file diff --git a/snowglobe-core/src/test/resources/com/nirima/simple-docker.sg b/snowglobe-core/src/test/resources/com/nirima/simple-docker.sg new file mode 100644 index 0000000..aeffb1c --- /dev/null +++ b/snowglobe-core/src/test/resources/com/nirima/simple-docker.sg @@ -0,0 +1,45 @@ +package com.nirima + +snowglobe +{ + module("base") { + docker_provider { + //host = "tcp://localhost:2376" + host = "unix:///var/run/docker.sock" + } + + docker_container("consul") { + image = "${docker_image("consul").name}" + name = "consul" + restart = "always" + + ports { + internal = 8300 + external = 8300 + } + + ports { + internal = 8301 + external = 8301 + } + + ports { + internal = 8302 + external = 8302 + } + + ports { + internal = 8500 + external = 8500 + } + + command = ["agent", "-dev", "-client", "0.0.0.0"] + } + + docker_image("consul") + { + name = "consul:v0.6.4" + } + + } +} \ No newline at end of file diff --git a/snowglobe-core/src/test/resources/logback.groovy b/snowglobe-core/src/test/resources/logback.groovy new file mode 100644 index 0000000..49eabd4 --- /dev/null +++ b/snowglobe-core/src/test/resources/logback.groovy @@ -0,0 +1,36 @@ +package com.nirima +// LOGBACK LOGGING CONFIGURATION + +import ch.qos.logback.classic.AsyncAppender +import ch.qos.logback.classic.encoder.PatternLayoutEncoder +import ch.qos.logback.core.ConsoleAppender +import ch.qos.logback.core.rolling.FixedWindowRollingPolicy +import ch.qos.logback.core.rolling.RollingFileAppender +import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy +import ch.qos.logback.classic.encoder.PatternLayoutEncoder + +import static ch.qos.logback.classic.Level.* + + + +def consolePattern = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" + +scan("30 seconds") + + + +appender("STDOUT", ConsoleAppender) { + encoder(PatternLayoutEncoder) { + pattern = consolePattern; + } +} + + +//logger("org.hibernate.cache", TRACE, ["STDOUT"]) +//logger("org.hibernate.impl", DEBUG, ["STDOUT"]) + +//logger("jdbc.sqlonly", INFO, ["STDOUT"], false) +//logger("log4jdbc.debug", DEBUG, ["STDOUT"], false) +//logger("jdbc.audit", DEBUG, ["STDOUT"], false) + +root(INFO, ["STDOUT", "logfile"]) diff --git a/snowglobe-exe/pom.xml b/snowglobe-exe/pom.xml new file mode 100644 index 0000000..8e9b1a0 --- /dev/null +++ b/snowglobe-exe/pom.xml @@ -0,0 +1,86 @@ + + + 4.0.0 + com.nirima.snowglobe + snowglobe-exe + + + com.nirima + snowglobe + 0.1-SNAPSHOT + + + + + + + + + maven-assembly-plugin + + + + com.nirima.snowglobe.SnowGlobeApp + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + + + + + + com.nirima.snowglobe + snowglobe-core + ${project.version} + + + + args4j + args4j + 2.0.31 + + + ch.qos.logback + logback-core + 1.1.7 + + + ch.qos.logback + logback-classic + 1.1.7 + + + + diff --git a/snowglobe-exe/src/main/java/com/nirima/snowglobe/Action.java b/snowglobe-exe/src/main/java/com/nirima/snowglobe/Action.java new file mode 100644 index 0000000..f120ab5 --- /dev/null +++ b/snowglobe-exe/src/main/java/com/nirima/snowglobe/Action.java @@ -0,0 +1,12 @@ +package com.nirima.snowglobe; + +/** + * Created by magnayn on 10/09/2016. + */ +public enum Action { + apply, + graph, + destroy; + + public static final Action[] ACTIONS = new Action[] {apply, graph, destroy}; +} diff --git a/snowglobe-exe/src/main/java/com/nirima/snowglobe/SnowGlobeApp.java b/snowglobe-exe/src/main/java/com/nirima/snowglobe/SnowGlobeApp.java new file mode 100644 index 0000000..f03d8ce --- /dev/null +++ b/snowglobe-exe/src/main/java/com/nirima/snowglobe/SnowGlobeApp.java @@ -0,0 +1,112 @@ +package com.nirima.snowglobe; + +import com.nirima.snowglobe.core.Core; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by magnayn on 06/09/2016. + */ +public class SnowGlobeApp { + + @Argument + Action action; + + + + public static void main(String[] args) throws IOException { + + + new SnowGlobeApp().doMain(args); + } + + public void doMain(String[] args) throws IOException { + CmdLineParser parser = new CmdLineParser(this); + + // if you have a wider console, you could increase the value; + // here 80 is also the default + parser.setUsageWidth(80); + + try { + // parse the arguments. + parser.parseArgument(args); + + // you can parse additional arguments if you want. + // parser.parseArgument("more","args"); + + // after parsing arguments, you should check + // if enough arguments are given. + // if( arguments.isEmpty() ) + // throw new CmdLineException(parser, "No argument is given"); + + } catch( CmdLineException e ) { + // if there's a problem in the command line, + // you'll get this exception. this will report + // an error message. + System.err.println(e.getMessage()); + System.err.println("snowglobe [options...] arguments..."); + // print the list of available options + parser.printUsage(System.err); + System.err.println(); + + + return; + } + + if (action == null) { + System.out.println("\nSnowglobe actions are:"); + for (Action a: Action.ACTIONS) { + System.out.println(String.format(" - %s", a.toString())); + } + return; + } + + // access non-option arguments + System.err.println("other arguments are:"); + + // Initialize + File f1 = new File("snowglobe.sg"); + File f2 = new File("snowglobe.sgstate"); + + SGExec exec = new SGExec(f1,f2); + + + switch(action) { + case graph: + exec.graph(System.out); + break; + case apply: + { + try { + exec.apply(); + } finally { + try(FileOutputStream fos = new FileOutputStream(f2)) + { + fos.write(exec.save().getBytes()); + } + } + } + break; + case destroy: + { + try { + exec.destroy(); + } finally { + try(FileOutputStream fos = new FileOutputStream(f2)) + { + fos.write(exec.save().getBytes()); + } + } + } + break; + } + } +} diff --git a/snowglobe-exe/src/main/resources/logback.groovy b/snowglobe-exe/src/main/resources/logback.groovy new file mode 100644 index 0000000..f394a07 --- /dev/null +++ b/snowglobe-exe/src/main/resources/logback.groovy @@ -0,0 +1,31 @@ +package com.nirima +// LOGBACK LOGGING CONFIGURATION +import ch.qos.logback.classic.encoder.PatternLayoutEncoder +import ch.qos.logback.core.ConsoleAppender + +import static ch.qos.logback.classic.Level.WARN + +def consolePattern = "%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n" + +scan("30 seconds") + + + +appender("STDERR", ConsoleAppender) { + encoder(PatternLayoutEncoder) { + pattern = consolePattern; + } + target = "System.err" +} + + +//logger("org.hibernate.cache", TRACE, ["STDOUT"]) +//logger("org.hibernate.impl", DEBUG, ["STDOUT"]) + +//logger("jdbc.sqlonly", INFO, ["STDOUT"], false) +//logger("log4jdbc.debug", DEBUG, ["STDOUT"], false) +//logger("jdbc.audit", DEBUG, ["STDOUT"], false) + +logger("com.nirima", INFO, ["STDERR"]) + +root(DEBUG) diff --git a/snowglobe-jenkins-plugin/.gitignore b/snowglobe-jenkins-plugin/.gitignore new file mode 100644 index 0000000..b8f99f5 --- /dev/null +++ b/snowglobe-jenkins-plugin/.gitignore @@ -0,0 +1 @@ +work diff --git a/snowglobe-jenkins-plugin/pom.xml b/snowglobe-jenkins-plugin/pom.xml new file mode 100644 index 0000000..b7b3729 --- /dev/null +++ b/snowglobe-jenkins-plugin/pom.xml @@ -0,0 +1,297 @@ + + + 4.0.0 + + + org.jenkins-ci.plugins + plugin + 2.14 + + + + com.nirima.snowglobe + snowglobe-jenkins-plugin + 0.1-SNAPSHOT + hpi + + Snowglobe plugin + SnowGlobe : Infrastructure As Code + https://wiki.jenkins-ci.org/display/JENKINS/SnowGlobe+Plugin + + + scm:git:git://github.com/nirima/SnowGlobe.git + scm:git:git@github.com:nirima/SnowGlobe.git + http://github.com/nirima/SnowGlobe + + + + + 0.1-SNAPSHOT + + UTF-8 + 3.5.1 + 2.5.1 + 3.0.4 + 0.16.2 + 1.119 + 2.7 + 1.207 + 18.0 + 3.1.0 + ${project.parent.version} + true + false + 1.8 + 1.8 + 8 + + + + + + magnayn + Nigel Magnay + + + + + + + org.jenkins-ci.main + jenkins-core + 2.7 + compile + + + org.jenkins-ci.main + jenkins-war + 2.7 + war-for-test + compile + + + org.jenkins-ci.main + jenkins-war + 2.7 + war + test + + + org.jenkins-ci.main + jenkins-test-harness + 2.14 + compile + + + com.nirima.snowglobe + snowglobe-shaded + ${project.version} + compile + + + com.nirima + docker-plugin + ${docker-plugin.version} + + + org.jenkins-ci.plugins.workflow + workflow-step-api + 2.3 + + + org.jenkins-ci.plugins + script-security + 1.5 + + + org.jenkins-ci.plugins + ssh-slaves + 1.6 + compile + + + org.jenkins-ci.plugins + token-macro + 1.7 + compile + true + + + org.jenkins-ci.plugins + durable-task + 1.3 + compile + + + org.hamcrest + hamcrest-all + 1.3 + test + + + org.mockito + mockito-core + 1.10.19 + test + + + org.jenkins-ci.modules + instance-identity + 1.4 + compile + + + com.google.code.findbugs + annotations + 3.0.0 + provided + true + + + net.jcip + jcip-annotations + 1.0 + provided + true + + + org.codehaus.mojo + animal-sniffer-annotations + 1.14 + provided + true + + + javax.servlet + javax.servlet-api + 3.1.0 + test + + + javax.servlet + servlet-api + 2.4 + provided + + + org.jenkins-ci + test-annotations + 1.2 + test + + + junit + junit + 4.12 + test + + + org.slf4j + slf4j-api + 1.7.7 + compile + true + + + org.slf4j + log4j-over-slf4j + 1.7.7 + test + + + org.slf4j + jcl-over-slf4j + 1.7.7 + test + + + org.slf4j + slf4j-jdk14 + 1.7.7 + test + + + + + + + maven-compiler-plugin + org.apache.maven.plugins + 3.5.1 + + ${jdk.source} + ${jdk.target} + ${jdk.debug} + ${jdk.optimize} + + + + maven-javadoc-plugin + 2.10.1 + + false + + + + org.jenkins-ci.tools + maven-hpi-plugin + ${hpi.plugin.version} + + + com.google.guava + guava + ${guava.version} + + + + org.kohsuke.stapler + stapler + ${stapler.version} + + + + + + + + org.apache.httpcomponents:*,com.github.docker-java*,com.google.guava* + + + + com.google.common + + + com.google.common. + + + + + org.apache.maven.plugins + maven-release-plugin + + + + org.codehaus.mojo + findbugs-maven-plugin + + + + + + + + + repo.jenkins-ci.org + http://repo.jenkins-ci.org/public/ + + + + + + repo.jenkins-ci.org + http://repo.jenkins-ci.org/public/ + + + diff --git a/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/Consts.java b/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/Consts.java new file mode 100644 index 0000000..be97ad9 --- /dev/null +++ b/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/Consts.java @@ -0,0 +1,18 @@ +package com.nirima.jenkins.plugins.snowglobe; + +/** + * Created by magnayn on 09/09/2016. + */ +public class Consts { + public static final String PLUGIN_URL = "/plugin/snowglobe-jenkins-plugin/"; + + /** + * The base URL of the plugin images. + */ + public static final String PLUGIN_IMAGES_URL = PLUGIN_URL + "images/"; + + /** + * The base URL of the plugin javascripts. + */ + public static final String PLUGIN_JS_URL = PLUGIN_URL + "js/"; +} diff --git a/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/SnowGlobePluginConfiguration.java b/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/SnowGlobePluginConfiguration.java new file mode 100644 index 0000000..bd40701 --- /dev/null +++ b/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/SnowGlobePluginConfiguration.java @@ -0,0 +1,61 @@ +package com.nirima.jenkins.plugins.snowglobe; + +import com.nirima.jenkins.plugins.snowglobe.registry.SnowGlobeRegistry; + +import net.sf.json.JSONObject; + +import org.kohsuke.stapler.StaplerRequest; + +import hudson.Extension; +import hudson.Functions; +import hudson.Util; +import jenkins.model.GlobalConfiguration; + +/** + * Created by magnayn on 06/09/2016. + */ +@Extension +public class SnowGlobePluginConfiguration extends GlobalConfiguration { + + private String dotExe; + + private SnowGlobeRegistry registry; + + public SnowGlobePluginConfiguration() { + load(); + } + + @Override + public boolean configure(StaplerRequest req, JSONObject json) throws FormException { + req.bindJSON(this,json); + return true; + } + + /** + * Returns this singleton instance. + * + * @return the singleton. + */ + public static SnowGlobePluginConfiguration get() { + return GlobalConfiguration.all().get(SnowGlobePluginConfiguration.class); + } + + public String getDotExeOrDefault() { + if (Util.fixEmptyAndTrim(dotExe) == null) { + return Functions.isWindows() ? "dot.exe" : "dot"; + } else { + return dotExe; + } + } + + public String getDotExe() { + return dotExe; + } + + public void setDotExe(String dotExe) { + this.dotExe = dotExe; + save(); + } + + +} diff --git a/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/action/SnowGlobeAction.java b/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/action/SnowGlobeAction.java new file mode 100644 index 0000000..2f0eecd --- /dev/null +++ b/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/action/SnowGlobeAction.java @@ -0,0 +1,194 @@ +package com.nirima.jenkins.plugins.snowglobe.action; + +import com.nirima.jenkins.plugins.snowglobe.Consts; +import com.nirima.jenkins.plugins.snowglobe.SnowGlobePluginConfiguration; +import com.nirima.jenkins.plugins.snowglobe.calls.SnowGlobeData; +import com.nirima.jenkins.plugins.snowglobe.registry.SnowGlobeRegistry; + +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.ServletException; + +import hudson.Extension; +import hudson.Launcher; +import hudson.model.Action; +import hudson.model.Describable; +import hudson.model.Descriptor; +import hudson.model.Run; +import hudson.util.LogTaskListener; +import jenkins.model.Jenkins; + + +/** + * SnowGlobes linked to a job (action badge) + */ +public class SnowGlobeAction implements Action, Serializable, + Describable { + + private static final Logger LOGGER = Logger.getLogger(SnowGlobeAction.class.getName()); + + private String id; + + public SnowGlobeAction(String runId) { + this.id = runId; + } + + public String getId() { + return id; + } + + public static class ActionData { + public String id; + public SnowGlobeData data; + + public ActionData(String id, SnowGlobeData data) { + this.id = id; + this.data = data; + } + + public String getId() { + return id; + } + + public SnowGlobeData getData() { + return data; + } + } + + + public SnowGlobeData createSnowglobe(String script) { + return null; + } + + public SnowGlobeData getDataById(String id) { + return SnowGlobeRegistry.get().getById(id); + } + + public Collection getStates() { + Set ids = SnowGlobeRegistry.get().getGlobesForRunId(this.id); + + Set data = new HashSet<>(); + + if( ids != null ) + ids.forEach( it -> data.add( new ActionData(it, SnowGlobeRegistry.get().getById(it) )) ); + + return data; + + } + + @Override + public String getIconFileName() { + return "/plugin/snowglobe-jenkins-plugin/images/32x32/snow-globe.png"; + } + + @Override + public String getDisplayName() { + return "Snowglobe"; + } + + @Override + public String getUrlName() { + return "snowglobeAction"; + } + + public DescriptorImpl getDescriptor() { + return (DescriptorImpl) Jenkins.getInstance().getDescriptorOrDie(getClass()); + } + + + public String getJsUrl(String jsName) { + return Consts.PLUGIN_JS_URL + jsName; + } + + public void doControlSubmit(@QueryParameter("action") String action, + @QueryParameter("launchId") String id, StaplerRequest req, StaplerResponse rsp) throws + ServletException, + IOException, + InterruptedException { + + if( action.equals("apply") ) { + apply(id); + } else if( action.equals("destroy") ) { + destroy(id); + } + + rsp.sendRedirect("."); + } + + public void doDynamic(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, InterruptedException { + //String path = req.getRestOfPath(); + + String id = req.getParameter("id"); + + String graphString = getDataById( id ).graph(); + + rsp.setContentType("image/png"); + + runDot(rsp.getOutputStream(), new ByteArrayInputStream(graphString.getBytes( + Charset.forName("UTF-8"))), "png"); + } + + /** + * Execute the dot command with given input and output stream + * @param type the parameter for the -T option of the graphviz tools + */ + protected void runDot(OutputStream output, InputStream input, String type) + throws IOException { + + String dotPath = SnowGlobePluginConfiguration.get().getDotExeOrDefault(); + Launcher launcher = Jenkins.getInstance().createLauncher(new LogTaskListener(LOGGER, Level.CONFIG)); + try { + launcher.launch() + .cmds(dotPath,"-T" + type, "-Gcharset=UTF-8", "-q1") + .stdin(input) + .stdout(output) + .start().join(); + } catch (InterruptedException e) { + LOGGER.log(Level.SEVERE, "Interrupted while waiting for dot-file to be created", e); + } + finally { + if (output != null) { + output.close(); + } + } + } + + private void apply(String id) throws IOException { + getDataById( id ).apply(); + SnowGlobeRegistry.get().save(); + } + + private void destroy(String id) throws IOException { + getDataById( id ).destroy(); + SnowGlobeRegistry.get().save(); + } + + + /** + * Just for assisting form related stuff. + */ + @Extension + public static class DescriptorImpl extends Descriptor { + public String getDisplayName() { + return "SnowGlobe"; + } + } +} diff --git a/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/calls/SnowGlobeData.java b/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/calls/SnowGlobeData.java new file mode 100644 index 0000000..5cb0750 --- /dev/null +++ b/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/calls/SnowGlobeData.java @@ -0,0 +1,97 @@ +package com.nirima.jenkins.plugins.snowglobe.calls; + +import com.nirima.jenkins.plugins.snowglobe.SnowGlobePluginConfiguration; +import com.nirima.jenkins.plugins.snowglobe.registry.SnowGlobeRegistry; +import com.nirima.snowglobe.SGExec; +import com.nirima.snowglobe.core.Core; +import com.nirima.snowglobe.jenkins.JenkinsDockerProvider; + +import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.Whitelisted; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; + +/** + * Created by magnayn on 09/09/2016. + */ +public class SnowGlobeData implements Serializable { + + public String script; + public String state; + + public String lastResult; + + public SnowGlobeData(String script, String state) { + + this.script = script; + this.state = state; + } + + @Whitelisted + public SnowGlobeData apply() + throws IOException { + InputStream a = new ByteArrayInputStream(script.getBytes("UTF-8")); + InputStream s = null; + if( state != null ) + s = new ByteArrayInputStream(state.getBytes("UTF-8")); + + SGExec exec = new SGExec(a, s ); + + try { + + exec.apply(); + lastResult = "[OK]"; + } catch(Exception ex) { + lastResult = ex.toString(); + } + finally { + state = exec.save(); + + } + return this; + } + + @Whitelisted + public SnowGlobeData destroy() + throws IOException { + InputStream a = new ByteArrayInputStream(script.getBytes("UTF-8")); + InputStream s = null; + if( state != null ) + s = new ByteArrayInputStream(state.getBytes("UTF-8")); + + SGExec exec = new SGExec( a, s ); + + try { + exec.destroy(); + lastResult = "[OK]"; + } catch(Exception ex) { + lastResult = ex.toString(); + } + finally { + state = exec.save(); + + } + return this; + } + + @Whitelisted + public String graph() throws IOException { + InputStream a = new ByteArrayInputStream(script.getBytes("UTF-8")); + InputStream s = null; + if( state != null ) + s = new ByteArrayInputStream(state.getBytes("UTF-8")); + + SGExec exec = new SGExec( a, s ); + + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + exec.graph(baos); + baos.close(); + return new String(baos.toByteArray(),"UTF-8"); + + } + +} diff --git a/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/registry/DeleteListener.java b/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/registry/DeleteListener.java new file mode 100644 index 0000000..5ddc7b7 --- /dev/null +++ b/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/registry/DeleteListener.java @@ -0,0 +1,16 @@ +package com.nirima.jenkins.plugins.snowglobe.registry; + +import hudson.Extension; +import hudson.model.Run; +import hudson.model.listeners.RunListener; + +/** + * Created by magnayn on 12/09/2016. + */ +@Extension +public class DeleteListener extends RunListener { + @Override + public void onDeleted(Run r) { + SnowGlobeRegistry.get().removeFromRun(r); + } +} \ No newline at end of file diff --git a/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/registry/SnowGlobeRegistry.java b/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/registry/SnowGlobeRegistry.java new file mode 100644 index 0000000..9587e84 --- /dev/null +++ b/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/registry/SnowGlobeRegistry.java @@ -0,0 +1,108 @@ +package com.nirima.jenkins.plugins.snowglobe.registry; + +import com.nirima.jenkins.plugins.snowglobe.action.SnowGlobeAction; +import com.nirima.jenkins.plugins.snowglobe.calls.SnowGlobeData; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import hudson.Extension; +import hudson.model.Run; +import jenkins.model.GlobalConfiguration; + +/** + * Created by magnayn on 12/09/2016. + */ +@Extension +public class SnowGlobeRegistry extends GlobalConfiguration implements Serializable { + + Map globes = new LinkedHashMap<>(); + + Map> runToIds = new HashMap<>(); + + + public SnowGlobeRegistry() { + load(); + } + + /** + * Returns this singleton instance. + * + * @return the singleton. + */ + public static SnowGlobeRegistry get() { + return GlobalConfiguration.all().get(SnowGlobeRegistry.class); + } + + public SnowGlobeData register(String id, SnowGlobeData data) { + globes.put(id, data); + save(); + return data; + } + + public void register(String id, SnowGlobeData data, Run run) throws IOException { + String runId = makeRunId(run); + register(id,data); + + SnowGlobeAction action = run.getAction(SnowGlobeAction.class); + if (action == null) { + action = new SnowGlobeAction(runId); + run.addAction(action); + } + run.save(); + + if( !runToIds.containsKey(runId)) { + runToIds.put(runId, new HashSet()); + } + + runToIds.get(runId).add(id); + + save(); + } + + public static String makeRunId(Run run) { + return run.getParent().getUrl() + run.getNumber(); + } + + public void remove(String id) { + globes.remove(id); + save(); + } + + public SnowGlobeData getById(String id) { + SnowGlobeData data = globes.get(id); + return data; + } + + public Collection getActiveGlobes() { + Set data = new HashSet<>(); + data.addAll(globes.values()); + + data.stream().filter( it -> it.state != null ); + + return data; + } + + public void removeFromRun(Run r) { + + String runId = makeRunId(r); + + Set items = runToIds.get( runId ); + items.forEach( it -> remove(it) ); + runToIds.remove( runId ); + + + } + + public Set getGlobesForRunId(String id) { + return runToIds.get(id); + } +} + + diff --git a/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/workflow/SnowGlobeWorkflowStep.java b/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/workflow/SnowGlobeWorkflowStep.java new file mode 100644 index 0000000..74b9128 --- /dev/null +++ b/snowglobe-jenkins-plugin/src/main/java/com/nirima/jenkins/plugins/snowglobe/workflow/SnowGlobeWorkflowStep.java @@ -0,0 +1,203 @@ +package com.nirima.jenkins.plugins.snowglobe.workflow; + + +import com.nirima.jenkins.plugins.snowglobe.calls.SnowGlobeData; +import com.nirima.jenkins.plugins.snowglobe.registry.SnowGlobeRegistry; + +import org.apache.commons.io.FileUtils; +import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.Whitelisted; +import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl; +import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl; +import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousStepExecution; +import org.jenkinsci.plugins.workflow.steps.StepContextParameter; +import org.jenkinsci.plugins.workflow.steps.StepExecution; +import org.kohsuke.stapler.DataBoundConstructor; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.util.UUID; + +import javax.inject.Inject; + +import hudson.Extension; +import hudson.FilePath; +import hudson.Launcher; +import hudson.model.Run; +import hudson.model.TaskListener; +import hudson.remoting.VirtualChannel; +import jenkins.MasterToSlaveFileCallable; + + +/** + * Created by magnayn on 12/09/2016. + */ +public class SnowGlobeWorkflowStep extends AbstractStepImpl implements Serializable { + + + @DataBoundConstructor + public SnowGlobeWorkflowStep() { //String dummy) { + + } + + + public static SnowGlobeData getScriptData(FilePath filePath, String path) throws IOException, InterruptedException { + final FilePath sgFile = new FilePath(filePath, path); + + System.out.println("Get Data for script @ " + sgFile.toString()); + + return sgFile.act(new MasterToSlaveFileCallable() { + @Override + public SnowGlobeData invoke(File file, VirtualChannel virtualChannel) + throws IOException, InterruptedException { + System.out.println("Get Data for script @ " + file); + + return new SnowGlobeData(new String(FileUtils.readFileToByteArray(file), "UTF-8"), null); + } + }); + } + + public static class SourceExection implements Serializable { + + private final Execution execution; + + SourceExection(Execution execution) { + this.execution = execution; + } + + @Whitelisted + BuildExecution fromFile(String file) throws IOException, InterruptedException { + return new BuildExecution(execution, SnowGlobeWorkflowStep.getScriptData(execution.filePath, file)); + } + + @Whitelisted + BuildExecution fromString(String string) { + SnowGlobeData data = new SnowGlobeData(string, null); + return new BuildExecution(execution,data); + } + } + + public static class BuildExecution implements Serializable { + SnowGlobeData data; + + private final Execution execution; + + public BuildExecution(Execution run, SnowGlobeData data) { + this.execution = run; + this.data = data; + } + + @Whitelisted + public void apply() throws IOException { + data.apply(); + } + @Whitelisted + public void destroy() throws IOException { + data.destroy(); + } + + @Whitelisted + public SnowGlobeRegister register() { + return new SnowGlobeRegister(this); + } + + } + + public static class SnowGlobeRegister implements Serializable { + + private BuildExecution data; + + private String id; + + private boolean withRun = true; + + @Whitelisted + public SnowGlobeRegister(BuildExecution snowGlobeData) { + this.data = snowGlobeData; + this.id = UUID.randomUUID().toString(); + } + + @Whitelisted + public SnowGlobeRegister withId(String id) { + this.id = id; + return this; + } + @Whitelisted + public SnowGlobeRegister withRun(boolean b) { + withRun = b; + + return this; + } + @Whitelisted + public BuildExecution exec() throws IOException { + if( !withRun ) + SnowGlobeRegistry.get().register(id, data.data); + else + SnowGlobeRegistry.get().register(id, data.data, data.execution.run); + + return data; + } + + } + + @Override + public DescriptorImpl getDescriptor() { + return (DescriptorImpl)super.getDescriptor(); + } + + public static class Execution extends AbstractSynchronousStepExecution { + + static final long serialVersionUID = 1L; + + + @StepContextParameter + private transient TaskListener taskListener; + + @StepContextParameter + private transient FilePath filePath; + + @StepContextParameter + private transient Run run; + + @StepContextParameter + private transient Launcher launcher; + + @Inject + private transient SnowGlobeWorkflowStep step; + + + protected SourceExection run() throws Exception { + return new SourceExection(this); + } + } + + + @Extension + public static class DescriptorImpl extends AbstractStepDescriptorImpl { + + public DescriptorImpl() { + super(Execution.class); + } + + public DescriptorImpl( + Class executionType) { + super(executionType); + } + + @Override + public String getFunctionName() { + return "snowglobe"; + } + + @Override + public String getDisplayName() { + return "Make SnowGlobes"; + } + + + } + + + + +} diff --git a/snowglobe-jenkins-plugin/src/main/resources/com/nirima/jenkins/plugins/snowglobe/SnowGlobePluginConfiguration/config.jelly b/snowglobe-jenkins-plugin/src/main/resources/com/nirima/jenkins/plugins/snowglobe/SnowGlobePluginConfiguration/config.jelly new file mode 100644 index 0000000..eca2b4b --- /dev/null +++ b/snowglobe-jenkins-plugin/src/main/resources/com/nirima/jenkins/plugins/snowglobe/SnowGlobePluginConfiguration/config.jelly @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/snowglobe-jenkins-plugin/src/main/resources/com/nirima/jenkins/plugins/snowglobe/action/SnowGlobeAction/index.jelly b/snowglobe-jenkins-plugin/src/main/resources/com/nirima/jenkins/plugins/snowglobe/action/SnowGlobeAction/index.jelly new file mode 100644 index 0000000..8ab5392 --- /dev/null +++ b/snowglobe-jenkins-plugin/src/main/resources/com/nirima/jenkins/plugins/snowglobe/action/SnowGlobeAction/index.jelly @@ -0,0 +1,42 @@ + + + + + + + + +

Snowglobes

+ + +

Run ID = ${it.id}

+ + +
+ + + + + + + +

SnowGlobe ${res.id}

+

Script

+
+                ${res.data.script}
+                
+

State

+
${res.data.state}
+

Graph

+

+

Last Result

+

${res.data.lastResult}

+
+ + +
+
+ +
+
+
diff --git a/snowglobe-jenkins-plugin/src/main/resources/com/nirima/jenkins/plugins/snowglobe/action/SnowGlobeAction/summary.jelly b/snowglobe-jenkins-plugin/src/main/resources/com/nirima/jenkins/plugins/snowglobe/action/SnowGlobeAction/summary.jelly new file mode 100644 index 0000000..0c13b40 --- /dev/null +++ b/snowglobe-jenkins-plugin/src/main/resources/com/nirima/jenkins/plugins/snowglobe/action/SnowGlobeAction/summary.jelly @@ -0,0 +1,11 @@ + + + + +

SnowGlobe

+ +

Follow link on Left-hand bar for more details

+
+
diff --git a/snowglobe-jenkins-plugin/src/main/resources/com/nirima/jenkins/plugins/snowglobe/calls/SnowGlobeData/config.jelly b/snowglobe-jenkins-plugin/src/main/resources/com/nirima/jenkins/plugins/snowglobe/calls/SnowGlobeData/config.jelly new file mode 100644 index 0000000..f5b2ee5 --- /dev/null +++ b/snowglobe-jenkins-plugin/src/main/resources/com/nirima/jenkins/plugins/snowglobe/calls/SnowGlobeData/config.jelly @@ -0,0 +1,7 @@ + + + + +

See documentation

+ +
diff --git a/snowglobe-jenkins-plugin/src/main/resources/com/nirima/jenkins/plugins/snowglobe/registry/SnowGlobeRegistry/config.jelly b/snowglobe-jenkins-plugin/src/main/resources/com/nirima/jenkins/plugins/snowglobe/registry/SnowGlobeRegistry/config.jelly new file mode 100644 index 0000000..897da22 --- /dev/null +++ b/snowglobe-jenkins-plugin/src/main/resources/com/nirima/jenkins/plugins/snowglobe/registry/SnowGlobeRegistry/config.jelly @@ -0,0 +1,5 @@ + + + + + diff --git a/snowglobe-jenkins-plugin/src/main/resources/com/nirima/jenkins/plugins/snowglobe/workflow/SnowGlobeWorkflowStep/config.jelly b/snowglobe-jenkins-plugin/src/main/resources/com/nirima/jenkins/plugins/snowglobe/workflow/SnowGlobeWorkflowStep/config.jelly new file mode 100644 index 0000000..f5b2ee5 --- /dev/null +++ b/snowglobe-jenkins-plugin/src/main/resources/com/nirima/jenkins/plugins/snowglobe/workflow/SnowGlobeWorkflowStep/config.jelly @@ -0,0 +1,7 @@ + + + + +

See documentation

+ +
diff --git a/snowglobe-jenkins-plugin/src/main/webapp/images/32x32/snow-globe.png b/snowglobe-jenkins-plugin/src/main/webapp/images/32x32/snow-globe.png new file mode 100644 index 0000000..16362ae Binary files /dev/null and b/snowglobe-jenkins-plugin/src/main/webapp/images/32x32/snow-globe.png differ diff --git a/snowglobe-jenkins-plugin/src/main/webapp/js/snowglobe-manage.js b/snowglobe-jenkins-plugin/src/main/webapp/js/snowglobe-manage.js new file mode 100644 index 0000000..8ba292c --- /dev/null +++ b/snowglobe-jenkins-plugin/src/main/webapp/js/snowglobe-manage.js @@ -0,0 +1,34 @@ + +function launch(theId) { + var input = document.getElementById('launchId'); + if (input != null) { + input.value = theId; + } + + var form = document.getElementById('control'); + form.submit(); +} + +function destroy(theId) { + go(theId,'destroy') +} + +function apply(theId) { + go(theId,'apply') +} + +function go(theId,action) { + var input = document.getElementById('launchId'); + if (input != null) { + input.value = theId; + } + + var input2 = document.getElementById('action'); + if (input2 != null) { + input2.value = action; + } + + var form = document.getElementById('control'); + form.submit(); +} + diff --git a/snowglobe-jenkins/pom.xml b/snowglobe-jenkins/pom.xml new file mode 100644 index 0000000..4c58a16 --- /dev/null +++ b/snowglobe-jenkins/pom.xml @@ -0,0 +1,121 @@ + + + 4.0.0 + com.nirima.snowglobe + snowglobe-jenkins + + + com.nirima + snowglobe + 0.1-SNAPSHOT + + + + 0.16.2 + + + + + + + org.codehaus.gmavenplus + gmavenplus-plugin + ${gmavenVersion} + + + + addSources + addTestSources + + compile + + testCompile + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.jenkins-ci.main + jenkins-core + 2.7 + compile + + + junit + junit + 4.12 + test + + + com.nirima.snowglobe + snowglobe-core + ${project.version} + + + com.nirima + docker-plugin + ${docker-plugin.version} + provided + + + + + org.codehaus.groovy + groovy-all + + 2.4.7 + + + + + + + repo.jenkins-ci.org + http://repo.jenkins-ci.org/public/ + + + + false + + central + Central Repository + https://repo.maven.apache.org/maven2 + + + + diff --git a/snowglobe-jenkins/src/main/groovy/com/nirima/snowglobe/jenkins/Jenkins.groovy b/snowglobe-jenkins/src/main/groovy/com/nirima/snowglobe/jenkins/Jenkins.groovy new file mode 100644 index 0000000..c1d3f26 --- /dev/null +++ b/snowglobe-jenkins/src/main/groovy/com/nirima/snowglobe/jenkins/Jenkins.groovy @@ -0,0 +1,26 @@ +package com.nirima.snowglobe.jenkins + +import com.github.dockerjava.api.DockerClient +import com.github.dockerjava.core.DefaultDockerClientConfig +import com.github.dockerjava.core.DockerClientBuilder +import com.nirima.jenkins.plugins.docker.DockerCloud +import com.nirima.snowglobe.core.Module +import com.nirima.snowglobe.core.Provider +import com.nirima.snowglobe.core.SGItem +import com.nirima.snowglobe.docker.DockerProvider + +@SGItem("jenkins_docker_provider") +public class JenkinsDockerProvider extends DockerProvider { + public String cloudName; + + JenkinsDockerProvider(Module module, String id, Closure closure) { + super(module, id, closure) + } + + public DockerClient getDockerClient() { + + def dc = jenkins.model.Jenkins.getInstance().getCloud(cloudName); + + return dc.getClient(); + } +} \ No newline at end of file diff --git a/snowglobe-shaded/pom.xml b/snowglobe-shaded/pom.xml new file mode 100644 index 0000000..0de80ff --- /dev/null +++ b/snowglobe-shaded/pom.xml @@ -0,0 +1,311 @@ + + + 4.0.0 + + com.nirima + snowglobe + 0.1-SNAPSHOT + + + + com.nirima.snowglobe + snowglobe-shaded + jar + + snowglobe shaded jar for jenkins plugin + + + UTF-8 + + + + 3.0.6-jenkins + + 2.23.1 + 2.6.4 + 4.5 + 1.12 + 1.10 + 2.5 + 2.6 + 1.7.21 + + 1.54 + 2015-01-27T15-02-14 + 19.0 + + + 1.1.7 + 6.9.10 + 4.1.3.Final + 1.3 + 1.8 + 2.3.3 + 1.10.19 + + + + 3.18.2-GA + + 3.0.2 + 3.5.1 + 2.5.3 + 2.19.1 + 2.19.1 + 1.8 + + + + + + com.nirima.snowglobe + snowglobe-core + ${project.version} + true + + + docker-java + com.github.docker-java + + + + + com.nirima.snowglobe + snowglobe-jenkins + ${project.version} + true + + + + com.orbitz.consul + consul-client + 0.12.7 + + + org.javers + javers-core + 2.1.2 + + + + + org.reflections + reflections + 0.9.10 + true + + + org.javassist + javassist + ${javassist.version} + false + + + + com.github.docker-java + docker-java + ${docker-java.version} + true + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + ${jackson-jaxrs.version} + + + + org.glassfish.jersey.connectors + jersey-apache-connector + ${jersey.version} + + + org.apache.httpcomponents + httpcore + 4.4.5 + + + org.apache.httpcomponents + httpclient + ${httpclient.version} + + + org.glassfish.jersey.core + jersey-client + ${jersey.version} + + + + + + + + com.kohlschutter.junixsocket + junixsocket-common + 2.0.4 + + + log4j + log4j + + + + + + com.kohlschutter.junixsocket + junixsocket-native-common + 2.0.4 + + + log4j + log4j + + + + + + org.apache.commons + commons-compress + ${commons-compress.version} + + + commons-codec + commons-codec + ${commons-codec.version} + + + commons-lang + commons-lang + ${commons-lang.version} + + + commons-io + commons-io + ${commons-io.version} + + + org.slf4j + slf4j-api + ${slf4j-api.version} + + + org.slf4j + jcl-over-slf4j + 1.7.21 + + + javax.ws.rs + javax.ws.rs-api + 2.0 + + + javax.annotation + javax.annotation-api + 1.2 + + + com.google.guava + guava + ${guava.version} + + + org.bouncycastle + bcpkix-jdk15on + ${bouncycastle.version} + + + + + + com.google.code.findbugs + annotations + 3.0.1 + provided + + + + io.netty + netty-codec-http + ${netty.version} + + + io.netty + netty-handler + ${netty.version} + + + io.netty + netty-handler-proxy + ${netty.version} + + + io.netty + netty-transport-native-epoll + ${netty.version} + linux-x86_64 + + + + + + + ${project.artifactId} + + + maven-shade-plugin + 2.4.3 + + + shade + package + + shade + + + + + true + + + org.glassfish*:* + org.apache.httpcomponents*:* + com.github.docker-java:* + com.nirima.snowglobe:* + com.google.guava:* + org.reflections:* + + + + + org.apache.http + shaded.org.apache.http + + + com.google.common + shaded.com.google.common + + + + + + + + + +