Skip to content

Commit 85940ac

Browse files
authored
Merge pull request #4 from clarsonneur/list-installed
list jenkins plugins version from an installation
2 parents ddb7ff1 + 0db0b3e commit 85940ac

File tree

11 files changed

+261
-2
lines changed

11 files changed

+261
-2
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@
1111
# Output of the go coverage tool, specifically when used with LiteIDE
1212
*.out
1313
.be-*
14+
15+
vendor/
16+
jplugins

DESIGN.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Application design
2+
3+
Step: List existing plugins in a Jenkins installation
4+
5+
1. cli with option
6+
jplugins list-installed [--jenkins-home path]
7+
2. loop on files *.[hj]pi to determine plugin name
8+
3. read version in MANIFEST file
9+
4. print out the list

build-env.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
BE_PROJECT=jplugins
44

55
# Add any module parameters here
6+
export CGO_ENABLED=0
67

78
source lib/source-build-env.sh

glide.lock

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

glide.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package: jplugins
2+
import:
3+
- package: github.com/alecthomas/kingpin
4+
version: ^2.2.6

jplugins-app.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package main
2+
3+
import (
4+
"github.com/alecthomas/kingpin"
5+
)
6+
7+
type jPluginsApp struct {
8+
app *kingpin.Application
9+
10+
listInstalled *kingpin.CmdClause
11+
jenkinsHomePath *string
12+
13+
installedPlugins plugins
14+
}
15+
16+
func (a *jPluginsApp) init() {
17+
a.app = kingpin.New("jplugins", "Jenkins plugins as Code management tool.")
18+
19+
a.setVersion()
20+
21+
a.listInstalled = a.app.Command("list-installed", "Display Jenkins plugins list of current Jenkins installation.")
22+
a.jenkinsHomePath = a.listInstalled.Flag("jenkins-home", "Where Jenkins is installed.").Default("/var/jenkins_home").String()
23+
}

jplugins-app_list-installed.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package main
2+
3+
import (
4+
"sort"
5+
"fmt"
6+
"io/ioutil"
7+
"os"
8+
"path"
9+
"regexp"
10+
"strings"
11+
12+
"github.com/forj-oss/forjj-modules/trace"
13+
"gopkg.in/yaml.v2"
14+
)
15+
16+
func (a *jPluginsApp) doListInstalled() {
17+
if !a.readFromJenkins() {
18+
return
19+
}
20+
a.printOutVersion(a.installedPlugins)
21+
}
22+
23+
// readFromJenkins read manifest of each plugins and store information in a.installedPlugins
24+
func (a *jPluginsApp) readFromJenkins() (_ bool) {
25+
pluginsPath := path.Join(*a.jenkinsHomePath, "plugins")
26+
27+
a.installedPlugins = make(plugins)
28+
29+
fEntries, err := ioutil.ReadDir(pluginsPath)
30+
31+
if err != nil {
32+
gotrace.Error("Invalid Jenkins home '%s'. %s", pluginsPath, err)
33+
return
34+
}
35+
36+
var fileRE, manifestRE *regexp.Regexp
37+
fileREDefine := `^(.*)\.[jh]pi*$`
38+
manifestREDefine := `([\w-]*: )(.*)\n`
39+
40+
if re, err := regexp.Compile(fileREDefine); err != nil {
41+
gotrace.Error("Internal error. Regex '%s': %s", fileREDefine, err)
42+
return
43+
} else {
44+
fileRE = re
45+
46+
}
47+
if re, err := regexp.Compile(manifestREDefine); err != nil {
48+
gotrace.Error("Internal error. Regex '%s': %s", fileREDefine, err)
49+
return
50+
} else {
51+
manifestRE = re
52+
}
53+
54+
for _, fEntry := range fEntries {
55+
if fEntry.IsDir() {
56+
continue
57+
}
58+
59+
if fileMatch := fileRE.FindAllStringSubmatch(fEntry.Name(), -1); fileMatch != nil {
60+
pluginFileName := fileMatch[0][0]
61+
pluginName := fileMatch[0][1]
62+
63+
if pluginFileName != "" && pluginName == "" {
64+
gotrace.Error("Invalid file '%s'. Ignored.", pluginFileName)
65+
continue
66+
}
67+
68+
pluginMetafile := path.Join(pluginsPath, pluginName, "META-INF", "MANIFEST.MF")
69+
70+
if _, err := os.Stat(pluginMetafile); err != nil && os.IsNotExist(err) {
71+
// TODO: Ignored for now. but may need to extract the plugin file to get the version
72+
gotrace.Warning("Plugin '%s' found but not expanded. Ignored. (fix in next jplugins version)", pluginMetafile)
73+
continue
74+
}
75+
76+
var manifest *pluginManifest
77+
78+
if d, err := ioutil.ReadFile(pluginMetafile); err != nil {
79+
gotrace.Error("Unable to read file '%s'. %s. Ignored", pluginMetafile, err)
80+
continue
81+
} else {
82+
// Remove DOS format if exist
83+
data := strings.Replace(string(d), "\r", "", -1)
84+
// and remove new lines ('\n ')
85+
data = strings.Replace(data, "\n ", "", -1)
86+
// and escape "
87+
data = strings.Replace(data, "\"", "\\\"", -1)
88+
// and embrace value part with "
89+
data = manifestRE.ReplaceAllString(data, `$1"$2"`+"\n")
90+
91+
manifest = new(pluginManifest)
92+
if err := yaml.Unmarshal([]byte(data), &manifest); err != nil {
93+
gotrace.Error("Unable to read file '%s' as yaml. %s. Ignored", pluginMetafile, err)
94+
fmt.Print(data)
95+
continue
96+
}
97+
}
98+
a.installedPlugins[manifest.Name] = manifest
99+
}
100+
}
101+
return true
102+
}
103+
104+
func (a *jPluginsApp) printOutVersion(plugins plugins) (_ bool) {
105+
if a.installedPlugins == nil {
106+
return
107+
}
108+
109+
pluginsList := make([]string, len(plugins))
110+
111+
112+
iCount := 0
113+
for name := range plugins {
114+
pluginsList[iCount] = name
115+
iCount++
116+
}
117+
118+
sort.Strings(pluginsList)
119+
120+
for _, name := range pluginsList {
121+
fmt.Printf("%s: %s\n", name, plugins[name].Version)
122+
}
123+
fmt.Println(iCount, "plugin(s)")
124+
return true
125+
}

jplugins-app_version.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
var build_branch, build_commit, build_date, build_tag string
8+
9+
const (
10+
// VERSION application
11+
VERSION = "0.0.1"
12+
// PRERELEASE = true if the version exposed is a pre-release
13+
PRERELEASE = true
14+
// AUTHOR is the project maintainer
15+
AUTHOR = "Christophe Larsonneur <[email protected]>"
16+
)
17+
18+
func (a *jPluginsApp) setVersion() {
19+
var version string
20+
21+
if PRERELEASE {
22+
version = " pre-release V" + VERSION
23+
} else {
24+
version = "forjj V" + VERSION
25+
}
26+
27+
if build_branch != "master" {
28+
version += fmt.Sprintf(" branch %s", build_branch)
29+
}
30+
if build_tag == "false" {
31+
version += fmt.Sprintf(" patched - %s - %s", build_date, build_commit)
32+
}
33+
34+
a.app.Version(version).Author(AUTHOR)
35+
36+
}

main.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
package main
22

3+
import (
4+
"os"
5+
6+
"github.com/alecthomas/kingpin"
7+
)
8+
9+
// App is the application core struct
10+
var App jPluginsApp
11+
312
func main() {
4-
5-
}
13+
App.init()
14+
15+
switch kingpin.MustParse(App.app.Parse(os.Args[1:])) {
16+
// Register user
17+
case App.listInstalled.FullCommand():
18+
App.doListInstalled()
19+
}
20+
}

plugin-manifest.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package main
2+
3+
type pluginManifest struct {
4+
Version string `yaml:"Plugin-Version"`
5+
Name string `yaml:"Extension-Name"`
6+
ShortName string `yaml:"Short-Name"`
7+
JenkinsVersion string `yaml:"Jenkins-Version"`
8+
LongName string `yaml:"Long-Name"`
9+
Dependencies string `yaml:"Plugin-Dependencies"`
10+
Description string `yaml:"Specification-Title"`
11+
}

0 commit comments

Comments
 (0)