// +build ignore package main import ( "bytes" "crypto/md5" "encoding/json" "flag" "fmt" "io" "io/ioutil" "log" "os" "os/exec" "path/filepath" "regexp" "runtime" "strconv" "strings" "time" ) var ( versionRe = regexp.MustCompile(`-[0-9]{1,3}-g[0-9a-f]{5,10}`) goarch string goos string version string = "v1" // deb & rpm does not support semver so have to handle their version a little differently linuxPackageVersion string = "v1" linuxPackageIteration string = "" race bool workingDir string binaries []string = []string{"grafana-server", "grafana-cli"} ) const minGoVersion = 1.3 func main() { log.SetOutput(os.Stdout) log.SetFlags(0) ensureGoPath() readVersionFromPackageJson() log.Printf("Version: %s, Linux Version: %s, Package Iteration: %s\n", version, linuxPackageVersion, linuxPackageIteration) flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH") flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS") flag.BoolVar(&race, "race", race, "Use race detector") flag.Parse() if flag.NArg() == 0 { log.Println("Usage: go run build.go build") return } workingDir, _ = os.Getwd() for _, cmd := range flag.Args() { switch cmd { case "setup": setup() case "build": clean() for _, binary := range binaries { build(binary, "./pkg/cmd/"+binary, []string{}) } case "test": test("./pkg/...") grunt("test") case "package": grunt("release", fmt.Sprintf("--pkgVer=%v-%v", linuxPackageVersion, linuxPackageIteration)) createLinuxPackages() case "pkg-rpm": grunt("release") createRpmPackages() case "pkg-deb": grunt("release") createDebPackages() case "latest": makeLatestDistCopies() case "clean": clean() default: log.Fatalf("Unknown command %q", cmd) } } } func makeLatestDistCopies() { rpmIteration := "-1" if linuxPackageIteration != "" { rpmIteration = linuxPackageIteration } runError("cp", fmt.Sprintf("dist/grafana_%v-%v_amd64.deb", linuxPackageVersion, linuxPackageIteration), "dist/grafana_latest_amd64.deb") runError("cp", fmt.Sprintf("dist/grafana-%v-%v.x86_64.rpm", linuxPackageVersion, rpmIteration), "dist/grafana-latest-1.x86_64.rpm") runError("cp", fmt.Sprintf("dist/grafana-%v-%v.linux-x64.tar.gz", linuxPackageVersion, linuxPackageIteration), "dist/grafana-latest.linux-x64.tar.gz") } func readVersionFromPackageJson() { reader, err := os.Open("package.json") if err != nil { log.Fatal("Failed to open package.json") return } defer reader.Close() jsonObj := map[string]interface{}{} jsonParser := json.NewDecoder(reader) if err := jsonParser.Decode(&jsonObj); err != nil { log.Fatal("Failed to decode package.json") } version = jsonObj["version"].(string) linuxPackageVersion = version linuxPackageIteration = "" // handle pre version stuff (deb / rpm does not support semver) parts := strings.Split(version, "-") if len(parts) > 1 { linuxPackageVersion = parts[0] linuxPackageIteration = parts[1] } // add timestamp to iteration linuxPackageIteration = fmt.Sprintf("%d%s", time.Now().Unix(), linuxPackageIteration) } type linuxPackageOptions struct { packageType string homeDir string binPath string serverBinPath string cliBinPath string configDir string configFilePath string ldapFilePath string etcDefaultPath string etcDefaultFilePath string initdScriptFilePath string systemdServiceFilePath string postinstSrc string initdScriptSrc string defaultFileSrc string systemdFileSrc string depends []string } func createDebPackages() { createPackage(linuxPackageOptions{ packageType: "deb", homeDir: "/usr/share/grafana", binPath: "/usr/sbin", configDir: "/etc/grafana", configFilePath: "/etc/grafana/grafana.ini", ldapFilePath: "/etc/grafana/ldap.toml", etcDefaultPath: "/etc/default", etcDefaultFilePath: "/etc/default/grafana-server", initdScriptFilePath: "/etc/init.d/grafana-server", systemdServiceFilePath: "/usr/lib/systemd/system/grafana-server.service", postinstSrc: "packaging/deb/control/postinst", initdScriptSrc: "packaging/deb/init.d/grafana-server", defaultFileSrc: "packaging/deb/default/grafana-server", systemdFileSrc: "packaging/deb/systemd/grafana-server.service", depends: []string{"adduser", "libfontconfig"}, }) } func createRpmPackages() { createPackage(linuxPackageOptions{ packageType: "rpm", homeDir: "/usr/share/grafana", binPath: "/usr/sbin", configDir: "/etc/grafana", configFilePath: "/etc/grafana/grafana.ini", ldapFilePath: "/etc/grafana/ldap.toml", etcDefaultPath: "/etc/sysconfig", etcDefaultFilePath: "/etc/sysconfig/grafana-server", initdScriptFilePath: "/etc/init.d/grafana-server", systemdServiceFilePath: "/usr/lib/systemd/system/grafana-server.service", postinstSrc: "packaging/rpm/control/postinst", initdScriptSrc: "packaging/rpm/init.d/grafana-server", defaultFileSrc: "packaging/rpm/sysconfig/grafana-server", systemdFileSrc: "packaging/rpm/systemd/grafana-server.service", depends: []string{"initscripts", "fontconfig"}, }) } func createLinuxPackages() { createDebPackages() createRpmPackages() } func createPackage(options linuxPackageOptions) { packageRoot, _ := ioutil.TempDir("", "grafana-linux-pack") // create directories runPrint("mkdir", "-p", filepath.Join(packageRoot, options.homeDir)) runPrint("mkdir", "-p", filepath.Join(packageRoot, options.configDir)) runPrint("mkdir", "-p", filepath.Join(packageRoot, "/etc/init.d")) runPrint("mkdir", "-p", filepath.Join(packageRoot, options.etcDefaultPath)) runPrint("mkdir", "-p", filepath.Join(packageRoot, "/usr/lib/systemd/system")) runPrint("mkdir", "-p", filepath.Join(packageRoot, "/usr/sbin")) // copy binary for _, binary := range binaries { runPrint("cp", "-p", filepath.Join(workingDir, "tmp/bin/"+binary), filepath.Join(packageRoot, "/usr/sbin/"+binary)) } // copy init.d script runPrint("cp", "-p", options.initdScriptSrc, filepath.Join(packageRoot, options.initdScriptFilePath)) // copy environment var file runPrint("cp", "-p", options.defaultFileSrc, filepath.Join(packageRoot, options.etcDefaultFilePath)) // copy systemd file runPrint("cp", "-p", options.systemdFileSrc, filepath.Join(packageRoot, options.systemdServiceFilePath)) // copy release files runPrint("cp", "-a", filepath.Join(workingDir, "tmp")+"/.", filepath.Join(packageRoot, options.homeDir)) // remove bin path runPrint("rm", "-rf", filepath.Join(packageRoot, options.homeDir, "bin")) // copy sample ini file to /etc/grafana runPrint("cp", "conf/sample.ini", filepath.Join(packageRoot, options.configFilePath)) // copy sample ldap toml config file to /etc/grafana/ldap.toml runPrint("cp", "conf/ldap.toml", filepath.Join(packageRoot, options.ldapFilePath)) args := []string{ "-s", "dir", "--description", "Grafana", "-C", packageRoot, "--vendor", "Grafana", "--url", "http://grafana.org", "--license", "\"Apache 2.0\"", "--maintainer", "contact@grafana.org", "--config-files", options.configFilePath, "--config-files", options.ldapFilePath, "--config-files", options.initdScriptFilePath, "--config-files", options.etcDefaultFilePath, "--config-files", options.systemdServiceFilePath, "--after-install", options.postinstSrc, "--name", "grafana", "--version", linuxPackageVersion, "-p", "./dist", } if linuxPackageIteration != "" { args = append(args, "--iteration", linuxPackageIteration) } // add dependenciesj for _, dep := range options.depends { args = append(args, "--depends", dep) } args = append(args, ".") fmt.Println("Creating package: ", options.packageType) runPrint("fpm", append([]string{"-t", options.packageType}, args...)...) } func verifyGitRepoIsClean() { rs, err := runError("git", "ls-files", "--modified") if err != nil { log.Fatalf("Failed to check if git tree was clean, %v, %v\n", string(rs), err) return } count := len(string(rs)) if count > 0 { log.Fatalf("Git repository has modified files, aborting") } log.Println("Git repository is clean") } func ensureGoPath() { if os.Getenv("GOPATH") == "" { cwd, err := os.Getwd() if err != nil { log.Fatal(err) } gopath := filepath.Clean(filepath.Join(cwd, "../../../../")) log.Println("GOPATH is", gopath) os.Setenv("GOPATH", gopath) } } func ChangeWorkingDir(dir string) { os.Chdir(dir) } func grunt(params ...string) { runPrint("./node_modules/.bin/grunt", params...) } func setup() { runPrint("go", "get", "-v", "github.com/tools/godep") runPrint("go", "get", "-v", "github.com/blang/semver") runPrint("go", "get", "-v", "github.com/mattn/go-sqlite3") runPrint("go", "install", "-v", "github.com/mattn/go-sqlite3") } func test(pkg string) { setBuildEnv() runPrint("go", "test", "-short", "-timeout", "60s", pkg) } func build(binaryName, pkg string, tags []string) { binary := "./bin/" + binaryName if goos == "windows" { binary += ".exe" } rmr(binary, binary+".md5") args := []string{"build", "-ldflags", ldflags()} if len(tags) > 0 { args = append(args, "-tags", strings.Join(tags, ",")) } if race { args = append(args, "-race") } args = append(args, "-o", binary) args = append(args, pkg) setBuildEnv() runPrint("go", "version") runPrint("go", args...) // Create an md5 checksum of the binary, to be included in the archive for // automatic upgrades. err := md5File(binary) if err != nil { log.Fatal(err) } } func ldflags() string { var b bytes.Buffer b.WriteString("-w") b.WriteString(fmt.Sprintf(" -X main.version=%s", version)) b.WriteString(fmt.Sprintf(" -X main.commit=%s", getGitSha())) b.WriteString(fmt.Sprintf(" -X main.buildstamp=%d", buildStamp())) return b.String() } func rmr(paths ...string) { for _, path := range paths { log.Println("rm -r", path) os.RemoveAll(path) } } func clean() { rmr("bin", "Godeps/_workspace/pkg", "Godeps/_workspace/bin") rmr("dist") rmr("tmp") rmr(filepath.Join(os.Getenv("GOPATH"), fmt.Sprintf("pkg/%s_%s/github.com/grafana", goos, goarch))) } func setBuildEnv() { os.Setenv("GOOS", goos) if strings.HasPrefix(goarch, "armv") { os.Setenv("GOARCH", "arm") os.Setenv("GOARM", goarch[4:]) } else { os.Setenv("GOARCH", goarch) } if goarch == "386" { os.Setenv("GO386", "387") } wd, err := os.Getwd() if err != nil { log.Println("Warning: can't determine current dir:", err) log.Println("Build might not work as expected") } os.Setenv("GOPATH", fmt.Sprintf("%s%c%s", filepath.Join(wd, "Godeps", "_workspace"), os.PathListSeparator, os.Getenv("GOPATH"))) log.Println("GOPATH=" + os.Getenv("GOPATH")) } func getGitSha() string { v, err := runError("git", "describe", "--always", "--dirty") if err != nil { return "unknown-dev" } v = versionRe.ReplaceAllFunc(v, func(s []byte) []byte { s[0] = '+' return s }) return string(v) } func buildStamp() int64 { bs, err := runError("git", "show", "-s", "--format=%ct") if err != nil { return time.Now().Unix() } s, _ := strconv.ParseInt(string(bs), 10, 64) return s } func buildArch() string { os := goos if os == "darwin" { os = "macosx" } return fmt.Sprintf("%s-%s", os, goarch) } func run(cmd string, args ...string) []byte { bs, err := runError(cmd, args...) if err != nil { log.Println(cmd, strings.Join(args, " ")) log.Println(string(bs)) log.Fatal(err) } return bytes.TrimSpace(bs) } func runError(cmd string, args ...string) ([]byte, error) { ecmd := exec.Command(cmd, args...) bs, err := ecmd.CombinedOutput() if err != nil { return nil, err } return bytes.TrimSpace(bs), nil } func runPrint(cmd string, args ...string) { log.Println(cmd, strings.Join(args, " ")) ecmd := exec.Command(cmd, args...) ecmd.Stdout = os.Stdout ecmd.Stderr = os.Stderr err := ecmd.Run() if err != nil { log.Fatal(err) } } func md5File(file string) error { fd, err := os.Open(file) if err != nil { return err } defer fd.Close() h := md5.New() _, err = io.Copy(h, fd) if err != nil { return err } out, err := os.Create(file + ".md5") if err != nil { return err } _, err = fmt.Fprintf(out, "%x\n", h.Sum(nil)) if err != nil { return err } return out.Close() }