├── main.go ├── actuator ├── health.go ├── metrics.go ├── env.go ├── actuator.go └── info.go ├── config ├── config_constants.go └── config.go ├── readme.MD └── eureka └── eureka.go /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /actuator/health.go: -------------------------------------------------------------------------------- 1 | package actuator 2 | 3 | import "encoding/json" 4 | 5 | func health() string { 6 | healthJson := new(healthJson) 7 | 8 | healthJson.Status = "UP" 9 | 10 | b, err := json.Marshal(healthJson) 11 | if err != nil { 12 | return err.Error() 13 | } 14 | return string(b) 15 | } 16 | 17 | type healthJson struct { 18 | Status string `json:"status"` 19 | } 20 | 21 | -------------------------------------------------------------------------------- /actuator/metrics.go: -------------------------------------------------------------------------------- 1 | package actuator 2 | 3 | import ( 4 | "encoding/json" 5 | "runtime" 6 | ) 7 | 8 | func metrics() string { 9 | 10 | values := refillMetricsMap() 11 | 12 | b, err := json.Marshal(values) 13 | if err != nil { 14 | return err.Error() 15 | } 16 | return string(b) 17 | } 18 | 19 | func refillMetricsMap() runtime.MemStats { 20 | var mem runtime.MemStats 21 | runtime.ReadMemStats(&mem) 22 | 23 | return mem 24 | } 25 | -------------------------------------------------------------------------------- /actuator/env.go: -------------------------------------------------------------------------------- 1 | package actuator 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | func env() string { 10 | env := new(envObject) 11 | env.SystemEnvironment = make(map[string]string) 12 | 13 | for _, e := range os.Environ() { 14 | pair := strings.Split(e, "=") 15 | env.SystemEnvironment[pair[0]] = pair[1] 16 | } 17 | 18 | 19 | b, err := json.Marshal(env) 20 | if err != nil { 21 | return err.Error() 22 | } 23 | return string(b) 24 | } 25 | 26 | type envObject struct { 27 | Configuration map[string]interface{} `json:"configuration"` 28 | SystemEnvironment map[string]string `json:"systemEnvironment"` 29 | } -------------------------------------------------------------------------------- /actuator/actuator.go: -------------------------------------------------------------------------------- 1 | package actuator 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | func InitializeActuator() { 8 | loadGitInfo() 9 | 10 | http.HandleFunc("/info", infoHandler) 11 | http.HandleFunc("/health", healthHandler) 12 | http.HandleFunc("/metrics", metricsHandler) 13 | http.HandleFunc("/env", envHandler) 14 | 15 | } 16 | 17 | func infoHandler(w http.ResponseWriter, r *http.Request) { 18 | w.Write([]byte(info())) 19 | } 20 | 21 | func healthHandler(w http.ResponseWriter, r *http.Request) { 22 | w.Write([]byte(health())) 23 | } 24 | 25 | func metricsHandler(w http.ResponseWriter, r *http.Request) { 26 | w.Write([]byte(metrics())) 27 | } 28 | 29 | func envHandler(w http.ResponseWriter, r *http.Request) { 30 | w.Write([]byte(env())) 31 | } -------------------------------------------------------------------------------- /config/config_constants.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const( 4 | spring_profiles_active = "spring_profiles_active" 5 | spring_cloud_config_uri = "spring_cloud_config_uri" 6 | spring_cloud_config_label = "spring_cloud_config_label" 7 | server_port = "server_port" 8 | eureka_instance_ip_address = "eureka_instance_ip_address" 9 | spring_application_name = "spring_application_name" 10 | 11 | springprofilesactive = "spring.profiles.active" 12 | springcloudconfiguri = "spring.cloud.config.uri" 13 | springcloudconfiglabel = "spring.cloud.config.label" 14 | serverport = "server.port" 15 | eurekainstanceipaddress = "eureka.instance.ip-address" 16 | springapplicationname = "spring.application.name" 17 | hostname = "hostname" 18 | 19 | label = "label" 20 | name = "name" 21 | propertySources = "propertySources" 22 | source = "source" 23 | ) 24 | -------------------------------------------------------------------------------- /actuator/info.go: -------------------------------------------------------------------------------- 1 | package actuator 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "strings" 10 | ) 11 | 12 | var gitInfo map[string]string = make(map[string]string) 13 | 14 | func loadGitInfo() { 15 | file, err := os.Open("git.properties") 16 | 17 | 18 | if err != nil { 19 | // can't open file or not exists 20 | out, err := exec.Command("bash", "-c", "git rev-parse --abbrev-ref HEAD; git show -s --format=\"%h%n%ci\"").Output() 21 | if err != nil { 22 | log.Println(err) 23 | } 24 | splitted := strings.Split(string(out), "\n") 25 | if len(splitted) > 3 { 26 | gitInfo["branch"] = splitted[0] 27 | gitInfo["commitid"] = splitted[1] 28 | gitInfo["committime"] = splitted[2] 29 | } 30 | 31 | } else { 32 | defer file.Close() 33 | scanner := bufio.NewScanner(file) 34 | scanner.Scan() 35 | gitInfo["branch"] = scanner.Text() 36 | scanner.Scan() 37 | gitInfo["commitid"] = scanner.Text() 38 | scanner.Scan() 39 | gitInfo["committime"] = scanner.Text() 40 | } 41 | 42 | } 43 | 44 | func info() string { 45 | infoJson := generateInfoData() 46 | b, err := json.Marshal(infoJson) 47 | if err != nil { 48 | return err.Error() 49 | } 50 | return string(b) 51 | } 52 | 53 | func generateInfoData() *infoJson { 54 | infoJson := new(infoJson) 55 | infoJson.Git = new(git) 56 | infoJson.Git.Commit = new(commit) 57 | 58 | infoJson.Git.Branch = gitInfo["branch"] 59 | infoJson.Git.Commit.ID = gitInfo["commitid"] 60 | infoJson.Git.Commit.Time = gitInfo["committime"] 61 | 62 | return infoJson 63 | } 64 | 65 | type infoJson struct { 66 | Git *git `json:"git"` 67 | } 68 | 69 | type git struct { 70 | Branch string `json:"branch"` 71 | Commit *commit `json:"commit"` 72 | } 73 | 74 | type commit struct { 75 | ID string `json:"id"` 76 | Time string `json:"time"` 77 | } -------------------------------------------------------------------------------- /readme.MD: -------------------------------------------------------------------------------- 1 | # golang-starter 2 | 3 | ## Eureka integration is deprecated 4 | 5 | golang-springboot is a project than implements with spring-cloud-config, eureka and actuator like a springboot implementation 6 | 7 | The project parts are: 8 | * actuator - exposes some methods like springboot actuator (/health, /metrics, /env, /info) 9 | * config - connects to "spring cloud config server" and loads the configuration 10 | * eureka - register, deregister and Heartbeat with eureka service 11 | 12 | ### How use it? 13 | 14 | we have two options for starting: 15 | * using env vars 16 | * using command line params 17 | 18 | #### env vars 19 | 20 | we must export the nexts env vars: 21 | 22 | | env vars | description | example | 23 | | ---------- | ---------- | ---------- | 24 | | spring_profiles_active | spring cloud config profile | test 25 | | spring_cloud_config_uri | uri of spring cloud config server | http://127.0.0.1:30606 26 | | spring_cloud_config_label | git branch | master 27 | | server_port | exposed port | 8080 28 | | eureka_instance_ip_address | IP of this application, accesible by eureka | 10.0.1.5 29 | | spring_application_name | name of application | my-golang-application 30 | 31 | #### command line params 32 | 33 | we report the following parameters in the same order: 34 | * spring_profiles_active 35 | * spring_cloud_config_uri 36 | * spring_cloud_config_label 37 | * server_port 38 | * eureka_instance_ip_address 39 | * spring_application_name 40 | 41 | example: 42 | 43 | ```golang 44 | go run main.go pre http://localhost:8080 pre 8080 10.5.5.10 my-golang-app 45 | ``` 46 | 47 | ### Import it in your project 48 | 49 | Just download and compile the code. Also you must call the methods for start it 50 | 51 | ```golang 52 | package main 53 | 54 | import ( 55 | "github.com/pineda89/golang-springboot/actuator" 56 | "github.com/pineda89/golang-springboot/config" 57 | "github.com/pineda89/golang-springboot/eureka" 58 | ) 59 | 60 | func main() { 61 | config.LoadConfig() 62 | 63 | go StartWebServer(config.Configuration["server.port"].(int)) 64 | go actuator.InitializeActuator() 65 | 66 | go eureka.Register(config.Configuration) 67 | CaptureInterruptSignal() 68 | eureka.Deregister() 69 | } 70 | 71 | func StartWebServer(port int) { 72 | http.HandleFunc("/", mainHandler) 73 | http.ListenAndServe(":" + strconv.Itoa(port), nil) 74 | } 75 | 76 | func mainHandler(w http.ResponseWriter, r *http.Request) { 77 | io.WriteString(w, service.MyApplicationMethod()) 78 | } 79 | 80 | func CaptureInterruptSignal() { 81 | c := make(chan os.Signal, 2) 82 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 83 | <-c 84 | } 85 | 86 | ``` 87 | 88 | This implementation exposes the methods "/", "/info", "/health", "/metrics" and "/env" 89 | Also loads configuration from spring cloud config server, and register, deregister and healthcheck on eureka 90 | 91 | -------------------------------------------------------------------------------- /eureka/eureka.go: -------------------------------------------------------------------------------- 1 | package eureka 2 | 3 | import ( 4 | "strconv" 5 | "github.com/hudl/fargo" 6 | "github.com/satori/go.uuid" 7 | "time" 8 | "net/http" 9 | "log" 10 | "strings" 11 | "os" 12 | "os/signal" 13 | "syscall" 14 | ) 15 | 16 | 17 | var _INSTANCEID string = uuid.NewV4().String() 18 | var _HEARTBEAT_MAX_CONSECUTIVE_ERRORS int = 5 19 | var _HEARTBEAT_SLEEPTIMEBETWEENHEARTBEATINSECONDS time.Duration = 10 20 | 21 | var _SECUREPORT int = 8443 22 | var _DATACENTER_NAME string = "MyOwn" 23 | 24 | var _Configuration map[string]interface{} 25 | 26 | type MyStruct struct { 27 | Name string `yml:"name"` 28 | } 29 | 30 | 31 | func Register(cfg map[string]interface{}) { 32 | _Configuration = cfg 33 | 34 | 35 | 36 | eurekaUrl := cleanEurekaUrlIfNeeded(_Configuration["eureka.client.serviceUrl.defaultZone"].(string)) 37 | 38 | conn := fargo.NewConn(eurekaUrl) 39 | instance := new(fargo.Instance) 40 | instance.App = _Configuration["spring.application.name"].(string) 41 | instance.DataCenterInfo.Name = _DATACENTER_NAME 42 | instance.HealthCheckUrl = "http://" + _Configuration["eureka.instance.ip-address"].(string) + ":" + strconv.Itoa(_Configuration["server.port"].(int)) + "/health" 43 | instance.HomePageUrl = "http://" + _Configuration["eureka.instance.ip-address"].(string) + ":" + strconv.Itoa(_Configuration["server.port"].(int)) + "/" 44 | instance.StatusPageUrl = "http://" + _Configuration["eureka.instance.ip-address"].(string) + ":" + strconv.Itoa(_Configuration["server.port"].(int)) + "/info" 45 | instance.IPAddr = _Configuration["eureka.instance.ip-address"].(string) 46 | instance.HostName = _Configuration["hostname"].(string) 47 | instance.SecurePort = _SECUREPORT 48 | instance.SecureVipAddress = _Configuration["spring.application.name"].(string) 49 | instance.VipAddress = _Configuration["spring.application.name"].(string) 50 | instance.Status = fargo.StatusType("UP") 51 | instance.SetMetadataString("instanceId", _INSTANCEID) 52 | 53 | err := conn.RegisterInstance(instance) 54 | 55 | if err != nil { 56 | log.Println("cannot register in eureka") 57 | } 58 | 59 | startHeartbeat(eurekaUrl, _Configuration["spring.application.name"].(string), _Configuration["hostname"].(string), _INSTANCEID) 60 | 61 | } 62 | 63 | func cleanEurekaUrlIfNeeded(eurekaUrl string) string { 64 | newEurekaUrl := strings.Split(eurekaUrl, ",")[0] 65 | if newEurekaUrl[len(newEurekaUrl)-1:] == "/" { 66 | newEurekaUrl = newEurekaUrl[:len(newEurekaUrl)-1] 67 | } 68 | return newEurekaUrl 69 | } 70 | 71 | func Deregister() { 72 | 73 | eurekaUrl := cleanEurekaUrlIfNeeded(_Configuration["eureka.client.serviceUrl.defaultZone"].(string)) + "/apps/" + _Configuration["spring.application.name"].(string) + "/" + _Configuration["hostname"].(string) + ":" + _INSTANCEID 74 | req, _ := http.NewRequest(http.MethodDelete, eurekaUrl, nil) 75 | res, _ := http.DefaultClient.Do(req) 76 | if res.StatusCode == http.StatusOK { 77 | log.Println("Deregistered correctly") 78 | } else { 79 | log.Println("Error while deregistering") 80 | } 81 | } 82 | 83 | func startHeartbeat(eurekaUrl string, appName string, hostname string, instance string) { 84 | consecutiveErrors := 0 85 | for { 86 | url := eurekaUrl + "/apps/" + appName + "/" + hostname + ":" + instance 87 | 88 | req, _ := http.NewRequest("PUT", url, nil) 89 | res, err := http.DefaultClient.Do(req) 90 | if err != nil || res.StatusCode != http.StatusOK { 91 | consecutiveErrors++ 92 | if consecutiveErrors >= _HEARTBEAT_MAX_CONSECUTIVE_ERRORS { 93 | Deregister() 94 | Register(_Configuration) 95 | } 96 | } else { 97 | res.Body.Close() 98 | } 99 | 100 | time.Sleep(_HEARTBEAT_SLEEPTIMEBETWEENHEARTBEATINSECONDS * time.Second) 101 | } 102 | } 103 | 104 | func CaptureInterruptSignal() { 105 | c := make(chan os.Signal, 2) 106 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 107 | <-c 108 | } -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | "net/http" 6 | "github.com/Jeffail/gabs" 7 | "io/ioutil" 8 | "reflect" 9 | "strings" 10 | "strconv" 11 | "log" 12 | "errors" 13 | ) 14 | 15 | var _DEFAULT_PORT int = 8080 16 | 17 | var Configuration map[string]interface{} = make(map[string]interface{}) 18 | 19 | 20 | func LoadConfig() error { 21 | log.Println("Loading config...") 22 | params := preloadConfigurationParams() 23 | newConfig, err := loadBasicsFromEnvironmentVars(params[0], params[1], params[2], params[3], params[4], params[5]) 24 | if err != nil { 25 | return err 26 | } 27 | err = getConfigFromSpringCloudConfigServer(newConfig[springcloudconfiguri].(string), newConfig) 28 | if err != nil { 29 | return err 30 | } 31 | Configuration = newConfig 32 | log.Println("Config loaded correctly") 33 | return nil 34 | } 35 | 36 | func loadBasicsFromEnvironmentVars(spring_profiles_active, spring_cloud_config_uri, spring_cloud_config_label, server_port, eureka_instance_ip_address, spring_application_name string) (map[string]interface{}, error) { 37 | var newConfig map[string]interface{} = make(map[string]interface{}) 38 | newConfig[springprofilesactive] = spring_profiles_active 39 | newConfig[springcloudconfiguri] = spring_cloud_config_uri 40 | newConfig[springcloudconfiglabel] = spring_cloud_config_label 41 | newConfig[serverport] = server_port 42 | newConfig[eurekainstanceipaddress] = eureka_instance_ip_address 43 | newConfig[springapplicationname] = spring_application_name 44 | newConfig[hostname], _ = os.Hostname() 45 | 46 | port, err := strconv.Atoi(newConfig["server.port"].(string)) 47 | if err != nil { 48 | newConfig[serverport] = _DEFAULT_PORT 49 | } else { 50 | newConfig[serverport] = port 51 | } 52 | 53 | if newConfig[springprofilesactive] == "" || newConfig[springcloudconfiguri] == "" || newConfig[springcloudconfiglabel] == "" || newConfig[serverport] == "" || newConfig[eurekainstanceipaddress] == 0 || newConfig[springapplicationname] == "" { 54 | return newConfig, errors.New(springprofilesactive + ", " + springcloudconfiguri + ", " + springcloudconfiglabel + ", " + serverport + ", " + eurekainstanceipaddress + ", " + springapplicationname + " environment vars are mandatories") 55 | } 56 | 57 | return newConfig, nil 58 | } 59 | 60 | func getConfigFromSpringCloudConfigServer(uriEndpoint string, newConfig map[string]interface{}) error { 61 | finalEndpoint := uriEndpoint + "/" + newConfig[springapplicationname].(string) + "/" + newConfig[springprofilesactive].(string) + "/" + newConfig[springcloudconfiglabel].(string) + "/" 62 | log.Println("Getting config from " + finalEndpoint) 63 | rs, err := getJsonFromSpringCloudConfigServer(finalEndpoint) 64 | if err != nil { 65 | finalEndpoint = uriEndpoint + "/" + newConfig[springapplicationname].(string) + "/" + newConfig[springprofilesactive].(string) + "/" 66 | log.Println("Getting config (2nd try) from " + finalEndpoint) 67 | rs, err = getJsonFromSpringCloudConfigServer(finalEndpoint) 68 | } 69 | 70 | if err != nil { 71 | return err 72 | } 73 | rewriteConfig(rs, newConfig) 74 | 75 | return nil 76 | } 77 | 78 | func rewriteConfig(container *gabs.Container, newConfig map[string]interface{}) { 79 | newConfig[label], _ = container.Path(label).Data().(string) 80 | newConfig[name], _ = container.Path(name).Data().(string) 81 | source := container.Path(propertySources).Path(source) 82 | propertySources, _ := source.Children() 83 | 84 | iterateOverEachKeyAndReplaceVars(propertySources, newConfig) 85 | replaceVars(newConfig) 86 | 87 | } 88 | 89 | func replaceVars(newConfig map[string]interface{}) { 90 | for field, value := range newConfig { 91 | if isString(value) { 92 | if strings.Contains(value.(string), "${") { 93 | modifiedValue := value.(string) 94 | splitted := strings.Split(value.(string), "${") 95 | for i:=0;i5 { 189 | for i:=0;i<6;i++ { 190 | params[i] = os.Args[i+1] 191 | } 192 | } 193 | 194 | return params 195 | } --------------------------------------------------------------------------------