-
Notifications
You must be signed in to change notification settings - Fork 321
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Set Consul-K8s-Version header on Consul API calls (#434)
* Upgrade to latest consul api package * Create common consul package that implements NewClient(). This function returns a Consul client that will set the header "Consul-K8s-Version" to the current consul-k8s version on all calls to Consul. * Swap to using this new constructor everywhere. * Add a linter that ensures we use our NewClient() function everywhere in the future.
- Loading branch information
Showing
15 changed files
with
252 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package consul | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/hashicorp/consul-k8s/version" | ||
capi "github.com/hashicorp/consul/api" | ||
) | ||
|
||
// NewClient returns a Consul API client. It adds a required User-Agent | ||
// header that describes the version of consul-k8s making the call. | ||
func NewClient(config *capi.Config) (*capi.Client, error) { | ||
client, err := capi.NewClient(config) | ||
if err != nil { | ||
return nil, err | ||
} | ||
client.AddHeader("User-Agent", fmt.Sprintf("consul-k8s/%s", version.GetHumanVersion())) | ||
return client, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package consul | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/hashicorp/consul-k8s/version" | ||
capi "github.com/hashicorp/consul/api" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestNewClient(t *testing.T) { | ||
type APICall struct { | ||
Method string | ||
Path string | ||
UserAgentHeader string | ||
} | ||
|
||
var consulAPICalls []APICall | ||
consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
// Record all the API calls made. | ||
consulAPICalls = append(consulAPICalls, APICall{ | ||
Method: r.Method, | ||
Path: r.URL.Path, | ||
UserAgentHeader: r.UserAgent(), | ||
}) | ||
fmt.Fprintln(w, "\"leader\"") | ||
})) | ||
defer consulServer.Close() | ||
|
||
client, err := NewClient(&capi.Config{Address: consulServer.URL}) | ||
require.NoError(t, err) | ||
leader, err := client.Status().Leader() | ||
require.NoError(t, err) | ||
require.Equal(t, "leader", leader) | ||
|
||
require.Len(t, consulAPICalls, 1) | ||
require.Equal(t, APICall{ | ||
Method: "GET", | ||
Path: "/v1/status/leader", | ||
UserAgentHeader: fmt.Sprintf("consul-k8s/%s", version.GetHumanVersion()), | ||
}, consulAPICalls[0]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// Parses golang code looking for github.com/hashicorp/consul/api.NewClient() | ||
// being used in non-test code. If it finds this, it will error. | ||
// The purpose of this lint is that we actually want to use our internal | ||
// github.com/hashicorp/consul-k8s/consul.NewClient() function because that | ||
// adds the consul-k8s version as a header. | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"go/ast" | ||
"go/parser" | ||
"go/token" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
var ( | ||
broken = make(map[string]bool, 0) // Stored in a map for deduplication | ||
exitCode = 0 | ||
fset = token.NewFileSet() | ||
consulApiPackage = "github.com/hashicorp/consul/api" | ||
) | ||
|
||
func main() { | ||
dir, err := os.Getwd() | ||
if err != nil { | ||
os.Stderr.WriteString(fmt.Sprintf("failed to get cwd: %v", err)) | ||
os.Exit(1) | ||
} | ||
err = walkDir(dir) | ||
if err != nil { | ||
os.Stderr.WriteString(err.Error()) | ||
os.Exit(1) | ||
} | ||
if len(broken) > 0 { | ||
exitCode = 1 | ||
os.Stderr.WriteString("Found code using github.com/hashicorp/consul/api.NewClient()\ninstead of github.com/hashicorp/consul-k8s/consul.NewClient()\nin the following files:\n") | ||
for filePath := range broken { | ||
os.Stderr.WriteString(fmt.Sprintf("- %s\n", filePath)) | ||
} | ||
} | ||
os.Exit(exitCode) | ||
} | ||
|
||
type visitor struct { | ||
path string | ||
alias string | ||
} | ||
|
||
func (v visitor) Visit(n ast.Node) ast.Visitor { | ||
switch node := n.(type) { | ||
case *ast.CallExpr: | ||
function, ok := node.Fun.(*ast.SelectorExpr) | ||
if !ok { | ||
break | ||
} | ||
pkg, ok := function.X.(*ast.Ident) | ||
if !ok { | ||
break | ||
} | ||
if !(pkg.Name == v.alias && function.Sel.Name == "NewClient") { | ||
break | ||
} | ||
broken[v.path] = true | ||
} | ||
return v | ||
} | ||
|
||
// imports returns true if file imports pkg and the name of the alias | ||
// used to import. | ||
func imports(file *ast.File, pkgName string) (bool, string) { | ||
var specs []ast.Spec | ||
|
||
for _, decl := range file.Decls { | ||
if general, ok := decl.(*ast.GenDecl); ok { | ||
specs = append(specs, general.Specs...) | ||
} | ||
} | ||
for _, spec := range specs { | ||
pkg, ok := spec.(*ast.ImportSpec) | ||
if !ok { | ||
continue | ||
} | ||
path := pkg.Path.Value | ||
// path may have leading/trailing quotes. | ||
path = strings.Trim(path, "\"") | ||
if path == pkgName { | ||
alias := filepath.Base(pkgName) | ||
if pkg.Name != nil { | ||
alias = pkg.Name.Name | ||
} | ||
return true, alias | ||
} | ||
} | ||
return false, "" | ||
} | ||
|
||
func walkDir(path string) error { | ||
return filepath.Walk(path, visitFile) | ||
} | ||
|
||
func visitFile(path string, f os.FileInfo, err error) error { | ||
if err != nil { | ||
return fmt.Errorf("failed to visit '%s', %v", path, err) | ||
} | ||
|
||
// consul/consul.go is where we have our re-implementation of NewClient() | ||
// which under the hood calls api.NewClient() so we need to discard that | ||
// path. | ||
if isNonTestFile(path, f) && !strings.Contains(path, "consul/consul.go") { | ||
tree, err := parser.ParseFile(fset, path, nil, parser.ParseComments) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Only process files importing github.com/hashicorp/consul/api. | ||
importsAPI, alias := imports(tree, consulApiPackage) | ||
if importsAPI { | ||
v := visitor{ | ||
path: path, | ||
alias: alias, | ||
} | ||
ast.Walk(v, tree) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func isNonTestFile(path string, f os.FileInfo) bool { | ||
return !f.IsDir() && !strings.Contains(path, "test") && filepath.Ext(path) == ".go" | ||
} |
Oops, something went wrong.