-
Notifications
You must be signed in to change notification settings - Fork 4
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
Send Application Sync request to Argo CD Server #44
Conversation
|
||
if err != nil { | ||
if strings.Contains(err.Error(), "another operation is already in progress") { | ||
log.Info("another operation is already in progress", "app", s) |
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 assume this means we proceed as normal - it's not an error per se - so I don't return 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.
I'm not really even sure we want to log this to be honest. This mean we tried to re sync the app while a sync was already in progress, correct? So what's the big deal? :)
I might miss a valid use case.
log.Info("another operation is already in progress", "app", s) | ||
} else { | ||
log.Error(err, "unable to sync app", "app", s) | ||
return ctrl.Result{}, err |
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 all we want to do? Do we want to somehow signal the stage as failed? Or is that future work?
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 want to somehow signal the stage as failed
For future work in #30 - as we don't do it at the moment
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 @maruina said... happy path only :-)
hack/dev-functions.sh
Outdated
@@ -87,6 +87,13 @@ function local_argocd_login() { | |||
echo "ARGOCD_LOCAL_ADDRESS=https://localhost:$argoserver_nodeport" | |||
} >.env.local | |||
|
|||
if [ ! -d .prcconfig ]; then |
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 sets up a config folder locally so we can make run
and use it to connect to the argocd-server
in kind
.
r.Log.Info("syncing app", "app", s) | ||
|
||
_, err := r.syncApp(s.Name) |
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.
The left-hand side variable is the synced *argov1alpha.Application
, when there's no error. Is there anything we want to do with 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.
I guess it's to have a lambda style approach where you can do things like r.syncApps(s.Name).Status()
or something like this in a single command.
Don't think we need to do something in particular right 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.
We're actually doing something funny with that one. the sync
from Argo takes an ApplicationRequest
that we get from the Name
. I guess the left hand side there is meant to tell you whether the selector worked. It could be useful down the line for us.
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 couple of comment to answer some of your questions, I'll do a deeper review later.
|
||
if err != nil { | ||
if strings.Contains(err.Error(), "another operation is already in progress") { | ||
log.Info("another operation is already in progress", "app", s) |
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 not really even sure we want to log this to be honest. This mean we tried to re sync the app while a sync was already in progress, correct? So what's the big deal? :)
I might miss a valid use case.
log.Info("another operation is already in progress", "app", s) | ||
} else { | ||
log.Error(err, "unable to sync app", "app", s) | ||
return ctrl.Result{}, err |
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 want to somehow signal the stage as failed
For future work in #30 - as we don't do it at the moment
@@ -1,10 +1,10 @@ | |||
|
|||
--- | |||
apiVersion: apiextensions.k8s.io/v1beta1 | |||
apiVersion: apiextensions.k8s.io/v1 |
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 the official release for v1?
Should we keep it a separate commit and release/package a specific version?
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.
Not at all. This was auto-generated by kubebuilder - I can revert the change and try to see where/why it was generated, but judging by https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/ v1beta1
seems to have been deprecated in v1.16?
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.
that confused the hell out of me :-)
my bad
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.
If we use apiextensions.k8s.io/v1
it will not run in our current control cluster, so please let's skip this for a little bit.
internal/utils/config.go
Outdated
ReadConfiguration returns a Configuration, reading it from environment variables if present, or from files in | ||
the configuration directory otherwise | ||
*/ | ||
func ReadConfiguration() (*Configuration, error) { |
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.
why using pointer semantics here?
we're using value semantics everywhere else?
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.
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.
Uhm - we can go with value semantics and return an empty config. As long as it also returns an error, we're able to handle 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.
Depending how we make things complicated, but it would be good to follow the same convention.
Co-authored-by: Sebastien Le Digabel <sledigabel@gmail.com>
README.md
Outdated
make deploy | ||
``` | ||
|
||
In order to so, a `.env.cluster` file needs to exist in the `config/manager` folder, containing |
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 am not sure about adding a file into the kustomize directory. Is that the only way to make kustomize work?
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.
Not very fond of it either, to be honest. But from https://kubectl.docs.kubernetes.io/faq/kustomize/: v2.0 added a security check that prevents kustomizations from reading files outside their own directory root.
This meant I can't find any file that's not inside that directory - I'm open to alternatives.
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.
Ah, so if I had not stopped reading at the first line, it says this:
Resources (including configmap and secret generators) can still be shared via the recommended best practice of placing them in a directory with their own kustomization file, and referring to this directory as a base from any kustomization that wants to use it. This encourages modularity and relocatability.
Let me investigate that.
README.md
Outdated
2. Files in the Config Directory. | ||
``` | ||
. | ||
├── .prcconfig |
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.
Should this be ~/.prcconfig
?
@@ -1,10 +1,10 @@ | |||
|
|||
--- | |||
apiVersion: apiextensions.k8s.io/v1beta1 | |||
apiVersion: apiextensions.k8s.io/v1 |
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.
If we use apiextensions.k8s.io/v1
it will not run in our current control cluster, so please let's skip this for a little bit.
config/manager/manager.yaml
Outdated
@@ -36,4 +41,8 @@ spec: | |||
requests: | |||
cpu: 100m | |||
memory: 20Mi | |||
volumeMounts: | |||
- mountPath: /.prcconfig |
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 not sure why we should use a .prcconfig
in the root filesystem. Maybe /etc/prcconfig
, or /opt/prcconfig
or ~/.prcconfig
?
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.
Good point, we should not. Which one do you prefer? I'm not unhappy with ~
, but maybe /etc
is a better place 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.
👍 for /etc/
go.mod
Outdated
k8s.io/api v0.20.1 | ||
k8s.io/apimachinery v0.20.1 | ||
k8s.io/client-go v11.0.1-0.20190816222228-6d55c1b1f1ca+incompatible | ||
k8s.io/kubernetes v1.20.1 // indirect |
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 you know where this is coming from?
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.
Tbh, no - it sure is weird because I've not installed nor updated any K8s-specific libraries. I'll get back to you on this.
internal/utils/config.go
Outdated
ReadConfiguration returns a Configuration, reading it from environment variables if present, or from files in | ||
the configuration directory otherwise | ||
*/ | ||
func ReadConfiguration() (*Configuration, error) { |
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.
Depending how we make things complicated, but it would be good to follow the same convention.
internal/utils/argocd_client.go
Outdated
func GetArgoCDAppClient(c *Configuration) ArgoCDAppClient { | ||
acdClientOpts := argocdclient.ClientOptions{ | ||
ServerAddr: c.ArgoCDServerAddr, | ||
Insecure: true, |
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.
mmmmh I'm not sure about this. could we parameterise this in the Configuration object instead of leaving this insecure by default?
@@ -22,6 +25,26 @@ const ( | |||
interval = time.Millisecond * 10 | |||
) | |||
|
|||
type mockArgoCDAppClientCounter struct { |
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 think there is something I don't understand here: why is this called Counter
? Is that because we're counting how many apps we sync?
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.
The idea of this mock is to mimic what in Java or Python would be assertCalledWith()
or similar - I want to make sure that Sync()
is called with a specific app name.
I think I called it Counter
because initially I was just counting number of calls - but that's wrong, we want to know that a specific app was synced, rather than how many times apps where synced. So perhaps there's a better name 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.
The idea of this mock is to mimic what in Java or Python would be assertCalledWith() or similar - I want to make sure that Sync() is called with a specific app name.
Thanks, now it make more sense to me. Maybe mockArgoCDAppClientWithApp
?
"testing" | ||
) | ||
|
||
type mockArgoCDAppClientSyncOK struct{} |
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 think there is something that I'm missing here: why we don't have a mockArgoCDAppClient
package with its own methods and tests?
The approach would be similar in https://github.com/Skyscanner/kms-issuer/tree/main/pkg/kmsmock
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.
For why the mocks are in the file and not a separate package, see the other comment - I'm happy to move them if we prefer that.
There's also the fact that I want different mocks to do different things - in fact, the one in suite_test
is more like a stub than a mock per se, it's there just to make sure the other tests pass by default.
controllers/suite_test.go
Outdated
@@ -44,6 +47,12 @@ var k8sClient client.Client | |||
var testEnv *envtest.Environment | |||
var reconciler *ProgressiveRolloutReconciler | |||
|
|||
type mockArgoCDAppClient struct{} | |||
|
|||
func (*mockArgoCDAppClient) Sync(ctx context.Context, in *applicationpkg.ApplicationSyncRequest, opts ...grpc.CallOption) (*argov1alpha1.Application, error) { |
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 not sure I understand we we need to redefine the mockArgoCDAppClient
and the sync method here. Can't we import it from a package?
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.
From what I saw, defining mocks in the test files they're used seems to be the most common practice (see https://github.com/kubernetes/kubernetes/blob/cea1d4e20b4a7886d8ff65f34c6d4f95efcb4742/staging/src/k8s.io/apiserver/pkg/authentication/token/union/unionauth_test.go#L30, for an example).
But if we prefer to have a package for the mocks, I'm happy to move 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.
I don't think I have good enough knowledge on the ecosystem and golang on this to be opinionated :)
I guess my point it was to be able to reuse the mock instead of redefining it multiple times? I guess when I see mockArgoCDAppClientCounter
, mockArgoCDAppClient
and mockArgoCDAppClientSyncOK
they feel the same thing to me with different methods? But maybe not.
@sledigabel, @danmx, @Smirl: what do you think about this?
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.
They serve different purposes in different tests, but are all implementing the same interface:
mockArgoCDAppClient
is not a mock, it really is a generic stub so that we can run the other tests when we don't care about what happens in the calls to Argo CD.mockArgoCDAppClientCounter
is really just a way toassertCalledWith
theSync()
method - we could use this mock as the 'general' one (in 1.) if we so wished, I opted to make the other entirely useless to make it clear we don't care about its functionality.mockArgoCDAppClientSyncOK
is used in unit testing to mock that the Argo CD app client returned OK, and conversely,SyncNotOK
returns an error.
There's good things with this approach, imo: it's clear that each mock does something different, for instance. There's also bad things: if we ever opt to use one more method from the Argo CD App Client in the code, we have to update all these mocks, which adds toil to future maintainers.
Thoughts from those with more knowldege of Golang are most welcome 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.
why bother creating your own mocks in the first place? Go can generate mocks for you (https://github.com/golang/mock) based on your interfaces.
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.
Don't see any reason not to use it, but I've never used any way of mocking in Golang, so I'm not opinionated on it.
Taking a step back then: what is the approach we want to take w.r.t. mocking in this project? If it's using gomock
, then let's change these mocks :). Again, I'm not opinionated either way, but would rather decide on one approach and have consistency going forward.
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 i was going to suggest having a /mock package would be convenient, in case we need to reuse this for other tests moving forward.
Not sure about using mock to do that, but why not if that helps in the future.
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.
Alright - so I did some playground stuff with gomock and I think that might be useful in the long term. Bearing in mind that the Sync is a blocker to other tasks, I've opted to simply move the mocks to a mocks
package for the time being. We should still agree on how we're doing these things long-term.
Alright, I think I've addressed all comments - let me know if I haven't. A few notes:
|
@@ -0,0 +1,35 @@ | |||
package controllers |
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.
Question for the wider audience: do we want to have a separate test file for the unit tests, or can we merge those tests with progressiverollout_controller_test.go
?
I would prefer a single test file for simplicity, but I'm open to suggestions
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.
There's an argument to separate integration tests vs unit tests as they look and are different.
How is it done on other operators?
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 never seen anything other controller.go and controller_test.go but I am happy if anyone can findaa different example.
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.
In other languages (such as Python or Java), tests would be in their own folder, and I feel like I'd separate unit and other types of tests in different files too. So I felt this makes sense.
But Golang seems to be different in that tests generally live in the same package as the code under test; so perhaps a 1:1 relation between code-under-test and test-code files is ideal? If so, I can move them to the same file. I'm not opinionated on this.
Co-authored-by: Matteo Ruina <matteo.ruina@gmail.com>
@DiogoMCampos I think we should also change the helm chart to exposes
in the values.yaml and create the secret. It is then up to the end-user how to pass to token to the chart. |
syncReq := applicationpkg.ApplicationSyncRequest{ | ||
Name: &appName, | ||
} | ||
|
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 think we have an opportunity here to validate that the app being synced is the app we passed as a parameter.
i'll raise a PR separately, it's not blocking that one.
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.
LGTM
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.
LGTM, adds a sync capability to the progressive rollout operator
Are we gonna merge this? |
Issue link: #22
Opted to use the code behind the CLI, authenticating against the server based on secrets read from a local file. For the initial implementation, we opted to use Insecure.
What this does
What this doesn't do
There's a couple of things missing that I intend to work on still, namely understanding what I need to change to the definitions/Makefile to use and mount the secrets as files.