«

»

Sep 24

Software Auto Identificável – Self Identifying Software

2identify_yourself_logo3333
esta imagem foi encontrada na web usando o images.google.com e eu achei que tinha a ver com o post

Eu não conhecia o conceito até ver este twit do CV falando deste post sobre Self Identifying Software. E lendo o post percebi que já passei e algumas vezes ainda passo pelo mesmo problema: Identificar qual versão do software esta instalada em um servidor, ou em que versão do sofware algum bug apareceu ou aconteceu ou acontece …

Bom, eu curti a idéia e fiquei pensando em como implementar isto, pelo menos em projetos Java, disto sairam estes “code snippets” abaixo …
Bom, normalmente trabalho com o ANT para fazer o build de projetos Java, e tenho utilizado o Subversion (sim, eu conheço o GIT e gosto dele, mas no momento não vai rolar no trampo, mas uso para projetos pessoais :D )
Então, fui a página do subversion e baixei o SVNANT, desenvolvido pelo pessoal do subclipse, e integrei ele no meu build assim:

1
2
3
4
5
6
7
8
9
10
        <path id="svn_tasks">
		<fileset dir="${directory_you_unzipped_the_svnant_package}" includes="svn*.jar">
		</fileset>
	</path>
	<taskdef classpathref="svn_tasks" resource="org/tigris/subversion/svnant/svnantlib.xml" />
	<target name="_setup_svn_info">
		<svn failonerror="false" javahl="true" svnkit="false">
			<info target="${basedir}" verbose="true"/>
		</svn>
	</target>

Depois disto, em qualquer parte do build em que você for criar um .jar, .war ou qualquer tipo de pacote java, basta fazer algo parecido com isto:

1
2
3
4
5
6
7
8
9
        <target name="build_jar" depends="_setup_svn_info,compile">
		<jar destfile="${dist.dir}/${jar.name}">
			<fileset dir="${basedir}/bin" includes="*.*" />
			<manifest>
				<attribute name="SVN-URL" value="${svn.info.url}" />
				<attribute name="SVN-REV" value="${svn.info.rev}" />
			</manifest>
		</jar>
        </target>

Claro que o importante é o depends e o manifest, o resto vai depender do seu build, isto não é nem um exemplo real, escrevi direto aqui no blog para dar a idéia, então se tiver algum problema com o código me avisem nos comentários :D

Mas isto não é útil se você não conseguir ler o MANIFEST.MF do .jar onde a sua classe se encontra, então estou colocando aqui também um exemplo de código para isto, mas lembre-se de alterar o nome da classe para cada pacote, caso contrário você nunca saberá de qual pacote a classe esta sendo carregada :D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package blog.urubatan;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.jar.Manifest;
 
public class ExemploDoUrubatan {
	private Manifest manifest;
 
	private void initManifest() throws URISyntaxException,
			FileNotFoundException, IOException {
		Class<?> clazz = getClass();
		URL classContainer = clazz.getProtectionDomain().getCodeSource()
				.getLocation();
		File manifestContainer = new File(classContainer.toURI());
		File metaInf = new File(manifestContainer, "META-INF");
		File manifestFile = new File(metaInf, "MANIFEST.MF");
		manifest = new Manifest(new FileInputStream(manifestFile));
 
	}
 
	public ExemploDoUrubatan() throws URISyntaxException,
			FileNotFoundException, IOException {
		initManifest();
	}
 
	public String getSvnUrl() {
		return manifest.getMainAttributes().getValue("SVN-URL");
	}
 
	public String getSvnRevision() {
		return manifest.getMainAttributes().getValue("SVN-REV");
	}
 
	public static void main(String[] args) throws FileNotFoundException,
			URISyntaxException, IOException {
		ExemploDoUrubatan ex = new ExemploDoUrubatan();
		System.out.println(ex.getSvnUrl());
		System.out.println(ex.getSvnRevision());
	}
}

Se for a versão de um arquivo .war o código pode ser colocado em um servlet com uma URL conhecida, ou em um listener que vai guardar esta informação no servlet context para ser impresso depois por uma URL conhecida …
Se o servlet for a opção selecionada, o código ficaria parecido com isto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package blog.urubatan;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
public class ServletExample extends HttpServlet {
	private static final long serialVersionUID = 1L;
 
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		super.doPost(req, resp);
	}
 
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		String warRoot = getServletContext().getRealPath(".");
		File manifestContainer = new File(warRoot);
		File metaInf = new File(manifestContainer, "META-INF");
		File manifestFile = new File(metaInf, "MANIFEST.MF");
		Manifest manifest = new Manifest(new FileInputStream(manifestFile));
		PrintWriter writer = resp.getWriter();
		Attributes mainAttributes = manifest.getMainAttributes();
		String svnUrl = mainAttributes.getValue("SVN-URL");
		String svnRev = mainAttributes.getValue("SVN-REV");
		writer.format("URL: %snRev:%sn", svnUrl, svnRev);
	}
 
}

Com isto, pelo menos para projetos java, já cobrimos duas das situações mais comuns, que são saber a versão de uma API e saber a versão de uma aplicação WEB.
Com isto já é possível verificar o deploy de aplicações durante o build se o script for um pouco mais inteligente, o pessoal de testes tem condições de dizer exatamente qual foi a build que gerou o problema, é possível construir um “dashboard” com a versão de tudo que é utilizado no sistema, facilitando bastante a identificação de onde o problema ocorre, e principalmente, no caso de clusters, permitindo que seja verificada a versão em cada um dos nós de uma forma fácil …

Agora no caso do Rails, eu ainda não consegui decidir qual a melhor abordagem para isto …
criar um arquivo com estes meta dados dentro do diretório config, atualizar este arquivo por uma task rake toda vez que for executar um deploy via capistrano e criar um controller para informar a versão?
As gems já tem um mecanismo de versionamento, seria só atualizar a versão da gem a cada build, coisa que pode ser feita até com keywork expansion, ou utilizando o mesmo esquema do rake mencionado antes.
Bom, vou pensar mais nisto, derepente rola até criar um plugin para aplicações rails pra facilitar a vida :D
O que vocês acham?