-
Notifications
You must be signed in to change notification settings - Fork 12
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 reconciler for pipeline and task resources - step 6 on operator-pipelines docs #13
Conversation
This commit implements decoding of yaml manifests into unstructured objs for client to apply. It doesn't use the controller-runtime client instead uses a client-go based client. For now it uses a local test.yaml as input to be replaced with operator-pipelines yaml manifests.
The applyManifests function is used within the reconcilePipelineDependencies to create both pipelines and tasks from the operator-pipeline project after executing a plain clone from the project.
This commit includes: 1 - few adjustments and constants on func reconcilePipelineDependencies 2 - kubebuilder marker with tekton.dev api group rbac permissions 3 - other changes due to make manifests commands such as role.yaml with tekton permissions.
Tekton pipelines and tasks are being created on the same namespace as the namespace where the CR is created. Just by catching the meta.Namespace on the specific func for reconciling the pipeline dependencies.
controllers/dependencies.go
Outdated
// yaml manifests that need to be applied beforehand | ||
// ref: https://github.com/redhat-openshift-ecosystem/certification-releases/blob/main/4.9/ga/ci-pipeline.md#step-6---install-the-certification-pipeline-and-dependencies-into-the-cluster | ||
|
||
repo := "https://github.com/redhat-openshift-ecosystem/operator-pipelines.git" |
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.
Could this be a constant as well?
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.
Fair enough. My bad. I think I passed too fast my eyes on that. I just changed it.
controllers/dependencies.go
Outdated
} | ||
|
||
// Removing cloned project | ||
if err = os.RemoveAll(PIPELINE_MANIFESTS_PATH); err != nil { |
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 removing the manifests should be a deferred function after git.PlainClone
. Otherwise, if any error encounters, the program execution will never reach here.
My other question is why we don't remove the files under TASK_MANIFEST_PATH?
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's fair. My mistake. It's not PIPELINE_MANIFESTS_PATH it's the whole REPO_CLONE_PATH. And the defer is a good suggestion already done.
controllers/dependencies.go
Outdated
|
||
func (r *OperatorPipelineReconciler) applyManifests(fileName string, Namespace string) error { | ||
|
||
b, err := ioutil.ReadFile(fileName) |
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.
ioutil is going to be deprecated soon (I think). It would be better to use os
package instead.
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.
+1
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 sure about ioutil. But no problem os is now the one on the code. They have exactly the same signature.
controllers/dependencies.go
Outdated
} | ||
|
||
// Decoding yaml files to resource objects and apply to cluster | ||
decoder := yamlutil.NewYAMLOrJSONDecoder(bytes.NewReader(b), 100) |
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 was having some fun today to resolve the same issue, and I think using yaml.Unmarshal is cleaner. This is the snippet of my code to read a csv file:
yfile, err := os.ReadFile("csv.yaml")
if err != nil {
log.Error(err)
}
var data operatorv1alpha1.ClusterServiceVersion
err = yaml.Unmarshal(yfile, &data)
I checked the pipeline and task folders and we need to handle two CRDs (Task and Pipeline). I think this could be simplified. Please take a look at how we manage a similar situation in preflight (but it is the opposite! we try to dump go struct into yaml manifest). https://github.com/redhat-openshift-ecosystem/openshift-preflight/blob/1a2ef8a3829ea911356cd41a0171130d568379cd/certification/internal/policy/operator/deployable_by_olm.go#L378
Please let me know if we need to discuss further.
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.
As it is it's working. Are we aiming for refactoring here? Wouldn't it be interesting having another iteration with another issue to simplify it? I would suggest that we unblock other tasks that depends on this step first and take the code refactoring and new approaches on another issue. Glad to jump in and make the changes there. 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.
Yes refactoring.. I understand this is working, but I think it is too complex to follow and some of these lines could be removed. I'd personally like to push a code that is easy to maintain the first time. The refactoring is usually more complicated as you need to have/add unit tests to ensure the refactored code is not breaking, and we don't have unit tests yet.
BTW, have you seen this?
kubernetes/client-go#193 (comment)
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 agree with @samira-barouti here, I'd rather see this clearly defined structs (or partial structs) and use yaml.Unmarshal
to simplify the code and make it easier to read/maintain.
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.
Hi Samira, nope. I didn't see this one. But just realized that going the generic approach with client-go was not necessary there. So I went on the specific approach which is actually way easier. I just flipped to controller runtime client and that's much better now I think. Please check it out.
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.
Two things...
- Unit tests?
- Please collapse all the commits. Unless a commit can stand on its own, it shouldn't be a separate commit. I see some of the individual commits have a bunch of commented out code. That definitely doesn't need to be in the repo as a part of the history.
controllers/dependencies.go
Outdated
|
||
func (r *OperatorPipelineReconciler) applyManifests(fileName string, Namespace string) error { | ||
|
||
b, err := ioutil.ReadFile(fileName) |
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.
+1
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.
A couple more comments.
controllers/dependencies.go
Outdated
defer r.RemovePipelineDependencyFiles(REPO_CLONE_PATH) | ||
|
||
// Reading pipeline manifests and applying to cluster | ||
root := REPO_CLONE_PATH + PIPELINE_MANIFESTS_PATH |
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.
These two blocks could be a loop.
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.
Can you clarify? What do you mean?
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.
paths := []string{PIPELINE_MANIFESTS_PATH, TASKS_MANIFESTS_PATH}
for _, path := range paths {
...
}
The blocks are identical. The only difference is the two paths.
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.
Done.
controllers/dependencies.go
Outdated
return nil | ||
} | ||
|
||
func (r *OperatorPipelineReconciler) RemovePipelineDependencyFiles(filePath string) 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.
This doesn't seem like it needs to be exported.
removePipelineDependencyFiles
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.
done.
|
We've used ginkgo/gomega on preflight. But we've also accepted plain old Go tests. Use whatever you're comfortable with. It's a bigger debt to have to add a test later, than to possibly migrate a test to a different framework.
My typical workflow when working on a PR like this is to |
1 - fileWalk on loop block 2 - Simplify code on manifest apply
Hi @bcrochet , so on the unit test I believe after we talked yesterday you won't need them anymore now. About the commits agreed. I pass to that practice from now on. I would suggest to squash them on merge when this PR gets approved. |
controllers/dependencies.go
Outdated
if !info.IsDir() { | ||
var pipeline tekton.Pipeline | ||
var task tekton.Task | ||
fmt.Print(path) |
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 seems to be a leftover from the debugging. Could be removed.
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's also done.
controllers/dependencies.go
Outdated
// Progress: os.Stdout, | ||
}) | ||
if err != nil { | ||
log.Log.Info("Couldn't clone the repository for operator-pipelines: " + err.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.
This should be log.Log.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.
Done.
controllers/dependencies.go
Outdated
return nil | ||
}) | ||
if err != nil { | ||
log.Log.Info("Couldn't iterate over operator-pipelines yaml manifest files: " + err.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.
same 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.
Done.
controllers/dependencies.go
Outdated
|
||
func (r *OperatorPipelineReconciler) removePipelineDependencyFiles(filePath string) error { | ||
if err := os.RemoveAll(filePath); err != nil { | ||
log.Log.Info("Couldn't remove operator-pipelines directory") |
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.
same 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.
Done.
controllers/dependencies.go
Outdated
|
||
b, err := os.ReadFile(fileName) | ||
if err != nil { | ||
log.Log.Info("Couldn't read manifest file " + err.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.
same 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.
Done.
controllers/dependencies.go
Outdated
mapper := restmapper.NewDiscoveryRESTMapper(gr) | ||
mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version) | ||
if err != nil { | ||
log.Log.Info("Couldn't the preferred resource mapping for given kind.") |
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 log missing a word?
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.
done.
controllers/dependencies.go
Outdated
} | ||
|
||
// Decoding yaml files to resource objects and apply to cluster | ||
decoder := yamlutil.NewYAMLOrJSONDecoder(bytes.NewReader(b), 100) |
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 agree with @samira-barouti here, I'd rather see this clearly defined structs (or partial structs) and use yaml.Unmarshal
to simplify the code and make it easier to read/maintain.
controllers/dependencies.go
Outdated
log.Error(err, "Couldn't clone the repository for operator-pipelines") | ||
return err | ||
} | ||
defer r.removePipelineDependencyFiles(REPO_CLONE_PATH) |
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 (and I might be wrong), that this method call should be place before the if statement on line 33. If cloning the repo fails in the middle, the control flow will return the error but not delete the created dir.
I overaly recommend 1) creating a tmp folder programmatically for the operator-pipelines (please check https://pkg.go.dev/os#MkdirTemp) to avoid unwanted surprises, and 2) move the defer method to line 33, after the git.PlainClone and before the if 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.
2- I'm not sure as well if the defer function would get triggered there so just in case I moved it to line 33 as requested.
1- On the other hand temporary files used by programs in Linux are historically stored in the /tmp directory that happens to have, in regular systems, garbage collection in place. This is a single Linux image deployment, single process container, single user with a single purpose. It's pretty straightforward. I don't see the need to add another package/method and more complexity to address 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.
No problem.
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
In a nutshell this PR solves the bare minimum on issue #5.
Added on new the dependencies.go file in package controllers:
OBS:
On Step 2:
The apply part is done via dynamic client-go with unstructured objects "rest mapped" to known GVKs such as pipelines and tasks that belong to tekton.dev apigroup; That requires that the openshift-pipeline operator aka tekton be installed before. This task doesn't seem to require any other to complete therefore I put it right after the pipeline operator subscription one on the resources.go file.
I'm assuming those will be deployed on the same namespace as the CR here.
The image I used for testing this on my AWS OCP environment is on my own repo for now as I don't know yet what tagging strategy for testing images I should use on opdev. You can find it here: quay.io/acmenezes/operator-certification-operator:0.0.1-2.
We may need unit tests, comments and more logging on it (maybe on the next iteration). Glad to discuss what is the level of preference on each one of those that this team prefers.