-
Notifications
You must be signed in to change notification settings - Fork 323
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add connect-init command and consul login api #446
Conversation
Add consul-init command and plumb it through init container
609e1f6
to
4f1512f
Compare
connect-inject/container_init.go
Outdated
-meta="pod=${POD_NAMESPACE}/${POD_NAME}" | ||
{{- /* The acl token file needs to be read by the consul-sidecar which runs | ||
as non-root user consul-k8s. TODO: will this be necessary anymore? */}} | ||
{{- end }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the endpoints controller is ready we will remove everything past this in the command tmpl
@@ -458,7 +453,7 @@ chmod 444 /consul/connect-inject/acl-token | |||
/consul/connect-inject/service.hcl | |||
|
|||
# Generate the envoy bootstrap code | |||
/bin/consul connect envoy \ | |||
/consul/connect-inject/consul connect envoy \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we are now using consul-k8s image instead of consul the path will be from the shared vol instead of /bin/
.. update all the tests accordingly.
subcommand/common/common_test.go
Outdated
Method: r.Method, | ||
Path: r.URL.Path, | ||
}) | ||
b := "{\n \"AccessorID\": \"926e2bd2-b344-d91b-0c83-ae89f372cd9b\",\n \"SecretID\": \"b78d37c7-0ca7-5f4d-99ee-6d9975ce4586\",\n \"Description\": \"token created via login\",\n \"Roles\": [\n {\n \"ID\": \"3356c67c-5535-403a-ad79-c1d5f9df8fc7\",\n \"Name\": \"demo\"\n }\n ],\n \"ServiceIdentities\": [\n {\n \"ServiceName\": \"example\"\n }\n ],\n \"Local\": true,\n \"AuthMethod\": \"minikube\",\n \"CreateTime\": \"2019-04-29T10:08:08.404370762-05:00\",\n \"Hash\": \"nLimyD+7l6miiHEBmN/tvCelAmE/SbIXxcnTzG3pbGY=\",\n \"CreateIndex\": 36,\n \"ModifyIndex\": 36\n}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a sample API output from consul.io that we are mocking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a nifty way to stub out the server and assert on the api call! I love what you've done here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking good! I think the biggest thing I'm wondering is that right now this command is only run when ACLs are enabled but the plan is for this command to eventually run when ACLs are disabled so does it make sense to write it right now so it essentially no-ops if ACLs are disabled?
Because otherwise we're writing tests that -auth-method
is a required flag but then later it will be run without that flag (if ACLs are disabled).
subcommand/consul-init/command.go
Outdated
ErrCh := make(chan error) | ||
ExitCh := make(chan bool) | ||
meta := map[string]string{"pod": strings.Split(c.flagMeta, "=")[1]} | ||
go func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This loop ignore any sigterms right now. I'm also not sure it needs to run in a go routine? Would something like this work:
retries := 0
for {
err = common.ConsulLogin(c.consulClient, c.flagBearerTokenFile, c.flagACLAuthMethod, c.flagTokenSinkFile, meta)
if err == nil {
break
}
retries++
if retries == c.numACLLoginRetries {
c.UI.Error("hit maximum retries for consul login")
return 1
}
c.UI.Error(fmt.Sprintf("consul login failed; retrying: %s", err))
select {
case <-time.After(1 * time.Second):
// retry loop
case sig := <-c.sigCh:
c.UI.Info(fmt.Sprintf("%s received, shutting down", sig))
return 0
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about using backoff.Retry
(example here). I think this package will already handle a lot the signals and make the logic much cleaner. You'd need to pass in backoff.WithMaxRetries(backoff.NewConstantBackOff(1*time.Second), 3)
as the second argument so that it stops after 3 tries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ishustava great idea, I like it and its much cleaner looking I'm not sure I understand how it handles signals. In get-consul-client-ca
I don't see it either and we're not testing that command for any signal handling. Could you can show me? 🙇🏽
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reason to have signal handling in this command?
In get-consul-client-ca, we don't handle interrupt signals because it doesn't start any goroutines, and so if an interrupt or a term signal is sent, it doesn't need to drain or stop any other processes that it's running. We also don't do it in other similar commands, e.g server-acl-init, probably for the same reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do loop for the login so wouldn't it be nice if you could delete the pod and it would exit quickly if it was in its init
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with not having it too if it's deemed too complicated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do loop for the login so wouldn't it be nice if you could delete the pod and it would exit quickly if it was in its init
If you delete a pod, then it should still exit quickly. If you have a program that doesn't handle interrupts and you send it an interrupt, it should still exit as soon as you send that signal because it's handled by golang and OS. In this command, we don't need any graceful shutdown behavior so I think it's ok to skip OS signal handling. Maybe I'm misunderstanding something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh I didn't know that there was default signal handling that gets turned off by signal.Notify
(https://golang.org/pkg/os/signal/).
Co-authored-by: Luke Kysow <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a clarification on the scope of this PR, the consul login logic and tests and command look great!!
subcommand/common/common_test.go
Outdated
Method: r.Method, | ||
Path: r.URL.Path, | ||
}) | ||
b := "{\n \"AccessorID\": \"926e2bd2-b344-d91b-0c83-ae89f372cd9b\",\n \"SecretID\": \"b78d37c7-0ca7-5f4d-99ee-6d9975ce4586\",\n \"Description\": \"token created via login\",\n \"Roles\": [\n {\n \"ID\": \"3356c67c-5535-403a-ad79-c1d5f9df8fc7\",\n \"Name\": \"demo\"\n }\n ],\n \"ServiceIdentities\": [\n {\n \"ServiceName\": \"example\"\n }\n ],\n \"Local\": true,\n \"AuthMethod\": \"minikube\",\n \"CreateTime\": \"2019-04-29T10:08:08.404370762-05:00\",\n \"Hash\": \"nLimyD+7l6miiHEBmN/tvCelAmE/SbIXxcnTzG3pbGY=\",\n \"CreateIndex\": 36,\n \"ModifyIndex\": 36\n}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a nifty way to stub out the server and assert on the api call! I love what you've done here.
connect-inject/container_init.go
Outdated
@@ -323,6 +323,21 @@ func (h *Handler) containerInit(pod *corev1.Pod, k8sNamespace string) (corev1.Co | |||
// and the connect-proxy service should come after the "main" service | |||
// because its alias health check depends on the main service to exist. | |||
const initContainerCommandTpl = ` | |||
{{- if .AuthMethod }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this just the command without the loop that waits for the consul client to have a service instance registered with the local agent? (i.e is this only don't the consul login for now)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh I think you can ignore this, I think that is the case, and for right now the command is mostly doing the consul login.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it is only doing consul login right now. trying to strike a line between this PR and the next PR which will bring in non-ACL support, etc.
@@ -199,7 +199,7 @@ func TestDelete_ConsulNamespaces(t *testing.T) { | |||
server, err := testutil.NewTestServerConfigT(t, nil) | |||
defer server.Stop() | |||
require.NoError(err) | |||
server.WaitForLeader(t) | |||
server.WaitForSerfCheck(t) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will rebase this at the end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested everything out and it's looking great. Just some small suggested changes remaining.
flags: []string{"-meta", "pod=abcdefg"}, | ||
expErr: "-method must be set", | ||
}, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about the other required flags?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have them taking in defaults right now in the flag parsing, wasn't sure how to handle it, what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say follow the other commands we have and how they deal with flag defaults. If there is no prior art then I'd say if a flag has a default then we don't need to check if it's nil
and then we also wouldn't write a test for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that is what I ended up doing here. but let me look at some other tests and update the PR accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, if they have defaults, then we don't need those validation errors since it's impossible for them to be empty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh interesting. I would have said that if:
if c.flagBearerTokenFile == "" {
c.UI.Error("-bearer-token-file must be set")
return 1
}
if c.flagTokenSinkFile == "" {
c.UI.Error("-token-sink-file must be set")
return 1
}
Will never run because those have defaults then we can just delete those lines and then no need to test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking pretty good! I really like the work you did here! I left some comments; they are mostly minor suggestions or questions. Otherwise, it looks good to me!
subcommand/common/common_test.go
Outdated
t.Parallel() | ||
require := require.New(t) | ||
counter := 0 | ||
consulServer, client, bearerTokenFile, tokenFile := startMockServer(t, "", &counter) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need the mock server for this test? I think it fails before it makes a call to consul
subcommand/common/common_test.go
Outdated
tokenFile, | ||
testPodMeta, | ||
) | ||
require.Error(err, "unable to read bearerTokenFile") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this error should be no bearer token found in ...
flags: []string{"-meta", "pod=abcdefg"}, | ||
expErr: "-method must be set", | ||
}, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, if they have defaults, then we don't need those validation errors since it's impossible for them to be empty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉
Left a couple of minor edits, but otherwise looks great!
Co-authored-by: Iryna Shustava <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🦊 great work. Note I didn't dockerize it myself on this last review.
// Validate that the token file was written to disk. | ||
data, err := ioutil.ReadFile(tokenFile) | ||
require.NoError(err) | ||
require.Equal(string(data), "b78d37c7-0ca7-5f4d-99ee-6d9975ce4586") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
require.Equal(string(data), "b78d37c7-0ca7-5f4d-99ee-6d9975ce4586") | |
require.Equal("b78d37c7-0ca7-5f4d-99ee-6d9975ce4586", string(data)) |
Order of these arguments is exp, act so they should be swapped.
subcommand/common/common_test.go
Outdated
// startMockServer starts an httptest server used to mock a Consul server's | ||
// /v1/acl/login endpoint. It also writes bearerTokenContents to a temp file. | ||
// apiCallCounter will be incremented on each call to /v1/acl/login. | ||
// It returns a consul client pointing at the server. | ||
func startMockServer(t *testing.T, apiCallCounter *int) *api.Client { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// startMockServer starts an httptest server used to mock a Consul server's | |
// /v1/acl/login endpoint. It also writes bearerTokenContents to a temp file. | |
// apiCallCounter will be incremented on each call to /v1/acl/login. | |
// It returns a consul client pointing at the server. | |
func startMockServer(t *testing.T, apiCallCounter *int) *api.Client { | |
// startMockServer starts an httptest server used to mock a Consul server's | |
// /v1/acl/login endpoint. | |
// apiCallCounter will be incremented on each call to /v1/acl/login. | |
// It returns a consul client pointing at the server. | |
func startMockServer(t *testing.T, apiCallCounter *int) *api.Client { |
TestRetry bool | ||
LoginAttemptsCount int | ||
ExpCode int | ||
ExpErr string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ExpErr
isn't used anymore
* Add connect-init command to consul-k8s and build consul login api Co-authored-by: Luke Kysow <[email protected]> Co-authored-by: Iryna Shustava <[email protected]>
* Add connect-init command to consul-k8s and build consul login api Co-authored-by: Luke Kysow <[email protected]> Co-authored-by: Iryna Shustava <[email protected]>
* Add connect-init command to consul-k8s and build consul login api Co-authored-by: Luke Kysow <[email protected]> Co-authored-by: Iryna Shustava <[email protected]>
* Add connect-init command to consul-k8s and build consul login api Co-authored-by: Luke Kysow <[email protected]> Co-authored-by: Iryna Shustava <[email protected]>
Changes proposed in this PR:
consul login
command which uses only Consul client API calls.How I've tested this PR:
Unit tests added and passed.
Manual test by deploying
kschoche/consul-k8s-dev
with ACLs enabled and deploy a connect injected application.How I expect reviewers to test this PR:
Manual test.
Checklist: