diff --git a/command/agent/agent.go b/command/agent/agent.go index d4e7faa50b..138f1799c5 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -19,6 +19,7 @@ import ( "github.com/hashicorp/consul/consul/state" "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/types" "github.com/hashicorp/serf/coordinate" "github.com/hashicorp/serf/serf" ) @@ -74,19 +75,19 @@ type Agent struct { state localState // checkMonitors maps the check ID to an associated monitor - checkMonitors map[string]*CheckMonitor + checkMonitors map[types.CheckID]*CheckMonitor // checkHTTPs maps the check ID to an associated HTTP check - checkHTTPs map[string]*CheckHTTP + checkHTTPs map[types.CheckID]*CheckHTTP // checkTCPs maps the check ID to an associated TCP check - checkTCPs map[string]*CheckTCP + checkTCPs map[types.CheckID]*CheckTCP // checkTTLs maps the check ID to an associated check TTL - checkTTLs map[string]*CheckTTL + checkTTLs map[types.CheckID]*CheckTTL // checkDockers maps the check ID to an associated Docker Exec based check - checkDockers map[string]*CheckDocker + checkDockers map[types.CheckID]*CheckDocker // checkLock protects updates to the check* maps checkLock sync.Mutex @@ -176,11 +177,11 @@ func Create(config *Config, logOutput io.Writer) (*Agent, error) { config: config, logger: log.New(logOutput, "", log.LstdFlags), logOutput: logOutput, - checkMonitors: make(map[string]*CheckMonitor), - checkTTLs: make(map[string]*CheckTTL), - checkHTTPs: make(map[string]*CheckHTTP), - checkTCPs: make(map[string]*CheckTCP), - checkDockers: make(map[string]*CheckDocker), + checkMonitors: make(map[types.CheckID]*CheckMonitor), + checkTTLs: make(map[types.CheckID]*CheckTTL), + checkHTTPs: make(map[types.CheckID]*CheckHTTP), + checkTCPs: make(map[types.CheckID]*CheckTCP), + checkDockers: make(map[types.CheckID]*CheckDocker), eventCh: make(chan serf.UserEvent, 1024), eventBuf: make([]*UserEvent, 256), shutdownCh: make(chan struct{}), @@ -696,7 +697,7 @@ func (a *Agent) purgeService(serviceID string) error { // persistCheck saves a check definition to the local agent's state directory func (a *Agent) persistCheck(check *structs.HealthCheck, chkType *CheckType) error { - checkPath := filepath.Join(a.config.DataDir, checksDir, stringHash(check.CheckID)) + checkPath := filepath.Join(a.config.DataDir, checksDir, checkIDHash(check.CheckID)) // Create the persisted check wrapped := persistedCheck{ @@ -724,8 +725,8 @@ func (a *Agent) persistCheck(check *structs.HealthCheck, chkType *CheckType) err } // purgeCheck removes a persisted check definition file from the data dir -func (a *Agent) purgeCheck(checkID string) error { - checkPath := filepath.Join(a.config.DataDir, checksDir, stringHash(checkID)) +func (a *Agent) purgeCheck(checkID types.CheckID) error { + checkPath := filepath.Join(a.config.DataDir, checksDir, checkIDHash(checkID)) if _, err := os.Stat(checkPath); err == nil { return os.Remove(checkPath) } @@ -791,7 +792,7 @@ func (a *Agent) AddService(service *structs.NodeService, chkTypes CheckTypes, pe } check := &structs.HealthCheck{ Node: a.config.NodeName, - CheckID: checkID, + CheckID: types.CheckID(checkID), Name: fmt.Sprintf("Service '%s' check", service.Service), Status: structs.HealthCritical, Notes: chkType.Notes, @@ -998,7 +999,7 @@ func (a *Agent) AddCheck(check *structs.HealthCheck, chkType *CheckType, persist // RemoveCheck is used to remove a health check. // The agent will make a best effort to ensure it is deregistered -func (a *Agent) RemoveCheck(checkID string, persist bool) error { +func (a *Agent) RemoveCheck(checkID types.CheckID, persist bool) error { // Validate CheckID if checkID == "" { return fmt.Errorf("CheckID missing") @@ -1041,7 +1042,7 @@ func (a *Agent) RemoveCheck(checkID string, persist bool) error { // UpdateCheck is used to update the status of a check. // This can only be used with checks of the TTL type. -func (a *Agent) UpdateCheck(checkID, status, output string) error { +func (a *Agent) UpdateCheck(checkID types.CheckID, status, output string) error { a.checkLock.Lock() defer a.checkLock.Unlock() @@ -1090,7 +1091,7 @@ func (a *Agent) persistCheckState(check *CheckTTL, status, output string) error } // Write the state to the file - file := filepath.Join(dir, stringHash(check.CheckID)) + file := filepath.Join(dir, checkIDHash(check.CheckID)) if err := ioutil.WriteFile(file, buf, 0600); err != nil { return fmt.Errorf("failed writing file %q: %s", file, err) } @@ -1101,7 +1102,7 @@ func (a *Agent) persistCheckState(check *CheckTTL, status, output string) error // loadCheckState is used to restore the persisted state of a check. func (a *Agent) loadCheckState(check *structs.HealthCheck) error { // Try to read the persisted state for this check - file := filepath.Join(a.config.DataDir, checkStateDir, stringHash(check.CheckID)) + file := filepath.Join(a.config.DataDir, checkStateDir, checkIDHash(check.CheckID)) buf, err := ioutil.ReadFile(file) if err != nil { if os.IsNotExist(err) { @@ -1129,8 +1130,8 @@ func (a *Agent) loadCheckState(check *structs.HealthCheck) error { } // purgeCheckState is used to purge the state of a check from the data dir -func (a *Agent) purgeCheckState(checkID string) error { - file := filepath.Join(a.config.DataDir, checkStateDir, stringHash(checkID)) +func (a *Agent) purgeCheckState(checkID types.CheckID) error { + file := filepath.Join(a.config.DataDir, checkStateDir, checkIDHash(checkID)) err := os.Remove(file) if os.IsNotExist(err) { return nil @@ -1393,22 +1394,22 @@ func (a *Agent) unloadChecks() error { // snapshotCheckState is used to snapshot the current state of the health // checks. This is done before we reload our checks, so that we can properly // restore into the same state. -func (a *Agent) snapshotCheckState() map[string]*structs.HealthCheck { +func (a *Agent) snapshotCheckState() map[types.CheckID]*structs.HealthCheck { return a.state.Checks() } // restoreCheckState is used to reset the health state based on a snapshot. // This is done after we finish the reload to avoid any unnecessary flaps // in health state and potential session invalidations. -func (a *Agent) restoreCheckState(snap map[string]*structs.HealthCheck) { +func (a *Agent) restoreCheckState(snap map[types.CheckID]*structs.HealthCheck) { for id, check := range snap { a.state.UpdateCheck(id, check.Status, check.Output) } } // serviceMaintCheckID returns the ID of a given service's maintenance check -func serviceMaintCheckID(serviceID string) string { - return fmt.Sprintf("%s:%s", serviceMaintCheckPrefix, serviceID) +func serviceMaintCheckID(serviceID string) types.CheckID { + return types.CheckID(fmt.Sprintf("%s:%s", serviceMaintCheckPrefix, serviceID)) } // EnableServiceMaintenance will register a false health check against the given diff --git a/command/agent/agent_endpoint.go b/command/agent/agent_endpoint.go index b897d52427..83e2e94cbe 100644 --- a/command/agent/agent_endpoint.go +++ b/command/agent/agent_endpoint.go @@ -2,12 +2,14 @@ package agent import ( "fmt" - "github.com/hashicorp/consul/consul/structs" - "github.com/hashicorp/serf/coordinate" - "github.com/hashicorp/serf/serf" "net/http" "strconv" "strings" + + "github.com/hashicorp/consul/consul/structs" + "github.com/hashicorp/consul/types" + "github.com/hashicorp/serf/coordinate" + "github.com/hashicorp/serf/serf" ) type AgentSelf struct { @@ -129,7 +131,7 @@ func (s *HTTPServer) AgentRegisterCheck(resp http.ResponseWriter, req *http.Requ } func (s *HTTPServer) AgentDeregisterCheck(resp http.ResponseWriter, req *http.Request) (interface{}, error) { - checkID := strings.TrimPrefix(req.URL.Path, "/v1/agent/check/deregister/") + checkID := types.CheckID(strings.TrimPrefix(req.URL.Path, "/v1/agent/check/deregister/")) if err := s.agent.RemoveCheck(checkID, true); err != nil { return nil, err } @@ -138,7 +140,7 @@ func (s *HTTPServer) AgentDeregisterCheck(resp http.ResponseWriter, req *http.Re } func (s *HTTPServer) AgentCheckPass(resp http.ResponseWriter, req *http.Request) (interface{}, error) { - checkID := strings.TrimPrefix(req.URL.Path, "/v1/agent/check/pass/") + checkID := types.CheckID(strings.TrimPrefix(req.URL.Path, "/v1/agent/check/pass/")) note := req.URL.Query().Get("note") if err := s.agent.UpdateCheck(checkID, structs.HealthPassing, note); err != nil { return nil, err @@ -148,7 +150,7 @@ func (s *HTTPServer) AgentCheckPass(resp http.ResponseWriter, req *http.Request) } func (s *HTTPServer) AgentCheckWarn(resp http.ResponseWriter, req *http.Request) (interface{}, error) { - checkID := strings.TrimPrefix(req.URL.Path, "/v1/agent/check/warn/") + checkID := types.CheckID(strings.TrimPrefix(req.URL.Path, "/v1/agent/check/warn/")) note := req.URL.Query().Get("note") if err := s.agent.UpdateCheck(checkID, structs.HealthWarning, note); err != nil { return nil, err @@ -158,7 +160,7 @@ func (s *HTTPServer) AgentCheckWarn(resp http.ResponseWriter, req *http.Request) } func (s *HTTPServer) AgentCheckFail(resp http.ResponseWriter, req *http.Request) (interface{}, error) { - checkID := strings.TrimPrefix(req.URL.Path, "/v1/agent/check/fail/") + checkID := types.CheckID(strings.TrimPrefix(req.URL.Path, "/v1/agent/check/fail/")) note := req.URL.Query().Get("note") if err := s.agent.UpdateCheck(checkID, structs.HealthCritical, note); err != nil { return nil, err @@ -211,7 +213,7 @@ func (s *HTTPServer) AgentCheckUpdate(resp http.ResponseWriter, req *http.Reques update.Output[:CheckBufSize], CheckBufSize, total) } - checkID := strings.TrimPrefix(req.URL.Path, "/v1/agent/check/update/") + checkID := types.CheckID(strings.TrimPrefix(req.URL.Path, "/v1/agent/check/update/")) if err := s.agent.UpdateCheck(checkID, update.Status, update.Output); err != nil { return nil, err } diff --git a/command/agent/agent_endpoint_test.go b/command/agent/agent_endpoint_test.go index d11842fc1a..9b8cdd4783 100644 --- a/command/agent/agent_endpoint_test.go +++ b/command/agent/agent_endpoint_test.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/consul/testutil" + "github.com/hashicorp/consul/types" "github.com/hashicorp/serf/serf" ) @@ -61,7 +62,7 @@ func TestHTTPAgentChecks(t *testing.T) { if err != nil { t.Fatalf("Err: %v", err) } - val := obj.(map[string]*structs.HealthCheck) + val := obj.(map[types.CheckID]*structs.HealthCheck) if len(val) != 1 { t.Fatalf("bad checks: %v", obj) } @@ -294,21 +295,22 @@ func TestHTTPAgentRegisterCheck(t *testing.T) { } // Ensure we have a check mapping - if _, ok := srv.agent.state.Checks()["test"]; !ok { + checkID := types.CheckID("test") + if _, ok := srv.agent.state.Checks()[checkID]; !ok { t.Fatalf("missing test check") } - if _, ok := srv.agent.checkTTLs["test"]; !ok { + if _, ok := srv.agent.checkTTLs[checkID]; !ok { t.Fatalf("missing test check ttl") } // Ensure the token was configured - if token := srv.agent.state.CheckToken("test"); token == "" { + if token := srv.agent.state.CheckToken(checkID); token == "" { t.Fatalf("missing token") } // By default, checks start in critical state. - state := srv.agent.state.Checks()["test"] + state := srv.agent.state.Checks()[checkID] if state.Status != structs.HealthCritical { t.Fatalf("bad: %v", state) } @@ -343,15 +345,16 @@ func TestHTTPAgentRegisterCheckPassing(t *testing.T) { } // Ensure we have a check mapping - if _, ok := srv.agent.state.Checks()["test"]; !ok { + checkID := types.CheckID("test") + if _, ok := srv.agent.state.Checks()[checkID]; !ok { t.Fatalf("missing test check") } - if _, ok := srv.agent.checkTTLs["test"]; !ok { + if _, ok := srv.agent.checkTTLs[checkID]; !ok { t.Fatalf("missing test check ttl") } - state := srv.agent.state.Checks()["test"] + state := srv.agent.state.Checks()[checkID] if state.Status != structs.HealthPassing { t.Fatalf("bad: %v", state) } diff --git a/command/agent/agent_test.go b/command/agent/agent_test.go index baf5c5dc53..c7331aa7a9 100644 --- a/command/agent/agent_test.go +++ b/command/agent/agent_test.go @@ -919,7 +919,7 @@ func TestAgent_PersistCheck(t *testing.T) { Interval: 10 * time.Second, } - file := filepath.Join(agent.config.DataDir, checksDir, stringHash(check.CheckID)) + file := filepath.Join(agent.config.DataDir, checksDir, checkIDHash(check.CheckID)) // Not persisted if not requested if err := agent.AddCheck(check, chkType, false, ""); err != nil { @@ -1014,7 +1014,7 @@ func TestAgent_PurgeCheck(t *testing.T) { Status: structs.HealthPassing, } - file := filepath.Join(agent.config.DataDir, checksDir, stringHash(check.CheckID)) + file := filepath.Join(agent.config.DataDir, checksDir, checkIDHash(check.CheckID)) if err := agent.AddCheck(check, nil, true, ""); err != nil { t.Fatalf("err: %v", err) } @@ -1074,7 +1074,7 @@ func TestAgent_PurgeCheckOnDuplicate(t *testing.T) { } defer agent2.Shutdown() - file := filepath.Join(agent.config.DataDir, checksDir, stringHash(check1.CheckID)) + file := filepath.Join(agent.config.DataDir, checksDir, checkIDHash(check1.CheckID)) if _, err := os.Stat(file); err == nil { t.Fatalf("should have removed persisted check") } @@ -1465,7 +1465,7 @@ func TestAgent_loadChecks_checkFails(t *testing.T) { } // Check to make sure the check was persisted - checkHash := stringHash(check.CheckID) + checkHash := checkIDHash(check.CheckID) checkPath := filepath.Join(config.DataDir, checksDir, checkHash) if _, err := os.Stat(checkPath); err != nil { t.Fatalf("err: %s", err) diff --git a/command/agent/check.go b/command/agent/check.go index 515fade406..a0102b9914 100644 --- a/command/agent/check.go +++ b/command/agent/check.go @@ -16,6 +16,7 @@ import ( docker "github.com/fsouza/go-dockerclient" "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/types" "github.com/hashicorp/go-cleanhttp" ) @@ -90,7 +91,7 @@ func (c *CheckType) IsDocker() bool { // to notify when a check has a status update. The update // should take care to be idempotent. type CheckNotifier interface { - UpdateCheck(checkID, status, output string) + UpdateCheck(checkID types.CheckID, status, output string) } // CheckMonitor is used to periodically invoke a script to @@ -98,7 +99,7 @@ type CheckNotifier interface { // nagios plugins and expects the output in the same format. type CheckMonitor struct { Notify CheckNotifier - CheckID string + CheckID types.CheckID Script string Interval time.Duration Timeout time.Duration @@ -231,7 +232,7 @@ func (c *CheckMonitor) check() { // automatically set to critical. type CheckTTL struct { Notify CheckNotifier - CheckID string + CheckID types.CheckID TTL time.Duration Logger *log.Logger @@ -322,7 +323,7 @@ type persistedCheck struct { // expiration timestamp which is used to determine staleness on later // agent restarts. type persistedCheckState struct { - CheckID string + CheckID types.CheckID Output string Status string Expires int64 @@ -336,7 +337,7 @@ type persistedCheckState struct { // or if the request returns an error type CheckHTTP struct { Notify CheckNotifier - CheckID string + CheckID types.CheckID HTTP string Interval time.Duration Timeout time.Duration @@ -462,7 +463,7 @@ func (c *CheckHTTP) check() { // The check is critical if the connection returns an error type CheckTCP struct { Notify CheckNotifier - CheckID string + CheckID types.CheckID TCP string Interval time.Duration Timeout time.Duration @@ -553,7 +554,7 @@ type DockerClient interface { // with nagios plugins and expects the output in the same format. type CheckDocker struct { Notify CheckNotifier - CheckID string + CheckID types.CheckID Script string DockerContainerID string Shell string diff --git a/command/agent/check_test.go b/command/agent/check_test.go index 7983014223..53d203eebe 100644 --- a/command/agent/check_test.go +++ b/command/agent/check_test.go @@ -18,15 +18,16 @@ import ( docker "github.com/fsouza/go-dockerclient" "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/consul/testutil" + "github.com/hashicorp/consul/types" ) type MockNotify struct { - state map[string]string - updates map[string]int - output map[string]string + state map[types.CheckID]string + updates map[types.CheckID]int + output map[types.CheckID]string } -func (m *MockNotify) UpdateCheck(id, status, output string) { +func (m *MockNotify) UpdateCheck(id types.CheckID, status, output string) { m.state[id] = status old := m.updates[id] m.updates[id] = old + 1 @@ -35,13 +36,13 @@ func (m *MockNotify) UpdateCheck(id, status, output string) { func expectStatus(t *testing.T, script, status string) { mock := &MockNotify{ - state: make(map[string]string), - updates: make(map[string]int), - output: make(map[string]string), + state: make(map[types.CheckID]string), + updates: make(map[types.CheckID]int), + output: make(map[types.CheckID]string), } check := &CheckMonitor{ Notify: mock, - CheckID: "foo", + CheckID: types.CheckID("foo"), Script: script, Interval: 10 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), @@ -84,13 +85,13 @@ func TestCheckMonitor_BadCmd(t *testing.T) { func TestCheckMonitor_Timeout(t *testing.T) { mock := &MockNotify{ - state: make(map[string]string), - updates: make(map[string]int), - output: make(map[string]string), + state: make(map[types.CheckID]string), + updates: make(map[types.CheckID]int), + output: make(map[types.CheckID]string), } check := &CheckMonitor{ Notify: mock, - CheckID: "foo", + CheckID: types.CheckID("foo"), Script: "sleep 1 && exit 0", Interval: 10 * time.Millisecond, Timeout: 5 * time.Millisecond, @@ -114,13 +115,13 @@ func TestCheckMonitor_Timeout(t *testing.T) { func TestCheckMonitor_RandomStagger(t *testing.T) { mock := &MockNotify{ - state: make(map[string]string), - updates: make(map[string]int), - output: make(map[string]string), + state: make(map[types.CheckID]string), + updates: make(map[types.CheckID]int), + output: make(map[types.CheckID]string), } check := &CheckMonitor{ Notify: mock, - CheckID: "foo", + CheckID: types.CheckID("foo"), Script: "exit 0", Interval: 25 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), @@ -143,13 +144,13 @@ func TestCheckMonitor_RandomStagger(t *testing.T) { func TestCheckMonitor_LimitOutput(t *testing.T) { mock := &MockNotify{ - state: make(map[string]string), - updates: make(map[string]int), - output: make(map[string]string), + state: make(map[types.CheckID]string), + updates: make(map[types.CheckID]int), + output: make(map[types.CheckID]string), } check := &CheckMonitor{ Notify: mock, - CheckID: "foo", + CheckID: types.CheckID("foo"), Script: "od -N 81920 /dev/urandom", Interval: 25 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), @@ -168,13 +169,13 @@ func TestCheckMonitor_LimitOutput(t *testing.T) { func TestCheckTTL(t *testing.T) { mock := &MockNotify{ - state: make(map[string]string), - updates: make(map[string]int), - output: make(map[string]string), + state: make(map[types.CheckID]string), + updates: make(map[types.CheckID]int), + output: make(map[types.CheckID]string), } check := &CheckTTL{ Notify: mock, - CheckID: "foo", + CheckID: types.CheckID("foo"), TTL: 100 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), } @@ -229,13 +230,13 @@ func mockHTTPServer(responseCode int) *httptest.Server { func expectHTTPStatus(t *testing.T, url string, status string) { mock := &MockNotify{ - state: make(map[string]string), - updates: make(map[string]int), - output: make(map[string]string), + state: make(map[types.CheckID]string), + updates: make(map[types.CheckID]int), + output: make(map[types.CheckID]string), } check := &CheckHTTP{ Notify: mock, - CheckID: "foo", + CheckID: types.CheckID("foo"), HTTP: url, Interval: 10 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), @@ -329,14 +330,14 @@ func TestCheckHTTPTimeout(t *testing.T) { defer server.Close() mock := &MockNotify{ - state: make(map[string]string), - updates: make(map[string]int), - output: make(map[string]string), + state: make(map[types.CheckID]string), + updates: make(map[types.CheckID]int), + output: make(map[types.CheckID]string), } check := &CheckHTTP{ Notify: mock, - CheckID: "bar", + CheckID: types.CheckID("bar"), HTTP: server.URL, Timeout: 5 * time.Millisecond, Interval: 10 * time.Millisecond, @@ -360,7 +361,7 @@ func TestCheckHTTPTimeout(t *testing.T) { func TestCheckHTTP_disablesKeepAlives(t *testing.T) { check := &CheckHTTP{ - CheckID: "foo", + CheckID: types.CheckID("foo"), HTTP: "http://foo.bar/baz", Interval: 10 * time.Second, Logger: log.New(os.Stderr, "", log.LstdFlags), @@ -395,13 +396,13 @@ func mockTCPServer(network string) net.Listener { func expectTCPStatus(t *testing.T, tcp string, status string) { mock := &MockNotify{ - state: make(map[string]string), - updates: make(map[string]int), - output: make(map[string]string), + state: make(map[types.CheckID]string), + updates: make(map[types.CheckID]int), + output: make(map[types.CheckID]string), } check := &CheckTCP{ Notify: mock, - CheckID: "foo", + CheckID: types.CheckID("foo"), TCP: tcp, Interval: 10 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), @@ -575,13 +576,13 @@ func (d *fakeDockerClientWithExecInfoErrors) InspectExec(id string) (*docker.Exe func expectDockerCheckStatus(t *testing.T, dockerClient DockerClient, status string, output string) { mock := &MockNotify{ - state: make(map[string]string), - updates: make(map[string]int), - output: make(map[string]string), + state: make(map[types.CheckID]string), + updates: make(map[types.CheckID]int), + output: make(map[types.CheckID]string), } check := &CheckDocker{ Notify: mock, - CheckID: "foo", + CheckID: types.CheckID("foo"), Script: "/health.sh", DockerContainerID: "54432bad1fc7", Shell: "/bin/sh", @@ -635,13 +636,13 @@ func TestDockerCheckWhenExecInfoFails(t *testing.T) { func TestDockerCheckDefaultToSh(t *testing.T) { os.Setenv("SHELL", "") mock := &MockNotify{ - state: make(map[string]string), - updates: make(map[string]int), - output: make(map[string]string), + state: make(map[types.CheckID]string), + updates: make(map[types.CheckID]int), + output: make(map[types.CheckID]string), } check := &CheckDocker{ Notify: mock, - CheckID: "foo", + CheckID: types.CheckID("foo"), Script: "/health.sh", DockerContainerID: "54432bad1fc7", Interval: 10 * time.Millisecond, @@ -659,14 +660,14 @@ func TestDockerCheckDefaultToSh(t *testing.T) { func TestDockerCheckUseShellFromEnv(t *testing.T) { mock := &MockNotify{ - state: make(map[string]string), - updates: make(map[string]int), - output: make(map[string]string), + state: make(map[types.CheckID]string), + updates: make(map[types.CheckID]int), + output: make(map[types.CheckID]string), } os.Setenv("SHELL", "/bin/bash") check := &CheckDocker{ Notify: mock, - CheckID: "foo", + CheckID: types.CheckID("foo"), Script: "/health.sh", DockerContainerID: "54432bad1fc7", Interval: 10 * time.Millisecond, @@ -685,13 +686,13 @@ func TestDockerCheckUseShellFromEnv(t *testing.T) { func TestDockerCheckTruncateOutput(t *testing.T) { mock := &MockNotify{ - state: make(map[string]string), - updates: make(map[string]int), - output: make(map[string]string), + state: make(map[types.CheckID]string), + updates: make(map[types.CheckID]int), + output: make(map[types.CheckID]string), } check := &CheckDocker{ Notify: mock, - CheckID: "foo", + CheckID: types.CheckID("foo"), Script: "/health.sh", DockerContainerID: "54432bad1fc7", Shell: "/bin/sh", diff --git a/command/agent/local.go b/command/agent/local.go index ed67258c53..6c6ae1362d 100644 --- a/command/agent/local.go +++ b/command/agent/local.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/consul/consul" "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/types" ) const ( @@ -56,12 +57,12 @@ type localState struct { serviceTokens map[string]string // Checks tracks the local checks - checks map[string]*structs.HealthCheck - checkStatus map[string]syncStatus - checkTokens map[string]string + checks map[types.CheckID]*structs.HealthCheck + checkStatus map[types.CheckID]syncStatus + checkTokens map[types.CheckID]string // Used to track checks that are being deferred - deferCheck map[string]*time.Timer + deferCheck map[types.CheckID]*time.Timer // consulCh is used to inform of a change to the known // consul nodes. This may be used to retry a sync run @@ -79,10 +80,10 @@ func (l *localState) Init(config *Config, logger *log.Logger) { l.services = make(map[string]*structs.NodeService) l.serviceStatus = make(map[string]syncStatus) l.serviceTokens = make(map[string]string) - l.checks = make(map[string]*structs.HealthCheck) - l.checkStatus = make(map[string]syncStatus) - l.checkTokens = make(map[string]string) - l.deferCheck = make(map[string]*time.Timer) + l.checks = make(map[types.CheckID]*structs.HealthCheck) + l.checkStatus = make(map[types.CheckID]syncStatus) + l.checkTokens = make(map[types.CheckID]string) + l.deferCheck = make(map[types.CheckID]*time.Timer) l.consulCh = make(chan struct{}, 1) l.triggerCh = make(chan struct{}, 1) } @@ -191,17 +192,17 @@ func (l *localState) Services() map[string]*structs.NodeService { return services } -// CheckToken is used to return the configured health check token, or -// if none is configured, the default agent ACL token. -func (l *localState) CheckToken(id string) string { +// CheckToken is used to return the configured health check token for a +// Check, or if none is configured, the default agent ACL token. +func (l *localState) CheckToken(checkID types.CheckID) string { l.RLock() defer l.RUnlock() - return l.checkToken(id) + return l.checkToken(checkID) } // checkToken returns an ACL token associated with a check. -func (l *localState) checkToken(id string) string { - token := l.checkTokens[id] +func (l *localState) checkToken(checkID types.CheckID) string { + token := l.checkTokens[checkID] if token == "" { token = l.config.ACLToken } @@ -226,7 +227,7 @@ func (l *localState) AddCheck(check *structs.HealthCheck, token string) { // RemoveCheck is used to remove a health check from the local state. // The agent will make a best effort to ensure it is deregistered -func (l *localState) RemoveCheck(checkID string) { +func (l *localState) RemoveCheck(checkID types.CheckID) { l.Lock() defer l.Unlock() @@ -237,7 +238,7 @@ func (l *localState) RemoveCheck(checkID string) { } // UpdateCheck is used to update the status of a check -func (l *localState) UpdateCheck(checkID, status, output string) { +func (l *localState) UpdateCheck(checkID types.CheckID, status, output string) { l.Lock() defer l.Unlock() @@ -282,13 +283,13 @@ func (l *localState) UpdateCheck(checkID, status, output string) { // Checks returns the locally registered checks that the // agent is aware of and are being kept in sync with the server -func (l *localState) Checks() map[string]*structs.HealthCheck { - checks := make(map[string]*structs.HealthCheck) +func (l *localState) Checks() map[types.CheckID]*structs.HealthCheck { + checks := make(map[types.CheckID]*structs.HealthCheck) l.RLock() defer l.RUnlock() - for name, check := range l.checks { - checks[name] = check + for checkID, check := range l.checks { + checks[checkID] = check } return checks } @@ -406,7 +407,7 @@ func (l *localState) setSyncState() error { } // Index the remote health checks to improve efficiency - checkIndex := make(map[string]*structs.HealthCheck, len(checks)) + checkIndex := make(map[types.CheckID]*structs.HealthCheck, len(checks)) for _, check := range checks { checkIndex[check.CheckID] = check } @@ -546,7 +547,7 @@ func (l *localState) deleteService(id string) error { } // deleteCheck is used to delete a service from the server -func (l *localState) deleteCheck(id string) error { +func (l *localState) deleteCheck(id types.CheckID) error { if id == "" { return fmt.Errorf("CheckID missing") } @@ -619,7 +620,7 @@ func (l *localState) syncService(id string) error { } // syncCheck is used to sync a check to the server -func (l *localState) syncCheck(id string) error { +func (l *localState) syncCheck(id types.CheckID) error { // Pull in the associated service if any check := l.checks[id] var service *structs.NodeService diff --git a/command/agent/session_endpoint.go b/command/agent/session_endpoint.go index 0f2685465f..4049cf7d6b 100644 --- a/command/agent/session_endpoint.go +++ b/command/agent/session_endpoint.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/consul/consul" "github.com/hashicorp/consul/consul/structs" + "github.com/hashicorp/consul/types" ) const ( @@ -38,7 +39,7 @@ func (s *HTTPServer) SessionCreate(resp http.ResponseWriter, req *http.Request) Op: structs.SessionCreate, Session: structs.Session{ Node: s.agent.config.NodeName, - Checks: []string{consul.SerfCheckID}, + Checks: []types.CheckID{consul.SerfCheckID}, LockDelay: 15 * time.Second, Behavior: structs.SessionKeysRelease, TTL: "", diff --git a/command/agent/session_endpoint_test.go b/command/agent/session_endpoint_test.go index c7f744c84a..d64f6b41eb 100644 --- a/command/agent/session_endpoint_test.go +++ b/command/agent/session_endpoint_test.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/consul/consul" "github.com/hashicorp/consul/consul/structs" + "github.com/hashicorp/consul/types" ) func TestSessionCreate(t *testing.T) { @@ -38,7 +39,7 @@ func TestSessionCreate(t *testing.T) { raw := map[string]interface{}{ "Name": "my-cool-session", "Node": srv.agent.config.NodeName, - "Checks": []string{consul.SerfCheckID, "consul"}, + "Checks": []types.CheckID{consul.SerfCheckID, "consul"}, "LockDelay": "20s", } enc.Encode(raw) @@ -86,7 +87,7 @@ func TestSessionCreateDelete(t *testing.T) { raw := map[string]interface{}{ "Name": "my-cool-session", "Node": srv.agent.config.NodeName, - "Checks": []string{consul.SerfCheckID, "consul"}, + "Checks": []types.CheckID{consul.SerfCheckID, "consul"}, "LockDelay": "20s", "Behavior": structs.SessionKeysDelete, } diff --git a/command/agent/structs.go b/command/agent/structs.go index 5ebc3b4559..a0f4c6bcb2 100644 --- a/command/agent/structs.go +++ b/command/agent/structs.go @@ -2,6 +2,7 @@ package agent import ( "github.com/hashicorp/consul/consul/structs" + "github.com/hashicorp/consul/types" ) // ServiceDefinition is used to JSON decode the Service definitions @@ -42,9 +43,9 @@ func (s *ServiceDefinition) CheckTypes() (checks CheckTypes) { return } -// ChecKDefinition is used to JSON decode the Check definitions +// CheckDefinition is used to JSON decode the Check definitions type CheckDefinition struct { - ID string + ID types.CheckID Name string Notes string ServiceID string @@ -66,7 +67,7 @@ func (c *CheckDefinition) HealthCheck(node string) *structs.HealthCheck { health.Status = c.Status } if health.CheckID == "" && health.Name != "" { - health.CheckID = health.Name + health.CheckID = types.CheckID(health.Name) } return health } diff --git a/command/agent/util.go b/command/agent/util.go index e8d802d786..3916c70c97 100644 --- a/command/agent/util.go +++ b/command/agent/util.go @@ -12,6 +12,7 @@ import ( "strconv" "time" + "github.com/hashicorp/consul/types" "github.com/hashicorp/go-msgpack/codec" ) @@ -71,6 +72,11 @@ func stringHash(s string) string { return fmt.Sprintf("%x", md5.Sum([]byte(s))) } +// checkIDHash returns a simple md5sum for a types.CheckID. +func checkIDHash(checkID types.CheckID) string { + return stringHash(string(checkID)) +} + // FilePermissions is an interface which allows a struct to set // ownership and permissions easily on a file it describes. type FilePermissions interface { diff --git a/command/maint.go b/command/maint.go index e1319db526..0151465650 100644 --- a/command/maint.go +++ b/command/maint.go @@ -117,7 +117,7 @@ func (c *MaintCommand) Run(args []string) int { c.Ui.Output(" Name: " + nodeName) c.Ui.Output(" Reason: " + check.Notes) c.Ui.Output("") - } else if strings.HasPrefix(check.CheckID, "_service_maintenance:") { + } else if strings.HasPrefix(string(check.CheckID), "_service_maintenance:") { c.Ui.Output("Service:") c.Ui.Output(" ID: " + check.ServiceID) c.Ui.Output(" Reason: " + check.Notes) diff --git a/consul/catalog_endpoint.go b/consul/catalog_endpoint.go index ec8389dc1b..06f81993b9 100644 --- a/consul/catalog_endpoint.go +++ b/consul/catalog_endpoint.go @@ -6,6 +6,7 @@ import ( "github.com/armon/go-metrics" "github.com/hashicorp/consul/consul/structs" + "github.com/hashicorp/consul/types" ) // Catalog endpoint is used to manipulate the service catalog @@ -57,7 +58,7 @@ func (c *Catalog) Register(args *structs.RegisterRequest, reply *struct{}) error } for _, check := range args.Checks { if check.CheckID == "" && check.Name != "" { - check.CheckID = check.Name + check.CheckID = types.CheckID(check.Name) } if check.Node == "" { check.Node = args.Node diff --git a/consul/fsm_test.go b/consul/fsm_test.go index 44c85e43e2..2f63fd89ac 100644 --- a/consul/fsm_test.go +++ b/consul/fsm_test.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/consul/consul/state" "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/types" "github.com/hashicorp/go-uuid" "github.com/hashicorp/raft" ) @@ -860,7 +861,7 @@ func TestFSM_SessionCreate_Destroy(t *testing.T) { Session: structs.Session{ ID: generateUUID(), Node: "foo", - Checks: []string{"web"}, + Checks: []types.CheckID{"web"}, }, } buf, err := structs.Encode(structs.SessionRequestType, req) diff --git a/consul/leader.go b/consul/leader.go index 8aa1f19948..790c3c330a 100644 --- a/consul/leader.go +++ b/consul/leader.go @@ -10,18 +10,19 @@ import ( "github.com/armon/go-metrics" "github.com/hashicorp/consul/consul/agent" "github.com/hashicorp/consul/consul/structs" + "github.com/hashicorp/consul/types" "github.com/hashicorp/raft" "github.com/hashicorp/serf/serf" ) const ( - SerfCheckID = "serfHealth" - SerfCheckName = "Serf Health Status" - SerfCheckAliveOutput = "Agent alive and reachable" - SerfCheckFailedOutput = "Agent not live or unreachable" - ConsulServiceID = "consul" - ConsulServiceName = "consul" - newLeaderEvent = "consul:new-leader" + SerfCheckID types.CheckID = "serfHealth" + SerfCheckName = "Serf Health Status" + SerfCheckAliveOutput = "Agent alive and reachable" + SerfCheckFailedOutput = "Agent not live or unreachable" + ConsulServiceID = "consul" + ConsulServiceName = "consul" + newLeaderEvent = "consul:new-leader" ) // monitorLeadership is used to monitor if we acquire or lose our role diff --git a/consul/state/state_store.go b/consul/state/state_store.go index a5b4b2f837..7c48a1feee 100644 --- a/consul/state/state_store.go +++ b/consul/state/state_store.go @@ -7,6 +7,7 @@ import ( "time" "github.com/hashicorp/consul/consul/structs" + "github.com/hashicorp/consul/types" "github.com/hashicorp/go-memdb" "github.com/hashicorp/serf/coordinate" ) @@ -82,7 +83,7 @@ type IndexEntry struct { // store and thus it is not exported. type sessionCheck struct { Node string - CheckID string + CheckID types.CheckID Session string } @@ -594,7 +595,7 @@ func (s *StateStore) deleteNodeTxn(tx *memdb.Txn, idx uint64, nodeID string) err if err != nil { return fmt.Errorf("failed check lookup: %s", err) } - var cids []string + var cids []types.CheckID for check := checks.Next(); check != nil; check = checks.Next() { cids = append(cids, check.(*structs.HealthCheck).CheckID) } @@ -917,7 +918,7 @@ func (s *StateStore) deleteServiceTxn(tx *memdb.Txn, idx uint64, watches *DumbWa if err != nil { return fmt.Errorf("failed service check lookup: %s", err) } - var cids []string + var cids []types.CheckID for check := checks.Next(); check != nil; check = checks.Next() { cids = append(cids, check.(*structs.HealthCheck).CheckID) } @@ -968,7 +969,7 @@ func (s *StateStore) EnsureCheck(idx uint64, hc *structs.HealthCheck) error { func (s *StateStore) ensureCheckTxn(tx *memdb.Txn, idx uint64, watches *DumbWatchManager, hc *structs.HealthCheck) error { // Check if we have an existing health check - existing, err := tx.First("checks", "id", hc.Node, hc.CheckID) + existing, err := tx.First("checks", "id", hc.Node, string(hc.CheckID)) if err != nil { return fmt.Errorf("failed health check lookup: %s", err) } @@ -1013,7 +1014,7 @@ func (s *StateStore) ensureCheckTxn(tx *memdb.Txn, idx uint64, watches *DumbWatc // Delete any sessions for this check if the health is critical. if hc.Status == structs.HealthCritical { - mappings, err := tx.Get("session_checks", "node_check", hc.Node, hc.CheckID) + mappings, err := tx.Get("session_checks", "node_check", hc.Node, string(hc.CheckID)) if err != nil { return fmt.Errorf("failed session checks lookup: %s", err) } @@ -1119,13 +1120,13 @@ func (s *StateStore) parseChecks(idx uint64, iter memdb.ResultIterator) (uint64, } // DeleteCheck is used to delete a health check registration. -func (s *StateStore) DeleteCheck(idx uint64, node, id string) error { +func (s *StateStore) DeleteCheck(idx uint64, node string, checkID types.CheckID) error { tx := s.db.Txn(true) defer tx.Abort() // Call the check deletion watches := NewDumbWatchManager(s.tableWatches) - if err := s.deleteCheckTxn(tx, idx, watches, node, id); err != nil { + if err := s.deleteCheckTxn(tx, idx, watches, node, checkID); err != nil { return err } @@ -1136,9 +1137,9 @@ func (s *StateStore) DeleteCheck(idx uint64, node, id string) error { // deleteCheckTxn is the inner method used to call a health // check deletion within an existing transaction. -func (s *StateStore) deleteCheckTxn(tx *memdb.Txn, idx uint64, watches *DumbWatchManager, node, id string) error { +func (s *StateStore) deleteCheckTxn(tx *memdb.Txn, idx uint64, watches *DumbWatchManager, node string, checkID types.CheckID) error { // Try to retrieve the existing health check. - hc, err := tx.First("checks", "id", node, id) + hc, err := tx.First("checks", "id", node, string(checkID)) if err != nil { return fmt.Errorf("check lookup failed: %s", err) } @@ -1155,7 +1156,7 @@ func (s *StateStore) deleteCheckTxn(tx *memdb.Txn, idx uint64, watches *DumbWatc } // Delete any sessions for this check. - mappings, err := tx.Get("session_checks", "node_check", node, id) + mappings, err := tx.Get("session_checks", "node_check", node, string(checkID)) if err != nil { return fmt.Errorf("failed session checks lookup: %s", err) } @@ -1412,7 +1413,7 @@ func (s *StateStore) sessionCreateTxn(tx *memdb.Txn, idx uint64, sess *structs.S // Go over the session checks and ensure they exist. for _, checkID := range sess.Checks { - check, err := tx.First("checks", "id", sess.Node, checkID) + check, err := tx.First("checks", "id", sess.Node, string(checkID)) if err != nil { return fmt.Errorf("failed check lookup: %s", err) } diff --git a/consul/state/state_store_test.go b/consul/state/state_store_test.go index d88275642f..246dfbc80b 100644 --- a/consul/state/state_store_test.go +++ b/consul/state/state_store_test.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/types" "github.com/hashicorp/serf/coordinate" ) @@ -82,7 +83,7 @@ func testRegisterService(t *testing.T, s *StateStore, idx uint64, nodeID, servic } func testRegisterCheck(t *testing.T, s *StateStore, idx uint64, - nodeID, serviceID, checkID, state string) { + nodeID string, serviceID string, checkID types.CheckID, state string) { chk := &structs.HealthCheck{ Node: nodeID, CheckID: checkID, @@ -95,7 +96,7 @@ func testRegisterCheck(t *testing.T, s *StateStore, idx uint64, tx := s.db.Txn(false) defer tx.Abort() - c, err := tx.First("checks", "id", nodeID, checkID) + c, err := tx.First("checks", "id", nodeID, string(checkID)) if err != nil { t.Fatalf("err: %s", err) } @@ -2150,7 +2151,7 @@ func TestStateStore_SessionCreate_SessionGet(t *testing.T) { sess = &structs.Session{ ID: testUUID(), Node: "node1", - Checks: []string{"check1"}, + Checks: []types.CheckID{"check1"}, } err = s.SessionCreate(3, sess) if err == nil || !strings.Contains(err.Error(), "Missing check") { @@ -2175,7 +2176,7 @@ func TestStateStore_SessionCreate_SessionGet(t *testing.T) { sess2 := &structs.Session{ ID: testUUID(), Node: "node1", - Checks: []string{"check1", "check2"}, + Checks: []types.CheckID{"check1", "check2"}, } if err := s.SessionCreate(6, sess2); err != nil { t.Fatalf("err: %s", err) @@ -2209,7 +2210,7 @@ func TestStateStore_SessionCreate_SessionGet(t *testing.T) { for i, check := 0, checks.Next(); check != nil; i, check = i+1, checks.Next() { expectCheck := &sessionCheck{ Node: "node1", - CheckID: fmt.Sprintf("check%d", i+1), + CheckID: types.CheckID(fmt.Sprintf("check%d", i+1)), Session: sess2.ID, } if actual := check.(*sessionCheck); !reflect.DeepEqual(actual, expectCheck) { @@ -2407,7 +2408,7 @@ func TestStateStore_Session_Snapshot_Restore(t *testing.T) { ID: session1, Node: "node1", Behavior: structs.SessionKeysDelete, - Checks: []string{"check1"}, + Checks: []types.CheckID{"check1"}, }, &structs.Session{ ID: testUUID(), @@ -2624,7 +2625,7 @@ func TestStateStore_Session_Invalidate_DeleteService(t *testing.T) { session := &structs.Session{ ID: testUUID(), Node: "foo", - Checks: []string{"api"}, + Checks: []types.CheckID{"api"}, } if err := s.SessionCreate(14, session); err != nil { t.Fatalf("err: %v", err) @@ -2672,7 +2673,7 @@ func TestStateStore_Session_Invalidate_Critical_Check(t *testing.T) { session := &structs.Session{ ID: testUUID(), Node: "foo", - Checks: []string{"bar"}, + Checks: []types.CheckID{"bar"}, } if err := s.SessionCreate(14, session); err != nil { t.Fatalf("err: %v", err) @@ -2719,7 +2720,7 @@ func TestStateStore_Session_Invalidate_DeleteCheck(t *testing.T) { session := &structs.Session{ ID: testUUID(), Node: "foo", - Checks: []string{"bar"}, + Checks: []types.CheckID{"bar"}, } if err := s.SessionCreate(14, session); err != nil { t.Fatalf("err: %v", err) diff --git a/consul/structs/structs.go b/consul/structs/structs.go index 08c0d8e5cd..4f99399b38 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -8,6 +8,7 @@ import ( "time" "github.com/hashicorp/consul/acl" + "github.com/hashicorp/consul/types" "github.com/hashicorp/go-msgpack/codec" "github.com/hashicorp/serf/coordinate" ) @@ -181,7 +182,7 @@ type DeregisterRequest struct { Datacenter string Node string ServiceID string - CheckID string + CheckID types.CheckID WriteRequest } @@ -367,13 +368,13 @@ type NodeServices struct { // HealthCheck represents a single check on a given node type HealthCheck struct { Node string - CheckID string // Unique per-node ID - Name string // Check name - Status string // The current check status - Notes string // Additional notes with the status - Output string // Holds output of script runs - ServiceID string // optional associated service - ServiceName string // optional service name + CheckID types.CheckID // Unique per-node ID + Name string // Check name + Status string // The current check status + Notes string // Additional notes with the status + Output string // Holds output of script runs + ServiceID string // optional associated service + ServiceName string // optional service name RaftIndex } @@ -617,7 +618,7 @@ type Session struct { ID string Name string Node string - Checks []string + Checks []types.CheckID LockDelay time.Duration Behavior SessionBehavior // What to do when session is invalidated TTL string diff --git a/consul/structs/structs_test.go b/consul/structs/structs_test.go index d7672cad57..1aa84b3af3 100644 --- a/consul/structs/structs_test.go +++ b/consul/structs/structs_test.go @@ -5,6 +5,8 @@ import ( "reflect" "strings" "testing" + + "github.com/hashicorp/consul/types" ) func TestEncodeDecode(t *testing.T) { @@ -184,7 +186,7 @@ func TestStructs_HealthCheck_IsSame(t *testing.T) { t.Fatalf("should not care about Raft fields") } - check := func(field *string) { + checkCheckIDField := func(field *types.CheckID) { if !hc.IsSame(other) || !other.IsSame(hc) { t.Fatalf("should be the same") } @@ -201,14 +203,31 @@ func TestStructs_HealthCheck_IsSame(t *testing.T) { } } - check(&other.Node) - check(&other.CheckID) - check(&other.Name) - check(&other.Status) - check(&other.Notes) - check(&other.Output) - check(&other.ServiceID) - check(&other.ServiceName) + checkStringField := func(field *string) { + if !hc.IsSame(other) || !other.IsSame(hc) { + t.Fatalf("should be the same") + } + + old := *field + *field = "XXX" + if hc.IsSame(other) || other.IsSame(hc) { + t.Fatalf("should not be the same") + } + *field = old + + if !hc.IsSame(other) || !other.IsSame(hc) { + t.Fatalf("should be the same") + } + } + + checkStringField(&other.Node) + checkCheckIDField(&other.CheckID) + checkStringField(&other.Name) + checkStringField(&other.Status) + checkStringField(&other.Notes) + checkStringField(&other.Output) + checkStringField(&other.ServiceID) + checkStringField(&other.ServiceName) } func TestStructs_HealthCheck_Clone(t *testing.T) { diff --git a/types/README.md b/types/README.md new file mode 100644 index 0000000000..da662f4a1c --- /dev/null +++ b/types/README.md @@ -0,0 +1,39 @@ +# Consul `types` Package + +The Go language has a strong type system built into the language. The +`types` package corrals named types into a single package that is terminal in +`go`'s import graph. The `types` package should not have any downstream +dependencies. Each subsystem that defines its own set of types exists in its +own file, but all types are defined in the same package. + +# Why + +> Everything should be made as simple as possible, but not simpler. + +`string` is a useful container and underlying type for identifiers, however +the `string` type is effectively opaque to the compiler in terms of how a +given string is intended to be used. For instance, there is nothing +preventing the following from happening: + +```go +// `map` of Widgets, looked up by ID +var widgetLookup map[string]*Widget +// ... +var widgetID string = "widgetID" +w, found := widgetLookup[widgetID] + +// Bad! +var widgetName string = "name of widget" +w, found := widgetLookup[widgetName] +``` + +but this class of problem is entirely preventable: + +```go +type WidgetID string +var widgetLookup map[WidgetID]*Widget +var widgetName +``` + +TL;DR: intentions and idioms aren't statically checked by compilers. The +`types` package uses Go's strong type system to prevent this class of bug. diff --git a/types/checks.go b/types/checks.go new file mode 100644 index 0000000000..25a136b4f4 --- /dev/null +++ b/types/checks.go @@ -0,0 +1,5 @@ +package types + +// CheckID is a strongly typed string used to uniquely represent a Consul +// Check on an Agent (a CheckID is not globally unique). +type CheckID string